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 struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Tile[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Tile[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Tile[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Tile[x][y];
1771 #if DEBUG_INIT_PLAYER
1772 Debug("game:init:player", "- player element %d activated",
1773 player->element_nr);
1774 Debug("game:init:player", " (local player is %d and currently %s)",
1775 local_player->element_nr,
1776 local_player->active ? "active" : "not active");
1780 Tile[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1786 // always check if player was just killed and should be reanimated
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Tile[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Tile[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1828 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1878 case EL_SPRING_LEFT:
1879 case EL_SPRING_RIGHT:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Tile[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Tile[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Tile[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Tile[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Tile[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error("'game_panel_controls' structure corrupted at %d", i);
2152 Fail("this should not happen -- please debug");
2155 // force update of game controls after initialization
2156 gpc->value = gpc->last_value = -1;
2157 gpc->frame = gpc->last_frame = -1;
2158 gpc->gfx_frame = -1;
2160 // determine panel value width for later calculation of alignment
2161 if (type == TYPE_INTEGER || type == TYPE_STRING)
2163 pos->width = pos->size * getFontWidth(pos->font);
2164 pos->height = getFontHeight(pos->font);
2166 else if (type == TYPE_ELEMENT)
2168 pos->width = pos->size;
2169 pos->height = pos->size;
2172 // fill structure for game panel draw order
2174 gpo->sort_priority = pos->sort_priority;
2177 // sort game panel controls according to sort_priority and control number
2178 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2179 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2182 static void UpdatePlayfieldElementCount(void)
2184 boolean use_element_count = FALSE;
2187 // first check if it is needed at all to calculate playfield element count
2188 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2189 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2190 use_element_count = TRUE;
2192 if (!use_element_count)
2195 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2196 element_info[i].element_count = 0;
2198 SCAN_PLAYFIELD(x, y)
2200 element_info[Tile[x][y]].element_count++;
2203 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2204 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2205 if (IS_IN_GROUP(j, i))
2206 element_info[EL_GROUP_START + i].element_count +=
2207 element_info[j].element_count;
2210 static void UpdateGameControlValues(void)
2213 int time = (game.LevelSolved ?
2214 game.LevelSolved_CountingTime :
2215 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2217 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2218 game_sp.time_played :
2219 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2220 game_mm.energy_left :
2221 game.no_time_limit ? TimePlayed : TimeLeft);
2222 int score = (game.LevelSolved ?
2223 game.LevelSolved_CountingScore :
2224 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2225 game_em.lev->score :
2226 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2231 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2232 game_em.lev->gems_needed :
2233 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2234 game_sp.infotrons_still_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2236 game_mm.kettles_still_needed :
2237 game.gems_still_needed);
2238 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2239 game_em.lev->gems_needed > 0 :
2240 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 game_sp.infotrons_still_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2243 game_mm.kettles_still_needed > 0 ||
2244 game_mm.lights_still_needed > 0 :
2245 game.gems_still_needed > 0 ||
2246 game.sokoban_fields_still_needed > 0 ||
2247 game.sokoban_objects_still_needed > 0 ||
2248 game.lights_still_needed > 0);
2249 int health = (game.LevelSolved ?
2250 game.LevelSolved_CountingHealth :
2251 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 MM_HEALTH(game_mm.laser_overload_value) :
2255 UpdatePlayfieldElementCount();
2257 // update game panel control values
2259 // used instead of "level_nr" (for network games)
2260 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2261 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2263 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2264 for (i = 0; i < MAX_NUM_KEYS; i++)
2265 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2269 if (game.centered_player_nr == -1)
2271 for (i = 0; i < MAX_PLAYERS; i++)
2273 // only one player in Supaplex game engine
2274 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2277 for (k = 0; k < MAX_NUM_KEYS; k++)
2279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2281 if (game_em.ply[i]->keys & (1 << k))
2282 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2283 get_key_element_from_nr(k);
2285 else if (stored_player[i].key[k])
2286 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2287 get_key_element_from_nr(k);
2290 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2291 getPlayerInventorySize(i);
2293 if (stored_player[i].num_white_keys > 0)
2294 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2298 stored_player[i].num_white_keys;
2303 int player_nr = game.centered_player_nr;
2305 for (k = 0; k < MAX_NUM_KEYS; k++)
2307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2309 if (game_em.ply[player_nr]->keys & (1 << k))
2310 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2311 get_key_element_from_nr(k);
2313 else if (stored_player[player_nr].key[k])
2314 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2315 get_key_element_from_nr(k);
2318 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2319 getPlayerInventorySize(player_nr);
2321 if (stored_player[player_nr].num_white_keys > 0)
2322 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2325 stored_player[player_nr].num_white_keys;
2328 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2330 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2331 get_inventory_element_from_pos(local_player, i);
2332 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2333 get_inventory_element_from_pos(local_player, -i - 1);
2336 game_panel_controls[GAME_PANEL_SCORE].value = score;
2337 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2339 game_panel_controls[GAME_PANEL_TIME].value = time;
2341 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2342 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2343 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2345 if (level.time == 0)
2346 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2348 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2350 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2351 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2353 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2355 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2356 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2358 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2359 local_player->shield_normal_time_left;
2360 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2361 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2363 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2364 local_player->shield_deadly_time_left;
2366 game_panel_controls[GAME_PANEL_EXIT].value =
2367 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2369 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2370 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2371 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2372 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2373 EL_EMC_MAGIC_BALL_SWITCH);
2375 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2376 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2377 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2378 game.light_time_left;
2380 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2381 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2382 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2383 game.timegate_time_left;
2385 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2386 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2388 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2389 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2390 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2391 game.lenses_time_left;
2393 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2394 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2395 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2396 game.magnify_time_left;
2398 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2399 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2400 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2401 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2402 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2403 EL_BALLOON_SWITCH_NONE);
2405 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2406 local_player->dynabomb_count;
2407 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2408 local_player->dynabomb_size;
2409 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2410 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2412 game_panel_controls[GAME_PANEL_PENGUINS].value =
2413 game.friends_still_needed;
2415 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2416 game.sokoban_objects_still_needed;
2417 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2418 game.sokoban_fields_still_needed;
2420 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2421 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2423 for (i = 0; i < NUM_BELTS; i++)
2425 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2426 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2427 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2428 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2429 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2432 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2433 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2434 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2435 game.magic_wall_time_left;
2437 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2438 local_player->gravity;
2440 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2441 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2443 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2444 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2445 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2446 game.panel.element[i].id : EL_UNDEFINED);
2448 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2449 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2450 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2451 element_info[game.panel.element_count[i].id].element_count : 0);
2453 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2454 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2455 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2456 element_info[game.panel.ce_score[i].id].collect_score : 0);
2458 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2459 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2460 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2461 element_info[game.panel.ce_score_element[i].id].collect_score :
2464 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2466 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2468 // update game panel control frames
2470 for (i = 0; game_panel_controls[i].nr != -1; i++)
2472 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2474 if (gpc->type == TYPE_ELEMENT)
2476 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2478 int last_anim_random_frame = gfx.anim_random_frame;
2479 int element = gpc->value;
2480 int graphic = el2panelimg(element);
2482 if (gpc->value != gpc->last_value)
2485 gpc->gfx_random = INIT_GFX_RANDOM();
2491 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2492 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2493 gpc->gfx_random = INIT_GFX_RANDOM();
2496 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2497 gfx.anim_random_frame = gpc->gfx_random;
2499 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2500 gpc->gfx_frame = element_info[element].collect_score;
2502 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2505 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2506 gfx.anim_random_frame = last_anim_random_frame;
2509 else if (gpc->type == TYPE_GRAPHIC)
2511 if (gpc->graphic != IMG_UNDEFINED)
2513 int last_anim_random_frame = gfx.anim_random_frame;
2514 int graphic = gpc->graphic;
2516 if (gpc->value != gpc->last_value)
2519 gpc->gfx_random = INIT_GFX_RANDOM();
2525 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2526 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2527 gpc->gfx_random = INIT_GFX_RANDOM();
2530 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2531 gfx.anim_random_frame = gpc->gfx_random;
2533 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2535 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2536 gfx.anim_random_frame = last_anim_random_frame;
2542 static void DisplayGameControlValues(void)
2544 boolean redraw_panel = FALSE;
2547 for (i = 0; game_panel_controls[i].nr != -1; i++)
2549 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2551 if (PANEL_DEACTIVATED(gpc->pos))
2554 if (gpc->value == gpc->last_value &&
2555 gpc->frame == gpc->last_frame)
2558 redraw_panel = TRUE;
2564 // copy default game door content to main double buffer
2566 // !!! CHECK AGAIN !!!
2567 SetPanelBackground();
2568 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2569 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2571 // redraw game control buttons
2572 RedrawGameButtons();
2574 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2576 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2578 int nr = game_panel_order[i].nr;
2579 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2580 struct TextPosInfo *pos = gpc->pos;
2581 int type = gpc->type;
2582 int value = gpc->value;
2583 int frame = gpc->frame;
2584 int size = pos->size;
2585 int font = pos->font;
2586 boolean draw_masked = pos->draw_masked;
2587 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2589 if (PANEL_DEACTIVATED(pos))
2592 gpc->last_value = value;
2593 gpc->last_frame = frame;
2595 if (type == TYPE_INTEGER)
2597 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2598 nr == GAME_PANEL_TIME)
2600 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2602 if (use_dynamic_size) // use dynamic number of digits
2604 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2605 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2606 int size2 = size1 + 1;
2607 int font1 = pos->font;
2608 int font2 = pos->font_alt;
2610 size = (value < value_change ? size1 : size2);
2611 font = (value < value_change ? font1 : font2);
2615 // correct text size if "digits" is zero or less
2617 size = strlen(int2str(value, size));
2619 // dynamically correct text alignment
2620 pos->width = size * getFontWidth(font);
2622 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2623 int2str(value, size), font, mask_mode);
2625 else if (type == TYPE_ELEMENT)
2627 int element, graphic;
2631 int dst_x = PANEL_XPOS(pos);
2632 int dst_y = PANEL_YPOS(pos);
2634 if (value != EL_UNDEFINED && value != EL_EMPTY)
2637 graphic = el2panelimg(value);
2640 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2641 element, EL_NAME(element), size);
2644 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2647 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2650 width = graphic_info[graphic].width * size / TILESIZE;
2651 height = graphic_info[graphic].height * size / TILESIZE;
2654 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2657 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2661 else if (type == TYPE_GRAPHIC)
2663 int graphic = gpc->graphic;
2664 int graphic_active = gpc->graphic_active;
2668 int dst_x = PANEL_XPOS(pos);
2669 int dst_y = PANEL_YPOS(pos);
2670 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2671 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2673 if (graphic != IMG_UNDEFINED && !skip)
2675 if (pos->style == STYLE_REVERSE)
2676 value = 100 - value;
2678 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2680 if (pos->direction & MV_HORIZONTAL)
2682 width = graphic_info[graphic_active].width * value / 100;
2683 height = graphic_info[graphic_active].height;
2685 if (pos->direction == MV_LEFT)
2687 src_x += graphic_info[graphic_active].width - width;
2688 dst_x += graphic_info[graphic_active].width - width;
2693 width = graphic_info[graphic_active].width;
2694 height = graphic_info[graphic_active].height * value / 100;
2696 if (pos->direction == MV_UP)
2698 src_y += graphic_info[graphic_active].height - height;
2699 dst_y += graphic_info[graphic_active].height - height;
2704 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2707 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2710 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2712 if (pos->direction & MV_HORIZONTAL)
2714 if (pos->direction == MV_RIGHT)
2721 dst_x = PANEL_XPOS(pos);
2724 width = graphic_info[graphic].width - width;
2728 if (pos->direction == MV_DOWN)
2735 dst_y = PANEL_YPOS(pos);
2738 height = graphic_info[graphic].height - height;
2742 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2745 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2749 else if (type == TYPE_STRING)
2751 boolean active = (value != 0);
2752 char *state_normal = "off";
2753 char *state_active = "on";
2754 char *state = (active ? state_active : state_normal);
2755 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2756 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2757 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2758 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2760 if (nr == GAME_PANEL_GRAVITY_STATE)
2762 int font1 = pos->font; // (used for normal state)
2763 int font2 = pos->font_alt; // (used for active state)
2765 font = (active ? font2 : font1);
2774 // don't truncate output if "chars" is zero or less
2777 // dynamically correct text alignment
2778 pos->width = size * getFontWidth(font);
2781 s_cut = getStringCopyN(s, size);
2783 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2784 s_cut, font, mask_mode);
2790 redraw_mask |= REDRAW_DOOR_1;
2793 SetGameStatus(GAME_MODE_PLAYING);
2796 void UpdateAndDisplayGameControlValues(void)
2798 if (tape.deactivate_display)
2801 UpdateGameControlValues();
2802 DisplayGameControlValues();
2806 static void UpdateGameDoorValues(void)
2808 UpdateGameControlValues();
2812 void DrawGameDoorValues(void)
2814 DisplayGameControlValues();
2818 // ============================================================================
2820 // ----------------------------------------------------------------------------
2821 // initialize game engine due to level / tape version number
2822 // ============================================================================
2824 static void InitGameEngine(void)
2826 int i, j, k, l, x, y;
2828 // set game engine from tape file when re-playing, else from level file
2829 game.engine_version = (tape.playing ? tape.engine_version :
2830 level.game_version);
2832 // set single or multi-player game mode (needed for re-playing tapes)
2833 game.team_mode = setup.team_mode;
2837 int num_players = 0;
2839 for (i = 0; i < MAX_PLAYERS; i++)
2840 if (tape.player_participates[i])
2843 // multi-player tapes contain input data for more than one player
2844 game.team_mode = (num_players > 1);
2848 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2849 level.game_version);
2850 Debug("game:init:level", " tape.file_version == %06d",
2852 Debug("game:init:level", " tape.game_version == %06d",
2854 Debug("game:init:level", " tape.engine_version == %06d",
2855 tape.engine_version);
2856 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2857 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2860 // --------------------------------------------------------------------------
2861 // set flags for bugs and changes according to active game engine version
2862 // --------------------------------------------------------------------------
2866 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2868 Bug was introduced in version:
2871 Bug was fixed in version:
2875 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2876 but the property "can fall" was missing, which caused some levels to be
2877 unsolvable. This was fixed in version 4.2.0.0.
2879 Affected levels/tapes:
2880 An example for a tape that was fixed by this bugfix is tape 029 from the
2881 level set "rnd_sam_bateman".
2882 The wrong behaviour will still be used for all levels or tapes that were
2883 created/recorded with it. An example for this is tape 023 from the level
2884 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2887 boolean use_amoeba_dropping_cannot_fall_bug =
2888 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2889 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2891 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2892 tape.game_version < VERSION_IDENT(4,2,0,0)));
2895 Summary of bugfix/change:
2896 Fixed move speed of elements entering or leaving magic wall.
2898 Fixed/changed in version:
2902 Before 2.0.1, move speed of elements entering or leaving magic wall was
2903 twice as fast as it is now.
2904 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2906 Affected levels/tapes:
2907 The first condition is generally needed for all levels/tapes before version
2908 2.0.1, which might use the old behaviour before it was changed; known tapes
2909 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2910 The second condition is an exception from the above case and is needed for
2911 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2912 above, but before it was known that this change would break tapes like the
2913 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2914 although the engine version while recording maybe was before 2.0.1. There
2915 are a lot of tapes that are affected by this exception, like tape 006 from
2916 the level set "rnd_conor_mancone".
2919 boolean use_old_move_stepsize_for_magic_wall =
2920 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2922 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2923 tape.game_version < VERSION_IDENT(4,2,0,0)));
2926 Summary of bugfix/change:
2927 Fixed handling for custom elements that change when pushed by the player.
2929 Fixed/changed in version:
2933 Before 3.1.0, custom elements that "change when pushing" changed directly
2934 after the player started pushing them (until then handled in "DigField()").
2935 Since 3.1.0, these custom elements are not changed until the "pushing"
2936 move of the element is finished (now handled in "ContinueMoving()").
2938 Affected levels/tapes:
2939 The first condition is generally needed for all levels/tapes before version
2940 3.1.0, which might use the old behaviour before it was changed; known tapes
2941 that are affected are some tapes from the level set "Walpurgis Gardens" by
2943 The second condition is an exception from the above case and is needed for
2944 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2945 above (including some development versions of 3.1.0), but before it was
2946 known that this change would break tapes like the above and was fixed in
2947 3.1.1, so that the changed behaviour was active although the engine version
2948 while recording maybe was before 3.1.0. There is at least one tape that is
2949 affected by this exception, which is the tape for the one-level set "Bug
2950 Machine" by Juergen Bonhagen.
2953 game.use_change_when_pushing_bug =
2954 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2956 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2957 tape.game_version < VERSION_IDENT(3,1,1,0)));
2960 Summary of bugfix/change:
2961 Fixed handling for blocking the field the player leaves when moving.
2963 Fixed/changed in version:
2967 Before 3.1.1, when "block last field when moving" was enabled, the field
2968 the player is leaving when moving was blocked for the time of the move,
2969 and was directly unblocked afterwards. This resulted in the last field
2970 being blocked for exactly one less than the number of frames of one player
2971 move. Additionally, even when blocking was disabled, the last field was
2972 blocked for exactly one frame.
2973 Since 3.1.1, due to changes in player movement handling, the last field
2974 is not blocked at all when blocking is disabled. When blocking is enabled,
2975 the last field is blocked for exactly the number of frames of one player
2976 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2977 last field is blocked for exactly one more than the number of frames of
2980 Affected levels/tapes:
2981 (!!! yet to be determined -- probably many !!!)
2984 game.use_block_last_field_bug =
2985 (game.engine_version < VERSION_IDENT(3,1,1,0));
2987 /* various special flags and settings for native Emerald Mine game engine */
2989 game_em.use_single_button =
2990 (game.engine_version > VERSION_IDENT(4,0,0,2));
2992 game_em.use_snap_key_bug =
2993 (game.engine_version < VERSION_IDENT(4,0,1,0));
2995 game_em.use_random_bug =
2996 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
2998 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3000 game_em.use_old_explosions = use_old_em_engine;
3001 game_em.use_old_android = use_old_em_engine;
3002 game_em.use_old_push_elements = use_old_em_engine;
3003 game_em.use_old_push_into_acid = use_old_em_engine;
3005 game_em.use_wrap_around = !use_old_em_engine;
3007 // --------------------------------------------------------------------------
3009 // set maximal allowed number of custom element changes per game frame
3010 game.max_num_changes_per_frame = 1;
3012 // default scan direction: scan playfield from top/left to bottom/right
3013 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3015 // dynamically adjust element properties according to game engine version
3016 InitElementPropertiesEngine(game.engine_version);
3018 // ---------- initialize special element properties -------------------------
3020 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3021 if (use_amoeba_dropping_cannot_fall_bug)
3022 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3024 // ---------- initialize player's initial move delay ------------------------
3026 // dynamically adjust player properties according to level information
3027 for (i = 0; i < MAX_PLAYERS; i++)
3028 game.initial_move_delay_value[i] =
3029 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3031 // dynamically adjust player properties according to game engine version
3032 for (i = 0; i < MAX_PLAYERS; i++)
3033 game.initial_move_delay[i] =
3034 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3035 game.initial_move_delay_value[i] : 0);
3037 // ---------- initialize player's initial push delay ------------------------
3039 // dynamically adjust player properties according to game engine version
3040 game.initial_push_delay_value =
3041 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3043 // ---------- initialize changing elements ----------------------------------
3045 // initialize changing elements information
3046 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3048 struct ElementInfo *ei = &element_info[i];
3050 // this pointer might have been changed in the level editor
3051 ei->change = &ei->change_page[0];
3053 if (!IS_CUSTOM_ELEMENT(i))
3055 ei->change->target_element = EL_EMPTY_SPACE;
3056 ei->change->delay_fixed = 0;
3057 ei->change->delay_random = 0;
3058 ei->change->delay_frames = 1;
3061 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3063 ei->has_change_event[j] = FALSE;
3065 ei->event_page_nr[j] = 0;
3066 ei->event_page[j] = &ei->change_page[0];
3070 // add changing elements from pre-defined list
3071 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3073 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3074 struct ElementInfo *ei = &element_info[ch_delay->element];
3076 ei->change->target_element = ch_delay->target_element;
3077 ei->change->delay_fixed = ch_delay->change_delay;
3079 ei->change->pre_change_function = ch_delay->pre_change_function;
3080 ei->change->change_function = ch_delay->change_function;
3081 ei->change->post_change_function = ch_delay->post_change_function;
3083 ei->change->can_change = TRUE;
3084 ei->change->can_change_or_has_action = TRUE;
3086 ei->has_change_event[CE_DELAY] = TRUE;
3088 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3089 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3092 // ---------- initialize internal run-time variables ------------------------
3094 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3096 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3098 for (j = 0; j < ei->num_change_pages; j++)
3100 ei->change_page[j].can_change_or_has_action =
3101 (ei->change_page[j].can_change |
3102 ei->change_page[j].has_action);
3106 // add change events from custom element configuration
3107 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3109 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3111 for (j = 0; j < ei->num_change_pages; j++)
3113 if (!ei->change_page[j].can_change_or_has_action)
3116 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3118 // only add event page for the first page found with this event
3119 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3121 ei->has_change_event[k] = TRUE;
3123 ei->event_page_nr[k] = j;
3124 ei->event_page[k] = &ei->change_page[j];
3130 // ---------- initialize reference elements in change conditions ------------
3132 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3134 int element = EL_CUSTOM_START + i;
3135 struct ElementInfo *ei = &element_info[element];
3137 for (j = 0; j < ei->num_change_pages; j++)
3139 int trigger_element = ei->change_page[j].initial_trigger_element;
3141 if (trigger_element >= EL_PREV_CE_8 &&
3142 trigger_element <= EL_NEXT_CE_8)
3143 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3145 ei->change_page[j].trigger_element = trigger_element;
3149 // ---------- initialize run-time trigger player and element ----------------
3151 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3153 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3155 for (j = 0; j < ei->num_change_pages; j++)
3157 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3158 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3159 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3160 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3161 ei->change_page[j].actual_trigger_ce_value = 0;
3162 ei->change_page[j].actual_trigger_ce_score = 0;
3166 // ---------- initialize trigger events -------------------------------------
3168 // initialize trigger events information
3169 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3170 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3171 trigger_events[i][j] = FALSE;
3173 // add trigger events from element change event properties
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3176 struct ElementInfo *ei = &element_info[i];
3178 for (j = 0; j < ei->num_change_pages; j++)
3180 if (!ei->change_page[j].can_change_or_has_action)
3183 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3185 int trigger_element = ei->change_page[j].trigger_element;
3187 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3189 if (ei->change_page[j].has_event[k])
3191 if (IS_GROUP_ELEMENT(trigger_element))
3193 struct ElementGroupInfo *group =
3194 element_info[trigger_element].group;
3196 for (l = 0; l < group->num_elements_resolved; l++)
3197 trigger_events[group->element_resolved[l]][k] = TRUE;
3199 else if (trigger_element == EL_ANY_ELEMENT)
3200 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3201 trigger_events[l][k] = TRUE;
3203 trigger_events[trigger_element][k] = TRUE;
3210 // ---------- initialize push delay -----------------------------------------
3212 // initialize push delay values to default
3213 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3215 if (!IS_CUSTOM_ELEMENT(i))
3217 // set default push delay values (corrected since version 3.0.7-1)
3218 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3220 element_info[i].push_delay_fixed = 2;
3221 element_info[i].push_delay_random = 8;
3225 element_info[i].push_delay_fixed = 8;
3226 element_info[i].push_delay_random = 8;
3231 // set push delay value for certain elements from pre-defined list
3232 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3234 int e = push_delay_list[i].element;
3236 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3237 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3240 // set push delay value for Supaplex elements for newer engine versions
3241 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3243 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3245 if (IS_SP_ELEMENT(i))
3247 // set SP push delay to just enough to push under a falling zonk
3248 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3250 element_info[i].push_delay_fixed = delay;
3251 element_info[i].push_delay_random = 0;
3256 // ---------- initialize move stepsize --------------------------------------
3258 // initialize move stepsize values to default
3259 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 if (!IS_CUSTOM_ELEMENT(i))
3261 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3263 // set move stepsize value for certain elements from pre-defined list
3264 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3266 int e = move_stepsize_list[i].element;
3268 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3270 // set move stepsize value for certain elements for older engine versions
3271 if (use_old_move_stepsize_for_magic_wall)
3273 if (e == EL_MAGIC_WALL_FILLING ||
3274 e == EL_MAGIC_WALL_EMPTYING ||
3275 e == EL_BD_MAGIC_WALL_FILLING ||
3276 e == EL_BD_MAGIC_WALL_EMPTYING)
3277 element_info[e].move_stepsize *= 2;
3281 // ---------- initialize collect score --------------------------------------
3283 // initialize collect score values for custom elements from initial value
3284 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3285 if (IS_CUSTOM_ELEMENT(i))
3286 element_info[i].collect_score = element_info[i].collect_score_initial;
3288 // ---------- initialize collect count --------------------------------------
3290 // initialize collect count values for non-custom elements
3291 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3292 if (!IS_CUSTOM_ELEMENT(i))
3293 element_info[i].collect_count_initial = 0;
3295 // add collect count values for all elements from pre-defined list
3296 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3297 element_info[collect_count_list[i].element].collect_count_initial =
3298 collect_count_list[i].count;
3300 // ---------- initialize access direction -----------------------------------
3302 // initialize access direction values to default (access from every side)
3303 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3304 if (!IS_CUSTOM_ELEMENT(i))
3305 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3307 // set access direction value for certain elements from pre-defined list
3308 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3309 element_info[access_direction_list[i].element].access_direction =
3310 access_direction_list[i].direction;
3312 // ---------- initialize explosion content ----------------------------------
3313 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3315 if (IS_CUSTOM_ELEMENT(i))
3318 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3320 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3322 element_info[i].content.e[x][y] =
3323 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3324 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3325 i == EL_PLAYER_3 ? EL_EMERALD :
3326 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3327 i == EL_MOLE ? EL_EMERALD_RED :
3328 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3329 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3330 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3331 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3332 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3333 i == EL_WALL_EMERALD ? EL_EMERALD :
3334 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3335 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3336 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3337 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3338 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3339 i == EL_WALL_PEARL ? EL_PEARL :
3340 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3345 // ---------- initialize recursion detection --------------------------------
3346 recursion_loop_depth = 0;
3347 recursion_loop_detected = FALSE;
3348 recursion_loop_element = EL_UNDEFINED;
3350 // ---------- initialize graphics engine ------------------------------------
3351 game.scroll_delay_value =
3352 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3353 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3354 !setup.forced_scroll_delay ? 0 :
3355 setup.scroll_delay ? setup.scroll_delay_value : 0);
3356 game.scroll_delay_value =
3357 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3359 // ---------- initialize game engine snapshots ------------------------------
3360 for (i = 0; i < MAX_PLAYERS; i++)
3361 game.snapshot.last_action[i] = 0;
3362 game.snapshot.changed_action = FALSE;
3363 game.snapshot.collected_item = FALSE;
3364 game.snapshot.mode =
3365 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3366 SNAPSHOT_MODE_EVERY_STEP :
3367 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3368 SNAPSHOT_MODE_EVERY_MOVE :
3369 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3370 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3371 game.snapshot.save_snapshot = FALSE;
3373 // ---------- initialize level time for Supaplex engine ---------------------
3374 // Supaplex levels with time limit currently unsupported -- should be added
3375 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3378 // ---------- initialize flags for handling game actions --------------------
3380 // set flags for game actions to default values
3381 game.use_key_actions = TRUE;
3382 game.use_mouse_actions = FALSE;
3384 // when using Mirror Magic game engine, handle mouse events only
3385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3387 game.use_key_actions = FALSE;
3388 game.use_mouse_actions = TRUE;
3391 // check for custom elements with mouse click events
3392 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3394 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3396 int element = EL_CUSTOM_START + i;
3398 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3399 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3400 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3401 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3402 game.use_mouse_actions = TRUE;
3407 static int get_num_special_action(int element, int action_first,
3410 int num_special_action = 0;
3413 for (i = action_first; i <= action_last; i++)
3415 boolean found = FALSE;
3417 for (j = 0; j < NUM_DIRECTIONS; j++)
3418 if (el_act_dir2img(element, i, j) !=
3419 el_act_dir2img(element, ACTION_DEFAULT, j))
3423 num_special_action++;
3428 return num_special_action;
3432 // ============================================================================
3434 // ----------------------------------------------------------------------------
3435 // initialize and start new game
3436 // ============================================================================
3438 #if DEBUG_INIT_PLAYER
3439 static void DebugPrintPlayerStatus(char *message)
3446 Debug("game:init:player", "%s:", message);
3448 for (i = 0; i < MAX_PLAYERS; i++)
3450 struct PlayerInfo *player = &stored_player[i];
3452 Debug("game:init:player",
3453 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3457 player->connected_locally,
3458 player->connected_network,
3460 (local_player == player ? " (local player)" : ""));
3467 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3468 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3469 int fade_mask = REDRAW_FIELD;
3471 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3472 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3473 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3474 int initial_move_dir = MV_DOWN;
3477 // required here to update video display before fading (FIX THIS)
3478 DrawMaskedBorder(REDRAW_DOOR_2);
3480 if (!game.restart_level)
3481 CloseDoor(DOOR_CLOSE_1);
3483 SetGameStatus(GAME_MODE_PLAYING);
3485 if (level_editor_test_game)
3486 FadeSkipNextFadeOut();
3488 FadeSetEnterScreen();
3491 fade_mask = REDRAW_ALL;
3493 FadeLevelSoundsAndMusic();
3495 ExpireSoundLoops(TRUE);
3499 if (level_editor_test_game)
3500 FadeSkipNextFadeIn();
3502 // needed if different viewport properties defined for playing
3503 ChangeViewportPropertiesIfNeeded();
3507 DrawCompleteVideoDisplay();
3509 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3512 InitGameControlValues();
3514 // initialize tape actions from game when recording tape
3517 tape.use_key_actions = game.use_key_actions;
3518 tape.use_mouse_actions = game.use_mouse_actions;
3521 // don't play tapes over network
3522 network_playing = (network.enabled && !tape.playing);
3524 for (i = 0; i < MAX_PLAYERS; i++)
3526 struct PlayerInfo *player = &stored_player[i];
3528 player->index_nr = i;
3529 player->index_bit = (1 << i);
3530 player->element_nr = EL_PLAYER_1 + i;
3532 player->present = FALSE;
3533 player->active = FALSE;
3534 player->mapped = FALSE;
3536 player->killed = FALSE;
3537 player->reanimated = FALSE;
3538 player->buried = FALSE;
3541 player->effective_action = 0;
3542 player->programmed_action = 0;
3543 player->snap_action = 0;
3545 player->mouse_action.lx = 0;
3546 player->mouse_action.ly = 0;
3547 player->mouse_action.button = 0;
3548 player->mouse_action.button_hint = 0;
3550 player->effective_mouse_action.lx = 0;
3551 player->effective_mouse_action.ly = 0;
3552 player->effective_mouse_action.button = 0;
3553 player->effective_mouse_action.button_hint = 0;
3555 for (j = 0; j < MAX_NUM_KEYS; j++)
3556 player->key[j] = FALSE;
3558 player->num_white_keys = 0;
3560 player->dynabomb_count = 0;
3561 player->dynabomb_size = 1;
3562 player->dynabombs_left = 0;
3563 player->dynabomb_xl = FALSE;
3565 player->MovDir = initial_move_dir;
3568 player->GfxDir = initial_move_dir;
3569 player->GfxAction = ACTION_DEFAULT;
3571 player->StepFrame = 0;
3573 player->initial_element = player->element_nr;
3574 player->artwork_element =
3575 (level.use_artwork_element[i] ? level.artwork_element[i] :
3576 player->element_nr);
3577 player->use_murphy = FALSE;
3579 player->block_last_field = FALSE; // initialized in InitPlayerField()
3580 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3582 player->gravity = level.initial_player_gravity[i];
3584 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3586 player->actual_frame_counter = 0;
3588 player->step_counter = 0;
3590 player->last_move_dir = initial_move_dir;
3592 player->is_active = FALSE;
3594 player->is_waiting = FALSE;
3595 player->is_moving = FALSE;
3596 player->is_auto_moving = FALSE;
3597 player->is_digging = FALSE;
3598 player->is_snapping = FALSE;
3599 player->is_collecting = FALSE;
3600 player->is_pushing = FALSE;
3601 player->is_switching = FALSE;
3602 player->is_dropping = FALSE;
3603 player->is_dropping_pressed = FALSE;
3605 player->is_bored = FALSE;
3606 player->is_sleeping = FALSE;
3608 player->was_waiting = TRUE;
3609 player->was_moving = FALSE;
3610 player->was_snapping = FALSE;
3611 player->was_dropping = FALSE;
3613 player->force_dropping = FALSE;
3615 player->frame_counter_bored = -1;
3616 player->frame_counter_sleeping = -1;
3618 player->anim_delay_counter = 0;
3619 player->post_delay_counter = 0;
3621 player->dir_waiting = initial_move_dir;
3622 player->action_waiting = ACTION_DEFAULT;
3623 player->last_action_waiting = ACTION_DEFAULT;
3624 player->special_action_bored = ACTION_DEFAULT;
3625 player->special_action_sleeping = ACTION_DEFAULT;
3627 player->switch_x = -1;
3628 player->switch_y = -1;
3630 player->drop_x = -1;
3631 player->drop_y = -1;
3633 player->show_envelope = 0;
3635 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3637 player->push_delay = -1; // initialized when pushing starts
3638 player->push_delay_value = game.initial_push_delay_value;
3640 player->drop_delay = 0;
3641 player->drop_pressed_delay = 0;
3643 player->last_jx = -1;
3644 player->last_jy = -1;
3648 player->shield_normal_time_left = 0;
3649 player->shield_deadly_time_left = 0;
3651 player->inventory_infinite_element = EL_UNDEFINED;
3652 player->inventory_size = 0;
3654 if (level.use_initial_inventory[i])
3656 for (j = 0; j < level.initial_inventory_size[i]; j++)
3658 int element = level.initial_inventory_content[i][j];
3659 int collect_count = element_info[element].collect_count_initial;
3662 if (!IS_CUSTOM_ELEMENT(element))
3665 if (collect_count == 0)
3666 player->inventory_infinite_element = element;
3668 for (k = 0; k < collect_count; k++)
3669 if (player->inventory_size < MAX_INVENTORY_SIZE)
3670 player->inventory_element[player->inventory_size++] = element;
3674 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3675 SnapField(player, 0, 0);
3677 map_player_action[i] = i;
3680 network_player_action_received = FALSE;
3682 // initial null action
3683 if (network_playing)
3684 SendToServer_MovePlayer(MV_NONE);
3689 TimeLeft = level.time;
3692 ScreenMovDir = MV_NONE;
3696 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3698 game.robot_wheel_x = -1;
3699 game.robot_wheel_y = -1;
3704 game.all_players_gone = FALSE;
3706 game.LevelSolved = FALSE;
3707 game.GameOver = FALSE;
3709 game.GamePlayed = !tape.playing;
3711 game.LevelSolved_GameWon = FALSE;
3712 game.LevelSolved_GameEnd = FALSE;
3713 game.LevelSolved_SaveTape = FALSE;
3714 game.LevelSolved_SaveScore = FALSE;
3716 game.LevelSolved_CountingTime = 0;
3717 game.LevelSolved_CountingScore = 0;
3718 game.LevelSolved_CountingHealth = 0;
3720 game.panel.active = TRUE;
3722 game.no_time_limit = (level.time == 0);
3724 game.yamyam_content_nr = 0;
3725 game.robot_wheel_active = FALSE;
3726 game.magic_wall_active = FALSE;
3727 game.magic_wall_time_left = 0;
3728 game.light_time_left = 0;
3729 game.timegate_time_left = 0;
3730 game.switchgate_pos = 0;
3731 game.wind_direction = level.wind_direction_initial;
3734 game.score_final = 0;
3736 game.health = MAX_HEALTH;
3737 game.health_final = MAX_HEALTH;
3739 game.gems_still_needed = level.gems_needed;
3740 game.sokoban_fields_still_needed = 0;
3741 game.sokoban_objects_still_needed = 0;
3742 game.lights_still_needed = 0;
3743 game.players_still_needed = 0;
3744 game.friends_still_needed = 0;
3746 game.lenses_time_left = 0;
3747 game.magnify_time_left = 0;
3749 game.ball_active = level.ball_active_initial;
3750 game.ball_content_nr = 0;
3752 game.explosions_delayed = TRUE;
3754 game.envelope_active = FALSE;
3756 for (i = 0; i < NUM_BELTS; i++)
3758 game.belt_dir[i] = MV_NONE;
3759 game.belt_dir_nr[i] = 3; // not moving, next moving left
3762 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3763 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3765 #if DEBUG_INIT_PLAYER
3766 DebugPrintPlayerStatus("Player status at level initialization");
3769 SCAN_PLAYFIELD(x, y)
3771 Tile[x][y] = Last[x][y] = level.field[x][y];
3772 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3773 ChangeDelay[x][y] = 0;
3774 ChangePage[x][y] = -1;
3775 CustomValue[x][y] = 0; // initialized in InitField()
3776 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3778 WasJustMoving[x][y] = 0;
3779 WasJustFalling[x][y] = 0;
3780 CheckCollision[x][y] = 0;
3781 CheckImpact[x][y] = 0;
3783 Pushed[x][y] = FALSE;
3785 ChangeCount[x][y] = 0;
3786 ChangeEvent[x][y] = -1;
3788 ExplodePhase[x][y] = 0;
3789 ExplodeDelay[x][y] = 0;
3790 ExplodeField[x][y] = EX_TYPE_NONE;
3792 RunnerVisit[x][y] = 0;
3793 PlayerVisit[x][y] = 0;
3796 GfxRandom[x][y] = INIT_GFX_RANDOM();
3797 GfxElement[x][y] = EL_UNDEFINED;
3798 GfxAction[x][y] = ACTION_DEFAULT;
3799 GfxDir[x][y] = MV_NONE;
3800 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3803 SCAN_PLAYFIELD(x, y)
3805 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3807 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3809 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3812 InitField(x, y, TRUE);
3814 ResetGfxAnimation(x, y);
3819 for (i = 0; i < MAX_PLAYERS; i++)
3821 struct PlayerInfo *player = &stored_player[i];
3823 // set number of special actions for bored and sleeping animation
3824 player->num_special_action_bored =
3825 get_num_special_action(player->artwork_element,
3826 ACTION_BORING_1, ACTION_BORING_LAST);
3827 player->num_special_action_sleeping =
3828 get_num_special_action(player->artwork_element,
3829 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3832 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3833 emulate_sb ? EMU_SOKOBAN :
3834 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3836 // initialize type of slippery elements
3837 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3839 if (!IS_CUSTOM_ELEMENT(i))
3841 // default: elements slip down either to the left or right randomly
3842 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3844 // SP style elements prefer to slip down on the left side
3845 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3846 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3848 // BD style elements prefer to slip down on the left side
3849 if (game.emulation == EMU_BOULDERDASH)
3850 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3854 // initialize explosion and ignition delay
3855 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3857 if (!IS_CUSTOM_ELEMENT(i))
3860 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3861 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3862 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3863 int last_phase = (num_phase + 1) * delay;
3864 int half_phase = (num_phase / 2) * delay;
3866 element_info[i].explosion_delay = last_phase - 1;
3867 element_info[i].ignition_delay = half_phase;
3869 if (i == EL_BLACK_ORB)
3870 element_info[i].ignition_delay = 1;
3874 // correct non-moving belts to start moving left
3875 for (i = 0; i < NUM_BELTS; i++)
3876 if (game.belt_dir[i] == MV_NONE)
3877 game.belt_dir_nr[i] = 3; // not moving, next moving left
3879 #if USE_NEW_PLAYER_ASSIGNMENTS
3880 // use preferred player also in local single-player mode
3881 if (!network.enabled && !game.team_mode)
3883 int new_index_nr = setup.network_player_nr;
3885 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3887 for (i = 0; i < MAX_PLAYERS; i++)
3888 stored_player[i].connected_locally = FALSE;
3890 stored_player[new_index_nr].connected_locally = TRUE;
3894 for (i = 0; i < MAX_PLAYERS; i++)
3896 stored_player[i].connected = FALSE;
3898 // in network game mode, the local player might not be the first player
3899 if (stored_player[i].connected_locally)
3900 local_player = &stored_player[i];
3903 if (!network.enabled)
3904 local_player->connected = TRUE;
3908 for (i = 0; i < MAX_PLAYERS; i++)
3909 stored_player[i].connected = tape.player_participates[i];
3911 else if (network.enabled)
3913 // add team mode players connected over the network (needed for correct
3914 // assignment of player figures from level to locally playing players)
3916 for (i = 0; i < MAX_PLAYERS; i++)
3917 if (stored_player[i].connected_network)
3918 stored_player[i].connected = TRUE;
3920 else if (game.team_mode)
3922 // try to guess locally connected team mode players (needed for correct
3923 // assignment of player figures from level to locally playing players)
3925 for (i = 0; i < MAX_PLAYERS; i++)
3926 if (setup.input[i].use_joystick ||
3927 setup.input[i].key.left != KSYM_UNDEFINED)
3928 stored_player[i].connected = TRUE;
3931 #if DEBUG_INIT_PLAYER
3932 DebugPrintPlayerStatus("Player status after level initialization");
3935 #if DEBUG_INIT_PLAYER
3936 Debug("game:init:player", "Reassigning players ...");
3939 // check if any connected player was not found in playfield
3940 for (i = 0; i < MAX_PLAYERS; i++)
3942 struct PlayerInfo *player = &stored_player[i];
3944 if (player->connected && !player->present)
3946 struct PlayerInfo *field_player = NULL;
3948 #if DEBUG_INIT_PLAYER
3949 Debug("game:init:player",
3950 "- looking for field player for player %d ...", i + 1);
3953 // assign first free player found that is present in the playfield
3955 // first try: look for unmapped playfield player that is not connected
3956 for (j = 0; j < MAX_PLAYERS; j++)
3957 if (field_player == NULL &&
3958 stored_player[j].present &&
3959 !stored_player[j].mapped &&
3960 !stored_player[j].connected)
3961 field_player = &stored_player[j];
3963 // second try: look for *any* unmapped playfield player
3964 for (j = 0; j < MAX_PLAYERS; j++)
3965 if (field_player == NULL &&
3966 stored_player[j].present &&
3967 !stored_player[j].mapped)
3968 field_player = &stored_player[j];
3970 if (field_player != NULL)
3972 int jx = field_player->jx, jy = field_player->jy;
3974 #if DEBUG_INIT_PLAYER
3975 Debug("game:init:player", "- found player %d",
3976 field_player->index_nr + 1);
3979 player->present = FALSE;
3980 player->active = FALSE;
3982 field_player->present = TRUE;
3983 field_player->active = TRUE;
3986 player->initial_element = field_player->initial_element;
3987 player->artwork_element = field_player->artwork_element;
3989 player->block_last_field = field_player->block_last_field;
3990 player->block_delay_adjustment = field_player->block_delay_adjustment;
3993 StorePlayer[jx][jy] = field_player->element_nr;
3995 field_player->jx = field_player->last_jx = jx;
3996 field_player->jy = field_player->last_jy = jy;
3998 if (local_player == player)
3999 local_player = field_player;
4001 map_player_action[field_player->index_nr] = i;
4003 field_player->mapped = TRUE;
4005 #if DEBUG_INIT_PLAYER
4006 Debug("game:init:player", "- map_player_action[%d] == %d",
4007 field_player->index_nr + 1, i + 1);
4012 if (player->connected && player->present)
4013 player->mapped = TRUE;
4016 #if DEBUG_INIT_PLAYER
4017 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4022 // check if any connected player was not found in playfield
4023 for (i = 0; i < MAX_PLAYERS; i++)
4025 struct PlayerInfo *player = &stored_player[i];
4027 if (player->connected && !player->present)
4029 for (j = 0; j < MAX_PLAYERS; j++)
4031 struct PlayerInfo *field_player = &stored_player[j];
4032 int jx = field_player->jx, jy = field_player->jy;
4034 // assign first free player found that is present in the playfield
4035 if (field_player->present && !field_player->connected)
4037 player->present = TRUE;
4038 player->active = TRUE;
4040 field_player->present = FALSE;
4041 field_player->active = FALSE;
4043 player->initial_element = field_player->initial_element;
4044 player->artwork_element = field_player->artwork_element;
4046 player->block_last_field = field_player->block_last_field;
4047 player->block_delay_adjustment = field_player->block_delay_adjustment;
4049 StorePlayer[jx][jy] = player->element_nr;
4051 player->jx = player->last_jx = jx;
4052 player->jy = player->last_jy = jy;
4062 Debug("game:init:player", "local_player->present == %d",
4063 local_player->present);
4066 // set focus to local player for network games, else to all players
4067 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4068 game.centered_player_nr_next = game.centered_player_nr;
4069 game.set_centered_player = FALSE;
4070 game.set_centered_player_wrap = FALSE;
4072 if (network_playing && tape.recording)
4074 // store client dependent player focus when recording network games
4075 tape.centered_player_nr_next = game.centered_player_nr_next;
4076 tape.set_centered_player = TRUE;
4081 // when playing a tape, eliminate all players who do not participate
4083 #if USE_NEW_PLAYER_ASSIGNMENTS
4085 if (!game.team_mode)
4087 for (i = 0; i < MAX_PLAYERS; i++)
4089 if (stored_player[i].active &&
4090 !tape.player_participates[map_player_action[i]])
4092 struct PlayerInfo *player = &stored_player[i];
4093 int jx = player->jx, jy = player->jy;
4095 #if DEBUG_INIT_PLAYER
4096 Debug("game:init:player", "Removing player %d at (%d, %d)",
4100 player->active = FALSE;
4101 StorePlayer[jx][jy] = 0;
4102 Tile[jx][jy] = EL_EMPTY;
4109 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].active &&
4112 !tape.player_participates[i])
4114 struct PlayerInfo *player = &stored_player[i];
4115 int jx = player->jx, jy = player->jy;
4117 player->active = FALSE;
4118 StorePlayer[jx][jy] = 0;
4119 Tile[jx][jy] = EL_EMPTY;
4124 else if (!network.enabled && !game.team_mode) // && !tape.playing
4126 // when in single player mode, eliminate all but the local player
4128 for (i = 0; i < MAX_PLAYERS; i++)
4130 struct PlayerInfo *player = &stored_player[i];
4132 if (player->active && player != local_player)
4134 int jx = player->jx, jy = player->jy;
4136 player->active = FALSE;
4137 player->present = FALSE;
4139 StorePlayer[jx][jy] = 0;
4140 Tile[jx][jy] = EL_EMPTY;
4145 for (i = 0; i < MAX_PLAYERS; i++)
4146 if (stored_player[i].active)
4147 game.players_still_needed++;
4149 if (level.solved_by_one_player)
4150 game.players_still_needed = 1;
4152 // when recording the game, store which players take part in the game
4155 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (stored_player[i].connected)
4158 tape.player_participates[i] = TRUE;
4160 for (i = 0; i < MAX_PLAYERS; i++)
4161 if (stored_player[i].active)
4162 tape.player_participates[i] = TRUE;
4166 #if DEBUG_INIT_PLAYER
4167 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4170 if (BorderElement == EL_EMPTY)
4173 SBX_Right = lev_fieldx - SCR_FIELDX;
4175 SBY_Lower = lev_fieldy - SCR_FIELDY;
4180 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4182 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4185 if (full_lev_fieldx <= SCR_FIELDX)
4186 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4187 if (full_lev_fieldy <= SCR_FIELDY)
4188 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4190 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4192 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4195 // if local player not found, look for custom element that might create
4196 // the player (make some assumptions about the right custom element)
4197 if (!local_player->present)
4199 int start_x = 0, start_y = 0;
4200 int found_rating = 0;
4201 int found_element = EL_UNDEFINED;
4202 int player_nr = local_player->index_nr;
4204 SCAN_PLAYFIELD(x, y)
4206 int element = Tile[x][y];
4211 if (level.use_start_element[player_nr] &&
4212 level.start_element[player_nr] == element &&
4219 found_element = element;
4222 if (!IS_CUSTOM_ELEMENT(element))
4225 if (CAN_CHANGE(element))
4227 for (i = 0; i < element_info[element].num_change_pages; i++)
4229 // check for player created from custom element as single target
4230 content = element_info[element].change_page[i].target_element;
4231 is_player = ELEM_IS_PLAYER(content);
4233 if (is_player && (found_rating < 3 ||
4234 (found_rating == 3 && element < found_element)))
4240 found_element = element;
4245 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4247 // check for player created from custom element as explosion content
4248 content = element_info[element].content.e[xx][yy];
4249 is_player = ELEM_IS_PLAYER(content);
4251 if (is_player && (found_rating < 2 ||
4252 (found_rating == 2 && element < found_element)))
4254 start_x = x + xx - 1;
4255 start_y = y + yy - 1;
4258 found_element = element;
4261 if (!CAN_CHANGE(element))
4264 for (i = 0; i < element_info[element].num_change_pages; i++)
4266 // check for player created from custom element as extended target
4268 element_info[element].change_page[i].target_content.e[xx][yy];
4270 is_player = ELEM_IS_PLAYER(content);
4272 if (is_player && (found_rating < 1 ||
4273 (found_rating == 1 && element < found_element)))
4275 start_x = x + xx - 1;
4276 start_y = y + yy - 1;
4279 found_element = element;
4285 scroll_x = SCROLL_POSITION_X(start_x);
4286 scroll_y = SCROLL_POSITION_Y(start_y);
4290 scroll_x = SCROLL_POSITION_X(local_player->jx);
4291 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4294 // !!! FIX THIS (START) !!!
4295 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4297 InitGameEngine_EM();
4299 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4301 InitGameEngine_SP();
4303 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4305 InitGameEngine_MM();
4309 DrawLevel(REDRAW_FIELD);
4312 // after drawing the level, correct some elements
4313 if (game.timegate_time_left == 0)
4314 CloseAllOpenTimegates();
4317 // blit playfield from scroll buffer to normal back buffer for fading in
4318 BlitScreenToBitmap(backbuffer);
4319 // !!! FIX THIS (END) !!!
4321 DrawMaskedBorder(fade_mask);
4326 // full screen redraw is required at this point in the following cases:
4327 // - special editor door undrawn when game was started from level editor
4328 // - drawing area (playfield) was changed and has to be removed completely
4329 redraw_mask = REDRAW_ALL;
4333 if (!game.restart_level)
4335 // copy default game door content to main double buffer
4337 // !!! CHECK AGAIN !!!
4338 SetPanelBackground();
4339 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4340 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4343 SetPanelBackground();
4344 SetDrawBackgroundMask(REDRAW_DOOR_1);
4346 UpdateAndDisplayGameControlValues();
4348 if (!game.restart_level)
4354 CreateGameButtons();
4359 // copy actual game door content to door double buffer for OpenDoor()
4360 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4362 OpenDoor(DOOR_OPEN_ALL);
4364 KeyboardAutoRepeatOffUnlessAutoplay();
4366 #if DEBUG_INIT_PLAYER
4367 DebugPrintPlayerStatus("Player status (final)");
4376 if (!game.restart_level && !tape.playing)
4378 LevelStats_incPlayed(level_nr);
4380 SaveLevelSetup_SeriesInfo();
4383 game.restart_level = FALSE;
4384 game.restart_game_message = NULL;
4385 game.request_active = FALSE;
4387 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4388 InitGameActions_MM();
4390 SaveEngineSnapshotToListInitial();
4392 if (!game.restart_level)
4394 PlaySound(SND_GAME_STARTING);
4396 if (setup.sound_music)
4401 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4402 int actual_player_x, int actual_player_y)
4404 // this is used for non-R'n'D game engines to update certain engine values
4406 // needed to determine if sounds are played within the visible screen area
4407 scroll_x = actual_scroll_x;
4408 scroll_y = actual_scroll_y;
4410 // needed to get player position for "follow finger" playing input method
4411 local_player->jx = actual_player_x;
4412 local_player->jy = actual_player_y;
4415 void InitMovDir(int x, int y)
4417 int i, element = Tile[x][y];
4418 static int xy[4][2] =
4425 static int direction[3][4] =
4427 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4428 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4429 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4438 Tile[x][y] = EL_BUG;
4439 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4442 case EL_SPACESHIP_RIGHT:
4443 case EL_SPACESHIP_UP:
4444 case EL_SPACESHIP_LEFT:
4445 case EL_SPACESHIP_DOWN:
4446 Tile[x][y] = EL_SPACESHIP;
4447 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4450 case EL_BD_BUTTERFLY_RIGHT:
4451 case EL_BD_BUTTERFLY_UP:
4452 case EL_BD_BUTTERFLY_LEFT:
4453 case EL_BD_BUTTERFLY_DOWN:
4454 Tile[x][y] = EL_BD_BUTTERFLY;
4455 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4458 case EL_BD_FIREFLY_RIGHT:
4459 case EL_BD_FIREFLY_UP:
4460 case EL_BD_FIREFLY_LEFT:
4461 case EL_BD_FIREFLY_DOWN:
4462 Tile[x][y] = EL_BD_FIREFLY;
4463 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4466 case EL_PACMAN_RIGHT:
4468 case EL_PACMAN_LEFT:
4469 case EL_PACMAN_DOWN:
4470 Tile[x][y] = EL_PACMAN;
4471 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4474 case EL_YAMYAM_LEFT:
4475 case EL_YAMYAM_RIGHT:
4477 case EL_YAMYAM_DOWN:
4478 Tile[x][y] = EL_YAMYAM;
4479 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4482 case EL_SP_SNIKSNAK:
4483 MovDir[x][y] = MV_UP;
4486 case EL_SP_ELECTRON:
4487 MovDir[x][y] = MV_LEFT;
4494 Tile[x][y] = EL_MOLE;
4495 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4498 case EL_SPRING_LEFT:
4499 case EL_SPRING_RIGHT:
4500 Tile[x][y] = EL_SPRING;
4501 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4505 if (IS_CUSTOM_ELEMENT(element))
4507 struct ElementInfo *ei = &element_info[element];
4508 int move_direction_initial = ei->move_direction_initial;
4509 int move_pattern = ei->move_pattern;
4511 if (move_direction_initial == MV_START_PREVIOUS)
4513 if (MovDir[x][y] != MV_NONE)
4516 move_direction_initial = MV_START_AUTOMATIC;
4519 if (move_direction_initial == MV_START_RANDOM)
4520 MovDir[x][y] = 1 << RND(4);
4521 else if (move_direction_initial & MV_ANY_DIRECTION)
4522 MovDir[x][y] = move_direction_initial;
4523 else if (move_pattern == MV_ALL_DIRECTIONS ||
4524 move_pattern == MV_TURNING_LEFT ||
4525 move_pattern == MV_TURNING_RIGHT ||
4526 move_pattern == MV_TURNING_LEFT_RIGHT ||
4527 move_pattern == MV_TURNING_RIGHT_LEFT ||
4528 move_pattern == MV_TURNING_RANDOM)
4529 MovDir[x][y] = 1 << RND(4);
4530 else if (move_pattern == MV_HORIZONTAL)
4531 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4532 else if (move_pattern == MV_VERTICAL)
4533 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4534 else if (move_pattern & MV_ANY_DIRECTION)
4535 MovDir[x][y] = element_info[element].move_pattern;
4536 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4537 move_pattern == MV_ALONG_RIGHT_SIDE)
4539 // use random direction as default start direction
4540 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4541 MovDir[x][y] = 1 << RND(4);
4543 for (i = 0; i < NUM_DIRECTIONS; i++)
4545 int x1 = x + xy[i][0];
4546 int y1 = y + xy[i][1];
4548 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4550 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4551 MovDir[x][y] = direction[0][i];
4553 MovDir[x][y] = direction[1][i];
4562 MovDir[x][y] = 1 << RND(4);
4564 if (element != EL_BUG &&
4565 element != EL_SPACESHIP &&
4566 element != EL_BD_BUTTERFLY &&
4567 element != EL_BD_FIREFLY)
4570 for (i = 0; i < NUM_DIRECTIONS; i++)
4572 int x1 = x + xy[i][0];
4573 int y1 = y + xy[i][1];
4575 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4577 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4579 MovDir[x][y] = direction[0][i];
4582 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4583 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4585 MovDir[x][y] = direction[1][i];
4594 GfxDir[x][y] = MovDir[x][y];
4597 void InitAmoebaNr(int x, int y)
4600 int group_nr = AmoebaNeighbourNr(x, y);
4604 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4606 if (AmoebaCnt[i] == 0)
4614 AmoebaNr[x][y] = group_nr;
4615 AmoebaCnt[group_nr]++;
4616 AmoebaCnt2[group_nr]++;
4619 static void LevelSolved(void)
4621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4622 game.players_still_needed > 0)
4625 game.LevelSolved = TRUE;
4626 game.GameOver = TRUE;
4628 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4629 game_em.lev->score :
4630 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4633 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4634 MM_HEALTH(game_mm.laser_overload_value) :
4637 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4638 game.LevelSolved_CountingScore = game.score_final;
4639 game.LevelSolved_CountingHealth = game.health_final;
4644 static int time_count_steps;
4645 static int time, time_final;
4646 static int score, score_final;
4647 static int health, health_final;
4648 static int game_over_delay_1 = 0;
4649 static int game_over_delay_2 = 0;
4650 static int game_over_delay_3 = 0;
4651 int game_over_delay_value_1 = 50;
4652 int game_over_delay_value_2 = 25;
4653 int game_over_delay_value_3 = 50;
4655 if (!game.LevelSolved_GameWon)
4659 // do not start end game actions before the player stops moving (to exit)
4660 if (local_player->active && local_player->MovPos)
4663 game.LevelSolved_GameWon = TRUE;
4664 game.LevelSolved_SaveTape = tape.recording;
4665 game.LevelSolved_SaveScore = !tape.playing;
4669 LevelStats_incSolved(level_nr);
4671 SaveLevelSetup_SeriesInfo();
4674 if (tape.auto_play) // tape might already be stopped here
4675 tape.auto_play_level_solved = TRUE;
4679 game_over_delay_1 = 0;
4680 game_over_delay_2 = 0;
4681 game_over_delay_3 = game_over_delay_value_3;
4683 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4684 score = score_final = game.score_final;
4685 health = health_final = game.health_final;
4687 if (level.score[SC_TIME_BONUS] > 0)
4692 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4694 else if (game.no_time_limit && TimePlayed < 999)
4697 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4700 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4702 game_over_delay_1 = game_over_delay_value_1;
4704 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4707 score_final += health * level.score[SC_TIME_BONUS];
4709 game_over_delay_2 = game_over_delay_value_2;
4712 game.score_final = score_final;
4713 game.health_final = health_final;
4716 if (level_editor_test_game)
4719 score = score_final;
4721 game.LevelSolved_CountingTime = time;
4722 game.LevelSolved_CountingScore = score;
4724 game_panel_controls[GAME_PANEL_TIME].value = time;
4725 game_panel_controls[GAME_PANEL_SCORE].value = score;
4727 DisplayGameControlValues();
4730 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4732 // check if last player has left the level
4733 if (game.exit_x >= 0 &&
4736 int x = game.exit_x;
4737 int y = game.exit_y;
4738 int element = Tile[x][y];
4740 // close exit door after last player
4741 if ((game.all_players_gone &&
4742 (element == EL_EXIT_OPEN ||
4743 element == EL_SP_EXIT_OPEN ||
4744 element == EL_STEEL_EXIT_OPEN)) ||
4745 element == EL_EM_EXIT_OPEN ||
4746 element == EL_EM_STEEL_EXIT_OPEN)
4750 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4751 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4752 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4753 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4754 EL_EM_STEEL_EXIT_CLOSING);
4756 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4759 // player disappears
4760 DrawLevelField(x, y);
4763 for (i = 0; i < MAX_PLAYERS; i++)
4765 struct PlayerInfo *player = &stored_player[i];
4767 if (player->present)
4769 RemovePlayer(player);
4771 // player disappears
4772 DrawLevelField(player->jx, player->jy);
4777 PlaySound(SND_GAME_WINNING);
4780 if (game_over_delay_1 > 0)
4782 game_over_delay_1--;
4787 if (time != time_final)
4789 int time_to_go = ABS(time_final - time);
4790 int time_count_dir = (time < time_final ? +1 : -1);
4792 if (time_to_go < time_count_steps)
4793 time_count_steps = 1;
4795 time += time_count_steps * time_count_dir;
4796 score += time_count_steps * level.score[SC_TIME_BONUS];
4798 game.LevelSolved_CountingTime = time;
4799 game.LevelSolved_CountingScore = score;
4801 game_panel_controls[GAME_PANEL_TIME].value = time;
4802 game_panel_controls[GAME_PANEL_SCORE].value = score;
4804 DisplayGameControlValues();
4806 if (time == time_final)
4807 StopSound(SND_GAME_LEVELTIME_BONUS);
4808 else if (setup.sound_loops)
4809 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4811 PlaySound(SND_GAME_LEVELTIME_BONUS);
4816 if (game_over_delay_2 > 0)
4818 game_over_delay_2--;
4823 if (health != health_final)
4825 int health_count_dir = (health < health_final ? +1 : -1);
4827 health += health_count_dir;
4828 score += level.score[SC_TIME_BONUS];
4830 game.LevelSolved_CountingHealth = health;
4831 game.LevelSolved_CountingScore = score;
4833 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4834 game_panel_controls[GAME_PANEL_SCORE].value = score;
4836 DisplayGameControlValues();
4838 if (health == health_final)
4839 StopSound(SND_GAME_LEVELTIME_BONUS);
4840 else if (setup.sound_loops)
4841 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4843 PlaySound(SND_GAME_LEVELTIME_BONUS);
4848 game.panel.active = FALSE;
4850 if (game_over_delay_3 > 0)
4852 game_over_delay_3--;
4862 // used instead of "level_nr" (needed for network games)
4863 int last_level_nr = levelset.level_nr;
4866 game.LevelSolved_GameEnd = TRUE;
4868 if (game.LevelSolved_SaveTape)
4870 // make sure that request dialog to save tape does not open door again
4871 if (!global.use_envelope_request)
4872 CloseDoor(DOOR_CLOSE_1);
4874 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4877 // if no tape is to be saved, close both doors simultaneously
4878 CloseDoor(DOOR_CLOSE_ALL);
4880 if (level_editor_test_game)
4882 SetGameStatus(GAME_MODE_MAIN);
4889 if (!game.LevelSolved_SaveScore)
4891 SetGameStatus(GAME_MODE_MAIN);
4898 if (level_nr == leveldir_current->handicap_level)
4900 leveldir_current->handicap_level++;
4902 SaveLevelSetup_SeriesInfo();
4905 if (setup.increment_levels &&
4906 level_nr < leveldir_current->last_level &&
4909 level_nr++; // advance to next level
4910 TapeErase(); // start with empty tape
4912 if (setup.auto_play_next_level)
4914 LoadLevel(level_nr);
4916 SaveLevelSetup_SeriesInfo();
4920 hi_pos = NewHiScore(last_level_nr);
4922 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4924 SetGameStatus(GAME_MODE_SCORES);
4926 DrawHallOfFame(last_level_nr, hi_pos);
4928 else if (setup.auto_play_next_level && setup.increment_levels &&
4929 last_level_nr < leveldir_current->last_level &&
4932 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4936 SetGameStatus(GAME_MODE_MAIN);
4942 int NewHiScore(int level_nr)
4946 boolean one_score_entry_per_name = !program.many_scores_per_name;
4948 LoadScore(level_nr);
4950 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4951 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4954 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4956 if (game.score_final > highscore[k].Score)
4958 // player has made it to the hall of fame
4960 if (k < MAX_SCORE_ENTRIES - 1)
4962 int m = MAX_SCORE_ENTRIES - 1;
4964 if (one_score_entry_per_name)
4966 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4967 if (strEqual(setup.player_name, highscore[l].Name))
4970 if (m == k) // player's new highscore overwrites his old one
4974 for (l = m; l > k; l--)
4976 strcpy(highscore[l].Name, highscore[l - 1].Name);
4977 highscore[l].Score = highscore[l - 1].Score;
4983 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4984 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4985 highscore[k].Score = game.score_final;
4990 else if (one_score_entry_per_name &&
4991 !strncmp(setup.player_name, highscore[k].Name,
4992 MAX_PLAYER_NAME_LEN))
4993 break; // player already there with a higher score
4997 SaveScore(level_nr);
5002 static int getElementMoveStepsizeExt(int x, int y, int direction)
5004 int element = Tile[x][y];
5005 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5006 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5007 int horiz_move = (dx != 0);
5008 int sign = (horiz_move ? dx : dy);
5009 int step = sign * element_info[element].move_stepsize;
5011 // special values for move stepsize for spring and things on conveyor belt
5014 if (CAN_FALL(element) &&
5015 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5016 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5017 else if (element == EL_SPRING)
5018 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5024 static int getElementMoveStepsize(int x, int y)
5026 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5029 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5031 if (player->GfxAction != action || player->GfxDir != dir)
5033 player->GfxAction = action;
5034 player->GfxDir = dir;
5036 player->StepFrame = 0;
5040 static void ResetGfxFrame(int x, int y)
5042 // profiling showed that "autotest" spends 10~20% of its time in this function
5043 if (DrawingDeactivatedField())
5046 int element = Tile[x][y];
5047 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5049 if (graphic_info[graphic].anim_global_sync)
5050 GfxFrame[x][y] = FrameCounter;
5051 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5052 GfxFrame[x][y] = CustomValue[x][y];
5053 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5054 GfxFrame[x][y] = element_info[element].collect_score;
5055 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5056 GfxFrame[x][y] = ChangeDelay[x][y];
5059 static void ResetGfxAnimation(int x, int y)
5061 GfxAction[x][y] = ACTION_DEFAULT;
5062 GfxDir[x][y] = MovDir[x][y];
5065 ResetGfxFrame(x, y);
5068 static void ResetRandomAnimationValue(int x, int y)
5070 GfxRandom[x][y] = INIT_GFX_RANDOM();
5073 static void InitMovingField(int x, int y, int direction)
5075 int element = Tile[x][y];
5076 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5077 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5080 boolean is_moving_before, is_moving_after;
5082 // check if element was/is moving or being moved before/after mode change
5083 is_moving_before = (WasJustMoving[x][y] != 0);
5084 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5086 // reset animation only for moving elements which change direction of moving
5087 // or which just started or stopped moving
5088 // (else CEs with property "can move" / "not moving" are reset each frame)
5089 if (is_moving_before != is_moving_after ||
5090 direction != MovDir[x][y])
5091 ResetGfxAnimation(x, y);
5093 MovDir[x][y] = direction;
5094 GfxDir[x][y] = direction;
5096 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5097 direction == MV_DOWN && CAN_FALL(element) ?
5098 ACTION_FALLING : ACTION_MOVING);
5100 // this is needed for CEs with property "can move" / "not moving"
5102 if (is_moving_after)
5104 if (Tile[newx][newy] == EL_EMPTY)
5105 Tile[newx][newy] = EL_BLOCKED;
5107 MovDir[newx][newy] = MovDir[x][y];
5109 CustomValue[newx][newy] = CustomValue[x][y];
5111 GfxFrame[newx][newy] = GfxFrame[x][y];
5112 GfxRandom[newx][newy] = GfxRandom[x][y];
5113 GfxAction[newx][newy] = GfxAction[x][y];
5114 GfxDir[newx][newy] = GfxDir[x][y];
5118 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5120 int direction = MovDir[x][y];
5121 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5122 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5128 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5130 int oldx = x, oldy = y;
5131 int direction = MovDir[x][y];
5133 if (direction == MV_LEFT)
5135 else if (direction == MV_RIGHT)
5137 else if (direction == MV_UP)
5139 else if (direction == MV_DOWN)
5142 *comes_from_x = oldx;
5143 *comes_from_y = oldy;
5146 static int MovingOrBlocked2Element(int x, int y)
5148 int element = Tile[x][y];
5150 if (element == EL_BLOCKED)
5154 Blocked2Moving(x, y, &oldx, &oldy);
5155 return Tile[oldx][oldy];
5161 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5163 // like MovingOrBlocked2Element(), but if element is moving
5164 // and (x,y) is the field the moving element is just leaving,
5165 // return EL_BLOCKED instead of the element value
5166 int element = Tile[x][y];
5168 if (IS_MOVING(x, y))
5170 if (element == EL_BLOCKED)
5174 Blocked2Moving(x, y, &oldx, &oldy);
5175 return Tile[oldx][oldy];
5184 static void RemoveField(int x, int y)
5186 Tile[x][y] = EL_EMPTY;
5192 CustomValue[x][y] = 0;
5195 ChangeDelay[x][y] = 0;
5196 ChangePage[x][y] = -1;
5197 Pushed[x][y] = FALSE;
5199 GfxElement[x][y] = EL_UNDEFINED;
5200 GfxAction[x][y] = ACTION_DEFAULT;
5201 GfxDir[x][y] = MV_NONE;
5204 static void RemoveMovingField(int x, int y)
5206 int oldx = x, oldy = y, newx = x, newy = y;
5207 int element = Tile[x][y];
5208 int next_element = EL_UNDEFINED;
5210 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5213 if (IS_MOVING(x, y))
5215 Moving2Blocked(x, y, &newx, &newy);
5217 if (Tile[newx][newy] != EL_BLOCKED)
5219 // element is moving, but target field is not free (blocked), but
5220 // already occupied by something different (example: acid pool);
5221 // in this case, only remove the moving field, but not the target
5223 RemoveField(oldx, oldy);
5225 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5227 TEST_DrawLevelField(oldx, oldy);
5232 else if (element == EL_BLOCKED)
5234 Blocked2Moving(x, y, &oldx, &oldy);
5235 if (!IS_MOVING(oldx, oldy))
5239 if (element == EL_BLOCKED &&
5240 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5241 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5242 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5243 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5244 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5245 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5246 next_element = get_next_element(Tile[oldx][oldy]);
5248 RemoveField(oldx, oldy);
5249 RemoveField(newx, newy);
5251 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5253 if (next_element != EL_UNDEFINED)
5254 Tile[oldx][oldy] = next_element;
5256 TEST_DrawLevelField(oldx, oldy);
5257 TEST_DrawLevelField(newx, newy);
5260 void DrawDynamite(int x, int y)
5262 int sx = SCREENX(x), sy = SCREENY(y);
5263 int graphic = el2img(Tile[x][y]);
5266 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5269 if (IS_WALKABLE_INSIDE(Back[x][y]))
5273 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5274 else if (Store[x][y])
5275 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5277 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5279 if (Back[x][y] || Store[x][y])
5280 DrawGraphicThruMask(sx, sy, graphic, frame);
5282 DrawGraphic(sx, sy, graphic, frame);
5285 static void CheckDynamite(int x, int y)
5287 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5291 if (MovDelay[x][y] != 0)
5294 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5300 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5305 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5307 boolean num_checked_players = 0;
5310 for (i = 0; i < MAX_PLAYERS; i++)
5312 if (stored_player[i].active)
5314 int sx = stored_player[i].jx;
5315 int sy = stored_player[i].jy;
5317 if (num_checked_players == 0)
5324 *sx1 = MIN(*sx1, sx);
5325 *sy1 = MIN(*sy1, sy);
5326 *sx2 = MAX(*sx2, sx);
5327 *sy2 = MAX(*sy2, sy);
5330 num_checked_players++;
5335 static boolean checkIfAllPlayersFitToScreen_RND(void)
5337 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5339 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5341 return (sx2 - sx1 < SCR_FIELDX &&
5342 sy2 - sy1 < SCR_FIELDY);
5345 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5347 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5349 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5351 *sx = (sx1 + sx2) / 2;
5352 *sy = (sy1 + sy2) / 2;
5355 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5356 boolean center_screen, boolean quick_relocation)
5358 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5359 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5360 boolean no_delay = (tape.warp_forward);
5361 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5362 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5363 int new_scroll_x, new_scroll_y;
5365 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5367 // case 1: quick relocation inside visible screen (without scrolling)
5374 if (!level.shifted_relocation || center_screen)
5376 // relocation _with_ centering of screen
5378 new_scroll_x = SCROLL_POSITION_X(x);
5379 new_scroll_y = SCROLL_POSITION_Y(y);
5383 // relocation _without_ centering of screen
5385 int center_scroll_x = SCROLL_POSITION_X(old_x);
5386 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5387 int offset_x = x + (scroll_x - center_scroll_x);
5388 int offset_y = y + (scroll_y - center_scroll_y);
5390 // for new screen position, apply previous offset to center position
5391 new_scroll_x = SCROLL_POSITION_X(offset_x);
5392 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5395 if (quick_relocation)
5397 // case 2: quick relocation (redraw without visible scrolling)
5399 scroll_x = new_scroll_x;
5400 scroll_y = new_scroll_y;
5407 // case 3: visible relocation (with scrolling to new position)
5409 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5411 SetVideoFrameDelay(wait_delay_value);
5413 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5415 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5416 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5418 if (dx == 0 && dy == 0) // no scrolling needed at all
5424 // set values for horizontal/vertical screen scrolling (half tile size)
5425 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5426 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5427 int pos_x = dx * TILEX / 2;
5428 int pos_y = dy * TILEY / 2;
5429 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5430 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5432 ScrollLevel(dx, dy);
5435 // scroll in two steps of half tile size to make things smoother
5436 BlitScreenToBitmapExt_RND(window, fx, fy);
5438 // scroll second step to align at full tile size
5439 BlitScreenToBitmap(window);
5445 SetVideoFrameDelay(frame_delay_value_old);
5448 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5450 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5451 int player_nr = GET_PLAYER_NR(el_player);
5452 struct PlayerInfo *player = &stored_player[player_nr];
5453 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5454 boolean no_delay = (tape.warp_forward);
5455 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5456 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5457 int old_jx = player->jx;
5458 int old_jy = player->jy;
5459 int old_element = Tile[old_jx][old_jy];
5460 int element = Tile[jx][jy];
5461 boolean player_relocated = (old_jx != jx || old_jy != jy);
5463 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5464 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5465 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5466 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5467 int leave_side_horiz = move_dir_horiz;
5468 int leave_side_vert = move_dir_vert;
5469 int enter_side = enter_side_horiz | enter_side_vert;
5470 int leave_side = leave_side_horiz | leave_side_vert;
5472 if (player->buried) // do not reanimate dead player
5475 if (!player_relocated) // no need to relocate the player
5478 if (IS_PLAYER(jx, jy)) // player already placed at new position
5480 RemoveField(jx, jy); // temporarily remove newly placed player
5481 DrawLevelField(jx, jy);
5484 if (player->present)
5486 while (player->MovPos)
5488 ScrollPlayer(player, SCROLL_GO_ON);
5489 ScrollScreen(NULL, SCROLL_GO_ON);
5491 AdvanceFrameAndPlayerCounters(player->index_nr);
5495 BackToFront_WithFrameDelay(wait_delay_value);
5498 DrawPlayer(player); // needed here only to cleanup last field
5499 DrawLevelField(player->jx, player->jy); // remove player graphic
5501 player->is_moving = FALSE;
5504 if (IS_CUSTOM_ELEMENT(old_element))
5505 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5507 player->index_bit, leave_side);
5509 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5511 player->index_bit, leave_side);
5513 Tile[jx][jy] = el_player;
5514 InitPlayerField(jx, jy, el_player, TRUE);
5516 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5517 possible that the relocation target field did not contain a player element,
5518 but a walkable element, to which the new player was relocated -- in this
5519 case, restore that (already initialized!) element on the player field */
5520 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5522 Tile[jx][jy] = element; // restore previously existing element
5525 // only visually relocate centered player
5526 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5527 FALSE, level.instant_relocation);
5529 TestIfPlayerTouchesBadThing(jx, jy);
5530 TestIfPlayerTouchesCustomElement(jx, jy);
5532 if (IS_CUSTOM_ELEMENT(element))
5533 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5534 player->index_bit, enter_side);
5536 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5537 player->index_bit, enter_side);
5539 if (player->is_switching)
5541 /* ensure that relocation while still switching an element does not cause
5542 a new element to be treated as also switched directly after relocation
5543 (this is important for teleporter switches that teleport the player to
5544 a place where another teleporter switch is in the same direction, which
5545 would then incorrectly be treated as immediately switched before the
5546 direction key that caused the switch was released) */
5548 player->switch_x += jx - old_jx;
5549 player->switch_y += jy - old_jy;
5553 static void Explode(int ex, int ey, int phase, int mode)
5559 // !!! eliminate this variable !!!
5560 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5562 if (game.explosions_delayed)
5564 ExplodeField[ex][ey] = mode;
5568 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5570 int center_element = Tile[ex][ey];
5571 int artwork_element, explosion_element; // set these values later
5573 // remove things displayed in background while burning dynamite
5574 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5577 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5579 // put moving element to center field (and let it explode there)
5580 center_element = MovingOrBlocked2Element(ex, ey);
5581 RemoveMovingField(ex, ey);
5582 Tile[ex][ey] = center_element;
5585 // now "center_element" is finally determined -- set related values now
5586 artwork_element = center_element; // for custom player artwork
5587 explosion_element = center_element; // for custom player artwork
5589 if (IS_PLAYER(ex, ey))
5591 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5593 artwork_element = stored_player[player_nr].artwork_element;
5595 if (level.use_explosion_element[player_nr])
5597 explosion_element = level.explosion_element[player_nr];
5598 artwork_element = explosion_element;
5602 if (mode == EX_TYPE_NORMAL ||
5603 mode == EX_TYPE_CENTER ||
5604 mode == EX_TYPE_CROSS)
5605 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5607 last_phase = element_info[explosion_element].explosion_delay + 1;
5609 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5611 int xx = x - ex + 1;
5612 int yy = y - ey + 1;
5615 if (!IN_LEV_FIELD(x, y) ||
5616 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5617 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5620 element = Tile[x][y];
5622 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5624 element = MovingOrBlocked2Element(x, y);
5626 if (!IS_EXPLOSION_PROOF(element))
5627 RemoveMovingField(x, y);
5630 // indestructible elements can only explode in center (but not flames)
5631 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5632 mode == EX_TYPE_BORDER)) ||
5633 element == EL_FLAMES)
5636 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5637 behaviour, for example when touching a yamyam that explodes to rocks
5638 with active deadly shield, a rock is created under the player !!! */
5639 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5641 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5642 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5643 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5645 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5648 if (IS_ACTIVE_BOMB(element))
5650 // re-activate things under the bomb like gate or penguin
5651 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5658 // save walkable background elements while explosion on same tile
5659 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5660 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5661 Back[x][y] = element;
5663 // ignite explodable elements reached by other explosion
5664 if (element == EL_EXPLOSION)
5665 element = Store2[x][y];
5667 if (AmoebaNr[x][y] &&
5668 (element == EL_AMOEBA_FULL ||
5669 element == EL_BD_AMOEBA ||
5670 element == EL_AMOEBA_GROWING))
5672 AmoebaCnt[AmoebaNr[x][y]]--;
5673 AmoebaCnt2[AmoebaNr[x][y]]--;
5678 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5680 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5682 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5684 if (PLAYERINFO(ex, ey)->use_murphy)
5685 Store[x][y] = EL_EMPTY;
5688 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5689 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5690 else if (ELEM_IS_PLAYER(center_element))
5691 Store[x][y] = EL_EMPTY;
5692 else if (center_element == EL_YAMYAM)
5693 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5694 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5695 Store[x][y] = element_info[center_element].content.e[xx][yy];
5697 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5698 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5699 // otherwise) -- FIX THIS !!!
5700 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5701 Store[x][y] = element_info[element].content.e[1][1];
5703 else if (!CAN_EXPLODE(element))
5704 Store[x][y] = element_info[element].content.e[1][1];
5707 Store[x][y] = EL_EMPTY;
5709 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5710 center_element == EL_AMOEBA_TO_DIAMOND)
5711 Store2[x][y] = element;
5713 Tile[x][y] = EL_EXPLOSION;
5714 GfxElement[x][y] = artwork_element;
5716 ExplodePhase[x][y] = 1;
5717 ExplodeDelay[x][y] = last_phase;
5722 if (center_element == EL_YAMYAM)
5723 game.yamyam_content_nr =
5724 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5736 GfxFrame[x][y] = 0; // restart explosion animation
5738 last_phase = ExplodeDelay[x][y];
5740 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5742 // this can happen if the player leaves an explosion just in time
5743 if (GfxElement[x][y] == EL_UNDEFINED)
5744 GfxElement[x][y] = EL_EMPTY;
5746 border_element = Store2[x][y];
5747 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5748 border_element = StorePlayer[x][y];
5750 if (phase == element_info[border_element].ignition_delay ||
5751 phase == last_phase)
5753 boolean border_explosion = FALSE;
5755 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5756 !PLAYER_EXPLOSION_PROTECTED(x, y))
5758 KillPlayerUnlessExplosionProtected(x, y);
5759 border_explosion = TRUE;
5761 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5763 Tile[x][y] = Store2[x][y];
5766 border_explosion = TRUE;
5768 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5770 AmoebaToDiamond(x, y);
5772 border_explosion = TRUE;
5775 // if an element just explodes due to another explosion (chain-reaction),
5776 // do not immediately end the new explosion when it was the last frame of
5777 // the explosion (as it would be done in the following "if"-statement!)
5778 if (border_explosion && phase == last_phase)
5782 if (phase == last_phase)
5786 element = Tile[x][y] = Store[x][y];
5787 Store[x][y] = Store2[x][y] = 0;
5788 GfxElement[x][y] = EL_UNDEFINED;
5790 // player can escape from explosions and might therefore be still alive
5791 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5792 element <= EL_PLAYER_IS_EXPLODING_4)
5794 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5795 int explosion_element = EL_PLAYER_1 + player_nr;
5796 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5797 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5799 if (level.use_explosion_element[player_nr])
5800 explosion_element = level.explosion_element[player_nr];
5802 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5803 element_info[explosion_element].content.e[xx][yy]);
5806 // restore probably existing indestructible background element
5807 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5808 element = Tile[x][y] = Back[x][y];
5811 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5812 GfxDir[x][y] = MV_NONE;
5813 ChangeDelay[x][y] = 0;
5814 ChangePage[x][y] = -1;
5816 CustomValue[x][y] = 0;
5818 InitField_WithBug2(x, y, FALSE);
5820 TEST_DrawLevelField(x, y);
5822 TestIfElementTouchesCustomElement(x, y);
5824 if (GFX_CRUMBLED(element))
5825 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5827 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5828 StorePlayer[x][y] = 0;
5830 if (ELEM_IS_PLAYER(element))
5831 RelocatePlayer(x, y, element);
5833 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5835 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5836 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5839 TEST_DrawLevelFieldCrumbled(x, y);
5841 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5843 DrawLevelElement(x, y, Back[x][y]);
5844 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5846 else if (IS_WALKABLE_UNDER(Back[x][y]))
5848 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5849 DrawLevelElementThruMask(x, y, Back[x][y]);
5851 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5852 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5856 static void DynaExplode(int ex, int ey)
5859 int dynabomb_element = Tile[ex][ey];
5860 int dynabomb_size = 1;
5861 boolean dynabomb_xl = FALSE;
5862 struct PlayerInfo *player;
5863 static int xy[4][2] =
5871 if (IS_ACTIVE_BOMB(dynabomb_element))
5873 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5874 dynabomb_size = player->dynabomb_size;
5875 dynabomb_xl = player->dynabomb_xl;
5876 player->dynabombs_left++;
5879 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5881 for (i = 0; i < NUM_DIRECTIONS; i++)
5883 for (j = 1; j <= dynabomb_size; j++)
5885 int x = ex + j * xy[i][0];
5886 int y = ey + j * xy[i][1];
5889 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5892 element = Tile[x][y];
5894 // do not restart explosions of fields with active bombs
5895 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5898 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5900 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5901 !IS_DIGGABLE(element) && !dynabomb_xl)
5907 void Bang(int x, int y)
5909 int element = MovingOrBlocked2Element(x, y);
5910 int explosion_type = EX_TYPE_NORMAL;
5912 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5914 struct PlayerInfo *player = PLAYERINFO(x, y);
5916 element = Tile[x][y] = player->initial_element;
5918 if (level.use_explosion_element[player->index_nr])
5920 int explosion_element = level.explosion_element[player->index_nr];
5922 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5923 explosion_type = EX_TYPE_CROSS;
5924 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5925 explosion_type = EX_TYPE_CENTER;
5933 case EL_BD_BUTTERFLY:
5936 case EL_DARK_YAMYAM:
5940 RaiseScoreElement(element);
5943 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5944 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5945 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5946 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5947 case EL_DYNABOMB_INCREASE_NUMBER:
5948 case EL_DYNABOMB_INCREASE_SIZE:
5949 case EL_DYNABOMB_INCREASE_POWER:
5950 explosion_type = EX_TYPE_DYNA;
5953 case EL_DC_LANDMINE:
5954 explosion_type = EX_TYPE_CENTER;
5959 case EL_LAMP_ACTIVE:
5960 case EL_AMOEBA_TO_DIAMOND:
5961 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5962 explosion_type = EX_TYPE_CENTER;
5966 if (element_info[element].explosion_type == EXPLODES_CROSS)
5967 explosion_type = EX_TYPE_CROSS;
5968 else if (element_info[element].explosion_type == EXPLODES_1X1)
5969 explosion_type = EX_TYPE_CENTER;
5973 if (explosion_type == EX_TYPE_DYNA)
5976 Explode(x, y, EX_PHASE_START, explosion_type);
5978 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5981 static void SplashAcid(int x, int y)
5983 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5984 (!IN_LEV_FIELD(x - 1, y - 2) ||
5985 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5986 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5988 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5989 (!IN_LEV_FIELD(x + 1, y - 2) ||
5990 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5991 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5993 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5996 static void InitBeltMovement(void)
5998 static int belt_base_element[4] =
6000 EL_CONVEYOR_BELT_1_LEFT,
6001 EL_CONVEYOR_BELT_2_LEFT,
6002 EL_CONVEYOR_BELT_3_LEFT,
6003 EL_CONVEYOR_BELT_4_LEFT
6005 static int belt_base_active_element[4] =
6007 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6008 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6009 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6010 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6015 // set frame order for belt animation graphic according to belt direction
6016 for (i = 0; i < NUM_BELTS; i++)
6020 for (j = 0; j < NUM_BELT_PARTS; j++)
6022 int element = belt_base_active_element[belt_nr] + j;
6023 int graphic_1 = el2img(element);
6024 int graphic_2 = el2panelimg(element);
6026 if (game.belt_dir[i] == MV_LEFT)
6028 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6029 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6033 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6034 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6039 SCAN_PLAYFIELD(x, y)
6041 int element = Tile[x][y];
6043 for (i = 0; i < NUM_BELTS; i++)
6045 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6047 int e_belt_nr = getBeltNrFromBeltElement(element);
6050 if (e_belt_nr == belt_nr)
6052 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6054 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6061 static void ToggleBeltSwitch(int x, int y)
6063 static int belt_base_element[4] =
6065 EL_CONVEYOR_BELT_1_LEFT,
6066 EL_CONVEYOR_BELT_2_LEFT,
6067 EL_CONVEYOR_BELT_3_LEFT,
6068 EL_CONVEYOR_BELT_4_LEFT
6070 static int belt_base_active_element[4] =
6072 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6073 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6074 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6075 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6077 static int belt_base_switch_element[4] =
6079 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6080 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6081 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6082 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6084 static int belt_move_dir[4] =
6092 int element = Tile[x][y];
6093 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6094 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6095 int belt_dir = belt_move_dir[belt_dir_nr];
6098 if (!IS_BELT_SWITCH(element))
6101 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6102 game.belt_dir[belt_nr] = belt_dir;
6104 if (belt_dir_nr == 3)
6107 // set frame order for belt animation graphic according to belt direction
6108 for (i = 0; i < NUM_BELT_PARTS; i++)
6110 int element = belt_base_active_element[belt_nr] + i;
6111 int graphic_1 = el2img(element);
6112 int graphic_2 = el2panelimg(element);
6114 if (belt_dir == MV_LEFT)
6116 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6117 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6121 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6122 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6126 SCAN_PLAYFIELD(xx, yy)
6128 int element = Tile[xx][yy];
6130 if (IS_BELT_SWITCH(element))
6132 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6134 if (e_belt_nr == belt_nr)
6136 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6137 TEST_DrawLevelField(xx, yy);
6140 else if (IS_BELT(element) && belt_dir != MV_NONE)
6142 int e_belt_nr = getBeltNrFromBeltElement(element);
6144 if (e_belt_nr == belt_nr)
6146 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6148 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6149 TEST_DrawLevelField(xx, yy);
6152 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6154 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6156 if (e_belt_nr == belt_nr)
6158 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6160 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6161 TEST_DrawLevelField(xx, yy);
6167 static void ToggleSwitchgateSwitch(int x, int y)
6171 game.switchgate_pos = !game.switchgate_pos;
6173 SCAN_PLAYFIELD(xx, yy)
6175 int element = Tile[xx][yy];
6177 if (element == EL_SWITCHGATE_SWITCH_UP)
6179 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6180 TEST_DrawLevelField(xx, yy);
6182 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6184 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6185 TEST_DrawLevelField(xx, yy);
6187 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6189 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6190 TEST_DrawLevelField(xx, yy);
6192 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6194 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6195 TEST_DrawLevelField(xx, yy);
6197 else if (element == EL_SWITCHGATE_OPEN ||
6198 element == EL_SWITCHGATE_OPENING)
6200 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6202 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6204 else if (element == EL_SWITCHGATE_CLOSED ||
6205 element == EL_SWITCHGATE_CLOSING)
6207 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6209 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6214 static int getInvisibleActiveFromInvisibleElement(int element)
6216 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6217 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6218 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6222 static int getInvisibleFromInvisibleActiveElement(int element)
6224 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6225 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6226 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6230 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6234 SCAN_PLAYFIELD(x, y)
6236 int element = Tile[x][y];
6238 if (element == EL_LIGHT_SWITCH &&
6239 game.light_time_left > 0)
6241 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6242 TEST_DrawLevelField(x, y);
6244 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6245 game.light_time_left == 0)
6247 Tile[x][y] = EL_LIGHT_SWITCH;
6248 TEST_DrawLevelField(x, y);
6250 else if (element == EL_EMC_DRIPPER &&
6251 game.light_time_left > 0)
6253 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6254 TEST_DrawLevelField(x, y);
6256 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6257 game.light_time_left == 0)
6259 Tile[x][y] = EL_EMC_DRIPPER;
6260 TEST_DrawLevelField(x, y);
6262 else if (element == EL_INVISIBLE_STEELWALL ||
6263 element == EL_INVISIBLE_WALL ||
6264 element == EL_INVISIBLE_SAND)
6266 if (game.light_time_left > 0)
6267 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6269 TEST_DrawLevelField(x, y);
6271 // uncrumble neighbour fields, if needed
6272 if (element == EL_INVISIBLE_SAND)
6273 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6275 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6276 element == EL_INVISIBLE_WALL_ACTIVE ||
6277 element == EL_INVISIBLE_SAND_ACTIVE)
6279 if (game.light_time_left == 0)
6280 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6282 TEST_DrawLevelField(x, y);
6284 // re-crumble neighbour fields, if needed
6285 if (element == EL_INVISIBLE_SAND)
6286 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6291 static void RedrawAllInvisibleElementsForLenses(void)
6295 SCAN_PLAYFIELD(x, y)
6297 int element = Tile[x][y];
6299 if (element == EL_EMC_DRIPPER &&
6300 game.lenses_time_left > 0)
6302 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6303 TEST_DrawLevelField(x, y);
6305 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6306 game.lenses_time_left == 0)
6308 Tile[x][y] = EL_EMC_DRIPPER;
6309 TEST_DrawLevelField(x, y);
6311 else if (element == EL_INVISIBLE_STEELWALL ||
6312 element == EL_INVISIBLE_WALL ||
6313 element == EL_INVISIBLE_SAND)
6315 if (game.lenses_time_left > 0)
6316 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6318 TEST_DrawLevelField(x, y);
6320 // uncrumble neighbour fields, if needed
6321 if (element == EL_INVISIBLE_SAND)
6322 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6324 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6325 element == EL_INVISIBLE_WALL_ACTIVE ||
6326 element == EL_INVISIBLE_SAND_ACTIVE)
6328 if (game.lenses_time_left == 0)
6329 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6331 TEST_DrawLevelField(x, y);
6333 // re-crumble neighbour fields, if needed
6334 if (element == EL_INVISIBLE_SAND)
6335 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6340 static void RedrawAllInvisibleElementsForMagnifier(void)
6344 SCAN_PLAYFIELD(x, y)
6346 int element = Tile[x][y];
6348 if (element == EL_EMC_FAKE_GRASS &&
6349 game.magnify_time_left > 0)
6351 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6352 TEST_DrawLevelField(x, y);
6354 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6355 game.magnify_time_left == 0)
6357 Tile[x][y] = EL_EMC_FAKE_GRASS;
6358 TEST_DrawLevelField(x, y);
6360 else if (IS_GATE_GRAY(element) &&
6361 game.magnify_time_left > 0)
6363 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6364 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6365 IS_EM_GATE_GRAY(element) ?
6366 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6367 IS_EMC_GATE_GRAY(element) ?
6368 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6369 IS_DC_GATE_GRAY(element) ?
6370 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6372 TEST_DrawLevelField(x, y);
6374 else if (IS_GATE_GRAY_ACTIVE(element) &&
6375 game.magnify_time_left == 0)
6377 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6378 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6379 IS_EM_GATE_GRAY_ACTIVE(element) ?
6380 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6381 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6382 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6383 IS_DC_GATE_GRAY_ACTIVE(element) ?
6384 EL_DC_GATE_WHITE_GRAY :
6386 TEST_DrawLevelField(x, y);
6391 static void ToggleLightSwitch(int x, int y)
6393 int element = Tile[x][y];
6395 game.light_time_left =
6396 (element == EL_LIGHT_SWITCH ?
6397 level.time_light * FRAMES_PER_SECOND : 0);
6399 RedrawAllLightSwitchesAndInvisibleElements();
6402 static void ActivateTimegateSwitch(int x, int y)
6406 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6408 SCAN_PLAYFIELD(xx, yy)
6410 int element = Tile[xx][yy];
6412 if (element == EL_TIMEGATE_CLOSED ||
6413 element == EL_TIMEGATE_CLOSING)
6415 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6416 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6420 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6422 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6423 TEST_DrawLevelField(xx, yy);
6429 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6430 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6433 static void Impact(int x, int y)
6435 boolean last_line = (y == lev_fieldy - 1);
6436 boolean object_hit = FALSE;
6437 boolean impact = (last_line || object_hit);
6438 int element = Tile[x][y];
6439 int smashed = EL_STEELWALL;
6441 if (!last_line) // check if element below was hit
6443 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6446 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6447 MovDir[x][y + 1] != MV_DOWN ||
6448 MovPos[x][y + 1] <= TILEY / 2));
6450 // do not smash moving elements that left the smashed field in time
6451 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6452 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6455 #if USE_QUICKSAND_IMPACT_BUGFIX
6456 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6458 RemoveMovingField(x, y + 1);
6459 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6460 Tile[x][y + 2] = EL_ROCK;
6461 TEST_DrawLevelField(x, y + 2);
6466 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6468 RemoveMovingField(x, y + 1);
6469 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6470 Tile[x][y + 2] = EL_ROCK;
6471 TEST_DrawLevelField(x, y + 2);
6478 smashed = MovingOrBlocked2Element(x, y + 1);
6480 impact = (last_line || object_hit);
6483 if (!last_line && smashed == EL_ACID) // element falls into acid
6485 SplashAcid(x, y + 1);
6489 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6490 // only reset graphic animation if graphic really changes after impact
6492 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6494 ResetGfxAnimation(x, y);
6495 TEST_DrawLevelField(x, y);
6498 if (impact && CAN_EXPLODE_IMPACT(element))
6503 else if (impact && element == EL_PEARL &&
6504 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6506 ResetGfxAnimation(x, y);
6508 Tile[x][y] = EL_PEARL_BREAKING;
6509 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6512 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6514 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6519 if (impact && element == EL_AMOEBA_DROP)
6521 if (object_hit && IS_PLAYER(x, y + 1))
6522 KillPlayerUnlessEnemyProtected(x, y + 1);
6523 else if (object_hit && smashed == EL_PENGUIN)
6527 Tile[x][y] = EL_AMOEBA_GROWING;
6528 Store[x][y] = EL_AMOEBA_WET;
6530 ResetRandomAnimationValue(x, y);
6535 if (object_hit) // check which object was hit
6537 if ((CAN_PASS_MAGIC_WALL(element) &&
6538 (smashed == EL_MAGIC_WALL ||
6539 smashed == EL_BD_MAGIC_WALL)) ||
6540 (CAN_PASS_DC_MAGIC_WALL(element) &&
6541 smashed == EL_DC_MAGIC_WALL))
6544 int activated_magic_wall =
6545 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6546 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6547 EL_DC_MAGIC_WALL_ACTIVE);
6549 // activate magic wall / mill
6550 SCAN_PLAYFIELD(xx, yy)
6552 if (Tile[xx][yy] == smashed)
6553 Tile[xx][yy] = activated_magic_wall;
6556 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6557 game.magic_wall_active = TRUE;
6559 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6560 SND_MAGIC_WALL_ACTIVATING :
6561 smashed == EL_BD_MAGIC_WALL ?
6562 SND_BD_MAGIC_WALL_ACTIVATING :
6563 SND_DC_MAGIC_WALL_ACTIVATING));
6566 if (IS_PLAYER(x, y + 1))
6568 if (CAN_SMASH_PLAYER(element))
6570 KillPlayerUnlessEnemyProtected(x, y + 1);
6574 else if (smashed == EL_PENGUIN)
6576 if (CAN_SMASH_PLAYER(element))
6582 else if (element == EL_BD_DIAMOND)
6584 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6590 else if (((element == EL_SP_INFOTRON ||
6591 element == EL_SP_ZONK) &&
6592 (smashed == EL_SP_SNIKSNAK ||
6593 smashed == EL_SP_ELECTRON ||
6594 smashed == EL_SP_DISK_ORANGE)) ||
6595 (element == EL_SP_INFOTRON &&
6596 smashed == EL_SP_DISK_YELLOW))
6601 else if (CAN_SMASH_EVERYTHING(element))
6603 if (IS_CLASSIC_ENEMY(smashed) ||
6604 CAN_EXPLODE_SMASHED(smashed))
6609 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6611 if (smashed == EL_LAMP ||
6612 smashed == EL_LAMP_ACTIVE)
6617 else if (smashed == EL_NUT)
6619 Tile[x][y + 1] = EL_NUT_BREAKING;
6620 PlayLevelSound(x, y, SND_NUT_BREAKING);
6621 RaiseScoreElement(EL_NUT);
6624 else if (smashed == EL_PEARL)
6626 ResetGfxAnimation(x, y);
6628 Tile[x][y + 1] = EL_PEARL_BREAKING;
6629 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6632 else if (smashed == EL_DIAMOND)
6634 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6635 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6638 else if (IS_BELT_SWITCH(smashed))
6640 ToggleBeltSwitch(x, y + 1);
6642 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6643 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6644 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6645 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6647 ToggleSwitchgateSwitch(x, y + 1);
6649 else if (smashed == EL_LIGHT_SWITCH ||
6650 smashed == EL_LIGHT_SWITCH_ACTIVE)
6652 ToggleLightSwitch(x, y + 1);
6656 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6658 CheckElementChangeBySide(x, y + 1, smashed, element,
6659 CE_SWITCHED, CH_SIDE_TOP);
6660 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6666 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6671 // play sound of magic wall / mill
6673 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6674 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6675 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6677 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6678 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6679 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6680 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6681 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6682 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6687 // play sound of object that hits the ground
6688 if (last_line || object_hit)
6689 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6692 static void TurnRoundExt(int x, int y)
6704 { 0, 0 }, { 0, 0 }, { 0, 0 },
6709 int left, right, back;
6713 { MV_DOWN, MV_UP, MV_RIGHT },
6714 { MV_UP, MV_DOWN, MV_LEFT },
6716 { MV_LEFT, MV_RIGHT, MV_DOWN },
6720 { MV_RIGHT, MV_LEFT, MV_UP }
6723 int element = Tile[x][y];
6724 int move_pattern = element_info[element].move_pattern;
6726 int old_move_dir = MovDir[x][y];
6727 int left_dir = turn[old_move_dir].left;
6728 int right_dir = turn[old_move_dir].right;
6729 int back_dir = turn[old_move_dir].back;
6731 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6732 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6733 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6734 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6736 int left_x = x + left_dx, left_y = y + left_dy;
6737 int right_x = x + right_dx, right_y = y + right_dy;
6738 int move_x = x + move_dx, move_y = y + move_dy;
6742 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6744 TestIfBadThingTouchesOtherBadThing(x, y);
6746 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6747 MovDir[x][y] = right_dir;
6748 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6749 MovDir[x][y] = left_dir;
6751 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6753 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6756 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6758 TestIfBadThingTouchesOtherBadThing(x, y);
6760 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6761 MovDir[x][y] = left_dir;
6762 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6763 MovDir[x][y] = right_dir;
6765 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6767 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6770 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6772 TestIfBadThingTouchesOtherBadThing(x, y);
6774 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6775 MovDir[x][y] = left_dir;
6776 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6777 MovDir[x][y] = right_dir;
6779 if (MovDir[x][y] != old_move_dir)
6782 else if (element == EL_YAMYAM)
6784 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6785 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6787 if (can_turn_left && can_turn_right)
6788 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6789 else if (can_turn_left)
6790 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6791 else if (can_turn_right)
6792 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6794 MovDir[x][y] = back_dir;
6796 MovDelay[x][y] = 16 + 16 * RND(3);
6798 else if (element == EL_DARK_YAMYAM)
6800 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6802 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6805 if (can_turn_left && can_turn_right)
6806 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6807 else if (can_turn_left)
6808 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6809 else if (can_turn_right)
6810 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6812 MovDir[x][y] = back_dir;
6814 MovDelay[x][y] = 16 + 16 * RND(3);
6816 else if (element == EL_PACMAN)
6818 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6819 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6821 if (can_turn_left && can_turn_right)
6822 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6823 else if (can_turn_left)
6824 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6825 else if (can_turn_right)
6826 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6828 MovDir[x][y] = back_dir;
6830 MovDelay[x][y] = 6 + RND(40);
6832 else if (element == EL_PIG)
6834 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6835 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6836 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6837 boolean should_turn_left, should_turn_right, should_move_on;
6839 int rnd = RND(rnd_value);
6841 should_turn_left = (can_turn_left &&
6843 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6844 y + back_dy + left_dy)));
6845 should_turn_right = (can_turn_right &&
6847 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6848 y + back_dy + right_dy)));
6849 should_move_on = (can_move_on &&
6852 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6853 y + move_dy + left_dy) ||
6854 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6855 y + move_dy + right_dy)));
6857 if (should_turn_left || should_turn_right || should_move_on)
6859 if (should_turn_left && should_turn_right && should_move_on)
6860 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6861 rnd < 2 * rnd_value / 3 ? right_dir :
6863 else if (should_turn_left && should_turn_right)
6864 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6865 else if (should_turn_left && should_move_on)
6866 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6867 else if (should_turn_right && should_move_on)
6868 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6869 else if (should_turn_left)
6870 MovDir[x][y] = left_dir;
6871 else if (should_turn_right)
6872 MovDir[x][y] = right_dir;
6873 else if (should_move_on)
6874 MovDir[x][y] = old_move_dir;
6876 else if (can_move_on && rnd > rnd_value / 8)
6877 MovDir[x][y] = old_move_dir;
6878 else if (can_turn_left && can_turn_right)
6879 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6880 else if (can_turn_left && rnd > rnd_value / 8)
6881 MovDir[x][y] = left_dir;
6882 else if (can_turn_right && rnd > rnd_value/8)
6883 MovDir[x][y] = right_dir;
6885 MovDir[x][y] = back_dir;
6887 xx = x + move_xy[MovDir[x][y]].dx;
6888 yy = y + move_xy[MovDir[x][y]].dy;
6890 if (!IN_LEV_FIELD(xx, yy) ||
6891 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6892 MovDir[x][y] = old_move_dir;
6896 else if (element == EL_DRAGON)
6898 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6899 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6900 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6902 int rnd = RND(rnd_value);
6904 if (can_move_on && rnd > rnd_value / 8)
6905 MovDir[x][y] = old_move_dir;
6906 else if (can_turn_left && can_turn_right)
6907 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6908 else if (can_turn_left && rnd > rnd_value / 8)
6909 MovDir[x][y] = left_dir;
6910 else if (can_turn_right && rnd > rnd_value / 8)
6911 MovDir[x][y] = right_dir;
6913 MovDir[x][y] = back_dir;
6915 xx = x + move_xy[MovDir[x][y]].dx;
6916 yy = y + move_xy[MovDir[x][y]].dy;
6918 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6919 MovDir[x][y] = old_move_dir;
6923 else if (element == EL_MOLE)
6925 boolean can_move_on =
6926 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6927 IS_AMOEBOID(Tile[move_x][move_y]) ||
6928 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6931 boolean can_turn_left =
6932 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6933 IS_AMOEBOID(Tile[left_x][left_y])));
6935 boolean can_turn_right =
6936 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6937 IS_AMOEBOID(Tile[right_x][right_y])));
6939 if (can_turn_left && can_turn_right)
6940 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6941 else if (can_turn_left)
6942 MovDir[x][y] = left_dir;
6944 MovDir[x][y] = right_dir;
6947 if (MovDir[x][y] != old_move_dir)
6950 else if (element == EL_BALLOON)
6952 MovDir[x][y] = game.wind_direction;
6955 else if (element == EL_SPRING)
6957 if (MovDir[x][y] & MV_HORIZONTAL)
6959 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6960 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6962 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6963 ResetGfxAnimation(move_x, move_y);
6964 TEST_DrawLevelField(move_x, move_y);
6966 MovDir[x][y] = back_dir;
6968 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6969 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6970 MovDir[x][y] = MV_NONE;
6975 else if (element == EL_ROBOT ||
6976 element == EL_SATELLITE ||
6977 element == EL_PENGUIN ||
6978 element == EL_EMC_ANDROID)
6980 int attr_x = -1, attr_y = -1;
6982 if (game.all_players_gone)
6984 attr_x = game.exit_x;
6985 attr_y = game.exit_y;
6991 for (i = 0; i < MAX_PLAYERS; i++)
6993 struct PlayerInfo *player = &stored_player[i];
6994 int jx = player->jx, jy = player->jy;
6996 if (!player->active)
7000 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7008 if (element == EL_ROBOT &&
7009 game.robot_wheel_x >= 0 &&
7010 game.robot_wheel_y >= 0 &&
7011 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7012 game.engine_version < VERSION_IDENT(3,1,0,0)))
7014 attr_x = game.robot_wheel_x;
7015 attr_y = game.robot_wheel_y;
7018 if (element == EL_PENGUIN)
7021 static int xy[4][2] =
7029 for (i = 0; i < NUM_DIRECTIONS; i++)
7031 int ex = x + xy[i][0];
7032 int ey = y + xy[i][1];
7034 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7035 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7036 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7037 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7046 MovDir[x][y] = MV_NONE;
7048 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7049 else if (attr_x > x)
7050 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7052 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7053 else if (attr_y > y)
7054 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7056 if (element == EL_ROBOT)
7060 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7061 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7062 Moving2Blocked(x, y, &newx, &newy);
7064 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7065 MovDelay[x][y] = 8 + 8 * !RND(3);
7067 MovDelay[x][y] = 16;
7069 else if (element == EL_PENGUIN)
7075 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7077 boolean first_horiz = RND(2);
7078 int new_move_dir = MovDir[x][y];
7081 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7082 Moving2Blocked(x, y, &newx, &newy);
7084 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7088 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7089 Moving2Blocked(x, y, &newx, &newy);
7091 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7094 MovDir[x][y] = old_move_dir;
7098 else if (element == EL_SATELLITE)
7104 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7106 boolean first_horiz = RND(2);
7107 int new_move_dir = MovDir[x][y];
7110 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7111 Moving2Blocked(x, y, &newx, &newy);
7113 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7117 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7118 Moving2Blocked(x, y, &newx, &newy);
7120 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7123 MovDir[x][y] = old_move_dir;
7127 else if (element == EL_EMC_ANDROID)
7129 static int check_pos[16] =
7131 -1, // 0 => (invalid)
7134 -1, // 3 => (invalid)
7136 0, // 5 => MV_LEFT | MV_UP
7137 2, // 6 => MV_RIGHT | MV_UP
7138 -1, // 7 => (invalid)
7140 6, // 9 => MV_LEFT | MV_DOWN
7141 4, // 10 => MV_RIGHT | MV_DOWN
7142 -1, // 11 => (invalid)
7143 -1, // 12 => (invalid)
7144 -1, // 13 => (invalid)
7145 -1, // 14 => (invalid)
7146 -1, // 15 => (invalid)
7154 { -1, -1, MV_LEFT | MV_UP },
7156 { +1, -1, MV_RIGHT | MV_UP },
7157 { +1, 0, MV_RIGHT },
7158 { +1, +1, MV_RIGHT | MV_DOWN },
7160 { -1, +1, MV_LEFT | MV_DOWN },
7163 int start_pos, check_order;
7164 boolean can_clone = FALSE;
7167 // check if there is any free field around current position
7168 for (i = 0; i < 8; i++)
7170 int newx = x + check_xy[i].dx;
7171 int newy = y + check_xy[i].dy;
7173 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7181 if (can_clone) // randomly find an element to clone
7185 start_pos = check_pos[RND(8)];
7186 check_order = (RND(2) ? -1 : +1);
7188 for (i = 0; i < 8; i++)
7190 int pos_raw = start_pos + i * check_order;
7191 int pos = (pos_raw + 8) % 8;
7192 int newx = x + check_xy[pos].dx;
7193 int newy = y + check_xy[pos].dy;
7195 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7197 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7198 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7200 Store[x][y] = Tile[newx][newy];
7209 if (can_clone) // randomly find a direction to move
7213 start_pos = check_pos[RND(8)];
7214 check_order = (RND(2) ? -1 : +1);
7216 for (i = 0; i < 8; i++)
7218 int pos_raw = start_pos + i * check_order;
7219 int pos = (pos_raw + 8) % 8;
7220 int newx = x + check_xy[pos].dx;
7221 int newy = y + check_xy[pos].dy;
7222 int new_move_dir = check_xy[pos].dir;
7224 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7226 MovDir[x][y] = new_move_dir;
7227 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7236 if (can_clone) // cloning and moving successful
7239 // cannot clone -- try to move towards player
7241 start_pos = check_pos[MovDir[x][y] & 0x0f];
7242 check_order = (RND(2) ? -1 : +1);
7244 for (i = 0; i < 3; i++)
7246 // first check start_pos, then previous/next or (next/previous) pos
7247 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7248 int pos = (pos_raw + 8) % 8;
7249 int newx = x + check_xy[pos].dx;
7250 int newy = y + check_xy[pos].dy;
7251 int new_move_dir = check_xy[pos].dir;
7253 if (IS_PLAYER(newx, newy))
7256 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7258 MovDir[x][y] = new_move_dir;
7259 MovDelay[x][y] = level.android_move_time * 8 + 1;
7266 else if (move_pattern == MV_TURNING_LEFT ||
7267 move_pattern == MV_TURNING_RIGHT ||
7268 move_pattern == MV_TURNING_LEFT_RIGHT ||
7269 move_pattern == MV_TURNING_RIGHT_LEFT ||
7270 move_pattern == MV_TURNING_RANDOM ||
7271 move_pattern == MV_ALL_DIRECTIONS)
7273 boolean can_turn_left =
7274 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7275 boolean can_turn_right =
7276 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7278 if (element_info[element].move_stepsize == 0) // "not moving"
7281 if (move_pattern == MV_TURNING_LEFT)
7282 MovDir[x][y] = left_dir;
7283 else if (move_pattern == MV_TURNING_RIGHT)
7284 MovDir[x][y] = right_dir;
7285 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7286 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7287 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7288 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7289 else if (move_pattern == MV_TURNING_RANDOM)
7290 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7291 can_turn_right && !can_turn_left ? right_dir :
7292 RND(2) ? left_dir : right_dir);
7293 else if (can_turn_left && can_turn_right)
7294 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7295 else if (can_turn_left)
7296 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7297 else if (can_turn_right)
7298 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7300 MovDir[x][y] = back_dir;
7302 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7304 else if (move_pattern == MV_HORIZONTAL ||
7305 move_pattern == MV_VERTICAL)
7307 if (move_pattern & old_move_dir)
7308 MovDir[x][y] = back_dir;
7309 else if (move_pattern == MV_HORIZONTAL)
7310 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7311 else if (move_pattern == MV_VERTICAL)
7312 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7314 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7316 else if (move_pattern & MV_ANY_DIRECTION)
7318 MovDir[x][y] = move_pattern;
7319 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7321 else if (move_pattern & MV_WIND_DIRECTION)
7323 MovDir[x][y] = game.wind_direction;
7324 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7326 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7328 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7329 MovDir[x][y] = left_dir;
7330 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7331 MovDir[x][y] = right_dir;
7333 if (MovDir[x][y] != old_move_dir)
7334 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7336 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7338 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7339 MovDir[x][y] = right_dir;
7340 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7341 MovDir[x][y] = left_dir;
7343 if (MovDir[x][y] != old_move_dir)
7344 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7346 else if (move_pattern == MV_TOWARDS_PLAYER ||
7347 move_pattern == MV_AWAY_FROM_PLAYER)
7349 int attr_x = -1, attr_y = -1;
7351 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7353 if (game.all_players_gone)
7355 attr_x = game.exit_x;
7356 attr_y = game.exit_y;
7362 for (i = 0; i < MAX_PLAYERS; i++)
7364 struct PlayerInfo *player = &stored_player[i];
7365 int jx = player->jx, jy = player->jy;
7367 if (!player->active)
7371 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7379 MovDir[x][y] = MV_NONE;
7381 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7382 else if (attr_x > x)
7383 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7385 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7386 else if (attr_y > y)
7387 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7389 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7391 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7393 boolean first_horiz = RND(2);
7394 int new_move_dir = MovDir[x][y];
7396 if (element_info[element].move_stepsize == 0) // "not moving"
7398 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7399 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7405 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7406 Moving2Blocked(x, y, &newx, &newy);
7408 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7412 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7413 Moving2Blocked(x, y, &newx, &newy);
7415 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7418 MovDir[x][y] = old_move_dir;
7421 else if (move_pattern == MV_WHEN_PUSHED ||
7422 move_pattern == MV_WHEN_DROPPED)
7424 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7425 MovDir[x][y] = MV_NONE;
7429 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7431 static int test_xy[7][2] =
7441 static int test_dir[7] =
7451 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7452 int move_preference = -1000000; // start with very low preference
7453 int new_move_dir = MV_NONE;
7454 int start_test = RND(4);
7457 for (i = 0; i < NUM_DIRECTIONS; i++)
7459 int move_dir = test_dir[start_test + i];
7460 int move_dir_preference;
7462 xx = x + test_xy[start_test + i][0];
7463 yy = y + test_xy[start_test + i][1];
7465 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7466 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7468 new_move_dir = move_dir;
7473 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7476 move_dir_preference = -1 * RunnerVisit[xx][yy];
7477 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7478 move_dir_preference = PlayerVisit[xx][yy];
7480 if (move_dir_preference > move_preference)
7482 // prefer field that has not been visited for the longest time
7483 move_preference = move_dir_preference;
7484 new_move_dir = move_dir;
7486 else if (move_dir_preference == move_preference &&
7487 move_dir == old_move_dir)
7489 // prefer last direction when all directions are preferred equally
7490 move_preference = move_dir_preference;
7491 new_move_dir = move_dir;
7495 MovDir[x][y] = new_move_dir;
7496 if (old_move_dir != new_move_dir)
7497 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7501 static void TurnRound(int x, int y)
7503 int direction = MovDir[x][y];
7507 GfxDir[x][y] = MovDir[x][y];
7509 if (direction != MovDir[x][y])
7513 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7515 ResetGfxFrame(x, y);
7518 static boolean JustBeingPushed(int x, int y)
7522 for (i = 0; i < MAX_PLAYERS; i++)
7524 struct PlayerInfo *player = &stored_player[i];
7526 if (player->active && player->is_pushing && player->MovPos)
7528 int next_jx = player->jx + (player->jx - player->last_jx);
7529 int next_jy = player->jy + (player->jy - player->last_jy);
7531 if (x == next_jx && y == next_jy)
7539 static void StartMoving(int x, int y)
7541 boolean started_moving = FALSE; // some elements can fall _and_ move
7542 int element = Tile[x][y];
7547 if (MovDelay[x][y] == 0)
7548 GfxAction[x][y] = ACTION_DEFAULT;
7550 if (CAN_FALL(element) && y < lev_fieldy - 1)
7552 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7553 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7554 if (JustBeingPushed(x, y))
7557 if (element == EL_QUICKSAND_FULL)
7559 if (IS_FREE(x, y + 1))
7561 InitMovingField(x, y, MV_DOWN);
7562 started_moving = TRUE;
7564 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7565 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7566 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7567 Store[x][y] = EL_ROCK;
7569 Store[x][y] = EL_ROCK;
7572 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7574 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7576 if (!MovDelay[x][y])
7578 MovDelay[x][y] = TILEY + 1;
7580 ResetGfxAnimation(x, y);
7581 ResetGfxAnimation(x, y + 1);
7586 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7587 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7594 Tile[x][y] = EL_QUICKSAND_EMPTY;
7595 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7596 Store[x][y + 1] = Store[x][y];
7599 PlayLevelSoundAction(x, y, ACTION_FILLING);
7601 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7603 if (!MovDelay[x][y])
7605 MovDelay[x][y] = TILEY + 1;
7607 ResetGfxAnimation(x, y);
7608 ResetGfxAnimation(x, y + 1);
7613 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7614 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7621 Tile[x][y] = EL_QUICKSAND_EMPTY;
7622 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7623 Store[x][y + 1] = Store[x][y];
7626 PlayLevelSoundAction(x, y, ACTION_FILLING);
7629 else if (element == EL_QUICKSAND_FAST_FULL)
7631 if (IS_FREE(x, y + 1))
7633 InitMovingField(x, y, MV_DOWN);
7634 started_moving = TRUE;
7636 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7637 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7638 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7639 Store[x][y] = EL_ROCK;
7641 Store[x][y] = EL_ROCK;
7644 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7646 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7648 if (!MovDelay[x][y])
7650 MovDelay[x][y] = TILEY + 1;
7652 ResetGfxAnimation(x, y);
7653 ResetGfxAnimation(x, y + 1);
7658 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7659 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7666 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7667 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7668 Store[x][y + 1] = Store[x][y];
7671 PlayLevelSoundAction(x, y, ACTION_FILLING);
7673 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7675 if (!MovDelay[x][y])
7677 MovDelay[x][y] = TILEY + 1;
7679 ResetGfxAnimation(x, y);
7680 ResetGfxAnimation(x, y + 1);
7685 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7686 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7693 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7694 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7695 Store[x][y + 1] = Store[x][y];
7698 PlayLevelSoundAction(x, y, ACTION_FILLING);
7701 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7702 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7704 InitMovingField(x, y, MV_DOWN);
7705 started_moving = TRUE;
7707 Tile[x][y] = EL_QUICKSAND_FILLING;
7708 Store[x][y] = element;
7710 PlayLevelSoundAction(x, y, ACTION_FILLING);
7712 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7713 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7719 Store[x][y] = element;
7721 PlayLevelSoundAction(x, y, ACTION_FILLING);
7723 else if (element == EL_MAGIC_WALL_FULL)
7725 if (IS_FREE(x, y + 1))
7727 InitMovingField(x, y, MV_DOWN);
7728 started_moving = TRUE;
7730 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7731 Store[x][y] = EL_CHANGED(Store[x][y]);
7733 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7735 if (!MovDelay[x][y])
7736 MovDelay[x][y] = TILEY / 4 + 1;
7745 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7746 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7747 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7751 else if (element == EL_BD_MAGIC_WALL_FULL)
7753 if (IS_FREE(x, y + 1))
7755 InitMovingField(x, y, MV_DOWN);
7756 started_moving = TRUE;
7758 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7759 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7761 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7763 if (!MovDelay[x][y])
7764 MovDelay[x][y] = TILEY / 4 + 1;
7773 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7774 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7775 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7779 else if (element == EL_DC_MAGIC_WALL_FULL)
7781 if (IS_FREE(x, y + 1))
7783 InitMovingField(x, y, MV_DOWN);
7784 started_moving = TRUE;
7786 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7787 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7789 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7791 if (!MovDelay[x][y])
7792 MovDelay[x][y] = TILEY / 4 + 1;
7801 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7802 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7803 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7807 else if ((CAN_PASS_MAGIC_WALL(element) &&
7808 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7809 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7810 (CAN_PASS_DC_MAGIC_WALL(element) &&
7811 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7814 InitMovingField(x, y, MV_DOWN);
7815 started_moving = TRUE;
7818 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7819 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7820 EL_DC_MAGIC_WALL_FILLING);
7821 Store[x][y] = element;
7823 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7825 SplashAcid(x, y + 1);
7827 InitMovingField(x, y, MV_DOWN);
7828 started_moving = TRUE;
7830 Store[x][y] = EL_ACID;
7833 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7834 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7835 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7836 CAN_FALL(element) && WasJustFalling[x][y] &&
7837 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7839 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7840 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7841 (Tile[x][y + 1] == EL_BLOCKED)))
7843 /* this is needed for a special case not covered by calling "Impact()"
7844 from "ContinueMoving()": if an element moves to a tile directly below
7845 another element which was just falling on that tile (which was empty
7846 in the previous frame), the falling element above would just stop
7847 instead of smashing the element below (in previous version, the above
7848 element was just checked for "moving" instead of "falling", resulting
7849 in incorrect smashes caused by horizontal movement of the above
7850 element; also, the case of the player being the element to smash was
7851 simply not covered here... :-/ ) */
7853 CheckCollision[x][y] = 0;
7854 CheckImpact[x][y] = 0;
7858 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7860 if (MovDir[x][y] == MV_NONE)
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7866 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7868 if (WasJustFalling[x][y]) // prevent animation from being restarted
7869 MovDir[x][y] = MV_DOWN;
7871 InitMovingField(x, y, MV_DOWN);
7872 started_moving = TRUE;
7874 else if (element == EL_AMOEBA_DROP)
7876 Tile[x][y] = EL_AMOEBA_GROWING;
7877 Store[x][y] = EL_AMOEBA_WET;
7879 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7880 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7881 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7882 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7884 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7885 (IS_FREE(x - 1, y + 1) ||
7886 Tile[x - 1][y + 1] == EL_ACID));
7887 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7888 (IS_FREE(x + 1, y + 1) ||
7889 Tile[x + 1][y + 1] == EL_ACID));
7890 boolean can_fall_any = (can_fall_left || can_fall_right);
7891 boolean can_fall_both = (can_fall_left && can_fall_right);
7892 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7894 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7896 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7897 can_fall_right = FALSE;
7898 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7899 can_fall_left = FALSE;
7900 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7901 can_fall_right = FALSE;
7902 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7903 can_fall_left = FALSE;
7905 can_fall_any = (can_fall_left || can_fall_right);
7906 can_fall_both = FALSE;
7911 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7912 can_fall_right = FALSE; // slip down on left side
7914 can_fall_left = !(can_fall_right = RND(2));
7916 can_fall_both = FALSE;
7921 // if not determined otherwise, prefer left side for slipping down
7922 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7923 started_moving = TRUE;
7926 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7928 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7929 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7930 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7931 int belt_dir = game.belt_dir[belt_nr];
7933 if ((belt_dir == MV_LEFT && left_is_free) ||
7934 (belt_dir == MV_RIGHT && right_is_free))
7936 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7938 InitMovingField(x, y, belt_dir);
7939 started_moving = TRUE;
7941 Pushed[x][y] = TRUE;
7942 Pushed[nextx][y] = TRUE;
7944 GfxAction[x][y] = ACTION_DEFAULT;
7948 MovDir[x][y] = 0; // if element was moving, stop it
7953 // not "else if" because of elements that can fall and move (EL_SPRING)
7954 if (CAN_MOVE(element) && !started_moving)
7956 int move_pattern = element_info[element].move_pattern;
7959 Moving2Blocked(x, y, &newx, &newy);
7961 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7964 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7965 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7967 WasJustMoving[x][y] = 0;
7968 CheckCollision[x][y] = 0;
7970 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7972 if (Tile[x][y] != element) // element has changed
7976 if (!MovDelay[x][y]) // start new movement phase
7978 // all objects that can change their move direction after each step
7979 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7981 if (element != EL_YAMYAM &&
7982 element != EL_DARK_YAMYAM &&
7983 element != EL_PACMAN &&
7984 !(move_pattern & MV_ANY_DIRECTION) &&
7985 move_pattern != MV_TURNING_LEFT &&
7986 move_pattern != MV_TURNING_RIGHT &&
7987 move_pattern != MV_TURNING_LEFT_RIGHT &&
7988 move_pattern != MV_TURNING_RIGHT_LEFT &&
7989 move_pattern != MV_TURNING_RANDOM)
7993 if (MovDelay[x][y] && (element == EL_BUG ||
7994 element == EL_SPACESHIP ||
7995 element == EL_SP_SNIKSNAK ||
7996 element == EL_SP_ELECTRON ||
7997 element == EL_MOLE))
7998 TEST_DrawLevelField(x, y);
8002 if (MovDelay[x][y]) // wait some time before next movement
8006 if (element == EL_ROBOT ||
8007 element == EL_YAMYAM ||
8008 element == EL_DARK_YAMYAM)
8010 DrawLevelElementAnimationIfNeeded(x, y, element);
8011 PlayLevelSoundAction(x, y, ACTION_WAITING);
8013 else if (element == EL_SP_ELECTRON)
8014 DrawLevelElementAnimationIfNeeded(x, y, element);
8015 else if (element == EL_DRAGON)
8018 int dir = MovDir[x][y];
8019 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8020 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8021 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8022 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8023 dir == MV_UP ? IMG_FLAMES_1_UP :
8024 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8025 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8027 GfxAction[x][y] = ACTION_ATTACKING;
8029 if (IS_PLAYER(x, y))
8030 DrawPlayerField(x, y);
8032 TEST_DrawLevelField(x, y);
8034 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8036 for (i = 1; i <= 3; i++)
8038 int xx = x + i * dx;
8039 int yy = y + i * dy;
8040 int sx = SCREENX(xx);
8041 int sy = SCREENY(yy);
8042 int flame_graphic = graphic + (i - 1);
8044 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8049 int flamed = MovingOrBlocked2Element(xx, yy);
8051 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8054 RemoveMovingField(xx, yy);
8056 ChangeDelay[xx][yy] = 0;
8058 Tile[xx][yy] = EL_FLAMES;
8060 if (IN_SCR_FIELD(sx, sy))
8062 TEST_DrawLevelFieldCrumbled(xx, yy);
8063 DrawGraphic(sx, sy, flame_graphic, frame);
8068 if (Tile[xx][yy] == EL_FLAMES)
8069 Tile[xx][yy] = EL_EMPTY;
8070 TEST_DrawLevelField(xx, yy);
8075 if (MovDelay[x][y]) // element still has to wait some time
8077 PlayLevelSoundAction(x, y, ACTION_WAITING);
8083 // now make next step
8085 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8087 if (DONT_COLLIDE_WITH(element) &&
8088 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8089 !PLAYER_ENEMY_PROTECTED(newx, newy))
8091 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8096 else if (CAN_MOVE_INTO_ACID(element) &&
8097 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8098 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8099 (MovDir[x][y] == MV_DOWN ||
8100 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8102 SplashAcid(newx, newy);
8103 Store[x][y] = EL_ACID;
8105 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8107 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8108 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8109 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8110 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8113 TEST_DrawLevelField(x, y);
8115 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8116 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8117 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8119 game.friends_still_needed--;
8120 if (!game.friends_still_needed &&
8122 game.all_players_gone)
8127 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8129 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8130 TEST_DrawLevelField(newx, newy);
8132 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8134 else if (!IS_FREE(newx, newy))
8136 GfxAction[x][y] = ACTION_WAITING;
8138 if (IS_PLAYER(x, y))
8139 DrawPlayerField(x, y);
8141 TEST_DrawLevelField(x, y);
8146 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8148 if (IS_FOOD_PIG(Tile[newx][newy]))
8150 if (IS_MOVING(newx, newy))
8151 RemoveMovingField(newx, newy);
8154 Tile[newx][newy] = EL_EMPTY;
8155 TEST_DrawLevelField(newx, newy);
8158 PlayLevelSound(x, y, SND_PIG_DIGGING);
8160 else if (!IS_FREE(newx, newy))
8162 if (IS_PLAYER(x, y))
8163 DrawPlayerField(x, y);
8165 TEST_DrawLevelField(x, y);
8170 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8172 if (Store[x][y] != EL_EMPTY)
8174 boolean can_clone = FALSE;
8177 // check if element to clone is still there
8178 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8180 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8188 // cannot clone or target field not free anymore -- do not clone
8189 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8190 Store[x][y] = EL_EMPTY;
8193 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8195 if (IS_MV_DIAGONAL(MovDir[x][y]))
8197 int diagonal_move_dir = MovDir[x][y];
8198 int stored = Store[x][y];
8199 int change_delay = 8;
8202 // android is moving diagonally
8204 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8206 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8207 GfxElement[x][y] = EL_EMC_ANDROID;
8208 GfxAction[x][y] = ACTION_SHRINKING;
8209 GfxDir[x][y] = diagonal_move_dir;
8210 ChangeDelay[x][y] = change_delay;
8212 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8215 DrawLevelGraphicAnimation(x, y, graphic);
8216 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8218 if (Tile[newx][newy] == EL_ACID)
8220 SplashAcid(newx, newy);
8225 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8227 Store[newx][newy] = EL_EMC_ANDROID;
8228 GfxElement[newx][newy] = EL_EMC_ANDROID;
8229 GfxAction[newx][newy] = ACTION_GROWING;
8230 GfxDir[newx][newy] = diagonal_move_dir;
8231 ChangeDelay[newx][newy] = change_delay;
8233 graphic = el_act_dir2img(GfxElement[newx][newy],
8234 GfxAction[newx][newy], GfxDir[newx][newy]);
8236 DrawLevelGraphicAnimation(newx, newy, graphic);
8237 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8243 Tile[newx][newy] = EL_EMPTY;
8244 TEST_DrawLevelField(newx, newy);
8246 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8249 else if (!IS_FREE(newx, newy))
8254 else if (IS_CUSTOM_ELEMENT(element) &&
8255 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8257 if (!DigFieldByCE(newx, newy, element))
8260 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8262 RunnerVisit[x][y] = FrameCounter;
8263 PlayerVisit[x][y] /= 8; // expire player visit path
8266 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8268 if (!IS_FREE(newx, newy))
8270 if (IS_PLAYER(x, y))
8271 DrawPlayerField(x, y);
8273 TEST_DrawLevelField(x, y);
8279 boolean wanna_flame = !RND(10);
8280 int dx = newx - x, dy = newy - y;
8281 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8282 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8283 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8284 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8285 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8286 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8289 IS_CLASSIC_ENEMY(element1) ||
8290 IS_CLASSIC_ENEMY(element2)) &&
8291 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8292 element1 != EL_FLAMES && element2 != EL_FLAMES)
8294 ResetGfxAnimation(x, y);
8295 GfxAction[x][y] = ACTION_ATTACKING;
8297 if (IS_PLAYER(x, y))
8298 DrawPlayerField(x, y);
8300 TEST_DrawLevelField(x, y);
8302 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8304 MovDelay[x][y] = 50;
8306 Tile[newx][newy] = EL_FLAMES;
8307 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8308 Tile[newx1][newy1] = EL_FLAMES;
8309 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8310 Tile[newx2][newy2] = EL_FLAMES;
8316 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8317 Tile[newx][newy] == EL_DIAMOND)
8319 if (IS_MOVING(newx, newy))
8320 RemoveMovingField(newx, newy);
8323 Tile[newx][newy] = EL_EMPTY;
8324 TEST_DrawLevelField(newx, newy);
8327 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8329 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8330 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8332 if (AmoebaNr[newx][newy])
8334 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8335 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8336 Tile[newx][newy] == EL_BD_AMOEBA)
8337 AmoebaCnt[AmoebaNr[newx][newy]]--;
8340 if (IS_MOVING(newx, newy))
8342 RemoveMovingField(newx, newy);
8346 Tile[newx][newy] = EL_EMPTY;
8347 TEST_DrawLevelField(newx, newy);
8350 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8352 else if ((element == EL_PACMAN || element == EL_MOLE)
8353 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8355 if (AmoebaNr[newx][newy])
8357 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8358 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8359 Tile[newx][newy] == EL_BD_AMOEBA)
8360 AmoebaCnt[AmoebaNr[newx][newy]]--;
8363 if (element == EL_MOLE)
8365 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8366 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8368 ResetGfxAnimation(x, y);
8369 GfxAction[x][y] = ACTION_DIGGING;
8370 TEST_DrawLevelField(x, y);
8372 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8374 return; // wait for shrinking amoeba
8376 else // element == EL_PACMAN
8378 Tile[newx][newy] = EL_EMPTY;
8379 TEST_DrawLevelField(newx, newy);
8380 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8383 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8384 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8385 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8387 // wait for shrinking amoeba to completely disappear
8390 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8392 // object was running against a wall
8396 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8397 DrawLevelElementAnimation(x, y, element);
8399 if (DONT_TOUCH(element))
8400 TestIfBadThingTouchesPlayer(x, y);
8405 InitMovingField(x, y, MovDir[x][y]);
8407 PlayLevelSoundAction(x, y, ACTION_MOVING);
8411 ContinueMoving(x, y);
8414 void ContinueMoving(int x, int y)
8416 int element = Tile[x][y];
8417 struct ElementInfo *ei = &element_info[element];
8418 int direction = MovDir[x][y];
8419 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8420 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8421 int newx = x + dx, newy = y + dy;
8422 int stored = Store[x][y];
8423 int stored_new = Store[newx][newy];
8424 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8425 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8426 boolean last_line = (newy == lev_fieldy - 1);
8428 MovPos[x][y] += getElementMoveStepsize(x, y);
8430 if (pushed_by_player) // special case: moving object pushed by player
8431 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8433 if (ABS(MovPos[x][y]) < TILEX)
8435 TEST_DrawLevelField(x, y);
8437 return; // element is still moving
8440 // element reached destination field
8442 Tile[x][y] = EL_EMPTY;
8443 Tile[newx][newy] = element;
8444 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8446 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8448 element = Tile[newx][newy] = EL_ACID;
8450 else if (element == EL_MOLE)
8452 Tile[x][y] = EL_SAND;
8454 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8456 else if (element == EL_QUICKSAND_FILLING)
8458 element = Tile[newx][newy] = get_next_element(element);
8459 Store[newx][newy] = Store[x][y];
8461 else if (element == EL_QUICKSAND_EMPTYING)
8463 Tile[x][y] = get_next_element(element);
8464 element = Tile[newx][newy] = Store[x][y];
8466 else if (element == EL_QUICKSAND_FAST_FILLING)
8468 element = Tile[newx][newy] = get_next_element(element);
8469 Store[newx][newy] = Store[x][y];
8471 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8473 Tile[x][y] = get_next_element(element);
8474 element = Tile[newx][newy] = Store[x][y];
8476 else if (element == EL_MAGIC_WALL_FILLING)
8478 element = Tile[newx][newy] = get_next_element(element);
8479 if (!game.magic_wall_active)
8480 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8481 Store[newx][newy] = Store[x][y];
8483 else if (element == EL_MAGIC_WALL_EMPTYING)
8485 Tile[x][y] = get_next_element(element);
8486 if (!game.magic_wall_active)
8487 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8488 element = Tile[newx][newy] = Store[x][y];
8490 InitField(newx, newy, FALSE);
8492 else if (element == EL_BD_MAGIC_WALL_FILLING)
8494 element = Tile[newx][newy] = get_next_element(element);
8495 if (!game.magic_wall_active)
8496 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8497 Store[newx][newy] = Store[x][y];
8499 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8501 Tile[x][y] = get_next_element(element);
8502 if (!game.magic_wall_active)
8503 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8504 element = Tile[newx][newy] = Store[x][y];
8506 InitField(newx, newy, FALSE);
8508 else if (element == EL_DC_MAGIC_WALL_FILLING)
8510 element = Tile[newx][newy] = get_next_element(element);
8511 if (!game.magic_wall_active)
8512 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8513 Store[newx][newy] = Store[x][y];
8515 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8517 Tile[x][y] = get_next_element(element);
8518 if (!game.magic_wall_active)
8519 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8520 element = Tile[newx][newy] = Store[x][y];
8522 InitField(newx, newy, FALSE);
8524 else if (element == EL_AMOEBA_DROPPING)
8526 Tile[x][y] = get_next_element(element);
8527 element = Tile[newx][newy] = Store[x][y];
8529 else if (element == EL_SOKOBAN_OBJECT)
8532 Tile[x][y] = Back[x][y];
8534 if (Back[newx][newy])
8535 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8537 Back[x][y] = Back[newx][newy] = 0;
8540 Store[x][y] = EL_EMPTY;
8545 MovDelay[newx][newy] = 0;
8547 if (CAN_CHANGE_OR_HAS_ACTION(element))
8549 // copy element change control values to new field
8550 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8551 ChangePage[newx][newy] = ChangePage[x][y];
8552 ChangeCount[newx][newy] = ChangeCount[x][y];
8553 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8556 CustomValue[newx][newy] = CustomValue[x][y];
8558 ChangeDelay[x][y] = 0;
8559 ChangePage[x][y] = -1;
8560 ChangeCount[x][y] = 0;
8561 ChangeEvent[x][y] = -1;
8563 CustomValue[x][y] = 0;
8565 // copy animation control values to new field
8566 GfxFrame[newx][newy] = GfxFrame[x][y];
8567 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8568 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8569 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8571 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8573 // some elements can leave other elements behind after moving
8574 if (ei->move_leave_element != EL_EMPTY &&
8575 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8576 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8578 int move_leave_element = ei->move_leave_element;
8580 // this makes it possible to leave the removed element again
8581 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8582 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8584 Tile[x][y] = move_leave_element;
8586 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8587 MovDir[x][y] = direction;
8589 InitField(x, y, FALSE);
8591 if (GFX_CRUMBLED(Tile[x][y]))
8592 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8594 if (ELEM_IS_PLAYER(move_leave_element))
8595 RelocatePlayer(x, y, move_leave_element);
8598 // do this after checking for left-behind element
8599 ResetGfxAnimation(x, y); // reset animation values for old field
8601 if (!CAN_MOVE(element) ||
8602 (CAN_FALL(element) && direction == MV_DOWN &&
8603 (element == EL_SPRING ||
8604 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8605 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8606 GfxDir[x][y] = MovDir[newx][newy] = 0;
8608 TEST_DrawLevelField(x, y);
8609 TEST_DrawLevelField(newx, newy);
8611 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8613 // prevent pushed element from moving on in pushed direction
8614 if (pushed_by_player && CAN_MOVE(element) &&
8615 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8616 !(element_info[element].move_pattern & direction))
8617 TurnRound(newx, newy);
8619 // prevent elements on conveyor belt from moving on in last direction
8620 if (pushed_by_conveyor && CAN_FALL(element) &&
8621 direction & MV_HORIZONTAL)
8622 MovDir[newx][newy] = 0;
8624 if (!pushed_by_player)
8626 int nextx = newx + dx, nexty = newy + dy;
8627 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8629 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8631 if (CAN_FALL(element) && direction == MV_DOWN)
8632 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8634 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8635 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8637 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8638 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8641 if (DONT_TOUCH(element)) // object may be nasty to player or others
8643 TestIfBadThingTouchesPlayer(newx, newy);
8644 TestIfBadThingTouchesFriend(newx, newy);
8646 if (!IS_CUSTOM_ELEMENT(element))
8647 TestIfBadThingTouchesOtherBadThing(newx, newy);
8649 else if (element == EL_PENGUIN)
8650 TestIfFriendTouchesBadThing(newx, newy);
8652 if (DONT_GET_HIT_BY(element))
8654 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8657 // give the player one last chance (one more frame) to move away
8658 if (CAN_FALL(element) && direction == MV_DOWN &&
8659 (last_line || (!IS_FREE(x, newy + 1) &&
8660 (!IS_PLAYER(x, newy + 1) ||
8661 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8664 if (pushed_by_player && !game.use_change_when_pushing_bug)
8666 int push_side = MV_DIR_OPPOSITE(direction);
8667 struct PlayerInfo *player = PLAYERINFO(x, y);
8669 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8670 player->index_bit, push_side);
8671 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8672 player->index_bit, push_side);
8675 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8676 MovDelay[newx][newy] = 1;
8678 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8680 TestIfElementTouchesCustomElement(x, y); // empty or new element
8681 TestIfElementHitsCustomElement(newx, newy, direction);
8682 TestIfPlayerTouchesCustomElement(newx, newy);
8683 TestIfElementTouchesCustomElement(newx, newy);
8685 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8686 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8687 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8688 MV_DIR_OPPOSITE(direction));
8691 int AmoebaNeighbourNr(int ax, int ay)
8694 int element = Tile[ax][ay];
8696 static int xy[4][2] =
8704 for (i = 0; i < NUM_DIRECTIONS; i++)
8706 int x = ax + xy[i][0];
8707 int y = ay + xy[i][1];
8709 if (!IN_LEV_FIELD(x, y))
8712 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8713 group_nr = AmoebaNr[x][y];
8719 static void AmoebaMerge(int ax, int ay)
8721 int i, x, y, xx, yy;
8722 int new_group_nr = AmoebaNr[ax][ay];
8723 static int xy[4][2] =
8731 if (new_group_nr == 0)
8734 for (i = 0; i < NUM_DIRECTIONS; i++)
8739 if (!IN_LEV_FIELD(x, y))
8742 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8743 Tile[x][y] == EL_BD_AMOEBA ||
8744 Tile[x][y] == EL_AMOEBA_DEAD) &&
8745 AmoebaNr[x][y] != new_group_nr)
8747 int old_group_nr = AmoebaNr[x][y];
8749 if (old_group_nr == 0)
8752 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8753 AmoebaCnt[old_group_nr] = 0;
8754 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8755 AmoebaCnt2[old_group_nr] = 0;
8757 SCAN_PLAYFIELD(xx, yy)
8759 if (AmoebaNr[xx][yy] == old_group_nr)
8760 AmoebaNr[xx][yy] = new_group_nr;
8766 void AmoebaToDiamond(int ax, int ay)
8770 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8772 int group_nr = AmoebaNr[ax][ay];
8777 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8778 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8784 SCAN_PLAYFIELD(x, y)
8786 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8789 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8793 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8794 SND_AMOEBA_TURNING_TO_GEM :
8795 SND_AMOEBA_TURNING_TO_ROCK));
8800 static int xy[4][2] =
8808 for (i = 0; i < NUM_DIRECTIONS; i++)
8813 if (!IN_LEV_FIELD(x, y))
8816 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8818 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8819 SND_AMOEBA_TURNING_TO_GEM :
8820 SND_AMOEBA_TURNING_TO_ROCK));
8827 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8830 int group_nr = AmoebaNr[ax][ay];
8831 boolean done = FALSE;
8836 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8837 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8843 SCAN_PLAYFIELD(x, y)
8845 if (AmoebaNr[x][y] == group_nr &&
8846 (Tile[x][y] == EL_AMOEBA_DEAD ||
8847 Tile[x][y] == EL_BD_AMOEBA ||
8848 Tile[x][y] == EL_AMOEBA_GROWING))
8851 Tile[x][y] = new_element;
8852 InitField(x, y, FALSE);
8853 TEST_DrawLevelField(x, y);
8859 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8860 SND_BD_AMOEBA_TURNING_TO_ROCK :
8861 SND_BD_AMOEBA_TURNING_TO_GEM));
8864 static void AmoebaGrowing(int x, int y)
8866 static unsigned int sound_delay = 0;
8867 static unsigned int sound_delay_value = 0;
8869 if (!MovDelay[x][y]) // start new growing cycle
8873 if (DelayReached(&sound_delay, sound_delay_value))
8875 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8876 sound_delay_value = 30;
8880 if (MovDelay[x][y]) // wait some time before growing bigger
8883 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8885 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8886 6 - MovDelay[x][y]);
8888 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8891 if (!MovDelay[x][y])
8893 Tile[x][y] = Store[x][y];
8895 TEST_DrawLevelField(x, y);
8900 static void AmoebaShrinking(int x, int y)
8902 static unsigned int sound_delay = 0;
8903 static unsigned int sound_delay_value = 0;
8905 if (!MovDelay[x][y]) // start new shrinking cycle
8909 if (DelayReached(&sound_delay, sound_delay_value))
8910 sound_delay_value = 30;
8913 if (MovDelay[x][y]) // wait some time before shrinking
8916 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8918 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8919 6 - MovDelay[x][y]);
8921 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8924 if (!MovDelay[x][y])
8926 Tile[x][y] = EL_EMPTY;
8927 TEST_DrawLevelField(x, y);
8929 // don't let mole enter this field in this cycle;
8930 // (give priority to objects falling to this field from above)
8936 static void AmoebaReproduce(int ax, int ay)
8939 int element = Tile[ax][ay];
8940 int graphic = el2img(element);
8941 int newax = ax, neway = ay;
8942 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8943 static int xy[4][2] =
8951 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8953 Tile[ax][ay] = EL_AMOEBA_DEAD;
8954 TEST_DrawLevelField(ax, ay);
8958 if (IS_ANIMATED(graphic))
8959 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8961 if (!MovDelay[ax][ay]) // start making new amoeba field
8962 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8964 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8967 if (MovDelay[ax][ay])
8971 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8974 int x = ax + xy[start][0];
8975 int y = ay + xy[start][1];
8977 if (!IN_LEV_FIELD(x, y))
8980 if (IS_FREE(x, y) ||
8981 CAN_GROW_INTO(Tile[x][y]) ||
8982 Tile[x][y] == EL_QUICKSAND_EMPTY ||
8983 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
8989 if (newax == ax && neway == ay)
8992 else // normal or "filled" (BD style) amoeba
8995 boolean waiting_for_player = FALSE;
8997 for (i = 0; i < NUM_DIRECTIONS; i++)
8999 int j = (start + i) % 4;
9000 int x = ax + xy[j][0];
9001 int y = ay + xy[j][1];
9003 if (!IN_LEV_FIELD(x, y))
9006 if (IS_FREE(x, y) ||
9007 CAN_GROW_INTO(Tile[x][y]) ||
9008 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9009 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9015 else if (IS_PLAYER(x, y))
9016 waiting_for_player = TRUE;
9019 if (newax == ax && neway == ay) // amoeba cannot grow
9021 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9023 Tile[ax][ay] = EL_AMOEBA_DEAD;
9024 TEST_DrawLevelField(ax, ay);
9025 AmoebaCnt[AmoebaNr[ax][ay]]--;
9027 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9029 if (element == EL_AMOEBA_FULL)
9030 AmoebaToDiamond(ax, ay);
9031 else if (element == EL_BD_AMOEBA)
9032 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9037 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9039 // amoeba gets larger by growing in some direction
9041 int new_group_nr = AmoebaNr[ax][ay];
9044 if (new_group_nr == 0)
9046 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9048 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9054 AmoebaNr[newax][neway] = new_group_nr;
9055 AmoebaCnt[new_group_nr]++;
9056 AmoebaCnt2[new_group_nr]++;
9058 // if amoeba touches other amoeba(s) after growing, unify them
9059 AmoebaMerge(newax, neway);
9061 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9063 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9069 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9070 (neway == lev_fieldy - 1 && newax != ax))
9072 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9073 Store[newax][neway] = element;
9075 else if (neway == ay || element == EL_EMC_DRIPPER)
9077 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9079 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9083 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9084 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9085 Store[ax][ay] = EL_AMOEBA_DROP;
9086 ContinueMoving(ax, ay);
9090 TEST_DrawLevelField(newax, neway);
9093 static void Life(int ax, int ay)
9097 int element = Tile[ax][ay];
9098 int graphic = el2img(element);
9099 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9101 boolean changed = FALSE;
9103 if (IS_ANIMATED(graphic))
9104 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9109 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9110 MovDelay[ax][ay] = life_time;
9112 if (MovDelay[ax][ay]) // wait some time before next cycle
9115 if (MovDelay[ax][ay])
9119 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9121 int xx = ax+x1, yy = ay+y1;
9122 int old_element = Tile[xx][yy];
9123 int num_neighbours = 0;
9125 if (!IN_LEV_FIELD(xx, yy))
9128 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9130 int x = xx+x2, y = yy+y2;
9132 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9135 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9136 boolean is_neighbour = FALSE;
9138 if (level.use_life_bugs)
9140 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9141 (IS_FREE(x, y) && Stop[x][y]));
9144 (Last[x][y] == element || is_player_cell);
9150 boolean is_free = FALSE;
9152 if (level.use_life_bugs)
9153 is_free = (IS_FREE(xx, yy));
9155 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9157 if (xx == ax && yy == ay) // field in the middle
9159 if (num_neighbours < life_parameter[0] ||
9160 num_neighbours > life_parameter[1])
9162 Tile[xx][yy] = EL_EMPTY;
9163 if (Tile[xx][yy] != old_element)
9164 TEST_DrawLevelField(xx, yy);
9165 Stop[xx][yy] = TRUE;
9169 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9170 { // free border field
9171 if (num_neighbours >= life_parameter[2] &&
9172 num_neighbours <= life_parameter[3])
9174 Tile[xx][yy] = element;
9175 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9176 if (Tile[xx][yy] != old_element)
9177 TEST_DrawLevelField(xx, yy);
9178 Stop[xx][yy] = TRUE;
9185 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9186 SND_GAME_OF_LIFE_GROWING);
9189 static void InitRobotWheel(int x, int y)
9191 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9194 static void RunRobotWheel(int x, int y)
9196 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9199 static void StopRobotWheel(int x, int y)
9201 if (game.robot_wheel_x == x &&
9202 game.robot_wheel_y == y)
9204 game.robot_wheel_x = -1;
9205 game.robot_wheel_y = -1;
9206 game.robot_wheel_active = FALSE;
9210 static void InitTimegateWheel(int x, int y)
9212 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9215 static void RunTimegateWheel(int x, int y)
9217 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9220 static void InitMagicBallDelay(int x, int y)
9222 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9225 static void ActivateMagicBall(int bx, int by)
9229 if (level.ball_random)
9231 int pos_border = RND(8); // select one of the eight border elements
9232 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9233 int xx = pos_content % 3;
9234 int yy = pos_content / 3;
9239 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9240 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9244 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9246 int xx = x - bx + 1;
9247 int yy = y - by + 1;
9249 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9250 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9254 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9257 static void CheckExit(int x, int y)
9259 if (game.gems_still_needed > 0 ||
9260 game.sokoban_fields_still_needed > 0 ||
9261 game.sokoban_objects_still_needed > 0 ||
9262 game.lights_still_needed > 0)
9264 int element = Tile[x][y];
9265 int graphic = el2img(element);
9267 if (IS_ANIMATED(graphic))
9268 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9273 // do not re-open exit door closed after last player
9274 if (game.all_players_gone)
9277 Tile[x][y] = EL_EXIT_OPENING;
9279 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9282 static void CheckExitEM(int x, int y)
9284 if (game.gems_still_needed > 0 ||
9285 game.sokoban_fields_still_needed > 0 ||
9286 game.sokoban_objects_still_needed > 0 ||
9287 game.lights_still_needed > 0)
9289 int element = Tile[x][y];
9290 int graphic = el2img(element);
9292 if (IS_ANIMATED(graphic))
9293 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9298 // do not re-open exit door closed after last player
9299 if (game.all_players_gone)
9302 Tile[x][y] = EL_EM_EXIT_OPENING;
9304 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9307 static void CheckExitSteel(int x, int y)
9309 if (game.gems_still_needed > 0 ||
9310 game.sokoban_fields_still_needed > 0 ||
9311 game.sokoban_objects_still_needed > 0 ||
9312 game.lights_still_needed > 0)
9314 int element = Tile[x][y];
9315 int graphic = el2img(element);
9317 if (IS_ANIMATED(graphic))
9318 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9323 // do not re-open exit door closed after last player
9324 if (game.all_players_gone)
9327 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9329 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9332 static void CheckExitSteelEM(int x, int y)
9334 if (game.gems_still_needed > 0 ||
9335 game.sokoban_fields_still_needed > 0 ||
9336 game.sokoban_objects_still_needed > 0 ||
9337 game.lights_still_needed > 0)
9339 int element = Tile[x][y];
9340 int graphic = el2img(element);
9342 if (IS_ANIMATED(graphic))
9343 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9348 // do not re-open exit door closed after last player
9349 if (game.all_players_gone)
9352 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9354 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9357 static void CheckExitSP(int x, int y)
9359 if (game.gems_still_needed > 0)
9361 int element = Tile[x][y];
9362 int graphic = el2img(element);
9364 if (IS_ANIMATED(graphic))
9365 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9370 // do not re-open exit door closed after last player
9371 if (game.all_players_gone)
9374 Tile[x][y] = EL_SP_EXIT_OPENING;
9376 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9379 static void CloseAllOpenTimegates(void)
9383 SCAN_PLAYFIELD(x, y)
9385 int element = Tile[x][y];
9387 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9389 Tile[x][y] = EL_TIMEGATE_CLOSING;
9391 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9396 static void DrawTwinkleOnField(int x, int y)
9398 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9401 if (Tile[x][y] == EL_BD_DIAMOND)
9404 if (MovDelay[x][y] == 0) // next animation frame
9405 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9407 if (MovDelay[x][y] != 0) // wait some time before next frame
9411 DrawLevelElementAnimation(x, y, Tile[x][y]);
9413 if (MovDelay[x][y] != 0)
9415 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9416 10 - MovDelay[x][y]);
9418 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9423 static void MauerWaechst(int x, int y)
9427 if (!MovDelay[x][y]) // next animation frame
9428 MovDelay[x][y] = 3 * delay;
9430 if (MovDelay[x][y]) // wait some time before next frame
9434 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9436 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9437 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9439 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9442 if (!MovDelay[x][y])
9444 if (MovDir[x][y] == MV_LEFT)
9446 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9447 TEST_DrawLevelField(x - 1, y);
9449 else if (MovDir[x][y] == MV_RIGHT)
9451 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9452 TEST_DrawLevelField(x + 1, y);
9454 else if (MovDir[x][y] == MV_UP)
9456 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9457 TEST_DrawLevelField(x, y - 1);
9461 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9462 TEST_DrawLevelField(x, y + 1);
9465 Tile[x][y] = Store[x][y];
9467 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9468 TEST_DrawLevelField(x, y);
9473 static void MauerAbleger(int ax, int ay)
9475 int element = Tile[ax][ay];
9476 int graphic = el2img(element);
9477 boolean oben_frei = FALSE, unten_frei = FALSE;
9478 boolean links_frei = FALSE, rechts_frei = FALSE;
9479 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9480 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9481 boolean new_wall = FALSE;
9483 if (IS_ANIMATED(graphic))
9484 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9486 if (!MovDelay[ax][ay]) // start building new wall
9487 MovDelay[ax][ay] = 6;
9489 if (MovDelay[ax][ay]) // wait some time before building new wall
9492 if (MovDelay[ax][ay])
9496 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9498 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9500 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9502 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9505 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9506 element == EL_EXPANDABLE_WALL_ANY)
9510 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9511 Store[ax][ay-1] = element;
9512 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9513 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9514 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9515 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9520 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9521 Store[ax][ay+1] = element;
9522 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9523 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9524 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9525 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9530 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9531 element == EL_EXPANDABLE_WALL_ANY ||
9532 element == EL_EXPANDABLE_WALL ||
9533 element == EL_BD_EXPANDABLE_WALL)
9537 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9538 Store[ax-1][ay] = element;
9539 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9540 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9541 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9542 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9548 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9549 Store[ax+1][ay] = element;
9550 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9551 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9552 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9553 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9558 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9559 TEST_DrawLevelField(ax, ay);
9561 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9563 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9564 unten_massiv = TRUE;
9565 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9566 links_massiv = TRUE;
9567 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9568 rechts_massiv = TRUE;
9570 if (((oben_massiv && unten_massiv) ||
9571 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9572 element == EL_EXPANDABLE_WALL) &&
9573 ((links_massiv && rechts_massiv) ||
9574 element == EL_EXPANDABLE_WALL_VERTICAL))
9575 Tile[ax][ay] = EL_WALL;
9578 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9581 static void MauerAblegerStahl(int ax, int ay)
9583 int element = Tile[ax][ay];
9584 int graphic = el2img(element);
9585 boolean oben_frei = FALSE, unten_frei = FALSE;
9586 boolean links_frei = FALSE, rechts_frei = FALSE;
9587 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9588 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9589 boolean new_wall = FALSE;
9591 if (IS_ANIMATED(graphic))
9592 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9594 if (!MovDelay[ax][ay]) // start building new wall
9595 MovDelay[ax][ay] = 6;
9597 if (MovDelay[ax][ay]) // wait some time before building new wall
9600 if (MovDelay[ax][ay])
9604 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9606 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9608 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9610 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9613 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9614 element == EL_EXPANDABLE_STEELWALL_ANY)
9618 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9619 Store[ax][ay-1] = element;
9620 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9621 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9622 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9623 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9628 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9629 Store[ax][ay+1] = element;
9630 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9631 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9632 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9633 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9638 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9639 element == EL_EXPANDABLE_STEELWALL_ANY)
9643 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9644 Store[ax-1][ay] = element;
9645 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9646 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9647 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9648 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9654 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9655 Store[ax+1][ay] = element;
9656 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9657 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9658 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9659 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9664 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9666 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9667 unten_massiv = TRUE;
9668 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9669 links_massiv = TRUE;
9670 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9671 rechts_massiv = TRUE;
9673 if (((oben_massiv && unten_massiv) ||
9674 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9675 ((links_massiv && rechts_massiv) ||
9676 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9677 Tile[ax][ay] = EL_STEELWALL;
9680 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9683 static void CheckForDragon(int x, int y)
9686 boolean dragon_found = FALSE;
9687 static int xy[4][2] =
9695 for (i = 0; i < NUM_DIRECTIONS; i++)
9697 for (j = 0; j < 4; j++)
9699 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9701 if (IN_LEV_FIELD(xx, yy) &&
9702 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9704 if (Tile[xx][yy] == EL_DRAGON)
9705 dragon_found = TRUE;
9714 for (i = 0; i < NUM_DIRECTIONS; i++)
9716 for (j = 0; j < 3; j++)
9718 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9720 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9722 Tile[xx][yy] = EL_EMPTY;
9723 TEST_DrawLevelField(xx, yy);
9732 static void InitBuggyBase(int x, int y)
9734 int element = Tile[x][y];
9735 int activating_delay = FRAMES_PER_SECOND / 4;
9738 (element == EL_SP_BUGGY_BASE ?
9739 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9740 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9742 element == EL_SP_BUGGY_BASE_ACTIVE ?
9743 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9746 static void WarnBuggyBase(int x, int y)
9749 static int xy[4][2] =
9757 for (i = 0; i < NUM_DIRECTIONS; i++)
9759 int xx = x + xy[i][0];
9760 int yy = y + xy[i][1];
9762 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9764 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9771 static void InitTrap(int x, int y)
9773 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9776 static void ActivateTrap(int x, int y)
9778 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9781 static void ChangeActiveTrap(int x, int y)
9783 int graphic = IMG_TRAP_ACTIVE;
9785 // if new animation frame was drawn, correct crumbled sand border
9786 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9787 TEST_DrawLevelFieldCrumbled(x, y);
9790 static int getSpecialActionElement(int element, int number, int base_element)
9792 return (element != EL_EMPTY ? element :
9793 number != -1 ? base_element + number - 1 :
9797 static int getModifiedActionNumber(int value_old, int operator, int operand,
9798 int value_min, int value_max)
9800 int value_new = (operator == CA_MODE_SET ? operand :
9801 operator == CA_MODE_ADD ? value_old + operand :
9802 operator == CA_MODE_SUBTRACT ? value_old - operand :
9803 operator == CA_MODE_MULTIPLY ? value_old * operand :
9804 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9805 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9808 return (value_new < value_min ? value_min :
9809 value_new > value_max ? value_max :
9813 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9815 struct ElementInfo *ei = &element_info[element];
9816 struct ElementChangeInfo *change = &ei->change_page[page];
9817 int target_element = change->target_element;
9818 int action_type = change->action_type;
9819 int action_mode = change->action_mode;
9820 int action_arg = change->action_arg;
9821 int action_element = change->action_element;
9824 if (!change->has_action)
9827 // ---------- determine action paramater values -----------------------------
9829 int level_time_value =
9830 (level.time > 0 ? TimeLeft :
9833 int action_arg_element_raw =
9834 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9835 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9836 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9837 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9838 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9839 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9840 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9842 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9844 int action_arg_direction =
9845 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9846 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9847 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9848 change->actual_trigger_side :
9849 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9850 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9853 int action_arg_number_min =
9854 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9857 int action_arg_number_max =
9858 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9859 action_type == CA_SET_LEVEL_GEMS ? 999 :
9860 action_type == CA_SET_LEVEL_TIME ? 9999 :
9861 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9862 action_type == CA_SET_CE_VALUE ? 9999 :
9863 action_type == CA_SET_CE_SCORE ? 9999 :
9866 int action_arg_number_reset =
9867 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9868 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9869 action_type == CA_SET_LEVEL_TIME ? level.time :
9870 action_type == CA_SET_LEVEL_SCORE ? 0 :
9871 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9872 action_type == CA_SET_CE_SCORE ? 0 :
9875 int action_arg_number =
9876 (action_arg <= CA_ARG_MAX ? action_arg :
9877 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9878 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9879 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9880 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9881 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9882 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9883 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9884 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9885 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9886 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9887 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9888 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9889 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9890 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9891 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9892 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9893 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9894 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9895 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9896 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9897 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9900 int action_arg_number_old =
9901 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9902 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9903 action_type == CA_SET_LEVEL_SCORE ? game.score :
9904 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9905 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9908 int action_arg_number_new =
9909 getModifiedActionNumber(action_arg_number_old,
9910 action_mode, action_arg_number,
9911 action_arg_number_min, action_arg_number_max);
9913 int trigger_player_bits =
9914 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9915 change->actual_trigger_player_bits : change->trigger_player);
9917 int action_arg_player_bits =
9918 (action_arg >= CA_ARG_PLAYER_1 &&
9919 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9920 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9921 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9924 // ---------- execute action -----------------------------------------------
9926 switch (action_type)
9933 // ---------- level actions ----------------------------------------------
9935 case CA_RESTART_LEVEL:
9937 game.restart_level = TRUE;
9942 case CA_SHOW_ENVELOPE:
9944 int element = getSpecialActionElement(action_arg_element,
9945 action_arg_number, EL_ENVELOPE_1);
9947 if (IS_ENVELOPE(element))
9948 local_player->show_envelope = element;
9953 case CA_SET_LEVEL_TIME:
9955 if (level.time > 0) // only modify limited time value
9957 TimeLeft = action_arg_number_new;
9959 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9961 DisplayGameControlValues();
9963 if (!TimeLeft && setup.time_limit)
9964 for (i = 0; i < MAX_PLAYERS; i++)
9965 KillPlayer(&stored_player[i]);
9971 case CA_SET_LEVEL_SCORE:
9973 game.score = action_arg_number_new;
9975 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9977 DisplayGameControlValues();
9982 case CA_SET_LEVEL_GEMS:
9984 game.gems_still_needed = action_arg_number_new;
9986 game.snapshot.collected_item = TRUE;
9988 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9990 DisplayGameControlValues();
9995 case CA_SET_LEVEL_WIND:
9997 game.wind_direction = action_arg_direction;
10002 case CA_SET_LEVEL_RANDOM_SEED:
10004 // ensure that setting a new random seed while playing is predictable
10005 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10010 // ---------- player actions ---------------------------------------------
10012 case CA_MOVE_PLAYER:
10013 case CA_MOVE_PLAYER_NEW:
10015 // automatically move to the next field in specified direction
10016 for (i = 0; i < MAX_PLAYERS; i++)
10017 if (trigger_player_bits & (1 << i))
10018 if (action_type == CA_MOVE_PLAYER ||
10019 stored_player[i].MovPos == 0)
10020 stored_player[i].programmed_action = action_arg_direction;
10025 case CA_EXIT_PLAYER:
10027 for (i = 0; i < MAX_PLAYERS; i++)
10028 if (action_arg_player_bits & (1 << i))
10029 ExitPlayer(&stored_player[i]);
10031 if (game.players_still_needed == 0)
10037 case CA_KILL_PLAYER:
10039 for (i = 0; i < MAX_PLAYERS; i++)
10040 if (action_arg_player_bits & (1 << i))
10041 KillPlayer(&stored_player[i]);
10046 case CA_SET_PLAYER_KEYS:
10048 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10049 int element = getSpecialActionElement(action_arg_element,
10050 action_arg_number, EL_KEY_1);
10052 if (IS_KEY(element))
10054 for (i = 0; i < MAX_PLAYERS; i++)
10056 if (trigger_player_bits & (1 << i))
10058 stored_player[i].key[KEY_NR(element)] = key_state;
10060 DrawGameDoorValues();
10068 case CA_SET_PLAYER_SPEED:
10070 for (i = 0; i < MAX_PLAYERS; i++)
10072 if (trigger_player_bits & (1 << i))
10074 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10076 if (action_arg == CA_ARG_SPEED_FASTER &&
10077 stored_player[i].cannot_move)
10079 action_arg_number = STEPSIZE_VERY_SLOW;
10081 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10082 action_arg == CA_ARG_SPEED_FASTER)
10084 action_arg_number = 2;
10085 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10088 else if (action_arg == CA_ARG_NUMBER_RESET)
10090 action_arg_number = level.initial_player_stepsize[i];
10094 getModifiedActionNumber(move_stepsize,
10097 action_arg_number_min,
10098 action_arg_number_max);
10100 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10107 case CA_SET_PLAYER_SHIELD:
10109 for (i = 0; i < MAX_PLAYERS; i++)
10111 if (trigger_player_bits & (1 << i))
10113 if (action_arg == CA_ARG_SHIELD_OFF)
10115 stored_player[i].shield_normal_time_left = 0;
10116 stored_player[i].shield_deadly_time_left = 0;
10118 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10120 stored_player[i].shield_normal_time_left = 999999;
10122 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10124 stored_player[i].shield_normal_time_left = 999999;
10125 stored_player[i].shield_deadly_time_left = 999999;
10133 case CA_SET_PLAYER_GRAVITY:
10135 for (i = 0; i < MAX_PLAYERS; i++)
10137 if (trigger_player_bits & (1 << i))
10139 stored_player[i].gravity =
10140 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10141 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10142 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10143 stored_player[i].gravity);
10150 case CA_SET_PLAYER_ARTWORK:
10152 for (i = 0; i < MAX_PLAYERS; i++)
10154 if (trigger_player_bits & (1 << i))
10156 int artwork_element = action_arg_element;
10158 if (action_arg == CA_ARG_ELEMENT_RESET)
10160 (level.use_artwork_element[i] ? level.artwork_element[i] :
10161 stored_player[i].element_nr);
10163 if (stored_player[i].artwork_element != artwork_element)
10164 stored_player[i].Frame = 0;
10166 stored_player[i].artwork_element = artwork_element;
10168 SetPlayerWaiting(&stored_player[i], FALSE);
10170 // set number of special actions for bored and sleeping animation
10171 stored_player[i].num_special_action_bored =
10172 get_num_special_action(artwork_element,
10173 ACTION_BORING_1, ACTION_BORING_LAST);
10174 stored_player[i].num_special_action_sleeping =
10175 get_num_special_action(artwork_element,
10176 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10183 case CA_SET_PLAYER_INVENTORY:
10185 for (i = 0; i < MAX_PLAYERS; i++)
10187 struct PlayerInfo *player = &stored_player[i];
10190 if (trigger_player_bits & (1 << i))
10192 int inventory_element = action_arg_element;
10194 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10195 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10196 action_arg == CA_ARG_ELEMENT_ACTION)
10198 int element = inventory_element;
10199 int collect_count = element_info[element].collect_count_initial;
10201 if (!IS_CUSTOM_ELEMENT(element))
10204 if (collect_count == 0)
10205 player->inventory_infinite_element = element;
10207 for (k = 0; k < collect_count; k++)
10208 if (player->inventory_size < MAX_INVENTORY_SIZE)
10209 player->inventory_element[player->inventory_size++] =
10212 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10213 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10214 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10216 if (player->inventory_infinite_element != EL_UNDEFINED &&
10217 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10218 action_arg_element_raw))
10219 player->inventory_infinite_element = EL_UNDEFINED;
10221 for (k = 0, j = 0; j < player->inventory_size; j++)
10223 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10224 action_arg_element_raw))
10225 player->inventory_element[k++] = player->inventory_element[j];
10228 player->inventory_size = k;
10230 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10232 if (player->inventory_size > 0)
10234 for (j = 0; j < player->inventory_size - 1; j++)
10235 player->inventory_element[j] = player->inventory_element[j + 1];
10237 player->inventory_size--;
10240 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10242 if (player->inventory_size > 0)
10243 player->inventory_size--;
10245 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10247 player->inventory_infinite_element = EL_UNDEFINED;
10248 player->inventory_size = 0;
10250 else if (action_arg == CA_ARG_INVENTORY_RESET)
10252 player->inventory_infinite_element = EL_UNDEFINED;
10253 player->inventory_size = 0;
10255 if (level.use_initial_inventory[i])
10257 for (j = 0; j < level.initial_inventory_size[i]; j++)
10259 int element = level.initial_inventory_content[i][j];
10260 int collect_count = element_info[element].collect_count_initial;
10262 if (!IS_CUSTOM_ELEMENT(element))
10265 if (collect_count == 0)
10266 player->inventory_infinite_element = element;
10268 for (k = 0; k < collect_count; k++)
10269 if (player->inventory_size < MAX_INVENTORY_SIZE)
10270 player->inventory_element[player->inventory_size++] =
10281 // ---------- CE actions -------------------------------------------------
10283 case CA_SET_CE_VALUE:
10285 int last_ce_value = CustomValue[x][y];
10287 CustomValue[x][y] = action_arg_number_new;
10289 if (CustomValue[x][y] != last_ce_value)
10291 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10292 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10294 if (CustomValue[x][y] == 0)
10296 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10297 ChangeCount[x][y] = 0; // allow at least one more change
10299 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10300 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10307 case CA_SET_CE_SCORE:
10309 int last_ce_score = ei->collect_score;
10311 ei->collect_score = action_arg_number_new;
10313 if (ei->collect_score != last_ce_score)
10315 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10316 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10318 if (ei->collect_score == 0)
10322 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10323 ChangeCount[x][y] = 0; // allow at least one more change
10325 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10326 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10329 This is a very special case that seems to be a mixture between
10330 CheckElementChange() and CheckTriggeredElementChange(): while
10331 the first one only affects single elements that are triggered
10332 directly, the second one affects multiple elements in the playfield
10333 that are triggered indirectly by another element. This is a third
10334 case: Changing the CE score always affects multiple identical CEs,
10335 so every affected CE must be checked, not only the single CE for
10336 which the CE score was changed in the first place (as every instance
10337 of that CE shares the same CE score, and therefore also can change)!
10339 SCAN_PLAYFIELD(xx, yy)
10341 if (Tile[xx][yy] == element)
10342 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10343 CE_SCORE_GETS_ZERO);
10351 case CA_SET_CE_ARTWORK:
10353 int artwork_element = action_arg_element;
10354 boolean reset_frame = FALSE;
10357 if (action_arg == CA_ARG_ELEMENT_RESET)
10358 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10361 if (ei->gfx_element != artwork_element)
10362 reset_frame = TRUE;
10364 ei->gfx_element = artwork_element;
10366 SCAN_PLAYFIELD(xx, yy)
10368 if (Tile[xx][yy] == element)
10372 ResetGfxAnimation(xx, yy);
10373 ResetRandomAnimationValue(xx, yy);
10376 TEST_DrawLevelField(xx, yy);
10383 // ---------- engine actions ---------------------------------------------
10385 case CA_SET_ENGINE_SCAN_MODE:
10387 InitPlayfieldScanMode(action_arg);
10397 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10399 int old_element = Tile[x][y];
10400 int new_element = GetElementFromGroupElement(element);
10401 int previous_move_direction = MovDir[x][y];
10402 int last_ce_value = CustomValue[x][y];
10403 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10404 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10405 boolean add_player_onto_element = (new_element_is_player &&
10406 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10407 IS_WALKABLE(old_element));
10409 if (!add_player_onto_element)
10411 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10412 RemoveMovingField(x, y);
10416 Tile[x][y] = new_element;
10418 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10419 MovDir[x][y] = previous_move_direction;
10421 if (element_info[new_element].use_last_ce_value)
10422 CustomValue[x][y] = last_ce_value;
10424 InitField_WithBug1(x, y, FALSE);
10426 new_element = Tile[x][y]; // element may have changed
10428 ResetGfxAnimation(x, y);
10429 ResetRandomAnimationValue(x, y);
10431 TEST_DrawLevelField(x, y);
10433 if (GFX_CRUMBLED(new_element))
10434 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10437 // check if element under the player changes from accessible to unaccessible
10438 // (needed for special case of dropping element which then changes)
10439 // (must be checked after creating new element for walkable group elements)
10440 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10441 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10448 // "ChangeCount" not set yet to allow "entered by player" change one time
10449 if (new_element_is_player)
10450 RelocatePlayer(x, y, new_element);
10453 ChangeCount[x][y]++; // count number of changes in the same frame
10455 TestIfBadThingTouchesPlayer(x, y);
10456 TestIfPlayerTouchesCustomElement(x, y);
10457 TestIfElementTouchesCustomElement(x, y);
10460 static void CreateField(int x, int y, int element)
10462 CreateFieldExt(x, y, element, FALSE);
10465 static void CreateElementFromChange(int x, int y, int element)
10467 element = GET_VALID_RUNTIME_ELEMENT(element);
10469 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10471 int old_element = Tile[x][y];
10473 // prevent changed element from moving in same engine frame
10474 // unless both old and new element can either fall or move
10475 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10476 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10480 CreateFieldExt(x, y, element, TRUE);
10483 static boolean ChangeElement(int x, int y, int element, int page)
10485 struct ElementInfo *ei = &element_info[element];
10486 struct ElementChangeInfo *change = &ei->change_page[page];
10487 int ce_value = CustomValue[x][y];
10488 int ce_score = ei->collect_score;
10489 int target_element;
10490 int old_element = Tile[x][y];
10492 // always use default change event to prevent running into a loop
10493 if (ChangeEvent[x][y] == -1)
10494 ChangeEvent[x][y] = CE_DELAY;
10496 if (ChangeEvent[x][y] == CE_DELAY)
10498 // reset actual trigger element, trigger player and action element
10499 change->actual_trigger_element = EL_EMPTY;
10500 change->actual_trigger_player = EL_EMPTY;
10501 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10502 change->actual_trigger_side = CH_SIDE_NONE;
10503 change->actual_trigger_ce_value = 0;
10504 change->actual_trigger_ce_score = 0;
10507 // do not change elements more than a specified maximum number of changes
10508 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10511 ChangeCount[x][y]++; // count number of changes in the same frame
10513 if (change->explode)
10520 if (change->use_target_content)
10522 boolean complete_replace = TRUE;
10523 boolean can_replace[3][3];
10526 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10529 boolean is_walkable;
10530 boolean is_diggable;
10531 boolean is_collectible;
10532 boolean is_removable;
10533 boolean is_destructible;
10534 int ex = x + xx - 1;
10535 int ey = y + yy - 1;
10536 int content_element = change->target_content.e[xx][yy];
10539 can_replace[xx][yy] = TRUE;
10541 if (ex == x && ey == y) // do not check changing element itself
10544 if (content_element == EL_EMPTY_SPACE)
10546 can_replace[xx][yy] = FALSE; // do not replace border with space
10551 if (!IN_LEV_FIELD(ex, ey))
10553 can_replace[xx][yy] = FALSE;
10554 complete_replace = FALSE;
10561 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10562 e = MovingOrBlocked2Element(ex, ey);
10564 is_empty = (IS_FREE(ex, ey) ||
10565 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10567 is_walkable = (is_empty || IS_WALKABLE(e));
10568 is_diggable = (is_empty || IS_DIGGABLE(e));
10569 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10570 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10571 is_removable = (is_diggable || is_collectible);
10573 can_replace[xx][yy] =
10574 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10575 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10576 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10577 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10578 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10579 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10580 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10582 if (!can_replace[xx][yy])
10583 complete_replace = FALSE;
10586 if (!change->only_if_complete || complete_replace)
10588 boolean something_has_changed = FALSE;
10590 if (change->only_if_complete && change->use_random_replace &&
10591 RND(100) < change->random_percentage)
10594 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10596 int ex = x + xx - 1;
10597 int ey = y + yy - 1;
10598 int content_element;
10600 if (can_replace[xx][yy] && (!change->use_random_replace ||
10601 RND(100) < change->random_percentage))
10603 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10604 RemoveMovingField(ex, ey);
10606 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10608 content_element = change->target_content.e[xx][yy];
10609 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10610 ce_value, ce_score);
10612 CreateElementFromChange(ex, ey, target_element);
10614 something_has_changed = TRUE;
10616 // for symmetry reasons, freeze newly created border elements
10617 if (ex != x || ey != y)
10618 Stop[ex][ey] = TRUE; // no more moving in this frame
10622 if (something_has_changed)
10624 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10625 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10631 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10632 ce_value, ce_score);
10634 if (element == EL_DIAGONAL_GROWING ||
10635 element == EL_DIAGONAL_SHRINKING)
10637 target_element = Store[x][y];
10639 Store[x][y] = EL_EMPTY;
10642 CreateElementFromChange(x, y, target_element);
10644 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10645 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10648 // this uses direct change before indirect change
10649 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10654 static void HandleElementChange(int x, int y, int page)
10656 int element = MovingOrBlocked2Element(x, y);
10657 struct ElementInfo *ei = &element_info[element];
10658 struct ElementChangeInfo *change = &ei->change_page[page];
10659 boolean handle_action_before_change = FALSE;
10662 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10663 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10665 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10666 x, y, element, element_info[element].token_name);
10667 Debug("game:playing:HandleElementChange", "This should never happen!");
10671 // this can happen with classic bombs on walkable, changing elements
10672 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10677 if (ChangeDelay[x][y] == 0) // initialize element change
10679 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10681 if (change->can_change)
10683 // !!! not clear why graphic animation should be reset at all here !!!
10684 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10685 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10688 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10690 When using an animation frame delay of 1 (this only happens with
10691 "sp_zonk.moving.left/right" in the classic graphics), the default
10692 (non-moving) animation shows wrong animation frames (while the
10693 moving animation, like "sp_zonk.moving.left/right", is correct,
10694 so this graphical bug never shows up with the classic graphics).
10695 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10696 be drawn instead of the correct frames 0,1,2,3. This is caused by
10697 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10698 an element change: First when the change delay ("ChangeDelay[][]")
10699 counter has reached zero after decrementing, then a second time in
10700 the next frame (after "GfxFrame[][]" was already incremented) when
10701 "ChangeDelay[][]" is reset to the initial delay value again.
10703 This causes frame 0 to be drawn twice, while the last frame won't
10704 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10706 As some animations may already be cleverly designed around this bug
10707 (at least the "Snake Bite" snake tail animation does this), it cannot
10708 simply be fixed here without breaking such existing animations.
10709 Unfortunately, it cannot easily be detected if a graphics set was
10710 designed "before" or "after" the bug was fixed. As a workaround,
10711 a new graphics set option "game.graphics_engine_version" was added
10712 to be able to specify the game's major release version for which the
10713 graphics set was designed, which can then be used to decide if the
10714 bugfix should be used (version 4 and above) or not (version 3 or
10715 below, or if no version was specified at all, as with old sets).
10717 (The wrong/fixed animation frames can be tested with the test level set
10718 "test_gfxframe" and level "000", which contains a specially prepared
10719 custom element at level position (x/y) == (11/9) which uses the zonk
10720 animation mentioned above. Using "game.graphics_engine_version: 4"
10721 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10722 This can also be seen from the debug output for this test element.)
10725 // when a custom element is about to change (for example by change delay),
10726 // do not reset graphic animation when the custom element is moving
10727 if (game.graphics_engine_version < 4 &&
10730 ResetGfxAnimation(x, y);
10731 ResetRandomAnimationValue(x, y);
10734 if (change->pre_change_function)
10735 change->pre_change_function(x, y);
10739 ChangeDelay[x][y]--;
10741 if (ChangeDelay[x][y] != 0) // continue element change
10743 if (change->can_change)
10745 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10747 if (IS_ANIMATED(graphic))
10748 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10750 if (change->change_function)
10751 change->change_function(x, y);
10754 else // finish element change
10756 if (ChangePage[x][y] != -1) // remember page from delayed change
10758 page = ChangePage[x][y];
10759 ChangePage[x][y] = -1;
10761 change = &ei->change_page[page];
10764 if (IS_MOVING(x, y)) // never change a running system ;-)
10766 ChangeDelay[x][y] = 1; // try change after next move step
10767 ChangePage[x][y] = page; // remember page to use for change
10772 // special case: set new level random seed before changing element
10773 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10774 handle_action_before_change = TRUE;
10776 if (change->has_action && handle_action_before_change)
10777 ExecuteCustomElementAction(x, y, element, page);
10779 if (change->can_change)
10781 if (ChangeElement(x, y, element, page))
10783 if (change->post_change_function)
10784 change->post_change_function(x, y);
10788 if (change->has_action && !handle_action_before_change)
10789 ExecuteCustomElementAction(x, y, element, page);
10793 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10794 int trigger_element,
10796 int trigger_player,
10800 boolean change_done_any = FALSE;
10801 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10804 if (!(trigger_events[trigger_element][trigger_event]))
10807 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10809 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10811 int element = EL_CUSTOM_START + i;
10812 boolean change_done = FALSE;
10815 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10816 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10819 for (p = 0; p < element_info[element].num_change_pages; p++)
10821 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10823 if (change->can_change_or_has_action &&
10824 change->has_event[trigger_event] &&
10825 change->trigger_side & trigger_side &&
10826 change->trigger_player & trigger_player &&
10827 change->trigger_page & trigger_page_bits &&
10828 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10830 change->actual_trigger_element = trigger_element;
10831 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10832 change->actual_trigger_player_bits = trigger_player;
10833 change->actual_trigger_side = trigger_side;
10834 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10835 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10837 if ((change->can_change && !change_done) || change->has_action)
10841 SCAN_PLAYFIELD(x, y)
10843 if (Tile[x][y] == element)
10845 if (change->can_change && !change_done)
10847 // if element already changed in this frame, not only prevent
10848 // another element change (checked in ChangeElement()), but
10849 // also prevent additional element actions for this element
10851 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10852 !level.use_action_after_change_bug)
10855 ChangeDelay[x][y] = 1;
10856 ChangeEvent[x][y] = trigger_event;
10858 HandleElementChange(x, y, p);
10860 else if (change->has_action)
10862 // if element already changed in this frame, not only prevent
10863 // another element change (checked in ChangeElement()), but
10864 // also prevent additional element actions for this element
10866 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10867 !level.use_action_after_change_bug)
10870 ExecuteCustomElementAction(x, y, element, p);
10871 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10876 if (change->can_change)
10878 change_done = TRUE;
10879 change_done_any = TRUE;
10886 RECURSION_LOOP_DETECTION_END();
10888 return change_done_any;
10891 static boolean CheckElementChangeExt(int x, int y,
10893 int trigger_element,
10895 int trigger_player,
10898 boolean change_done = FALSE;
10901 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10902 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10905 if (Tile[x][y] == EL_BLOCKED)
10907 Blocked2Moving(x, y, &x, &y);
10908 element = Tile[x][y];
10911 // check if element has already changed or is about to change after moving
10912 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10913 Tile[x][y] != element) ||
10915 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10916 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10917 ChangePage[x][y] != -1)))
10920 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10922 for (p = 0; p < element_info[element].num_change_pages; p++)
10924 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10926 /* check trigger element for all events where the element that is checked
10927 for changing interacts with a directly adjacent element -- this is
10928 different to element changes that affect other elements to change on the
10929 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10930 boolean check_trigger_element =
10931 (trigger_event == CE_TOUCHING_X ||
10932 trigger_event == CE_HITTING_X ||
10933 trigger_event == CE_HIT_BY_X ||
10934 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10936 if (change->can_change_or_has_action &&
10937 change->has_event[trigger_event] &&
10938 change->trigger_side & trigger_side &&
10939 change->trigger_player & trigger_player &&
10940 (!check_trigger_element ||
10941 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10943 change->actual_trigger_element = trigger_element;
10944 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10945 change->actual_trigger_player_bits = trigger_player;
10946 change->actual_trigger_side = trigger_side;
10947 change->actual_trigger_ce_value = CustomValue[x][y];
10948 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10950 // special case: trigger element not at (x,y) position for some events
10951 if (check_trigger_element)
10963 { 0, 0 }, { 0, 0 }, { 0, 0 },
10967 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10968 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10970 change->actual_trigger_ce_value = CustomValue[xx][yy];
10971 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10974 if (change->can_change && !change_done)
10976 ChangeDelay[x][y] = 1;
10977 ChangeEvent[x][y] = trigger_event;
10979 HandleElementChange(x, y, p);
10981 change_done = TRUE;
10983 else if (change->has_action)
10985 ExecuteCustomElementAction(x, y, element, p);
10986 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10991 RECURSION_LOOP_DETECTION_END();
10993 return change_done;
10996 static void PlayPlayerSound(struct PlayerInfo *player)
10998 int jx = player->jx, jy = player->jy;
10999 int sound_element = player->artwork_element;
11000 int last_action = player->last_action_waiting;
11001 int action = player->action_waiting;
11003 if (player->is_waiting)
11005 if (action != last_action)
11006 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11008 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11012 if (action != last_action)
11013 StopSound(element_info[sound_element].sound[last_action]);
11015 if (last_action == ACTION_SLEEPING)
11016 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11020 static void PlayAllPlayersSound(void)
11024 for (i = 0; i < MAX_PLAYERS; i++)
11025 if (stored_player[i].active)
11026 PlayPlayerSound(&stored_player[i]);
11029 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11031 boolean last_waiting = player->is_waiting;
11032 int move_dir = player->MovDir;
11034 player->dir_waiting = move_dir;
11035 player->last_action_waiting = player->action_waiting;
11039 if (!last_waiting) // not waiting -> waiting
11041 player->is_waiting = TRUE;
11043 player->frame_counter_bored =
11045 game.player_boring_delay_fixed +
11046 GetSimpleRandom(game.player_boring_delay_random);
11047 player->frame_counter_sleeping =
11049 game.player_sleeping_delay_fixed +
11050 GetSimpleRandom(game.player_sleeping_delay_random);
11052 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11055 if (game.player_sleeping_delay_fixed +
11056 game.player_sleeping_delay_random > 0 &&
11057 player->anim_delay_counter == 0 &&
11058 player->post_delay_counter == 0 &&
11059 FrameCounter >= player->frame_counter_sleeping)
11060 player->is_sleeping = TRUE;
11061 else if (game.player_boring_delay_fixed +
11062 game.player_boring_delay_random > 0 &&
11063 FrameCounter >= player->frame_counter_bored)
11064 player->is_bored = TRUE;
11066 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11067 player->is_bored ? ACTION_BORING :
11070 if (player->is_sleeping && player->use_murphy)
11072 // special case for sleeping Murphy when leaning against non-free tile
11074 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11075 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11076 !IS_MOVING(player->jx - 1, player->jy)))
11077 move_dir = MV_LEFT;
11078 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11079 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11080 !IS_MOVING(player->jx + 1, player->jy)))
11081 move_dir = MV_RIGHT;
11083 player->is_sleeping = FALSE;
11085 player->dir_waiting = move_dir;
11088 if (player->is_sleeping)
11090 if (player->num_special_action_sleeping > 0)
11092 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11094 int last_special_action = player->special_action_sleeping;
11095 int num_special_action = player->num_special_action_sleeping;
11096 int special_action =
11097 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11098 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11099 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11100 last_special_action + 1 : ACTION_SLEEPING);
11101 int special_graphic =
11102 el_act_dir2img(player->artwork_element, special_action, move_dir);
11104 player->anim_delay_counter =
11105 graphic_info[special_graphic].anim_delay_fixed +
11106 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11107 player->post_delay_counter =
11108 graphic_info[special_graphic].post_delay_fixed +
11109 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11111 player->special_action_sleeping = special_action;
11114 if (player->anim_delay_counter > 0)
11116 player->action_waiting = player->special_action_sleeping;
11117 player->anim_delay_counter--;
11119 else if (player->post_delay_counter > 0)
11121 player->post_delay_counter--;
11125 else if (player->is_bored)
11127 if (player->num_special_action_bored > 0)
11129 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11131 int special_action =
11132 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11133 int special_graphic =
11134 el_act_dir2img(player->artwork_element, special_action, move_dir);
11136 player->anim_delay_counter =
11137 graphic_info[special_graphic].anim_delay_fixed +
11138 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11139 player->post_delay_counter =
11140 graphic_info[special_graphic].post_delay_fixed +
11141 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11143 player->special_action_bored = special_action;
11146 if (player->anim_delay_counter > 0)
11148 player->action_waiting = player->special_action_bored;
11149 player->anim_delay_counter--;
11151 else if (player->post_delay_counter > 0)
11153 player->post_delay_counter--;
11158 else if (last_waiting) // waiting -> not waiting
11160 player->is_waiting = FALSE;
11161 player->is_bored = FALSE;
11162 player->is_sleeping = FALSE;
11164 player->frame_counter_bored = -1;
11165 player->frame_counter_sleeping = -1;
11167 player->anim_delay_counter = 0;
11168 player->post_delay_counter = 0;
11170 player->dir_waiting = player->MovDir;
11171 player->action_waiting = ACTION_DEFAULT;
11173 player->special_action_bored = ACTION_DEFAULT;
11174 player->special_action_sleeping = ACTION_DEFAULT;
11178 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11180 if ((!player->is_moving && player->was_moving) ||
11181 (player->MovPos == 0 && player->was_moving) ||
11182 (player->is_snapping && !player->was_snapping) ||
11183 (player->is_dropping && !player->was_dropping))
11185 if (!CheckSaveEngineSnapshotToList())
11188 player->was_moving = FALSE;
11189 player->was_snapping = TRUE;
11190 player->was_dropping = TRUE;
11194 if (player->is_moving)
11195 player->was_moving = TRUE;
11197 if (!player->is_snapping)
11198 player->was_snapping = FALSE;
11200 if (!player->is_dropping)
11201 player->was_dropping = FALSE;
11205 static void CheckSingleStepMode(struct PlayerInfo *player)
11207 if (tape.single_step && tape.recording && !tape.pausing)
11209 /* as it is called "single step mode", just return to pause mode when the
11210 player stopped moving after one tile (or never starts moving at all) */
11211 if (!player->is_moving &&
11212 !player->is_pushing &&
11213 !player->is_dropping_pressed)
11214 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11217 CheckSaveEngineSnapshot(player);
11220 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11222 int left = player_action & JOY_LEFT;
11223 int right = player_action & JOY_RIGHT;
11224 int up = player_action & JOY_UP;
11225 int down = player_action & JOY_DOWN;
11226 int button1 = player_action & JOY_BUTTON_1;
11227 int button2 = player_action & JOY_BUTTON_2;
11228 int dx = (left ? -1 : right ? 1 : 0);
11229 int dy = (up ? -1 : down ? 1 : 0);
11231 if (!player->active || tape.pausing)
11237 SnapField(player, dx, dy);
11241 DropElement(player);
11243 MovePlayer(player, dx, dy);
11246 CheckSingleStepMode(player);
11248 SetPlayerWaiting(player, FALSE);
11250 return player_action;
11254 // no actions for this player (no input at player's configured device)
11256 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11257 SnapField(player, 0, 0);
11258 CheckGravityMovementWhenNotMoving(player);
11260 if (player->MovPos == 0)
11261 SetPlayerWaiting(player, TRUE);
11263 if (player->MovPos == 0) // needed for tape.playing
11264 player->is_moving = FALSE;
11266 player->is_dropping = FALSE;
11267 player->is_dropping_pressed = FALSE;
11268 player->drop_pressed_delay = 0;
11270 CheckSingleStepMode(player);
11276 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11279 if (!tape.use_mouse_actions)
11282 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11283 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11284 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11287 static void SetTapeActionFromMouseAction(byte *tape_action,
11288 struct MouseActionInfo *mouse_action)
11290 if (!tape.use_mouse_actions)
11293 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11294 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11295 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11298 static void CheckLevelSolved(void)
11300 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11302 if (game_em.level_solved &&
11303 !game_em.game_over) // game won
11307 game_em.game_over = TRUE;
11309 game.all_players_gone = TRUE;
11312 if (game_em.game_over) // game lost
11313 game.all_players_gone = TRUE;
11315 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11317 if (game_sp.level_solved &&
11318 !game_sp.game_over) // game won
11322 game_sp.game_over = TRUE;
11324 game.all_players_gone = TRUE;
11327 if (game_sp.game_over) // game lost
11328 game.all_players_gone = TRUE;
11330 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11332 if (game_mm.level_solved &&
11333 !game_mm.game_over) // game won
11337 game_mm.game_over = TRUE;
11339 game.all_players_gone = TRUE;
11342 if (game_mm.game_over) // game lost
11343 game.all_players_gone = TRUE;
11347 static void CheckLevelTime(void)
11351 if (TimeFrames >= FRAMES_PER_SECOND)
11356 for (i = 0; i < MAX_PLAYERS; i++)
11358 struct PlayerInfo *player = &stored_player[i];
11360 if (SHIELD_ON(player))
11362 player->shield_normal_time_left--;
11364 if (player->shield_deadly_time_left > 0)
11365 player->shield_deadly_time_left--;
11369 if (!game.LevelSolved && !level.use_step_counter)
11377 if (TimeLeft <= 10 && setup.time_limit)
11378 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11380 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11381 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11383 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11385 if (!TimeLeft && setup.time_limit)
11387 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11388 game_em.lev->killed_out_of_time = TRUE;
11390 for (i = 0; i < MAX_PLAYERS; i++)
11391 KillPlayer(&stored_player[i]);
11394 else if (game.no_time_limit && !game.all_players_gone)
11396 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11399 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11402 if (tape.recording || tape.playing)
11403 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11406 if (tape.recording || tape.playing)
11407 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11409 UpdateAndDisplayGameControlValues();
11412 void AdvanceFrameAndPlayerCounters(int player_nr)
11416 // advance frame counters (global frame counter and time frame counter)
11420 // advance player counters (counters for move delay, move animation etc.)
11421 for (i = 0; i < MAX_PLAYERS; i++)
11423 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11424 int move_delay_value = stored_player[i].move_delay_value;
11425 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11427 if (!advance_player_counters) // not all players may be affected
11430 if (move_frames == 0) // less than one move per game frame
11432 int stepsize = TILEX / move_delay_value;
11433 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11434 int count = (stored_player[i].is_moving ?
11435 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11437 if (count % delay == 0)
11441 stored_player[i].Frame += move_frames;
11443 if (stored_player[i].MovPos != 0)
11444 stored_player[i].StepFrame += move_frames;
11446 if (stored_player[i].move_delay > 0)
11447 stored_player[i].move_delay--;
11449 // due to bugs in previous versions, counter must count up, not down
11450 if (stored_player[i].push_delay != -1)
11451 stored_player[i].push_delay++;
11453 if (stored_player[i].drop_delay > 0)
11454 stored_player[i].drop_delay--;
11456 if (stored_player[i].is_dropping_pressed)
11457 stored_player[i].drop_pressed_delay++;
11461 void StartGameActions(boolean init_network_game, boolean record_tape,
11464 unsigned int new_random_seed = InitRND(random_seed);
11467 TapeStartRecording(new_random_seed);
11469 if (init_network_game)
11471 SendToServer_LevelFile();
11472 SendToServer_StartPlaying();
11480 static void GameActionsExt(void)
11483 static unsigned int game_frame_delay = 0;
11485 unsigned int game_frame_delay_value;
11486 byte *recorded_player_action;
11487 byte summarized_player_action = 0;
11488 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11491 // detect endless loops, caused by custom element programming
11492 if (recursion_loop_detected && recursion_loop_depth == 0)
11494 char *message = getStringCat3("Internal Error! Element ",
11495 EL_NAME(recursion_loop_element),
11496 " caused endless loop! Quit the game?");
11498 Warn("element '%s' caused endless loop in game engine",
11499 EL_NAME(recursion_loop_element));
11501 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11503 recursion_loop_detected = FALSE; // if game should be continued
11510 if (game.restart_level)
11511 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11513 CheckLevelSolved();
11515 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11518 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11521 if (game_status != GAME_MODE_PLAYING) // status might have changed
11524 game_frame_delay_value =
11525 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11527 if (tape.playing && tape.warp_forward && !tape.pausing)
11528 game_frame_delay_value = 0;
11530 SetVideoFrameDelay(game_frame_delay_value);
11532 // (de)activate virtual buttons depending on current game status
11533 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11535 if (game.all_players_gone) // if no players there to be controlled anymore
11536 SetOverlayActive(FALSE);
11537 else if (!tape.playing) // if game continues after tape stopped playing
11538 SetOverlayActive(TRUE);
11543 // ---------- main game synchronization point ----------
11545 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11547 Debug("game:playing:skip", "skip == %d", skip);
11550 // ---------- main game synchronization point ----------
11552 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11556 if (network_playing && !network_player_action_received)
11558 // try to get network player actions in time
11560 // last chance to get network player actions without main loop delay
11561 HandleNetworking();
11563 // game was quit by network peer
11564 if (game_status != GAME_MODE_PLAYING)
11567 // check if network player actions still missing and game still running
11568 if (!network_player_action_received && !checkGameEnded())
11569 return; // failed to get network player actions in time
11571 // do not yet reset "network_player_action_received" (for tape.pausing)
11577 // at this point we know that we really continue executing the game
11579 network_player_action_received = FALSE;
11581 // when playing tape, read previously recorded player input from tape data
11582 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11584 local_player->effective_mouse_action = local_player->mouse_action;
11586 if (recorded_player_action != NULL)
11587 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11588 recorded_player_action);
11590 // TapePlayAction() may return NULL when toggling to "pause before death"
11594 if (tape.set_centered_player)
11596 game.centered_player_nr_next = tape.centered_player_nr_next;
11597 game.set_centered_player = TRUE;
11600 for (i = 0; i < MAX_PLAYERS; i++)
11602 summarized_player_action |= stored_player[i].action;
11604 if (!network_playing && (game.team_mode || tape.playing))
11605 stored_player[i].effective_action = stored_player[i].action;
11608 if (network_playing && !checkGameEnded())
11609 SendToServer_MovePlayer(summarized_player_action);
11611 // summarize all actions at local players mapped input device position
11612 // (this allows using different input devices in single player mode)
11613 if (!network.enabled && !game.team_mode)
11614 stored_player[map_player_action[local_player->index_nr]].effective_action =
11615 summarized_player_action;
11617 // summarize all actions at centered player in local team mode
11618 if (tape.recording &&
11619 setup.team_mode && !network.enabled &&
11620 setup.input_on_focus &&
11621 game.centered_player_nr != -1)
11623 for (i = 0; i < MAX_PLAYERS; i++)
11624 stored_player[map_player_action[i]].effective_action =
11625 (i == game.centered_player_nr ? summarized_player_action : 0);
11628 if (recorded_player_action != NULL)
11629 for (i = 0; i < MAX_PLAYERS; i++)
11630 stored_player[i].effective_action = recorded_player_action[i];
11632 for (i = 0; i < MAX_PLAYERS; i++)
11634 tape_action[i] = stored_player[i].effective_action;
11636 /* (this may happen in the RND game engine if a player was not present on
11637 the playfield on level start, but appeared later from a custom element */
11638 if (setup.team_mode &&
11641 !tape.player_participates[i])
11642 tape.player_participates[i] = TRUE;
11645 SetTapeActionFromMouseAction(tape_action,
11646 &local_player->effective_mouse_action);
11648 // only record actions from input devices, but not programmed actions
11649 if (tape.recording)
11650 TapeRecordAction(tape_action);
11652 // remember if game was played (especially after tape stopped playing)
11653 if (!tape.playing && summarized_player_action)
11654 game.GamePlayed = TRUE;
11656 #if USE_NEW_PLAYER_ASSIGNMENTS
11657 // !!! also map player actions in single player mode !!!
11658 // if (game.team_mode)
11661 byte mapped_action[MAX_PLAYERS];
11663 #if DEBUG_PLAYER_ACTIONS
11665 for (i = 0; i < MAX_PLAYERS; i++)
11666 Print(" %d, ", stored_player[i].effective_action);
11669 for (i = 0; i < MAX_PLAYERS; i++)
11670 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11672 for (i = 0; i < MAX_PLAYERS; i++)
11673 stored_player[i].effective_action = mapped_action[i];
11675 #if DEBUG_PLAYER_ACTIONS
11677 for (i = 0; i < MAX_PLAYERS; i++)
11678 Print(" %d, ", stored_player[i].effective_action);
11682 #if DEBUG_PLAYER_ACTIONS
11686 for (i = 0; i < MAX_PLAYERS; i++)
11687 Print(" %d, ", stored_player[i].effective_action);
11693 for (i = 0; i < MAX_PLAYERS; i++)
11695 // allow engine snapshot in case of changed movement attempt
11696 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11697 (stored_player[i].effective_action & KEY_MOTION))
11698 game.snapshot.changed_action = TRUE;
11700 // allow engine snapshot in case of snapping/dropping attempt
11701 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11702 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11703 game.snapshot.changed_action = TRUE;
11705 game.snapshot.last_action[i] = stored_player[i].effective_action;
11708 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11710 GameActions_EM_Main();
11712 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11714 GameActions_SP_Main();
11716 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11718 GameActions_MM_Main();
11722 GameActions_RND_Main();
11725 BlitScreenToBitmap(backbuffer);
11727 CheckLevelSolved();
11730 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11732 if (global.show_frames_per_second)
11734 static unsigned int fps_counter = 0;
11735 static int fps_frames = 0;
11736 unsigned int fps_delay_ms = Counter() - fps_counter;
11740 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11742 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11745 fps_counter = Counter();
11747 // always draw FPS to screen after FPS value was updated
11748 redraw_mask |= REDRAW_FPS;
11751 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11752 if (GetDrawDeactivationMask() == REDRAW_NONE)
11753 redraw_mask |= REDRAW_FPS;
11757 static void GameActions_CheckSaveEngineSnapshot(void)
11759 if (!game.snapshot.save_snapshot)
11762 // clear flag for saving snapshot _before_ saving snapshot
11763 game.snapshot.save_snapshot = FALSE;
11765 SaveEngineSnapshotToList();
11768 void GameActions(void)
11772 GameActions_CheckSaveEngineSnapshot();
11775 void GameActions_EM_Main(void)
11777 byte effective_action[MAX_PLAYERS];
11778 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11781 for (i = 0; i < MAX_PLAYERS; i++)
11782 effective_action[i] = stored_player[i].effective_action;
11784 GameActions_EM(effective_action, warp_mode);
11787 void GameActions_SP_Main(void)
11789 byte effective_action[MAX_PLAYERS];
11790 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11793 for (i = 0; i < MAX_PLAYERS; i++)
11794 effective_action[i] = stored_player[i].effective_action;
11796 GameActions_SP(effective_action, warp_mode);
11798 for (i = 0; i < MAX_PLAYERS; i++)
11800 if (stored_player[i].force_dropping)
11801 stored_player[i].action |= KEY_BUTTON_DROP;
11803 stored_player[i].force_dropping = FALSE;
11807 void GameActions_MM_Main(void)
11809 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11811 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11814 void GameActions_RND_Main(void)
11819 void GameActions_RND(void)
11821 static struct MouseActionInfo mouse_action_last = { 0 };
11822 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11823 int magic_wall_x = 0, magic_wall_y = 0;
11824 int i, x, y, element, graphic, last_gfx_frame;
11826 InitPlayfieldScanModeVars();
11828 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11830 SCAN_PLAYFIELD(x, y)
11832 ChangeCount[x][y] = 0;
11833 ChangeEvent[x][y] = -1;
11837 if (game.set_centered_player)
11839 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11841 // switching to "all players" only possible if all players fit to screen
11842 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11844 game.centered_player_nr_next = game.centered_player_nr;
11845 game.set_centered_player = FALSE;
11848 // do not switch focus to non-existing (or non-active) player
11849 if (game.centered_player_nr_next >= 0 &&
11850 !stored_player[game.centered_player_nr_next].active)
11852 game.centered_player_nr_next = game.centered_player_nr;
11853 game.set_centered_player = FALSE;
11857 if (game.set_centered_player &&
11858 ScreenMovPos == 0) // screen currently aligned at tile position
11862 if (game.centered_player_nr_next == -1)
11864 setScreenCenteredToAllPlayers(&sx, &sy);
11868 sx = stored_player[game.centered_player_nr_next].jx;
11869 sy = stored_player[game.centered_player_nr_next].jy;
11872 game.centered_player_nr = game.centered_player_nr_next;
11873 game.set_centered_player = FALSE;
11875 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11876 DrawGameDoorValues();
11879 for (i = 0; i < MAX_PLAYERS; i++)
11881 int actual_player_action = stored_player[i].effective_action;
11884 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11885 - rnd_equinox_tetrachloride 048
11886 - rnd_equinox_tetrachloride_ii 096
11887 - rnd_emanuel_schmieg 002
11888 - doctor_sloan_ww 001, 020
11890 if (stored_player[i].MovPos == 0)
11891 CheckGravityMovement(&stored_player[i]);
11894 // overwrite programmed action with tape action
11895 if (stored_player[i].programmed_action)
11896 actual_player_action = stored_player[i].programmed_action;
11898 PlayerActions(&stored_player[i], actual_player_action);
11900 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11903 ScrollScreen(NULL, SCROLL_GO_ON);
11905 /* for backwards compatibility, the following code emulates a fixed bug that
11906 occured when pushing elements (causing elements that just made their last
11907 pushing step to already (if possible) make their first falling step in the
11908 same game frame, which is bad); this code is also needed to use the famous
11909 "spring push bug" which is used in older levels and might be wanted to be
11910 used also in newer levels, but in this case the buggy pushing code is only
11911 affecting the "spring" element and no other elements */
11913 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11915 for (i = 0; i < MAX_PLAYERS; i++)
11917 struct PlayerInfo *player = &stored_player[i];
11918 int x = player->jx;
11919 int y = player->jy;
11921 if (player->active && player->is_pushing && player->is_moving &&
11923 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11924 Tile[x][y] == EL_SPRING))
11926 ContinueMoving(x, y);
11928 // continue moving after pushing (this is actually a bug)
11929 if (!IS_MOVING(x, y))
11930 Stop[x][y] = FALSE;
11935 SCAN_PLAYFIELD(x, y)
11937 Last[x][y] = Tile[x][y];
11939 ChangeCount[x][y] = 0;
11940 ChangeEvent[x][y] = -1;
11942 // this must be handled before main playfield loop
11943 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11946 if (MovDelay[x][y] <= 0)
11950 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
11953 if (MovDelay[x][y] <= 0)
11956 TEST_DrawLevelField(x, y);
11958 TestIfElementTouchesCustomElement(x, y); // for empty space
11963 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11965 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
11967 Debug("game:playing:GameActions_RND", "This should never happen!");
11969 ChangePage[x][y] = -1;
11973 Stop[x][y] = FALSE;
11974 if (WasJustMoving[x][y] > 0)
11975 WasJustMoving[x][y]--;
11976 if (WasJustFalling[x][y] > 0)
11977 WasJustFalling[x][y]--;
11978 if (CheckCollision[x][y] > 0)
11979 CheckCollision[x][y]--;
11980 if (CheckImpact[x][y] > 0)
11981 CheckImpact[x][y]--;
11985 /* reset finished pushing action (not done in ContinueMoving() to allow
11986 continuous pushing animation for elements with zero push delay) */
11987 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11989 ResetGfxAnimation(x, y);
11990 TEST_DrawLevelField(x, y);
11994 if (IS_BLOCKED(x, y))
11998 Blocked2Moving(x, y, &oldx, &oldy);
11999 if (!IS_MOVING(oldx, oldy))
12001 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12002 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12003 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12004 Debug("game:playing:GameActions_RND", "This should never happen!");
12010 if (mouse_action.button)
12012 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12014 x = mouse_action.lx;
12015 y = mouse_action.ly;
12016 element = Tile[x][y];
12020 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12021 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12024 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12025 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12028 SCAN_PLAYFIELD(x, y)
12030 element = Tile[x][y];
12031 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12032 last_gfx_frame = GfxFrame[x][y];
12034 ResetGfxFrame(x, y);
12036 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12037 DrawLevelGraphicAnimation(x, y, graphic);
12039 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12040 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12041 ResetRandomAnimationValue(x, y);
12043 SetRandomAnimationValue(x, y);
12045 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12047 if (IS_INACTIVE(element))
12049 if (IS_ANIMATED(graphic))
12050 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12055 // this may take place after moving, so 'element' may have changed
12056 if (IS_CHANGING(x, y) &&
12057 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12059 int page = element_info[element].event_page_nr[CE_DELAY];
12061 HandleElementChange(x, y, page);
12063 element = Tile[x][y];
12064 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12067 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12071 element = Tile[x][y];
12072 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12074 if (IS_ANIMATED(graphic) &&
12075 !IS_MOVING(x, y) &&
12077 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12079 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12080 TEST_DrawTwinkleOnField(x, y);
12082 else if (element == EL_ACID)
12085 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12087 else if ((element == EL_EXIT_OPEN ||
12088 element == EL_EM_EXIT_OPEN ||
12089 element == EL_SP_EXIT_OPEN ||
12090 element == EL_STEEL_EXIT_OPEN ||
12091 element == EL_EM_STEEL_EXIT_OPEN ||
12092 element == EL_SP_TERMINAL ||
12093 element == EL_SP_TERMINAL_ACTIVE ||
12094 element == EL_EXTRA_TIME ||
12095 element == EL_SHIELD_NORMAL ||
12096 element == EL_SHIELD_DEADLY) &&
12097 IS_ANIMATED(graphic))
12098 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12099 else if (IS_MOVING(x, y))
12100 ContinueMoving(x, y);
12101 else if (IS_ACTIVE_BOMB(element))
12102 CheckDynamite(x, y);
12103 else if (element == EL_AMOEBA_GROWING)
12104 AmoebaGrowing(x, y);
12105 else if (element == EL_AMOEBA_SHRINKING)
12106 AmoebaShrinking(x, y);
12108 #if !USE_NEW_AMOEBA_CODE
12109 else if (IS_AMOEBALIVE(element))
12110 AmoebaReproduce(x, y);
12113 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12115 else if (element == EL_EXIT_CLOSED)
12117 else if (element == EL_EM_EXIT_CLOSED)
12119 else if (element == EL_STEEL_EXIT_CLOSED)
12120 CheckExitSteel(x, y);
12121 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12122 CheckExitSteelEM(x, y);
12123 else if (element == EL_SP_EXIT_CLOSED)
12125 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12126 element == EL_EXPANDABLE_STEELWALL_GROWING)
12127 MauerWaechst(x, y);
12128 else if (element == EL_EXPANDABLE_WALL ||
12129 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12130 element == EL_EXPANDABLE_WALL_VERTICAL ||
12131 element == EL_EXPANDABLE_WALL_ANY ||
12132 element == EL_BD_EXPANDABLE_WALL)
12133 MauerAbleger(x, y);
12134 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12135 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12136 element == EL_EXPANDABLE_STEELWALL_ANY)
12137 MauerAblegerStahl(x, y);
12138 else if (element == EL_FLAMES)
12139 CheckForDragon(x, y);
12140 else if (element == EL_EXPLOSION)
12141 ; // drawing of correct explosion animation is handled separately
12142 else if (element == EL_ELEMENT_SNAPPING ||
12143 element == EL_DIAGONAL_SHRINKING ||
12144 element == EL_DIAGONAL_GROWING)
12146 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12148 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12150 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12151 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12153 if (IS_BELT_ACTIVE(element))
12154 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12156 if (game.magic_wall_active)
12158 int jx = local_player->jx, jy = local_player->jy;
12160 // play the element sound at the position nearest to the player
12161 if ((element == EL_MAGIC_WALL_FULL ||
12162 element == EL_MAGIC_WALL_ACTIVE ||
12163 element == EL_MAGIC_WALL_EMPTYING ||
12164 element == EL_BD_MAGIC_WALL_FULL ||
12165 element == EL_BD_MAGIC_WALL_ACTIVE ||
12166 element == EL_BD_MAGIC_WALL_EMPTYING ||
12167 element == EL_DC_MAGIC_WALL_FULL ||
12168 element == EL_DC_MAGIC_WALL_ACTIVE ||
12169 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12170 ABS(x - jx) + ABS(y - jy) <
12171 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12179 #if USE_NEW_AMOEBA_CODE
12180 // new experimental amoeba growth stuff
12181 if (!(FrameCounter % 8))
12183 static unsigned int random = 1684108901;
12185 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12187 x = RND(lev_fieldx);
12188 y = RND(lev_fieldy);
12189 element = Tile[x][y];
12191 if (!IS_PLAYER(x,y) &&
12192 (element == EL_EMPTY ||
12193 CAN_GROW_INTO(element) ||
12194 element == EL_QUICKSAND_EMPTY ||
12195 element == EL_QUICKSAND_FAST_EMPTY ||
12196 element == EL_ACID_SPLASH_LEFT ||
12197 element == EL_ACID_SPLASH_RIGHT))
12199 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12200 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12201 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12202 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12203 Tile[x][y] = EL_AMOEBA_DROP;
12206 random = random * 129 + 1;
12211 game.explosions_delayed = FALSE;
12213 SCAN_PLAYFIELD(x, y)
12215 element = Tile[x][y];
12217 if (ExplodeField[x][y])
12218 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12219 else if (element == EL_EXPLOSION)
12220 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12222 ExplodeField[x][y] = EX_TYPE_NONE;
12225 game.explosions_delayed = TRUE;
12227 if (game.magic_wall_active)
12229 if (!(game.magic_wall_time_left % 4))
12231 int element = Tile[magic_wall_x][magic_wall_y];
12233 if (element == EL_BD_MAGIC_WALL_FULL ||
12234 element == EL_BD_MAGIC_WALL_ACTIVE ||
12235 element == EL_BD_MAGIC_WALL_EMPTYING)
12236 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12237 else if (element == EL_DC_MAGIC_WALL_FULL ||
12238 element == EL_DC_MAGIC_WALL_ACTIVE ||
12239 element == EL_DC_MAGIC_WALL_EMPTYING)
12240 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12242 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12245 if (game.magic_wall_time_left > 0)
12247 game.magic_wall_time_left--;
12249 if (!game.magic_wall_time_left)
12251 SCAN_PLAYFIELD(x, y)
12253 element = Tile[x][y];
12255 if (element == EL_MAGIC_WALL_ACTIVE ||
12256 element == EL_MAGIC_WALL_FULL)
12258 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12259 TEST_DrawLevelField(x, y);
12261 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12262 element == EL_BD_MAGIC_WALL_FULL)
12264 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12265 TEST_DrawLevelField(x, y);
12267 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12268 element == EL_DC_MAGIC_WALL_FULL)
12270 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12271 TEST_DrawLevelField(x, y);
12275 game.magic_wall_active = FALSE;
12280 if (game.light_time_left > 0)
12282 game.light_time_left--;
12284 if (game.light_time_left == 0)
12285 RedrawAllLightSwitchesAndInvisibleElements();
12288 if (game.timegate_time_left > 0)
12290 game.timegate_time_left--;
12292 if (game.timegate_time_left == 0)
12293 CloseAllOpenTimegates();
12296 if (game.lenses_time_left > 0)
12298 game.lenses_time_left--;
12300 if (game.lenses_time_left == 0)
12301 RedrawAllInvisibleElementsForLenses();
12304 if (game.magnify_time_left > 0)
12306 game.magnify_time_left--;
12308 if (game.magnify_time_left == 0)
12309 RedrawAllInvisibleElementsForMagnifier();
12312 for (i = 0; i < MAX_PLAYERS; i++)
12314 struct PlayerInfo *player = &stored_player[i];
12316 if (SHIELD_ON(player))
12318 if (player->shield_deadly_time_left)
12319 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12320 else if (player->shield_normal_time_left)
12321 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12325 #if USE_DELAYED_GFX_REDRAW
12326 SCAN_PLAYFIELD(x, y)
12328 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12330 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12331 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12333 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12334 DrawLevelField(x, y);
12336 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12337 DrawLevelFieldCrumbled(x, y);
12339 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12340 DrawLevelFieldCrumbledNeighbours(x, y);
12342 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12343 DrawTwinkleOnField(x, y);
12346 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12351 PlayAllPlayersSound();
12353 for (i = 0; i < MAX_PLAYERS; i++)
12355 struct PlayerInfo *player = &stored_player[i];
12357 if (player->show_envelope != 0 && (!player->active ||
12358 player->MovPos == 0))
12360 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12362 player->show_envelope = 0;
12366 // use random number generator in every frame to make it less predictable
12367 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12370 mouse_action_last = mouse_action;
12373 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12375 int min_x = x, min_y = y, max_x = x, max_y = y;
12378 for (i = 0; i < MAX_PLAYERS; i++)
12380 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12382 if (!stored_player[i].active || &stored_player[i] == player)
12385 min_x = MIN(min_x, jx);
12386 min_y = MIN(min_y, jy);
12387 max_x = MAX(max_x, jx);
12388 max_y = MAX(max_y, jy);
12391 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12394 static boolean AllPlayersInVisibleScreen(void)
12398 for (i = 0; i < MAX_PLAYERS; i++)
12400 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12402 if (!stored_player[i].active)
12405 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12412 void ScrollLevel(int dx, int dy)
12414 int scroll_offset = 2 * TILEX_VAR;
12417 BlitBitmap(drawto_field, drawto_field,
12418 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12419 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12420 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12421 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12422 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12423 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12427 x = (dx == 1 ? BX1 : BX2);
12428 for (y = BY1; y <= BY2; y++)
12429 DrawScreenField(x, y);
12434 y = (dy == 1 ? BY1 : BY2);
12435 for (x = BX1; x <= BX2; x++)
12436 DrawScreenField(x, y);
12439 redraw_mask |= REDRAW_FIELD;
12442 static boolean canFallDown(struct PlayerInfo *player)
12444 int jx = player->jx, jy = player->jy;
12446 return (IN_LEV_FIELD(jx, jy + 1) &&
12447 (IS_FREE(jx, jy + 1) ||
12448 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12449 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12450 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12453 static boolean canPassField(int x, int y, int move_dir)
12455 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12456 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12457 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12458 int nextx = x + dx;
12459 int nexty = y + dy;
12460 int element = Tile[x][y];
12462 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12463 !CAN_MOVE(element) &&
12464 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12465 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12466 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12469 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12471 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12472 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12473 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12477 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12478 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12479 (IS_DIGGABLE(Tile[newx][newy]) ||
12480 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12481 canPassField(newx, newy, move_dir)));
12484 static void CheckGravityMovement(struct PlayerInfo *player)
12486 if (player->gravity && !player->programmed_action)
12488 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12489 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12490 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12491 int jx = player->jx, jy = player->jy;
12492 boolean player_is_moving_to_valid_field =
12493 (!player_is_snapping &&
12494 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12495 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12496 boolean player_can_fall_down = canFallDown(player);
12498 if (player_can_fall_down &&
12499 !player_is_moving_to_valid_field)
12500 player->programmed_action = MV_DOWN;
12504 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12506 return CheckGravityMovement(player);
12508 if (player->gravity && !player->programmed_action)
12510 int jx = player->jx, jy = player->jy;
12511 boolean field_under_player_is_free =
12512 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12513 boolean player_is_standing_on_valid_field =
12514 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12515 (IS_WALKABLE(Tile[jx][jy]) &&
12516 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12518 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12519 player->programmed_action = MV_DOWN;
12524 MovePlayerOneStep()
12525 -----------------------------------------------------------------------------
12526 dx, dy: direction (non-diagonal) to try to move the player to
12527 real_dx, real_dy: direction as read from input device (can be diagonal)
12530 boolean MovePlayerOneStep(struct PlayerInfo *player,
12531 int dx, int dy, int real_dx, int real_dy)
12533 int jx = player->jx, jy = player->jy;
12534 int new_jx = jx + dx, new_jy = jy + dy;
12536 boolean player_can_move = !player->cannot_move;
12538 if (!player->active || (!dx && !dy))
12539 return MP_NO_ACTION;
12541 player->MovDir = (dx < 0 ? MV_LEFT :
12542 dx > 0 ? MV_RIGHT :
12544 dy > 0 ? MV_DOWN : MV_NONE);
12546 if (!IN_LEV_FIELD(new_jx, new_jy))
12547 return MP_NO_ACTION;
12549 if (!player_can_move)
12551 if (player->MovPos == 0)
12553 player->is_moving = FALSE;
12554 player->is_digging = FALSE;
12555 player->is_collecting = FALSE;
12556 player->is_snapping = FALSE;
12557 player->is_pushing = FALSE;
12561 if (!network.enabled && game.centered_player_nr == -1 &&
12562 !AllPlayersInSight(player, new_jx, new_jy))
12563 return MP_NO_ACTION;
12565 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12566 if (can_move != MP_MOVING)
12569 // check if DigField() has caused relocation of the player
12570 if (player->jx != jx || player->jy != jy)
12571 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12573 StorePlayer[jx][jy] = 0;
12574 player->last_jx = jx;
12575 player->last_jy = jy;
12576 player->jx = new_jx;
12577 player->jy = new_jy;
12578 StorePlayer[new_jx][new_jy] = player->element_nr;
12580 if (player->move_delay_value_next != -1)
12582 player->move_delay_value = player->move_delay_value_next;
12583 player->move_delay_value_next = -1;
12587 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12589 player->step_counter++;
12591 PlayerVisit[jx][jy] = FrameCounter;
12593 player->is_moving = TRUE;
12596 // should better be called in MovePlayer(), but this breaks some tapes
12597 ScrollPlayer(player, SCROLL_INIT);
12603 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12605 int jx = player->jx, jy = player->jy;
12606 int old_jx = jx, old_jy = jy;
12607 int moved = MP_NO_ACTION;
12609 if (!player->active)
12614 if (player->MovPos == 0)
12616 player->is_moving = FALSE;
12617 player->is_digging = FALSE;
12618 player->is_collecting = FALSE;
12619 player->is_snapping = FALSE;
12620 player->is_pushing = FALSE;
12626 if (player->move_delay > 0)
12629 player->move_delay = -1; // set to "uninitialized" value
12631 // store if player is automatically moved to next field
12632 player->is_auto_moving = (player->programmed_action != MV_NONE);
12634 // remove the last programmed player action
12635 player->programmed_action = 0;
12637 if (player->MovPos)
12639 // should only happen if pre-1.2 tape recordings are played
12640 // this is only for backward compatibility
12642 int original_move_delay_value = player->move_delay_value;
12645 Debug("game:playing:MovePlayer",
12646 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12650 // scroll remaining steps with finest movement resolution
12651 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12653 while (player->MovPos)
12655 ScrollPlayer(player, SCROLL_GO_ON);
12656 ScrollScreen(NULL, SCROLL_GO_ON);
12658 AdvanceFrameAndPlayerCounters(player->index_nr);
12661 BackToFront_WithFrameDelay(0);
12664 player->move_delay_value = original_move_delay_value;
12667 player->is_active = FALSE;
12669 if (player->last_move_dir & MV_HORIZONTAL)
12671 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12672 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12676 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12677 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12680 if (!moved && !player->is_active)
12682 player->is_moving = FALSE;
12683 player->is_digging = FALSE;
12684 player->is_collecting = FALSE;
12685 player->is_snapping = FALSE;
12686 player->is_pushing = FALSE;
12692 if (moved & MP_MOVING && !ScreenMovPos &&
12693 (player->index_nr == game.centered_player_nr ||
12694 game.centered_player_nr == -1))
12696 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12698 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12700 // actual player has left the screen -- scroll in that direction
12701 if (jx != old_jx) // player has moved horizontally
12702 scroll_x += (jx - old_jx);
12703 else // player has moved vertically
12704 scroll_y += (jy - old_jy);
12708 int offset_raw = game.scroll_delay_value;
12710 if (jx != old_jx) // player has moved horizontally
12712 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12713 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12714 int new_scroll_x = jx - MIDPOSX + offset_x;
12716 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12717 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12718 scroll_x = new_scroll_x;
12720 // don't scroll over playfield boundaries
12721 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12723 // don't scroll more than one field at a time
12724 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12726 // don't scroll against the player's moving direction
12727 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12728 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12729 scroll_x = old_scroll_x;
12731 else // player has moved vertically
12733 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12734 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12735 int new_scroll_y = jy - MIDPOSY + offset_y;
12737 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12738 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12739 scroll_y = new_scroll_y;
12741 // don't scroll over playfield boundaries
12742 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12744 // don't scroll more than one field at a time
12745 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12747 // don't scroll against the player's moving direction
12748 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12749 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12750 scroll_y = old_scroll_y;
12754 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12756 if (!network.enabled && game.centered_player_nr == -1 &&
12757 !AllPlayersInVisibleScreen())
12759 scroll_x = old_scroll_x;
12760 scroll_y = old_scroll_y;
12764 ScrollScreen(player, SCROLL_INIT);
12765 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12770 player->StepFrame = 0;
12772 if (moved & MP_MOVING)
12774 if (old_jx != jx && old_jy == jy)
12775 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12776 else if (old_jx == jx && old_jy != jy)
12777 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12779 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12781 player->last_move_dir = player->MovDir;
12782 player->is_moving = TRUE;
12783 player->is_snapping = FALSE;
12784 player->is_switching = FALSE;
12785 player->is_dropping = FALSE;
12786 player->is_dropping_pressed = FALSE;
12787 player->drop_pressed_delay = 0;
12790 // should better be called here than above, but this breaks some tapes
12791 ScrollPlayer(player, SCROLL_INIT);
12796 CheckGravityMovementWhenNotMoving(player);
12798 player->is_moving = FALSE;
12800 /* at this point, the player is allowed to move, but cannot move right now
12801 (e.g. because of something blocking the way) -- ensure that the player
12802 is also allowed to move in the next frame (in old versions before 3.1.1,
12803 the player was forced to wait again for eight frames before next try) */
12805 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12806 player->move_delay = 0; // allow direct movement in the next frame
12809 if (player->move_delay == -1) // not yet initialized by DigField()
12810 player->move_delay = player->move_delay_value;
12812 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12814 TestIfPlayerTouchesBadThing(jx, jy);
12815 TestIfPlayerTouchesCustomElement(jx, jy);
12818 if (!player->active)
12819 RemovePlayer(player);
12824 void ScrollPlayer(struct PlayerInfo *player, int mode)
12826 int jx = player->jx, jy = player->jy;
12827 int last_jx = player->last_jx, last_jy = player->last_jy;
12828 int move_stepsize = TILEX / player->move_delay_value;
12830 if (!player->active)
12833 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12836 if (mode == SCROLL_INIT)
12838 player->actual_frame_counter = FrameCounter;
12839 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12841 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12842 Tile[last_jx][last_jy] == EL_EMPTY)
12844 int last_field_block_delay = 0; // start with no blocking at all
12845 int block_delay_adjustment = player->block_delay_adjustment;
12847 // if player blocks last field, add delay for exactly one move
12848 if (player->block_last_field)
12850 last_field_block_delay += player->move_delay_value;
12852 // when blocking enabled, prevent moving up despite gravity
12853 if (player->gravity && player->MovDir == MV_UP)
12854 block_delay_adjustment = -1;
12857 // add block delay adjustment (also possible when not blocking)
12858 last_field_block_delay += block_delay_adjustment;
12860 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12861 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12864 if (player->MovPos != 0) // player has not yet reached destination
12867 else if (!FrameReached(&player->actual_frame_counter, 1))
12870 if (player->MovPos != 0)
12872 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12873 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12875 // before DrawPlayer() to draw correct player graphic for this case
12876 if (player->MovPos == 0)
12877 CheckGravityMovement(player);
12880 if (player->MovPos == 0) // player reached destination field
12882 if (player->move_delay_reset_counter > 0)
12884 player->move_delay_reset_counter--;
12886 if (player->move_delay_reset_counter == 0)
12888 // continue with normal speed after quickly moving through gate
12889 HALVE_PLAYER_SPEED(player);
12891 // be able to make the next move without delay
12892 player->move_delay = 0;
12896 player->last_jx = jx;
12897 player->last_jy = jy;
12899 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12900 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12901 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12902 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12903 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12904 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12905 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12906 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12908 ExitPlayer(player);
12910 if (game.players_still_needed == 0 &&
12911 (game.friends_still_needed == 0 ||
12912 IS_SP_ELEMENT(Tile[jx][jy])))
12916 // this breaks one level: "machine", level 000
12918 int move_direction = player->MovDir;
12919 int enter_side = MV_DIR_OPPOSITE(move_direction);
12920 int leave_side = move_direction;
12921 int old_jx = last_jx;
12922 int old_jy = last_jy;
12923 int old_element = Tile[old_jx][old_jy];
12924 int new_element = Tile[jx][jy];
12926 if (IS_CUSTOM_ELEMENT(old_element))
12927 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12929 player->index_bit, leave_side);
12931 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12932 CE_PLAYER_LEAVES_X,
12933 player->index_bit, leave_side);
12935 if (IS_CUSTOM_ELEMENT(new_element))
12936 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12937 player->index_bit, enter_side);
12939 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12940 CE_PLAYER_ENTERS_X,
12941 player->index_bit, enter_side);
12943 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12944 CE_MOVE_OF_X, move_direction);
12947 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12949 TestIfPlayerTouchesBadThing(jx, jy);
12950 TestIfPlayerTouchesCustomElement(jx, jy);
12952 /* needed because pushed element has not yet reached its destination,
12953 so it would trigger a change event at its previous field location */
12954 if (!player->is_pushing)
12955 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12957 if (!player->active)
12958 RemovePlayer(player);
12961 if (!game.LevelSolved && level.use_step_counter)
12971 if (TimeLeft <= 10 && setup.time_limit)
12972 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12974 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12976 DisplayGameControlValues();
12978 if (!TimeLeft && setup.time_limit)
12979 for (i = 0; i < MAX_PLAYERS; i++)
12980 KillPlayer(&stored_player[i]);
12982 else if (game.no_time_limit && !game.all_players_gone)
12984 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12986 DisplayGameControlValues();
12990 if (tape.single_step && tape.recording && !tape.pausing &&
12991 !player->programmed_action)
12992 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12994 if (!player->programmed_action)
12995 CheckSaveEngineSnapshot(player);
12999 void ScrollScreen(struct PlayerInfo *player, int mode)
13001 static unsigned int screen_frame_counter = 0;
13003 if (mode == SCROLL_INIT)
13005 // set scrolling step size according to actual player's moving speed
13006 ScrollStepSize = TILEX / player->move_delay_value;
13008 screen_frame_counter = FrameCounter;
13009 ScreenMovDir = player->MovDir;
13010 ScreenMovPos = player->MovPos;
13011 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13014 else if (!FrameReached(&screen_frame_counter, 1))
13019 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13020 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13021 redraw_mask |= REDRAW_FIELD;
13024 ScreenMovDir = MV_NONE;
13027 void TestIfPlayerTouchesCustomElement(int x, int y)
13029 static int xy[4][2] =
13036 static int trigger_sides[4][2] =
13038 // center side border side
13039 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13040 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13041 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13042 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13044 static int touch_dir[4] =
13046 MV_LEFT | MV_RIGHT,
13051 int center_element = Tile[x][y]; // should always be non-moving!
13054 for (i = 0; i < NUM_DIRECTIONS; i++)
13056 int xx = x + xy[i][0];
13057 int yy = y + xy[i][1];
13058 int center_side = trigger_sides[i][0];
13059 int border_side = trigger_sides[i][1];
13060 int border_element;
13062 if (!IN_LEV_FIELD(xx, yy))
13065 if (IS_PLAYER(x, y)) // player found at center element
13067 struct PlayerInfo *player = PLAYERINFO(x, y);
13069 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13070 border_element = Tile[xx][yy]; // may be moving!
13071 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13072 border_element = Tile[xx][yy];
13073 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13074 border_element = MovingOrBlocked2Element(xx, yy);
13076 continue; // center and border element do not touch
13078 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13079 player->index_bit, border_side);
13080 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13081 CE_PLAYER_TOUCHES_X,
13082 player->index_bit, border_side);
13085 /* use player element that is initially defined in the level playfield,
13086 not the player element that corresponds to the runtime player number
13087 (example: a level that contains EL_PLAYER_3 as the only player would
13088 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13089 int player_element = PLAYERINFO(x, y)->initial_element;
13091 CheckElementChangeBySide(xx, yy, border_element, player_element,
13092 CE_TOUCHING_X, border_side);
13095 else if (IS_PLAYER(xx, yy)) // player found at border element
13097 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13099 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13101 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13102 continue; // center and border element do not touch
13105 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13106 player->index_bit, center_side);
13107 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13108 CE_PLAYER_TOUCHES_X,
13109 player->index_bit, center_side);
13112 /* use player element that is initially defined in the level playfield,
13113 not the player element that corresponds to the runtime player number
13114 (example: a level that contains EL_PLAYER_3 as the only player would
13115 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13116 int player_element = PLAYERINFO(xx, yy)->initial_element;
13118 CheckElementChangeBySide(x, y, center_element, player_element,
13119 CE_TOUCHING_X, center_side);
13127 void TestIfElementTouchesCustomElement(int x, int y)
13129 static int xy[4][2] =
13136 static int trigger_sides[4][2] =
13138 // center side border side
13139 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13140 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13141 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13142 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13144 static int touch_dir[4] =
13146 MV_LEFT | MV_RIGHT,
13151 boolean change_center_element = FALSE;
13152 int center_element = Tile[x][y]; // should always be non-moving!
13153 int border_element_old[NUM_DIRECTIONS];
13156 for (i = 0; i < NUM_DIRECTIONS; i++)
13158 int xx = x + xy[i][0];
13159 int yy = y + xy[i][1];
13160 int border_element;
13162 border_element_old[i] = -1;
13164 if (!IN_LEV_FIELD(xx, yy))
13167 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13168 border_element = Tile[xx][yy]; // may be moving!
13169 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13170 border_element = Tile[xx][yy];
13171 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13172 border_element = MovingOrBlocked2Element(xx, yy);
13174 continue; // center and border element do not touch
13176 border_element_old[i] = border_element;
13179 for (i = 0; i < NUM_DIRECTIONS; i++)
13181 int xx = x + xy[i][0];
13182 int yy = y + xy[i][1];
13183 int center_side = trigger_sides[i][0];
13184 int border_element = border_element_old[i];
13186 if (border_element == -1)
13189 // check for change of border element
13190 CheckElementChangeBySide(xx, yy, border_element, center_element,
13191 CE_TOUCHING_X, center_side);
13193 // (center element cannot be player, so we dont have to check this here)
13196 for (i = 0; i < NUM_DIRECTIONS; i++)
13198 int xx = x + xy[i][0];
13199 int yy = y + xy[i][1];
13200 int border_side = trigger_sides[i][1];
13201 int border_element = border_element_old[i];
13203 if (border_element == -1)
13206 // check for change of center element (but change it only once)
13207 if (!change_center_element)
13208 change_center_element =
13209 CheckElementChangeBySide(x, y, center_element, border_element,
13210 CE_TOUCHING_X, border_side);
13212 if (IS_PLAYER(xx, yy))
13214 /* use player element that is initially defined in the level playfield,
13215 not the player element that corresponds to the runtime player number
13216 (example: a level that contains EL_PLAYER_3 as the only player would
13217 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13218 int player_element = PLAYERINFO(xx, yy)->initial_element;
13220 CheckElementChangeBySide(x, y, center_element, player_element,
13221 CE_TOUCHING_X, border_side);
13226 void TestIfElementHitsCustomElement(int x, int y, int direction)
13228 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13229 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13230 int hitx = x + dx, hity = y + dy;
13231 int hitting_element = Tile[x][y];
13232 int touched_element;
13234 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13237 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13238 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13240 if (IN_LEV_FIELD(hitx, hity))
13242 int opposite_direction = MV_DIR_OPPOSITE(direction);
13243 int hitting_side = direction;
13244 int touched_side = opposite_direction;
13245 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13246 MovDir[hitx][hity] != direction ||
13247 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13253 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13254 CE_HITTING_X, touched_side);
13256 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13257 CE_HIT_BY_X, hitting_side);
13259 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13260 CE_HIT_BY_SOMETHING, opposite_direction);
13262 if (IS_PLAYER(hitx, hity))
13264 /* use player element that is initially defined in the level playfield,
13265 not the player element that corresponds to the runtime player number
13266 (example: a level that contains EL_PLAYER_3 as the only player would
13267 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13268 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13270 CheckElementChangeBySide(x, y, hitting_element, player_element,
13271 CE_HITTING_X, touched_side);
13276 // "hitting something" is also true when hitting the playfield border
13277 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13278 CE_HITTING_SOMETHING, direction);
13281 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13283 int i, kill_x = -1, kill_y = -1;
13285 int bad_element = -1;
13286 static int test_xy[4][2] =
13293 static int test_dir[4] =
13301 for (i = 0; i < NUM_DIRECTIONS; i++)
13303 int test_x, test_y, test_move_dir, test_element;
13305 test_x = good_x + test_xy[i][0];
13306 test_y = good_y + test_xy[i][1];
13308 if (!IN_LEV_FIELD(test_x, test_y))
13312 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13314 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13316 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13317 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13319 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13320 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13324 bad_element = test_element;
13330 if (kill_x != -1 || kill_y != -1)
13332 if (IS_PLAYER(good_x, good_y))
13334 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13336 if (player->shield_deadly_time_left > 0 &&
13337 !IS_INDESTRUCTIBLE(bad_element))
13338 Bang(kill_x, kill_y);
13339 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13340 KillPlayer(player);
13343 Bang(good_x, good_y);
13347 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13349 int i, kill_x = -1, kill_y = -1;
13350 int bad_element = Tile[bad_x][bad_y];
13351 static int test_xy[4][2] =
13358 static int touch_dir[4] =
13360 MV_LEFT | MV_RIGHT,
13365 static int test_dir[4] =
13373 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13376 for (i = 0; i < NUM_DIRECTIONS; i++)
13378 int test_x, test_y, test_move_dir, test_element;
13380 test_x = bad_x + test_xy[i][0];
13381 test_y = bad_y + test_xy[i][1];
13383 if (!IN_LEV_FIELD(test_x, test_y))
13387 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13389 test_element = Tile[test_x][test_y];
13391 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13392 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13394 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13395 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13397 // good thing is player or penguin that does not move away
13398 if (IS_PLAYER(test_x, test_y))
13400 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13402 if (bad_element == EL_ROBOT && player->is_moving)
13403 continue; // robot does not kill player if he is moving
13405 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13407 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13408 continue; // center and border element do not touch
13416 else if (test_element == EL_PENGUIN)
13426 if (kill_x != -1 || kill_y != -1)
13428 if (IS_PLAYER(kill_x, kill_y))
13430 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13432 if (player->shield_deadly_time_left > 0 &&
13433 !IS_INDESTRUCTIBLE(bad_element))
13434 Bang(bad_x, bad_y);
13435 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13436 KillPlayer(player);
13439 Bang(kill_x, kill_y);
13443 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13445 int bad_element = Tile[bad_x][bad_y];
13446 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13447 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13448 int test_x = bad_x + dx, test_y = bad_y + dy;
13449 int test_move_dir, test_element;
13450 int kill_x = -1, kill_y = -1;
13452 if (!IN_LEV_FIELD(test_x, test_y))
13456 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13458 test_element = Tile[test_x][test_y];
13460 if (test_move_dir != bad_move_dir)
13462 // good thing can be player or penguin that does not move away
13463 if (IS_PLAYER(test_x, test_y))
13465 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13467 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13468 player as being hit when he is moving towards the bad thing, because
13469 the "get hit by" condition would be lost after the player stops) */
13470 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13471 return; // player moves away from bad thing
13476 else if (test_element == EL_PENGUIN)
13483 if (kill_x != -1 || kill_y != -1)
13485 if (IS_PLAYER(kill_x, kill_y))
13487 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13489 if (player->shield_deadly_time_left > 0 &&
13490 !IS_INDESTRUCTIBLE(bad_element))
13491 Bang(bad_x, bad_y);
13492 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13493 KillPlayer(player);
13496 Bang(kill_x, kill_y);
13500 void TestIfPlayerTouchesBadThing(int x, int y)
13502 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13505 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13507 TestIfGoodThingHitsBadThing(x, y, move_dir);
13510 void TestIfBadThingTouchesPlayer(int x, int y)
13512 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13515 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13517 TestIfBadThingHitsGoodThing(x, y, move_dir);
13520 void TestIfFriendTouchesBadThing(int x, int y)
13522 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13525 void TestIfBadThingTouchesFriend(int x, int y)
13527 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13530 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13532 int i, kill_x = bad_x, kill_y = bad_y;
13533 static int xy[4][2] =
13541 for (i = 0; i < NUM_DIRECTIONS; i++)
13545 x = bad_x + xy[i][0];
13546 y = bad_y + xy[i][1];
13547 if (!IN_LEV_FIELD(x, y))
13550 element = Tile[x][y];
13551 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13552 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13560 if (kill_x != bad_x || kill_y != bad_y)
13561 Bang(bad_x, bad_y);
13564 void KillPlayer(struct PlayerInfo *player)
13566 int jx = player->jx, jy = player->jy;
13568 if (!player->active)
13572 Debug("game:playing:KillPlayer",
13573 "0: killed == %d, active == %d, reanimated == %d",
13574 player->killed, player->active, player->reanimated);
13577 /* the following code was introduced to prevent an infinite loop when calling
13579 -> CheckTriggeredElementChangeExt()
13580 -> ExecuteCustomElementAction()
13582 -> (infinitely repeating the above sequence of function calls)
13583 which occurs when killing the player while having a CE with the setting
13584 "kill player X when explosion of <player X>"; the solution using a new
13585 field "player->killed" was chosen for backwards compatibility, although
13586 clever use of the fields "player->active" etc. would probably also work */
13588 if (player->killed)
13592 player->killed = TRUE;
13594 // remove accessible field at the player's position
13595 Tile[jx][jy] = EL_EMPTY;
13597 // deactivate shield (else Bang()/Explode() would not work right)
13598 player->shield_normal_time_left = 0;
13599 player->shield_deadly_time_left = 0;
13602 Debug("game:playing:KillPlayer",
13603 "1: killed == %d, active == %d, reanimated == %d",
13604 player->killed, player->active, player->reanimated);
13610 Debug("game:playing:KillPlayer",
13611 "2: killed == %d, active == %d, reanimated == %d",
13612 player->killed, player->active, player->reanimated);
13615 if (player->reanimated) // killed player may have been reanimated
13616 player->killed = player->reanimated = FALSE;
13618 BuryPlayer(player);
13621 static void KillPlayerUnlessEnemyProtected(int x, int y)
13623 if (!PLAYER_ENEMY_PROTECTED(x, y))
13624 KillPlayer(PLAYERINFO(x, y));
13627 static void KillPlayerUnlessExplosionProtected(int x, int y)
13629 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13630 KillPlayer(PLAYERINFO(x, y));
13633 void BuryPlayer(struct PlayerInfo *player)
13635 int jx = player->jx, jy = player->jy;
13637 if (!player->active)
13640 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13641 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13643 RemovePlayer(player);
13645 player->buried = TRUE;
13647 if (game.all_players_gone)
13648 game.GameOver = TRUE;
13651 void RemovePlayer(struct PlayerInfo *player)
13653 int jx = player->jx, jy = player->jy;
13654 int i, found = FALSE;
13656 player->present = FALSE;
13657 player->active = FALSE;
13659 // required for some CE actions (even if the player is not active anymore)
13660 player->MovPos = 0;
13662 if (!ExplodeField[jx][jy])
13663 StorePlayer[jx][jy] = 0;
13665 if (player->is_moving)
13666 TEST_DrawLevelField(player->last_jx, player->last_jy);
13668 for (i = 0; i < MAX_PLAYERS; i++)
13669 if (stored_player[i].active)
13674 game.all_players_gone = TRUE;
13675 game.GameOver = TRUE;
13678 game.exit_x = game.robot_wheel_x = jx;
13679 game.exit_y = game.robot_wheel_y = jy;
13682 void ExitPlayer(struct PlayerInfo *player)
13684 DrawPlayer(player); // needed here only to cleanup last field
13685 RemovePlayer(player);
13687 if (game.players_still_needed > 0)
13688 game.players_still_needed--;
13691 static void setFieldForSnapping(int x, int y, int element, int direction)
13693 struct ElementInfo *ei = &element_info[element];
13694 int direction_bit = MV_DIR_TO_BIT(direction);
13695 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13696 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13697 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13699 Tile[x][y] = EL_ELEMENT_SNAPPING;
13700 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13702 ResetGfxAnimation(x, y);
13704 GfxElement[x][y] = element;
13705 GfxAction[x][y] = action;
13706 GfxDir[x][y] = direction;
13707 GfxFrame[x][y] = -1;
13711 =============================================================================
13712 checkDiagonalPushing()
13713 -----------------------------------------------------------------------------
13714 check if diagonal input device direction results in pushing of object
13715 (by checking if the alternative direction is walkable, diggable, ...)
13716 =============================================================================
13719 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13720 int x, int y, int real_dx, int real_dy)
13722 int jx, jy, dx, dy, xx, yy;
13724 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13727 // diagonal direction: check alternative direction
13732 xx = jx + (dx == 0 ? real_dx : 0);
13733 yy = jy + (dy == 0 ? real_dy : 0);
13735 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13739 =============================================================================
13741 -----------------------------------------------------------------------------
13742 x, y: field next to player (non-diagonal) to try to dig to
13743 real_dx, real_dy: direction as read from input device (can be diagonal)
13744 =============================================================================
13747 static int DigField(struct PlayerInfo *player,
13748 int oldx, int oldy, int x, int y,
13749 int real_dx, int real_dy, int mode)
13751 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13752 boolean player_was_pushing = player->is_pushing;
13753 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13754 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13755 int jx = oldx, jy = oldy;
13756 int dx = x - jx, dy = y - jy;
13757 int nextx = x + dx, nexty = y + dy;
13758 int move_direction = (dx == -1 ? MV_LEFT :
13759 dx == +1 ? MV_RIGHT :
13761 dy == +1 ? MV_DOWN : MV_NONE);
13762 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13763 int dig_side = MV_DIR_OPPOSITE(move_direction);
13764 int old_element = Tile[jx][jy];
13765 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13768 if (is_player) // function can also be called by EL_PENGUIN
13770 if (player->MovPos == 0)
13772 player->is_digging = FALSE;
13773 player->is_collecting = FALSE;
13776 if (player->MovPos == 0) // last pushing move finished
13777 player->is_pushing = FALSE;
13779 if (mode == DF_NO_PUSH) // player just stopped pushing
13781 player->is_switching = FALSE;
13782 player->push_delay = -1;
13784 return MP_NO_ACTION;
13788 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13789 old_element = Back[jx][jy];
13791 // in case of element dropped at player position, check background
13792 else if (Back[jx][jy] != EL_EMPTY &&
13793 game.engine_version >= VERSION_IDENT(2,2,0,0))
13794 old_element = Back[jx][jy];
13796 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13797 return MP_NO_ACTION; // field has no opening in this direction
13799 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13800 return MP_NO_ACTION; // field has no opening in this direction
13802 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13806 Tile[jx][jy] = player->artwork_element;
13807 InitMovingField(jx, jy, MV_DOWN);
13808 Store[jx][jy] = EL_ACID;
13809 ContinueMoving(jx, jy);
13810 BuryPlayer(player);
13812 return MP_DONT_RUN_INTO;
13815 if (player_can_move && DONT_RUN_INTO(element))
13817 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13819 return MP_DONT_RUN_INTO;
13822 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13823 return MP_NO_ACTION;
13825 collect_count = element_info[element].collect_count_initial;
13827 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13828 return MP_NO_ACTION;
13830 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13831 player_can_move = player_can_move_or_snap;
13833 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13834 game.engine_version >= VERSION_IDENT(2,2,0,0))
13836 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13837 player->index_bit, dig_side);
13838 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13839 player->index_bit, dig_side);
13841 if (element == EL_DC_LANDMINE)
13844 if (Tile[x][y] != element) // field changed by snapping
13847 return MP_NO_ACTION;
13850 if (player->gravity && is_player && !player->is_auto_moving &&
13851 canFallDown(player) && move_direction != MV_DOWN &&
13852 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13853 return MP_NO_ACTION; // player cannot walk here due to gravity
13855 if (player_can_move &&
13856 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13858 int sound_element = SND_ELEMENT(element);
13859 int sound_action = ACTION_WALKING;
13861 if (IS_RND_GATE(element))
13863 if (!player->key[RND_GATE_NR(element)])
13864 return MP_NO_ACTION;
13866 else if (IS_RND_GATE_GRAY(element))
13868 if (!player->key[RND_GATE_GRAY_NR(element)])
13869 return MP_NO_ACTION;
13871 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13873 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13874 return MP_NO_ACTION;
13876 else if (element == EL_EXIT_OPEN ||
13877 element == EL_EM_EXIT_OPEN ||
13878 element == EL_EM_EXIT_OPENING ||
13879 element == EL_STEEL_EXIT_OPEN ||
13880 element == EL_EM_STEEL_EXIT_OPEN ||
13881 element == EL_EM_STEEL_EXIT_OPENING ||
13882 element == EL_SP_EXIT_OPEN ||
13883 element == EL_SP_EXIT_OPENING)
13885 sound_action = ACTION_PASSING; // player is passing exit
13887 else if (element == EL_EMPTY)
13889 sound_action = ACTION_MOVING; // nothing to walk on
13892 // play sound from background or player, whatever is available
13893 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13894 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13896 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13898 else if (player_can_move &&
13899 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13901 if (!ACCESS_FROM(element, opposite_direction))
13902 return MP_NO_ACTION; // field not accessible from this direction
13904 if (CAN_MOVE(element)) // only fixed elements can be passed!
13905 return MP_NO_ACTION;
13907 if (IS_EM_GATE(element))
13909 if (!player->key[EM_GATE_NR(element)])
13910 return MP_NO_ACTION;
13912 else if (IS_EM_GATE_GRAY(element))
13914 if (!player->key[EM_GATE_GRAY_NR(element)])
13915 return MP_NO_ACTION;
13917 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13919 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13920 return MP_NO_ACTION;
13922 else if (IS_EMC_GATE(element))
13924 if (!player->key[EMC_GATE_NR(element)])
13925 return MP_NO_ACTION;
13927 else if (IS_EMC_GATE_GRAY(element))
13929 if (!player->key[EMC_GATE_GRAY_NR(element)])
13930 return MP_NO_ACTION;
13932 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13934 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13935 return MP_NO_ACTION;
13937 else if (element == EL_DC_GATE_WHITE ||
13938 element == EL_DC_GATE_WHITE_GRAY ||
13939 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13941 if (player->num_white_keys == 0)
13942 return MP_NO_ACTION;
13944 player->num_white_keys--;
13946 else if (IS_SP_PORT(element))
13948 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13949 element == EL_SP_GRAVITY_PORT_RIGHT ||
13950 element == EL_SP_GRAVITY_PORT_UP ||
13951 element == EL_SP_GRAVITY_PORT_DOWN)
13952 player->gravity = !player->gravity;
13953 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13954 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13955 element == EL_SP_GRAVITY_ON_PORT_UP ||
13956 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13957 player->gravity = TRUE;
13958 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13959 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13960 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13961 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13962 player->gravity = FALSE;
13965 // automatically move to the next field with double speed
13966 player->programmed_action = move_direction;
13968 if (player->move_delay_reset_counter == 0)
13970 player->move_delay_reset_counter = 2; // two double speed steps
13972 DOUBLE_PLAYER_SPEED(player);
13975 PlayLevelSoundAction(x, y, ACTION_PASSING);
13977 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13981 if (mode != DF_SNAP)
13983 GfxElement[x][y] = GFX_ELEMENT(element);
13984 player->is_digging = TRUE;
13987 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13989 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13990 player->index_bit, dig_side);
13992 if (mode == DF_SNAP)
13994 if (level.block_snap_field)
13995 setFieldForSnapping(x, y, element, move_direction);
13997 TestIfElementTouchesCustomElement(x, y); // for empty space
13999 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14000 player->index_bit, dig_side);
14003 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14007 if (is_player && mode != DF_SNAP)
14009 GfxElement[x][y] = element;
14010 player->is_collecting = TRUE;
14013 if (element == EL_SPEED_PILL)
14015 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14017 else if (element == EL_EXTRA_TIME && level.time > 0)
14019 TimeLeft += level.extra_time;
14021 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14023 DisplayGameControlValues();
14025 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14027 player->shield_normal_time_left += level.shield_normal_time;
14028 if (element == EL_SHIELD_DEADLY)
14029 player->shield_deadly_time_left += level.shield_deadly_time;
14031 else if (element == EL_DYNAMITE ||
14032 element == EL_EM_DYNAMITE ||
14033 element == EL_SP_DISK_RED)
14035 if (player->inventory_size < MAX_INVENTORY_SIZE)
14036 player->inventory_element[player->inventory_size++] = element;
14038 DrawGameDoorValues();
14040 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14042 player->dynabomb_count++;
14043 player->dynabombs_left++;
14045 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14047 player->dynabomb_size++;
14049 else if (element == EL_DYNABOMB_INCREASE_POWER)
14051 player->dynabomb_xl = TRUE;
14053 else if (IS_KEY(element))
14055 player->key[KEY_NR(element)] = TRUE;
14057 DrawGameDoorValues();
14059 else if (element == EL_DC_KEY_WHITE)
14061 player->num_white_keys++;
14063 // display white keys?
14064 // DrawGameDoorValues();
14066 else if (IS_ENVELOPE(element))
14068 player->show_envelope = element;
14070 else if (element == EL_EMC_LENSES)
14072 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14074 RedrawAllInvisibleElementsForLenses();
14076 else if (element == EL_EMC_MAGNIFIER)
14078 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14080 RedrawAllInvisibleElementsForMagnifier();
14082 else if (IS_DROPPABLE(element) ||
14083 IS_THROWABLE(element)) // can be collected and dropped
14087 if (collect_count == 0)
14088 player->inventory_infinite_element = element;
14090 for (i = 0; i < collect_count; i++)
14091 if (player->inventory_size < MAX_INVENTORY_SIZE)
14092 player->inventory_element[player->inventory_size++] = element;
14094 DrawGameDoorValues();
14096 else if (collect_count > 0)
14098 game.gems_still_needed -= collect_count;
14099 if (game.gems_still_needed < 0)
14100 game.gems_still_needed = 0;
14102 game.snapshot.collected_item = TRUE;
14104 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14106 DisplayGameControlValues();
14109 RaiseScoreElement(element);
14110 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14113 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14114 player->index_bit, dig_side);
14116 if (mode == DF_SNAP)
14118 if (level.block_snap_field)
14119 setFieldForSnapping(x, y, element, move_direction);
14121 TestIfElementTouchesCustomElement(x, y); // for empty space
14123 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14124 player->index_bit, dig_side);
14127 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14129 if (mode == DF_SNAP && element != EL_BD_ROCK)
14130 return MP_NO_ACTION;
14132 if (CAN_FALL(element) && dy)
14133 return MP_NO_ACTION;
14135 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14136 !(element == EL_SPRING && level.use_spring_bug))
14137 return MP_NO_ACTION;
14139 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14140 ((move_direction & MV_VERTICAL &&
14141 ((element_info[element].move_pattern & MV_LEFT &&
14142 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14143 (element_info[element].move_pattern & MV_RIGHT &&
14144 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14145 (move_direction & MV_HORIZONTAL &&
14146 ((element_info[element].move_pattern & MV_UP &&
14147 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14148 (element_info[element].move_pattern & MV_DOWN &&
14149 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14150 return MP_NO_ACTION;
14152 // do not push elements already moving away faster than player
14153 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14154 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14155 return MP_NO_ACTION;
14157 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14159 if (player->push_delay_value == -1 || !player_was_pushing)
14160 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14162 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14164 if (player->push_delay_value == -1)
14165 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14167 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14169 if (!player->is_pushing)
14170 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14173 player->is_pushing = TRUE;
14174 player->is_active = TRUE;
14176 if (!(IN_LEV_FIELD(nextx, nexty) &&
14177 (IS_FREE(nextx, nexty) ||
14178 (IS_SB_ELEMENT(element) &&
14179 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14180 (IS_CUSTOM_ELEMENT(element) &&
14181 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14182 return MP_NO_ACTION;
14184 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14185 return MP_NO_ACTION;
14187 if (player->push_delay == -1) // new pushing; restart delay
14188 player->push_delay = 0;
14190 if (player->push_delay < player->push_delay_value &&
14191 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14192 element != EL_SPRING && element != EL_BALLOON)
14194 // make sure that there is no move delay before next try to push
14195 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14196 player->move_delay = 0;
14198 return MP_NO_ACTION;
14201 if (IS_CUSTOM_ELEMENT(element) &&
14202 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14204 if (!DigFieldByCE(nextx, nexty, element))
14205 return MP_NO_ACTION;
14208 if (IS_SB_ELEMENT(element))
14210 boolean sokoban_task_solved = FALSE;
14212 if (element == EL_SOKOBAN_FIELD_FULL)
14214 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14216 IncrementSokobanFieldsNeeded();
14217 IncrementSokobanObjectsNeeded();
14220 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14222 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14224 DecrementSokobanFieldsNeeded();
14225 DecrementSokobanObjectsNeeded();
14227 // sokoban object was pushed from empty field to sokoban field
14228 if (Back[x][y] == EL_EMPTY)
14229 sokoban_task_solved = TRUE;
14232 Tile[x][y] = EL_SOKOBAN_OBJECT;
14234 if (Back[x][y] == Back[nextx][nexty])
14235 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14236 else if (Back[x][y] != 0)
14237 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14240 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14243 if (sokoban_task_solved &&
14244 game.sokoban_fields_still_needed == 0 &&
14245 game.sokoban_objects_still_needed == 0 &&
14246 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14248 game.players_still_needed = 0;
14252 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14256 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14258 InitMovingField(x, y, move_direction);
14259 GfxAction[x][y] = ACTION_PUSHING;
14261 if (mode == DF_SNAP)
14262 ContinueMoving(x, y);
14264 MovPos[x][y] = (dx != 0 ? dx : dy);
14266 Pushed[x][y] = TRUE;
14267 Pushed[nextx][nexty] = TRUE;
14269 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14270 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14272 player->push_delay_value = -1; // get new value later
14274 // check for element change _after_ element has been pushed
14275 if (game.use_change_when_pushing_bug)
14277 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14278 player->index_bit, dig_side);
14279 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14280 player->index_bit, dig_side);
14283 else if (IS_SWITCHABLE(element))
14285 if (PLAYER_SWITCHING(player, x, y))
14287 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14288 player->index_bit, dig_side);
14293 player->is_switching = TRUE;
14294 player->switch_x = x;
14295 player->switch_y = y;
14297 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14299 if (element == EL_ROBOT_WHEEL)
14301 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14303 game.robot_wheel_x = x;
14304 game.robot_wheel_y = y;
14305 game.robot_wheel_active = TRUE;
14307 TEST_DrawLevelField(x, y);
14309 else if (element == EL_SP_TERMINAL)
14313 SCAN_PLAYFIELD(xx, yy)
14315 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14319 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14321 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14323 ResetGfxAnimation(xx, yy);
14324 TEST_DrawLevelField(xx, yy);
14328 else if (IS_BELT_SWITCH(element))
14330 ToggleBeltSwitch(x, y);
14332 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14333 element == EL_SWITCHGATE_SWITCH_DOWN ||
14334 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14335 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14337 ToggleSwitchgateSwitch(x, y);
14339 else if (element == EL_LIGHT_SWITCH ||
14340 element == EL_LIGHT_SWITCH_ACTIVE)
14342 ToggleLightSwitch(x, y);
14344 else if (element == EL_TIMEGATE_SWITCH ||
14345 element == EL_DC_TIMEGATE_SWITCH)
14347 ActivateTimegateSwitch(x, y);
14349 else if (element == EL_BALLOON_SWITCH_LEFT ||
14350 element == EL_BALLOON_SWITCH_RIGHT ||
14351 element == EL_BALLOON_SWITCH_UP ||
14352 element == EL_BALLOON_SWITCH_DOWN ||
14353 element == EL_BALLOON_SWITCH_NONE ||
14354 element == EL_BALLOON_SWITCH_ANY)
14356 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14357 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14358 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14359 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14360 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14363 else if (element == EL_LAMP)
14365 Tile[x][y] = EL_LAMP_ACTIVE;
14366 game.lights_still_needed--;
14368 ResetGfxAnimation(x, y);
14369 TEST_DrawLevelField(x, y);
14371 else if (element == EL_TIME_ORB_FULL)
14373 Tile[x][y] = EL_TIME_ORB_EMPTY;
14375 if (level.time > 0 || level.use_time_orb_bug)
14377 TimeLeft += level.time_orb_time;
14378 game.no_time_limit = FALSE;
14380 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14382 DisplayGameControlValues();
14385 ResetGfxAnimation(x, y);
14386 TEST_DrawLevelField(x, y);
14388 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14389 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14393 game.ball_active = !game.ball_active;
14395 SCAN_PLAYFIELD(xx, yy)
14397 int e = Tile[xx][yy];
14399 if (game.ball_active)
14401 if (e == EL_EMC_MAGIC_BALL)
14402 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14403 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14404 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14408 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14409 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14410 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14411 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14416 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14417 player->index_bit, dig_side);
14419 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14420 player->index_bit, dig_side);
14422 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14423 player->index_bit, dig_side);
14429 if (!PLAYER_SWITCHING(player, x, y))
14431 player->is_switching = TRUE;
14432 player->switch_x = x;
14433 player->switch_y = y;
14435 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14436 player->index_bit, dig_side);
14437 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14438 player->index_bit, dig_side);
14440 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14441 player->index_bit, dig_side);
14442 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14443 player->index_bit, dig_side);
14446 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14447 player->index_bit, dig_side);
14448 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14449 player->index_bit, dig_side);
14451 return MP_NO_ACTION;
14454 player->push_delay = -1;
14456 if (is_player) // function can also be called by EL_PENGUIN
14458 if (Tile[x][y] != element) // really digged/collected something
14460 player->is_collecting = !player->is_digging;
14461 player->is_active = TRUE;
14468 static boolean DigFieldByCE(int x, int y, int digging_element)
14470 int element = Tile[x][y];
14472 if (!IS_FREE(x, y))
14474 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14475 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14478 // no element can dig solid indestructible elements
14479 if (IS_INDESTRUCTIBLE(element) &&
14480 !IS_DIGGABLE(element) &&
14481 !IS_COLLECTIBLE(element))
14484 if (AmoebaNr[x][y] &&
14485 (element == EL_AMOEBA_FULL ||
14486 element == EL_BD_AMOEBA ||
14487 element == EL_AMOEBA_GROWING))
14489 AmoebaCnt[AmoebaNr[x][y]]--;
14490 AmoebaCnt2[AmoebaNr[x][y]]--;
14493 if (IS_MOVING(x, y))
14494 RemoveMovingField(x, y);
14498 TEST_DrawLevelField(x, y);
14501 // if digged element was about to explode, prevent the explosion
14502 ExplodeField[x][y] = EX_TYPE_NONE;
14504 PlayLevelSoundAction(x, y, action);
14507 Store[x][y] = EL_EMPTY;
14509 // this makes it possible to leave the removed element again
14510 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14511 Store[x][y] = element;
14516 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14518 int jx = player->jx, jy = player->jy;
14519 int x = jx + dx, y = jy + dy;
14520 int snap_direction = (dx == -1 ? MV_LEFT :
14521 dx == +1 ? MV_RIGHT :
14523 dy == +1 ? MV_DOWN : MV_NONE);
14524 boolean can_continue_snapping = (level.continuous_snapping &&
14525 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14527 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14530 if (!player->active || !IN_LEV_FIELD(x, y))
14538 if (player->MovPos == 0)
14539 player->is_pushing = FALSE;
14541 player->is_snapping = FALSE;
14543 if (player->MovPos == 0)
14545 player->is_moving = FALSE;
14546 player->is_digging = FALSE;
14547 player->is_collecting = FALSE;
14553 // prevent snapping with already pressed snap key when not allowed
14554 if (player->is_snapping && !can_continue_snapping)
14557 player->MovDir = snap_direction;
14559 if (player->MovPos == 0)
14561 player->is_moving = FALSE;
14562 player->is_digging = FALSE;
14563 player->is_collecting = FALSE;
14566 player->is_dropping = FALSE;
14567 player->is_dropping_pressed = FALSE;
14568 player->drop_pressed_delay = 0;
14570 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14573 player->is_snapping = TRUE;
14574 player->is_active = TRUE;
14576 if (player->MovPos == 0)
14578 player->is_moving = FALSE;
14579 player->is_digging = FALSE;
14580 player->is_collecting = FALSE;
14583 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14584 TEST_DrawLevelField(player->last_jx, player->last_jy);
14586 TEST_DrawLevelField(x, y);
14591 static boolean DropElement(struct PlayerInfo *player)
14593 int old_element, new_element;
14594 int dropx = player->jx, dropy = player->jy;
14595 int drop_direction = player->MovDir;
14596 int drop_side = drop_direction;
14597 int drop_element = get_next_dropped_element(player);
14599 /* do not drop an element on top of another element; when holding drop key
14600 pressed without moving, dropped element must move away before the next
14601 element can be dropped (this is especially important if the next element
14602 is dynamite, which can be placed on background for historical reasons) */
14603 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14606 if (IS_THROWABLE(drop_element))
14608 dropx += GET_DX_FROM_DIR(drop_direction);
14609 dropy += GET_DY_FROM_DIR(drop_direction);
14611 if (!IN_LEV_FIELD(dropx, dropy))
14615 old_element = Tile[dropx][dropy]; // old element at dropping position
14616 new_element = drop_element; // default: no change when dropping
14618 // check if player is active, not moving and ready to drop
14619 if (!player->active || player->MovPos || player->drop_delay > 0)
14622 // check if player has anything that can be dropped
14623 if (new_element == EL_UNDEFINED)
14626 // only set if player has anything that can be dropped
14627 player->is_dropping_pressed = TRUE;
14629 // check if drop key was pressed long enough for EM style dynamite
14630 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14633 // check if anything can be dropped at the current position
14634 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14637 // collected custom elements can only be dropped on empty fields
14638 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14641 if (old_element != EL_EMPTY)
14642 Back[dropx][dropy] = old_element; // store old element on this field
14644 ResetGfxAnimation(dropx, dropy);
14645 ResetRandomAnimationValue(dropx, dropy);
14647 if (player->inventory_size > 0 ||
14648 player->inventory_infinite_element != EL_UNDEFINED)
14650 if (player->inventory_size > 0)
14652 player->inventory_size--;
14654 DrawGameDoorValues();
14656 if (new_element == EL_DYNAMITE)
14657 new_element = EL_DYNAMITE_ACTIVE;
14658 else if (new_element == EL_EM_DYNAMITE)
14659 new_element = EL_EM_DYNAMITE_ACTIVE;
14660 else if (new_element == EL_SP_DISK_RED)
14661 new_element = EL_SP_DISK_RED_ACTIVE;
14664 Tile[dropx][dropy] = new_element;
14666 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14667 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14668 el2img(Tile[dropx][dropy]), 0);
14670 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14672 // needed if previous element just changed to "empty" in the last frame
14673 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14675 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14676 player->index_bit, drop_side);
14677 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14679 player->index_bit, drop_side);
14681 TestIfElementTouchesCustomElement(dropx, dropy);
14683 else // player is dropping a dyna bomb
14685 player->dynabombs_left--;
14687 Tile[dropx][dropy] = new_element;
14689 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14690 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14691 el2img(Tile[dropx][dropy]), 0);
14693 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14696 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14697 InitField_WithBug1(dropx, dropy, FALSE);
14699 new_element = Tile[dropx][dropy]; // element might have changed
14701 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14702 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14704 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14705 MovDir[dropx][dropy] = drop_direction;
14707 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14709 // do not cause impact style collision by dropping elements that can fall
14710 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14713 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14714 player->is_dropping = TRUE;
14716 player->drop_pressed_delay = 0;
14717 player->is_dropping_pressed = FALSE;
14719 player->drop_x = dropx;
14720 player->drop_y = dropy;
14725 // ----------------------------------------------------------------------------
14726 // game sound playing functions
14727 // ----------------------------------------------------------------------------
14729 static int *loop_sound_frame = NULL;
14730 static int *loop_sound_volume = NULL;
14732 void InitPlayLevelSound(void)
14734 int num_sounds = getSoundListSize();
14736 checked_free(loop_sound_frame);
14737 checked_free(loop_sound_volume);
14739 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14740 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14743 static void PlayLevelSound(int x, int y, int nr)
14745 int sx = SCREENX(x), sy = SCREENY(y);
14746 int volume, stereo_position;
14747 int max_distance = 8;
14748 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14750 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14751 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14754 if (!IN_LEV_FIELD(x, y) ||
14755 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14756 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14759 volume = SOUND_MAX_VOLUME;
14761 if (!IN_SCR_FIELD(sx, sy))
14763 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14764 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14766 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14769 stereo_position = (SOUND_MAX_LEFT +
14770 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14771 (SCR_FIELDX + 2 * max_distance));
14773 if (IS_LOOP_SOUND(nr))
14775 /* This assures that quieter loop sounds do not overwrite louder ones,
14776 while restarting sound volume comparison with each new game frame. */
14778 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14781 loop_sound_volume[nr] = volume;
14782 loop_sound_frame[nr] = FrameCounter;
14785 PlaySoundExt(nr, volume, stereo_position, type);
14788 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14790 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14791 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14792 y < LEVELY(BY1) ? LEVELY(BY1) :
14793 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14797 static void PlayLevelSoundAction(int x, int y, int action)
14799 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14802 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14804 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14806 if (sound_effect != SND_UNDEFINED)
14807 PlayLevelSound(x, y, sound_effect);
14810 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14813 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14815 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14816 PlayLevelSound(x, y, sound_effect);
14819 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14821 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14823 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14824 PlayLevelSound(x, y, sound_effect);
14827 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14829 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14831 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14832 StopSound(sound_effect);
14835 static int getLevelMusicNr(void)
14837 if (levelset.music[level_nr] != MUS_UNDEFINED)
14838 return levelset.music[level_nr]; // from config file
14840 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14843 static void FadeLevelSounds(void)
14848 static void FadeLevelMusic(void)
14850 int music_nr = getLevelMusicNr();
14851 char *curr_music = getCurrentlyPlayingMusicFilename();
14852 char *next_music = getMusicInfoEntryFilename(music_nr);
14854 if (!strEqual(curr_music, next_music))
14858 void FadeLevelSoundsAndMusic(void)
14864 static void PlayLevelMusic(void)
14866 int music_nr = getLevelMusicNr();
14867 char *curr_music = getCurrentlyPlayingMusicFilename();
14868 char *next_music = getMusicInfoEntryFilename(music_nr);
14870 if (!strEqual(curr_music, next_music))
14871 PlayMusicLoop(music_nr);
14874 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14876 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14878 int x = xx - offset;
14879 int y = yy - offset;
14884 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14888 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14892 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14896 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14900 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14904 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14908 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14911 case SOUND_android_clone:
14912 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14915 case SOUND_android_move:
14916 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14920 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14924 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14928 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14931 case SOUND_eater_eat:
14932 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14936 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14939 case SOUND_collect:
14940 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14943 case SOUND_diamond:
14944 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14948 // !!! CHECK THIS !!!
14950 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14952 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14956 case SOUND_wonderfall:
14957 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14961 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14965 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14969 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14973 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14977 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14981 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14985 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14989 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14992 case SOUND_exit_open:
14993 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14996 case SOUND_exit_leave:
14997 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15000 case SOUND_dynamite:
15001 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15005 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15009 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15013 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15017 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15021 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15025 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15029 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15034 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15036 int element = map_element_SP_to_RND(element_sp);
15037 int action = map_action_SP_to_RND(action_sp);
15038 int offset = (setup.sp_show_border_elements ? 0 : 1);
15039 int x = xx - offset;
15040 int y = yy - offset;
15042 PlayLevelSoundElementAction(x, y, element, action);
15045 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15047 int element = map_element_MM_to_RND(element_mm);
15048 int action = map_action_MM_to_RND(action_mm);
15050 int x = xx - offset;
15051 int y = yy - offset;
15053 if (!IS_MM_ELEMENT(element))
15054 element = EL_MM_DEFAULT;
15056 PlayLevelSoundElementAction(x, y, element, action);
15059 void PlaySound_MM(int sound_mm)
15061 int sound = map_sound_MM_to_RND(sound_mm);
15063 if (sound == SND_UNDEFINED)
15069 void PlaySoundLoop_MM(int sound_mm)
15071 int sound = map_sound_MM_to_RND(sound_mm);
15073 if (sound == SND_UNDEFINED)
15076 PlaySoundLoop(sound);
15079 void StopSound_MM(int sound_mm)
15081 int sound = map_sound_MM_to_RND(sound_mm);
15083 if (sound == SND_UNDEFINED)
15089 void RaiseScore(int value)
15091 game.score += value;
15093 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15095 DisplayGameControlValues();
15098 void RaiseScoreElement(int element)
15103 case EL_BD_DIAMOND:
15104 case EL_EMERALD_YELLOW:
15105 case EL_EMERALD_RED:
15106 case EL_EMERALD_PURPLE:
15107 case EL_SP_INFOTRON:
15108 RaiseScore(level.score[SC_EMERALD]);
15111 RaiseScore(level.score[SC_DIAMOND]);
15114 RaiseScore(level.score[SC_CRYSTAL]);
15117 RaiseScore(level.score[SC_PEARL]);
15120 case EL_BD_BUTTERFLY:
15121 case EL_SP_ELECTRON:
15122 RaiseScore(level.score[SC_BUG]);
15125 case EL_BD_FIREFLY:
15126 case EL_SP_SNIKSNAK:
15127 RaiseScore(level.score[SC_SPACESHIP]);
15130 case EL_DARK_YAMYAM:
15131 RaiseScore(level.score[SC_YAMYAM]);
15134 RaiseScore(level.score[SC_ROBOT]);
15137 RaiseScore(level.score[SC_PACMAN]);
15140 RaiseScore(level.score[SC_NUT]);
15143 case EL_EM_DYNAMITE:
15144 case EL_SP_DISK_RED:
15145 case EL_DYNABOMB_INCREASE_NUMBER:
15146 case EL_DYNABOMB_INCREASE_SIZE:
15147 case EL_DYNABOMB_INCREASE_POWER:
15148 RaiseScore(level.score[SC_DYNAMITE]);
15150 case EL_SHIELD_NORMAL:
15151 case EL_SHIELD_DEADLY:
15152 RaiseScore(level.score[SC_SHIELD]);
15154 case EL_EXTRA_TIME:
15155 RaiseScore(level.extra_time_score);
15169 case EL_DC_KEY_WHITE:
15170 RaiseScore(level.score[SC_KEY]);
15173 RaiseScore(element_info[element].collect_score);
15178 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15180 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15182 // closing door required in case of envelope style request dialogs
15185 // prevent short reactivation of overlay buttons while closing door
15186 SetOverlayActive(FALSE);
15188 CloseDoor(DOOR_CLOSE_1);
15191 if (network.enabled)
15192 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15196 FadeSkipNextFadeIn();
15198 SetGameStatus(GAME_MODE_MAIN);
15203 else // continue playing the game
15205 if (tape.playing && tape.deactivate_display)
15206 TapeDeactivateDisplayOff(TRUE);
15208 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15210 if (tape.playing && tape.deactivate_display)
15211 TapeDeactivateDisplayOn();
15215 void RequestQuitGame(boolean ask_if_really_quit)
15217 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15218 boolean skip_request = game.all_players_gone || quick_quit;
15220 RequestQuitGameExt(skip_request, quick_quit,
15221 "Do you really want to quit the game?");
15224 void RequestRestartGame(char *message)
15226 game.restart_game_message = NULL;
15228 boolean has_started_game = hasStartedNetworkGame();
15229 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15231 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15233 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15237 SetGameStatus(GAME_MODE_MAIN);
15243 void CheckGameOver(void)
15245 static boolean last_game_over = FALSE;
15246 static int game_over_delay = 0;
15247 int game_over_delay_value = 50;
15248 boolean game_over = checkGameFailed();
15250 // do not handle game over if request dialog is already active
15251 if (game.request_active)
15254 // do not ask to play again if game was never actually played
15255 if (!game.GamePlayed)
15260 last_game_over = FALSE;
15261 game_over_delay = game_over_delay_value;
15266 if (game_over_delay > 0)
15273 if (last_game_over != game_over)
15274 game.restart_game_message = (hasStartedNetworkGame() ?
15275 "Game over! Play it again?" :
15278 last_game_over = game_over;
15281 boolean checkGameSolved(void)
15283 // set for all game engines if level was solved
15284 return game.LevelSolved_GameEnd;
15287 boolean checkGameFailed(void)
15289 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15290 return (game_em.game_over && !game_em.level_solved);
15291 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15292 return (game_sp.game_over && !game_sp.level_solved);
15293 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15294 return (game_mm.game_over && !game_mm.level_solved);
15295 else // GAME_ENGINE_TYPE_RND
15296 return (game.GameOver && !game.LevelSolved);
15299 boolean checkGameEnded(void)
15301 return (checkGameSolved() || checkGameFailed());
15305 // ----------------------------------------------------------------------------
15306 // random generator functions
15307 // ----------------------------------------------------------------------------
15309 unsigned int InitEngineRandom_RND(int seed)
15311 game.num_random_calls = 0;
15313 return InitEngineRandom(seed);
15316 unsigned int RND(int max)
15320 game.num_random_calls++;
15322 return GetEngineRandom(max);
15329 // ----------------------------------------------------------------------------
15330 // game engine snapshot handling functions
15331 // ----------------------------------------------------------------------------
15333 struct EngineSnapshotInfo
15335 // runtime values for custom element collect score
15336 int collect_score[NUM_CUSTOM_ELEMENTS];
15338 // runtime values for group element choice position
15339 int choice_pos[NUM_GROUP_ELEMENTS];
15341 // runtime values for belt position animations
15342 int belt_graphic[4][NUM_BELT_PARTS];
15343 int belt_anim_mode[4][NUM_BELT_PARTS];
15346 static struct EngineSnapshotInfo engine_snapshot_rnd;
15347 static char *snapshot_level_identifier = NULL;
15348 static int snapshot_level_nr = -1;
15350 static void SaveEngineSnapshotValues_RND(void)
15352 static int belt_base_active_element[4] =
15354 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15355 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15356 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15357 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15361 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15363 int element = EL_CUSTOM_START + i;
15365 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15368 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15370 int element = EL_GROUP_START + i;
15372 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15375 for (i = 0; i < 4; i++)
15377 for (j = 0; j < NUM_BELT_PARTS; j++)
15379 int element = belt_base_active_element[i] + j;
15380 int graphic = el2img(element);
15381 int anim_mode = graphic_info[graphic].anim_mode;
15383 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15384 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15389 static void LoadEngineSnapshotValues_RND(void)
15391 unsigned int num_random_calls = game.num_random_calls;
15394 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15396 int element = EL_CUSTOM_START + i;
15398 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15401 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15403 int element = EL_GROUP_START + i;
15405 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15408 for (i = 0; i < 4; i++)
15410 for (j = 0; j < NUM_BELT_PARTS; j++)
15412 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15413 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15415 graphic_info[graphic].anim_mode = anim_mode;
15419 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15421 InitRND(tape.random_seed);
15422 for (i = 0; i < num_random_calls; i++)
15426 if (game.num_random_calls != num_random_calls)
15428 Error("number of random calls out of sync");
15429 Error("number of random calls should be %d", num_random_calls);
15430 Error("number of random calls is %d", game.num_random_calls);
15432 Fail("this should not happen -- please debug");
15436 void FreeEngineSnapshotSingle(void)
15438 FreeSnapshotSingle();
15440 setString(&snapshot_level_identifier, NULL);
15441 snapshot_level_nr = -1;
15444 void FreeEngineSnapshotList(void)
15446 FreeSnapshotList();
15449 static ListNode *SaveEngineSnapshotBuffers(void)
15451 ListNode *buffers = NULL;
15453 // copy some special values to a structure better suited for the snapshot
15455 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15456 SaveEngineSnapshotValues_RND();
15457 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15458 SaveEngineSnapshotValues_EM();
15459 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15460 SaveEngineSnapshotValues_SP(&buffers);
15461 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15462 SaveEngineSnapshotValues_MM(&buffers);
15464 // save values stored in special snapshot structure
15466 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15467 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15468 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15469 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15470 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15471 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15472 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15475 // save further RND engine values
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15485 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15510 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15513 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15516 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15519 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15523 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15525 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15527 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15529 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15531 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15532 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15535 ListNode *node = engine_snapshot_list_rnd;
15538 while (node != NULL)
15540 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15545 Debug("game:playing:SaveEngineSnapshotBuffers",
15546 "size of engine snapshot: %d bytes", num_bytes);
15552 void SaveEngineSnapshotSingle(void)
15554 ListNode *buffers = SaveEngineSnapshotBuffers();
15556 // finally save all snapshot buffers to single snapshot
15557 SaveSnapshotSingle(buffers);
15559 // save level identification information
15560 setString(&snapshot_level_identifier, leveldir_current->identifier);
15561 snapshot_level_nr = level_nr;
15564 boolean CheckSaveEngineSnapshotToList(void)
15566 boolean save_snapshot =
15567 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15568 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15569 game.snapshot.changed_action) ||
15570 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15571 game.snapshot.collected_item));
15573 game.snapshot.changed_action = FALSE;
15574 game.snapshot.collected_item = FALSE;
15575 game.snapshot.save_snapshot = save_snapshot;
15577 return save_snapshot;
15580 void SaveEngineSnapshotToList(void)
15582 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15586 ListNode *buffers = SaveEngineSnapshotBuffers();
15588 // finally save all snapshot buffers to snapshot list
15589 SaveSnapshotToList(buffers);
15592 void SaveEngineSnapshotToListInitial(void)
15594 FreeEngineSnapshotList();
15596 SaveEngineSnapshotToList();
15599 static void LoadEngineSnapshotValues(void)
15601 // restore special values from snapshot structure
15603 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15604 LoadEngineSnapshotValues_RND();
15605 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15606 LoadEngineSnapshotValues_EM();
15607 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15608 LoadEngineSnapshotValues_SP();
15609 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15610 LoadEngineSnapshotValues_MM();
15613 void LoadEngineSnapshotSingle(void)
15615 LoadSnapshotSingle();
15617 LoadEngineSnapshotValues();
15620 static void LoadEngineSnapshot_Undo(int steps)
15622 LoadSnapshotFromList_Older(steps);
15624 LoadEngineSnapshotValues();
15627 static void LoadEngineSnapshot_Redo(int steps)
15629 LoadSnapshotFromList_Newer(steps);
15631 LoadEngineSnapshotValues();
15634 boolean CheckEngineSnapshotSingle(void)
15636 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15637 snapshot_level_nr == level_nr);
15640 boolean CheckEngineSnapshotList(void)
15642 return CheckSnapshotList();
15646 // ---------- new game button stuff -------------------------------------------
15653 boolean *setup_value;
15654 boolean allowed_on_tape;
15655 boolean is_touch_button;
15657 } gamebutton_info[NUM_GAME_BUTTONS] =
15660 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15661 GAME_CTRL_ID_STOP, NULL,
15662 TRUE, FALSE, "stop game"
15665 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15666 GAME_CTRL_ID_PAUSE, NULL,
15667 TRUE, FALSE, "pause game"
15670 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15671 GAME_CTRL_ID_PLAY, NULL,
15672 TRUE, FALSE, "play game"
15675 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15676 GAME_CTRL_ID_UNDO, NULL,
15677 TRUE, FALSE, "undo step"
15680 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15681 GAME_CTRL_ID_REDO, NULL,
15682 TRUE, FALSE, "redo step"
15685 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15686 GAME_CTRL_ID_SAVE, NULL,
15687 TRUE, FALSE, "save game"
15690 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15691 GAME_CTRL_ID_PAUSE2, NULL,
15692 TRUE, FALSE, "pause game"
15695 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15696 GAME_CTRL_ID_LOAD, NULL,
15697 TRUE, FALSE, "load game"
15700 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15701 GAME_CTRL_ID_PANEL_STOP, NULL,
15702 FALSE, FALSE, "stop game"
15705 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15706 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15707 FALSE, FALSE, "pause game"
15710 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15711 GAME_CTRL_ID_PANEL_PLAY, NULL,
15712 FALSE, FALSE, "play game"
15715 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15716 GAME_CTRL_ID_TOUCH_STOP, NULL,
15717 FALSE, TRUE, "stop game"
15720 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15721 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15722 FALSE, TRUE, "pause game"
15725 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15726 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15727 TRUE, FALSE, "background music on/off"
15730 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15731 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15732 TRUE, FALSE, "sound loops on/off"
15735 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15736 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15737 TRUE, FALSE, "normal sounds on/off"
15740 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15741 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15742 FALSE, FALSE, "background music on/off"
15745 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15746 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15747 FALSE, FALSE, "sound loops on/off"
15750 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15751 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15752 FALSE, FALSE, "normal sounds on/off"
15756 void CreateGameButtons(void)
15760 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15762 int graphic = gamebutton_info[i].graphic;
15763 struct GraphicInfo *gfx = &graphic_info[graphic];
15764 struct XY *pos = gamebutton_info[i].pos;
15765 struct GadgetInfo *gi;
15768 unsigned int event_mask;
15769 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15770 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15771 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15772 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15773 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15774 int gd_x = gfx->src_x;
15775 int gd_y = gfx->src_y;
15776 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15777 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15778 int gd_xa = gfx->src_x + gfx->active_xoffset;
15779 int gd_ya = gfx->src_y + gfx->active_yoffset;
15780 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15781 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15782 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15783 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15786 if (gfx->bitmap == NULL)
15788 game_gadget[id] = NULL;
15793 if (id == GAME_CTRL_ID_STOP ||
15794 id == GAME_CTRL_ID_PANEL_STOP ||
15795 id == GAME_CTRL_ID_TOUCH_STOP ||
15796 id == GAME_CTRL_ID_PLAY ||
15797 id == GAME_CTRL_ID_PANEL_PLAY ||
15798 id == GAME_CTRL_ID_SAVE ||
15799 id == GAME_CTRL_ID_LOAD)
15801 button_type = GD_TYPE_NORMAL_BUTTON;
15803 event_mask = GD_EVENT_RELEASED;
15805 else if (id == GAME_CTRL_ID_UNDO ||
15806 id == GAME_CTRL_ID_REDO)
15808 button_type = GD_TYPE_NORMAL_BUTTON;
15810 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15814 button_type = GD_TYPE_CHECK_BUTTON;
15815 checked = (gamebutton_info[i].setup_value != NULL ?
15816 *gamebutton_info[i].setup_value : FALSE);
15817 event_mask = GD_EVENT_PRESSED;
15820 gi = CreateGadget(GDI_CUSTOM_ID, id,
15821 GDI_IMAGE_ID, graphic,
15822 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15825 GDI_WIDTH, gfx->width,
15826 GDI_HEIGHT, gfx->height,
15827 GDI_TYPE, button_type,
15828 GDI_STATE, GD_BUTTON_UNPRESSED,
15829 GDI_CHECKED, checked,
15830 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15831 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15832 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15833 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15834 GDI_DIRECT_DRAW, FALSE,
15835 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15836 GDI_EVENT_MASK, event_mask,
15837 GDI_CALLBACK_ACTION, HandleGameButtons,
15841 Fail("cannot create gadget");
15843 game_gadget[id] = gi;
15847 void FreeGameButtons(void)
15851 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15852 FreeGadget(game_gadget[i]);
15855 static void UnmapGameButtonsAtSamePosition(int id)
15859 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15861 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15862 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15863 UnmapGadget(game_gadget[i]);
15866 static void UnmapGameButtonsAtSamePosition_All(void)
15868 if (setup.show_snapshot_buttons)
15870 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15871 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15872 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15876 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15877 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15878 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15880 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15881 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15882 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15886 static void MapGameButtonsAtSamePosition(int id)
15890 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15892 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15893 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15894 MapGadget(game_gadget[i]);
15896 UnmapGameButtonsAtSamePosition_All();
15899 void MapUndoRedoButtons(void)
15901 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15902 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15904 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15905 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15908 void UnmapUndoRedoButtons(void)
15910 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15911 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15913 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15914 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15917 void ModifyPauseButtons(void)
15921 GAME_CTRL_ID_PAUSE,
15922 GAME_CTRL_ID_PAUSE2,
15923 GAME_CTRL_ID_PANEL_PAUSE,
15924 GAME_CTRL_ID_TOUCH_PAUSE,
15929 for (i = 0; ids[i] > -1; i++)
15930 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15933 static void MapGameButtonsExt(boolean on_tape)
15937 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15938 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15939 i != GAME_CTRL_ID_UNDO &&
15940 i != GAME_CTRL_ID_REDO)
15941 MapGadget(game_gadget[i]);
15943 UnmapGameButtonsAtSamePosition_All();
15945 RedrawGameButtons();
15948 static void UnmapGameButtonsExt(boolean on_tape)
15952 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15953 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15954 UnmapGadget(game_gadget[i]);
15957 static void RedrawGameButtonsExt(boolean on_tape)
15961 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15962 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15963 RedrawGadget(game_gadget[i]);
15966 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15971 gi->checked = state;
15974 static void RedrawSoundButtonGadget(int id)
15976 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15977 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15978 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15979 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15980 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15981 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15984 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15985 RedrawGadget(game_gadget[id2]);
15988 void MapGameButtons(void)
15990 MapGameButtonsExt(FALSE);
15993 void UnmapGameButtons(void)
15995 UnmapGameButtonsExt(FALSE);
15998 void RedrawGameButtons(void)
16000 RedrawGameButtonsExt(FALSE);
16003 void MapGameButtonsOnTape(void)
16005 MapGameButtonsExt(TRUE);
16008 void UnmapGameButtonsOnTape(void)
16010 UnmapGameButtonsExt(TRUE);
16013 void RedrawGameButtonsOnTape(void)
16015 RedrawGameButtonsExt(TRUE);
16018 static void GameUndoRedoExt(void)
16020 ClearPlayerAction();
16022 tape.pausing = TRUE;
16025 UpdateAndDisplayGameControlValues();
16027 DrawCompleteVideoDisplay();
16028 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16029 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16030 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16035 static void GameUndo(int steps)
16037 if (!CheckEngineSnapshotList())
16040 LoadEngineSnapshot_Undo(steps);
16045 static void GameRedo(int steps)
16047 if (!CheckEngineSnapshotList())
16050 LoadEngineSnapshot_Redo(steps);
16055 static void HandleGameButtonsExt(int id, int button)
16057 static boolean game_undo_executed = FALSE;
16058 int steps = BUTTON_STEPSIZE(button);
16059 boolean handle_game_buttons =
16060 (game_status == GAME_MODE_PLAYING ||
16061 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16063 if (!handle_game_buttons)
16068 case GAME_CTRL_ID_STOP:
16069 case GAME_CTRL_ID_PANEL_STOP:
16070 case GAME_CTRL_ID_TOUCH_STOP:
16071 if (game_status == GAME_MODE_MAIN)
16077 RequestQuitGame(TRUE);
16081 case GAME_CTRL_ID_PAUSE:
16082 case GAME_CTRL_ID_PAUSE2:
16083 case GAME_CTRL_ID_PANEL_PAUSE:
16084 case GAME_CTRL_ID_TOUCH_PAUSE:
16085 if (network.enabled && game_status == GAME_MODE_PLAYING)
16088 SendToServer_ContinuePlaying();
16090 SendToServer_PausePlaying();
16093 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16095 game_undo_executed = FALSE;
16099 case GAME_CTRL_ID_PLAY:
16100 case GAME_CTRL_ID_PANEL_PLAY:
16101 if (game_status == GAME_MODE_MAIN)
16103 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16105 else if (tape.pausing)
16107 if (network.enabled)
16108 SendToServer_ContinuePlaying();
16110 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16114 case GAME_CTRL_ID_UNDO:
16115 // Important: When using "save snapshot when collecting an item" mode,
16116 // load last (current) snapshot for first "undo" after pressing "pause"
16117 // (else the last-but-one snapshot would be loaded, because the snapshot
16118 // pointer already points to the last snapshot when pressing "pause",
16119 // which is fine for "every step/move" mode, but not for "every collect")
16120 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16121 !game_undo_executed)
16124 game_undo_executed = TRUE;
16129 case GAME_CTRL_ID_REDO:
16133 case GAME_CTRL_ID_SAVE:
16137 case GAME_CTRL_ID_LOAD:
16141 case SOUND_CTRL_ID_MUSIC:
16142 case SOUND_CTRL_ID_PANEL_MUSIC:
16143 if (setup.sound_music)
16145 setup.sound_music = FALSE;
16149 else if (audio.music_available)
16151 setup.sound = setup.sound_music = TRUE;
16153 SetAudioMode(setup.sound);
16155 if (game_status == GAME_MODE_PLAYING)
16159 RedrawSoundButtonGadget(id);
16163 case SOUND_CTRL_ID_LOOPS:
16164 case SOUND_CTRL_ID_PANEL_LOOPS:
16165 if (setup.sound_loops)
16166 setup.sound_loops = FALSE;
16167 else if (audio.loops_available)
16169 setup.sound = setup.sound_loops = TRUE;
16171 SetAudioMode(setup.sound);
16174 RedrawSoundButtonGadget(id);
16178 case SOUND_CTRL_ID_SIMPLE:
16179 case SOUND_CTRL_ID_PANEL_SIMPLE:
16180 if (setup.sound_simple)
16181 setup.sound_simple = FALSE;
16182 else if (audio.sound_available)
16184 setup.sound = setup.sound_simple = TRUE;
16186 SetAudioMode(setup.sound);
16189 RedrawSoundButtonGadget(id);
16198 static void HandleGameButtons(struct GadgetInfo *gi)
16200 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16203 void HandleSoundButtonKeys(Key key)
16205 if (key == setup.shortcut.sound_simple)
16206 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16207 else if (key == setup.shortcut.sound_loops)
16208 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16209 else if (key == setup.shortcut.sound_music)
16210 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);