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(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i);
2151 Error(ERR_EXIT, "this should not happen -- please debug");
2154 // force update of game controls after initialization
2155 gpc->value = gpc->last_value = -1;
2156 gpc->frame = gpc->last_frame = -1;
2157 gpc->gfx_frame = -1;
2159 // determine panel value width for later calculation of alignment
2160 if (type == TYPE_INTEGER || type == TYPE_STRING)
2162 pos->width = pos->size * getFontWidth(pos->font);
2163 pos->height = getFontHeight(pos->font);
2165 else if (type == TYPE_ELEMENT)
2167 pos->width = pos->size;
2168 pos->height = pos->size;
2171 // fill structure for game panel draw order
2173 gpo->sort_priority = pos->sort_priority;
2176 // sort game panel controls according to sort_priority and control number
2177 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2178 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2181 static void UpdatePlayfieldElementCount(void)
2183 boolean use_element_count = FALSE;
2186 // first check if it is needed at all to calculate playfield element count
2187 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2188 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2189 use_element_count = TRUE;
2191 if (!use_element_count)
2194 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2195 element_info[i].element_count = 0;
2197 SCAN_PLAYFIELD(x, y)
2199 element_info[Tile[x][y]].element_count++;
2202 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2203 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2204 if (IS_IN_GROUP(j, i))
2205 element_info[EL_GROUP_START + i].element_count +=
2206 element_info[j].element_count;
2209 static void UpdateGameControlValues(void)
2212 int time = (game.LevelSolved ?
2213 game.LevelSolved_CountingTime :
2214 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2216 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2217 game_sp.time_played :
2218 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2219 game_mm.energy_left :
2220 game.no_time_limit ? TimePlayed : TimeLeft);
2221 int score = (game.LevelSolved ?
2222 game.LevelSolved_CountingScore :
2223 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2224 game_em.lev->score :
2225 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->gems_needed :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2233 game_sp.infotrons_still_needed :
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2235 game_mm.kettles_still_needed :
2236 game.gems_still_needed);
2237 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed > 0 :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed > 0 :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed > 0 ||
2243 game_mm.lights_still_needed > 0 :
2244 game.gems_still_needed > 0 ||
2245 game.sokoban_fields_still_needed > 0 ||
2246 game.sokoban_objects_still_needed > 0 ||
2247 game.lights_still_needed > 0);
2248 int health = (game.LevelSolved ?
2249 game.LevelSolved_CountingHealth :
2250 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2251 MM_HEALTH(game_mm.laser_overload_value) :
2254 UpdatePlayfieldElementCount();
2256 // update game panel control values
2258 // used instead of "level_nr" (for network games)
2259 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2260 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2262 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2263 for (i = 0; i < MAX_NUM_KEYS; i++)
2264 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2265 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2268 if (game.centered_player_nr == -1)
2270 for (i = 0; i < MAX_PLAYERS; i++)
2272 // only one player in Supaplex game engine
2273 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2276 for (k = 0; k < MAX_NUM_KEYS; k++)
2278 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2280 if (game_em.ply[i]->keys & (1 << k))
2281 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2282 get_key_element_from_nr(k);
2284 else if (stored_player[i].key[k])
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2289 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2290 getPlayerInventorySize(i);
2292 if (stored_player[i].num_white_keys > 0)
2293 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2296 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2297 stored_player[i].num_white_keys;
2302 int player_nr = game.centered_player_nr;
2304 for (k = 0; k < MAX_NUM_KEYS; k++)
2306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2308 if (game_em.ply[player_nr]->keys & (1 << k))
2309 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2310 get_key_element_from_nr(k);
2312 else if (stored_player[player_nr].key[k])
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2317 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2318 getPlayerInventorySize(player_nr);
2320 if (stored_player[player_nr].num_white_keys > 0)
2321 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2323 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2324 stored_player[player_nr].num_white_keys;
2327 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2329 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2330 get_inventory_element_from_pos(local_player, i);
2331 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2332 get_inventory_element_from_pos(local_player, -i - 1);
2335 game_panel_controls[GAME_PANEL_SCORE].value = score;
2336 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2338 game_panel_controls[GAME_PANEL_TIME].value = time;
2340 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2341 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2342 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2344 if (level.time == 0)
2345 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2347 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2349 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2350 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2352 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2354 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2355 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2357 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2358 local_player->shield_normal_time_left;
2359 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2360 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2362 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2363 local_player->shield_deadly_time_left;
2365 game_panel_controls[GAME_PANEL_EXIT].value =
2366 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2368 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2369 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2370 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2371 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2372 EL_EMC_MAGIC_BALL_SWITCH);
2374 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2375 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2376 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2377 game.light_time_left;
2379 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2380 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2381 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2382 game.timegate_time_left;
2384 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2385 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2387 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2388 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2389 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2390 game.lenses_time_left;
2392 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2393 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2394 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2395 game.magnify_time_left;
2397 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2398 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2399 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2400 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2401 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2402 EL_BALLOON_SWITCH_NONE);
2404 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2405 local_player->dynabomb_count;
2406 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2407 local_player->dynabomb_size;
2408 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2409 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2411 game_panel_controls[GAME_PANEL_PENGUINS].value =
2412 game.friends_still_needed;
2414 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2415 game.sokoban_objects_still_needed;
2416 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2417 game.sokoban_fields_still_needed;
2419 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2420 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2422 for (i = 0; i < NUM_BELTS; i++)
2424 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2425 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2426 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2427 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2428 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2431 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2432 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2433 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2434 game.magic_wall_time_left;
2436 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2437 local_player->gravity;
2439 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2440 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2442 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2443 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2444 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2445 game.panel.element[i].id : EL_UNDEFINED);
2447 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2448 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2449 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2450 element_info[game.panel.element_count[i].id].element_count : 0);
2452 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2453 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2454 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2455 element_info[game.panel.ce_score[i].id].collect_score : 0);
2457 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2458 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2459 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2460 element_info[game.panel.ce_score_element[i].id].collect_score :
2463 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2464 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2467 // update game panel control frames
2469 for (i = 0; game_panel_controls[i].nr != -1; i++)
2471 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2473 if (gpc->type == TYPE_ELEMENT)
2475 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2477 int last_anim_random_frame = gfx.anim_random_frame;
2478 int element = gpc->value;
2479 int graphic = el2panelimg(element);
2481 if (gpc->value != gpc->last_value)
2484 gpc->gfx_random = INIT_GFX_RANDOM();
2490 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2491 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2492 gpc->gfx_random = INIT_GFX_RANDOM();
2495 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2496 gfx.anim_random_frame = gpc->gfx_random;
2498 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2499 gpc->gfx_frame = element_info[element].collect_score;
2501 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2504 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2505 gfx.anim_random_frame = last_anim_random_frame;
2508 else if (gpc->type == TYPE_GRAPHIC)
2510 if (gpc->graphic != IMG_UNDEFINED)
2512 int last_anim_random_frame = gfx.anim_random_frame;
2513 int graphic = gpc->graphic;
2515 if (gpc->value != gpc->last_value)
2518 gpc->gfx_random = INIT_GFX_RANDOM();
2524 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2525 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2526 gpc->gfx_random = INIT_GFX_RANDOM();
2529 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2530 gfx.anim_random_frame = gpc->gfx_random;
2532 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2534 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2535 gfx.anim_random_frame = last_anim_random_frame;
2541 static void DisplayGameControlValues(void)
2543 boolean redraw_panel = FALSE;
2546 for (i = 0; game_panel_controls[i].nr != -1; i++)
2548 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2550 if (PANEL_DEACTIVATED(gpc->pos))
2553 if (gpc->value == gpc->last_value &&
2554 gpc->frame == gpc->last_frame)
2557 redraw_panel = TRUE;
2563 // copy default game door content to main double buffer
2565 // !!! CHECK AGAIN !!!
2566 SetPanelBackground();
2567 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2568 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2570 // redraw game control buttons
2571 RedrawGameButtons();
2573 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2575 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2577 int nr = game_panel_order[i].nr;
2578 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2579 struct TextPosInfo *pos = gpc->pos;
2580 int type = gpc->type;
2581 int value = gpc->value;
2582 int frame = gpc->frame;
2583 int size = pos->size;
2584 int font = pos->font;
2585 boolean draw_masked = pos->draw_masked;
2586 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2588 if (PANEL_DEACTIVATED(pos))
2591 gpc->last_value = value;
2592 gpc->last_frame = frame;
2594 if (type == TYPE_INTEGER)
2596 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2597 nr == GAME_PANEL_TIME)
2599 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2601 if (use_dynamic_size) // use dynamic number of digits
2603 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2604 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2605 int size2 = size1 + 1;
2606 int font1 = pos->font;
2607 int font2 = pos->font_alt;
2609 size = (value < value_change ? size1 : size2);
2610 font = (value < value_change ? font1 : font2);
2614 // correct text size if "digits" is zero or less
2616 size = strlen(int2str(value, size));
2618 // dynamically correct text alignment
2619 pos->width = size * getFontWidth(font);
2621 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2622 int2str(value, size), font, mask_mode);
2624 else if (type == TYPE_ELEMENT)
2626 int element, graphic;
2630 int dst_x = PANEL_XPOS(pos);
2631 int dst_y = PANEL_YPOS(pos);
2633 if (value != EL_UNDEFINED && value != EL_EMPTY)
2636 graphic = el2panelimg(value);
2638 // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size);
2640 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2643 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2646 width = graphic_info[graphic].width * size / TILESIZE;
2647 height = graphic_info[graphic].height * size / TILESIZE;
2650 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2653 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2657 else if (type == TYPE_GRAPHIC)
2659 int graphic = gpc->graphic;
2660 int graphic_active = gpc->graphic_active;
2664 int dst_x = PANEL_XPOS(pos);
2665 int dst_y = PANEL_YPOS(pos);
2666 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2667 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2669 if (graphic != IMG_UNDEFINED && !skip)
2671 if (pos->style == STYLE_REVERSE)
2672 value = 100 - value;
2674 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2676 if (pos->direction & MV_HORIZONTAL)
2678 width = graphic_info[graphic_active].width * value / 100;
2679 height = graphic_info[graphic_active].height;
2681 if (pos->direction == MV_LEFT)
2683 src_x += graphic_info[graphic_active].width - width;
2684 dst_x += graphic_info[graphic_active].width - width;
2689 width = graphic_info[graphic_active].width;
2690 height = graphic_info[graphic_active].height * value / 100;
2692 if (pos->direction == MV_UP)
2694 src_y += graphic_info[graphic_active].height - height;
2695 dst_y += graphic_info[graphic_active].height - height;
2700 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2703 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2708 if (pos->direction & MV_HORIZONTAL)
2710 if (pos->direction == MV_RIGHT)
2717 dst_x = PANEL_XPOS(pos);
2720 width = graphic_info[graphic].width - width;
2724 if (pos->direction == MV_DOWN)
2731 dst_y = PANEL_YPOS(pos);
2734 height = graphic_info[graphic].height - height;
2738 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2741 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2745 else if (type == TYPE_STRING)
2747 boolean active = (value != 0);
2748 char *state_normal = "off";
2749 char *state_active = "on";
2750 char *state = (active ? state_active : state_normal);
2751 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2752 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2753 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2754 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2756 if (nr == GAME_PANEL_GRAVITY_STATE)
2758 int font1 = pos->font; // (used for normal state)
2759 int font2 = pos->font_alt; // (used for active state)
2761 font = (active ? font2 : font1);
2770 // don't truncate output if "chars" is zero or less
2773 // dynamically correct text alignment
2774 pos->width = size * getFontWidth(font);
2777 s_cut = getStringCopyN(s, size);
2779 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2780 s_cut, font, mask_mode);
2786 redraw_mask |= REDRAW_DOOR_1;
2789 SetGameStatus(GAME_MODE_PLAYING);
2792 void UpdateAndDisplayGameControlValues(void)
2794 if (tape.deactivate_display)
2797 UpdateGameControlValues();
2798 DisplayGameControlValues();
2802 static void UpdateGameDoorValues(void)
2804 UpdateGameControlValues();
2808 void DrawGameDoorValues(void)
2810 DisplayGameControlValues();
2814 // ============================================================================
2816 // ----------------------------------------------------------------------------
2817 // initialize game engine due to level / tape version number
2818 // ============================================================================
2820 static void InitGameEngine(void)
2822 int i, j, k, l, x, y;
2824 // set game engine from tape file when re-playing, else from level file
2825 game.engine_version = (tape.playing ? tape.engine_version :
2826 level.game_version);
2828 // set single or multi-player game mode (needed for re-playing tapes)
2829 game.team_mode = setup.team_mode;
2833 int num_players = 0;
2835 for (i = 0; i < MAX_PLAYERS; i++)
2836 if (tape.player_participates[i])
2839 // multi-player tapes contain input data for more than one player
2840 game.team_mode = (num_players > 1);
2844 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2845 level.game_version);
2846 Debug("game:init:level", " tape.file_version == %06d",
2848 Debug("game:init:level", " tape.game_version == %06d",
2850 Debug("game:init:level", " tape.engine_version == %06d",
2851 tape.engine_version);
2852 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2853 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2856 // --------------------------------------------------------------------------
2857 // set flags for bugs and changes according to active game engine version
2858 // --------------------------------------------------------------------------
2862 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2864 Bug was introduced in version:
2867 Bug was fixed in version:
2871 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2872 but the property "can fall" was missing, which caused some levels to be
2873 unsolvable. This was fixed in version 4.2.0.0.
2875 Affected levels/tapes:
2876 An example for a tape that was fixed by this bugfix is tape 029 from the
2877 level set "rnd_sam_bateman".
2878 The wrong behaviour will still be used for all levels or tapes that were
2879 created/recorded with it. An example for this is tape 023 from the level
2880 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2883 boolean use_amoeba_dropping_cannot_fall_bug =
2884 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2885 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2887 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2888 tape.game_version < VERSION_IDENT(4,2,0,0)));
2891 Summary of bugfix/change:
2892 Fixed move speed of elements entering or leaving magic wall.
2894 Fixed/changed in version:
2898 Before 2.0.1, move speed of elements entering or leaving magic wall was
2899 twice as fast as it is now.
2900 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2902 Affected levels/tapes:
2903 The first condition is generally needed for all levels/tapes before version
2904 2.0.1, which might use the old behaviour before it was changed; known tapes
2905 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2906 The second condition is an exception from the above case and is needed for
2907 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2908 above, but before it was known that this change would break tapes like the
2909 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2910 although the engine version while recording maybe was before 2.0.1. There
2911 are a lot of tapes that are affected by this exception, like tape 006 from
2912 the level set "rnd_conor_mancone".
2915 boolean use_old_move_stepsize_for_magic_wall =
2916 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2918 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2919 tape.game_version < VERSION_IDENT(4,2,0,0)));
2922 Summary of bugfix/change:
2923 Fixed handling for custom elements that change when pushed by the player.
2925 Fixed/changed in version:
2929 Before 3.1.0, custom elements that "change when pushing" changed directly
2930 after the player started pushing them (until then handled in "DigField()").
2931 Since 3.1.0, these custom elements are not changed until the "pushing"
2932 move of the element is finished (now handled in "ContinueMoving()").
2934 Affected levels/tapes:
2935 The first condition is generally needed for all levels/tapes before version
2936 3.1.0, which might use the old behaviour before it was changed; known tapes
2937 that are affected are some tapes from the level set "Walpurgis Gardens" by
2939 The second condition is an exception from the above case and is needed for
2940 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2941 above (including some development versions of 3.1.0), but before it was
2942 known that this change would break tapes like the above and was fixed in
2943 3.1.1, so that the changed behaviour was active although the engine version
2944 while recording maybe was before 3.1.0. There is at least one tape that is
2945 affected by this exception, which is the tape for the one-level set "Bug
2946 Machine" by Juergen Bonhagen.
2949 game.use_change_when_pushing_bug =
2950 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2952 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2953 tape.game_version < VERSION_IDENT(3,1,1,0)));
2956 Summary of bugfix/change:
2957 Fixed handling for blocking the field the player leaves when moving.
2959 Fixed/changed in version:
2963 Before 3.1.1, when "block last field when moving" was enabled, the field
2964 the player is leaving when moving was blocked for the time of the move,
2965 and was directly unblocked afterwards. This resulted in the last field
2966 being blocked for exactly one less than the number of frames of one player
2967 move. Additionally, even when blocking was disabled, the last field was
2968 blocked for exactly one frame.
2969 Since 3.1.1, due to changes in player movement handling, the last field
2970 is not blocked at all when blocking is disabled. When blocking is enabled,
2971 the last field is blocked for exactly the number of frames of one player
2972 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2973 last field is blocked for exactly one more than the number of frames of
2976 Affected levels/tapes:
2977 (!!! yet to be determined -- probably many !!!)
2980 game.use_block_last_field_bug =
2981 (game.engine_version < VERSION_IDENT(3,1,1,0));
2983 /* various special flags and settings for native Emerald Mine game engine */
2985 game_em.use_single_button =
2986 (game.engine_version > VERSION_IDENT(4,0,0,2));
2988 game_em.use_snap_key_bug =
2989 (game.engine_version < VERSION_IDENT(4,0,1,0));
2991 game_em.use_random_bug =
2992 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
2994 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
2996 game_em.use_old_explosions = use_old_em_engine;
2997 game_em.use_old_android = use_old_em_engine;
2998 game_em.use_old_push_elements = use_old_em_engine;
2999 game_em.use_old_push_into_acid = use_old_em_engine;
3001 game_em.use_wrap_around = !use_old_em_engine;
3003 // --------------------------------------------------------------------------
3005 // set maximal allowed number of custom element changes per game frame
3006 game.max_num_changes_per_frame = 1;
3008 // default scan direction: scan playfield from top/left to bottom/right
3009 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3011 // dynamically adjust element properties according to game engine version
3012 InitElementPropertiesEngine(game.engine_version);
3014 // ---------- initialize special element properties -------------------------
3016 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3017 if (use_amoeba_dropping_cannot_fall_bug)
3018 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3020 // ---------- initialize player's initial move delay ------------------------
3022 // dynamically adjust player properties according to level information
3023 for (i = 0; i < MAX_PLAYERS; i++)
3024 game.initial_move_delay_value[i] =
3025 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3027 // dynamically adjust player properties according to game engine version
3028 for (i = 0; i < MAX_PLAYERS; i++)
3029 game.initial_move_delay[i] =
3030 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3031 game.initial_move_delay_value[i] : 0);
3033 // ---------- initialize player's initial push delay ------------------------
3035 // dynamically adjust player properties according to game engine version
3036 game.initial_push_delay_value =
3037 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3039 // ---------- initialize changing elements ----------------------------------
3041 // initialize changing elements information
3042 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3044 struct ElementInfo *ei = &element_info[i];
3046 // this pointer might have been changed in the level editor
3047 ei->change = &ei->change_page[0];
3049 if (!IS_CUSTOM_ELEMENT(i))
3051 ei->change->target_element = EL_EMPTY_SPACE;
3052 ei->change->delay_fixed = 0;
3053 ei->change->delay_random = 0;
3054 ei->change->delay_frames = 1;
3057 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3059 ei->has_change_event[j] = FALSE;
3061 ei->event_page_nr[j] = 0;
3062 ei->event_page[j] = &ei->change_page[0];
3066 // add changing elements from pre-defined list
3067 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3069 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3070 struct ElementInfo *ei = &element_info[ch_delay->element];
3072 ei->change->target_element = ch_delay->target_element;
3073 ei->change->delay_fixed = ch_delay->change_delay;
3075 ei->change->pre_change_function = ch_delay->pre_change_function;
3076 ei->change->change_function = ch_delay->change_function;
3077 ei->change->post_change_function = ch_delay->post_change_function;
3079 ei->change->can_change = TRUE;
3080 ei->change->can_change_or_has_action = TRUE;
3082 ei->has_change_event[CE_DELAY] = TRUE;
3084 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3085 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3088 // ---------- initialize internal run-time variables ------------------------
3090 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3092 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3094 for (j = 0; j < ei->num_change_pages; j++)
3096 ei->change_page[j].can_change_or_has_action =
3097 (ei->change_page[j].can_change |
3098 ei->change_page[j].has_action);
3102 // add change events from custom element configuration
3103 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3105 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3107 for (j = 0; j < ei->num_change_pages; j++)
3109 if (!ei->change_page[j].can_change_or_has_action)
3112 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3114 // only add event page for the first page found with this event
3115 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3117 ei->has_change_event[k] = TRUE;
3119 ei->event_page_nr[k] = j;
3120 ei->event_page[k] = &ei->change_page[j];
3126 // ---------- initialize reference elements in change conditions ------------
3128 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3130 int element = EL_CUSTOM_START + i;
3131 struct ElementInfo *ei = &element_info[element];
3133 for (j = 0; j < ei->num_change_pages; j++)
3135 int trigger_element = ei->change_page[j].initial_trigger_element;
3137 if (trigger_element >= EL_PREV_CE_8 &&
3138 trigger_element <= EL_NEXT_CE_8)
3139 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3141 ei->change_page[j].trigger_element = trigger_element;
3145 // ---------- initialize run-time trigger player and element ----------------
3147 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3149 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3151 for (j = 0; j < ei->num_change_pages; j++)
3153 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3154 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3155 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3156 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3157 ei->change_page[j].actual_trigger_ce_value = 0;
3158 ei->change_page[j].actual_trigger_ce_score = 0;
3162 // ---------- initialize trigger events -------------------------------------
3164 // initialize trigger events information
3165 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3166 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3167 trigger_events[i][j] = FALSE;
3169 // add trigger events from element change event properties
3170 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3172 struct ElementInfo *ei = &element_info[i];
3174 for (j = 0; j < ei->num_change_pages; j++)
3176 if (!ei->change_page[j].can_change_or_has_action)
3179 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3181 int trigger_element = ei->change_page[j].trigger_element;
3183 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3185 if (ei->change_page[j].has_event[k])
3187 if (IS_GROUP_ELEMENT(trigger_element))
3189 struct ElementGroupInfo *group =
3190 element_info[trigger_element].group;
3192 for (l = 0; l < group->num_elements_resolved; l++)
3193 trigger_events[group->element_resolved[l]][k] = TRUE;
3195 else if (trigger_element == EL_ANY_ELEMENT)
3196 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3197 trigger_events[l][k] = TRUE;
3199 trigger_events[trigger_element][k] = TRUE;
3206 // ---------- initialize push delay -----------------------------------------
3208 // initialize push delay values to default
3209 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3211 if (!IS_CUSTOM_ELEMENT(i))
3213 // set default push delay values (corrected since version 3.0.7-1)
3214 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3216 element_info[i].push_delay_fixed = 2;
3217 element_info[i].push_delay_random = 8;
3221 element_info[i].push_delay_fixed = 8;
3222 element_info[i].push_delay_random = 8;
3227 // set push delay value for certain elements from pre-defined list
3228 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3230 int e = push_delay_list[i].element;
3232 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3233 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3236 // set push delay value for Supaplex elements for newer engine versions
3237 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3239 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3241 if (IS_SP_ELEMENT(i))
3243 // set SP push delay to just enough to push under a falling zonk
3244 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3246 element_info[i].push_delay_fixed = delay;
3247 element_info[i].push_delay_random = 0;
3252 // ---------- initialize move stepsize --------------------------------------
3254 // initialize move stepsize values to default
3255 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3256 if (!IS_CUSTOM_ELEMENT(i))
3257 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3259 // set move stepsize value for certain elements from pre-defined list
3260 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3262 int e = move_stepsize_list[i].element;
3264 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3266 // set move stepsize value for certain elements for older engine versions
3267 if (use_old_move_stepsize_for_magic_wall)
3269 if (e == EL_MAGIC_WALL_FILLING ||
3270 e == EL_MAGIC_WALL_EMPTYING ||
3271 e == EL_BD_MAGIC_WALL_FILLING ||
3272 e == EL_BD_MAGIC_WALL_EMPTYING)
3273 element_info[e].move_stepsize *= 2;
3277 // ---------- initialize collect score --------------------------------------
3279 // initialize collect score values for custom elements from initial value
3280 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3281 if (IS_CUSTOM_ELEMENT(i))
3282 element_info[i].collect_score = element_info[i].collect_score_initial;
3284 // ---------- initialize collect count --------------------------------------
3286 // initialize collect count values for non-custom elements
3287 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3289 element_info[i].collect_count_initial = 0;
3291 // add collect count values for all elements from pre-defined list
3292 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3293 element_info[collect_count_list[i].element].collect_count_initial =
3294 collect_count_list[i].count;
3296 // ---------- initialize access direction -----------------------------------
3298 // initialize access direction values to default (access from every side)
3299 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3300 if (!IS_CUSTOM_ELEMENT(i))
3301 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3303 // set access direction value for certain elements from pre-defined list
3304 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3305 element_info[access_direction_list[i].element].access_direction =
3306 access_direction_list[i].direction;
3308 // ---------- initialize explosion content ----------------------------------
3309 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3311 if (IS_CUSTOM_ELEMENT(i))
3314 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3316 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3318 element_info[i].content.e[x][y] =
3319 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3320 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3321 i == EL_PLAYER_3 ? EL_EMERALD :
3322 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3323 i == EL_MOLE ? EL_EMERALD_RED :
3324 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3325 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3326 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3327 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3328 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3329 i == EL_WALL_EMERALD ? EL_EMERALD :
3330 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3331 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3332 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3333 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3334 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3335 i == EL_WALL_PEARL ? EL_PEARL :
3336 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3341 // ---------- initialize recursion detection --------------------------------
3342 recursion_loop_depth = 0;
3343 recursion_loop_detected = FALSE;
3344 recursion_loop_element = EL_UNDEFINED;
3346 // ---------- initialize graphics engine ------------------------------------
3347 game.scroll_delay_value =
3348 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3349 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3350 !setup.forced_scroll_delay ? 0 :
3351 setup.scroll_delay ? setup.scroll_delay_value : 0);
3352 game.scroll_delay_value =
3353 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3355 // ---------- initialize game engine snapshots ------------------------------
3356 for (i = 0; i < MAX_PLAYERS; i++)
3357 game.snapshot.last_action[i] = 0;
3358 game.snapshot.changed_action = FALSE;
3359 game.snapshot.collected_item = FALSE;
3360 game.snapshot.mode =
3361 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3362 SNAPSHOT_MODE_EVERY_STEP :
3363 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3364 SNAPSHOT_MODE_EVERY_MOVE :
3365 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3366 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3367 game.snapshot.save_snapshot = FALSE;
3369 // ---------- initialize level time for Supaplex engine ---------------------
3370 // Supaplex levels with time limit currently unsupported -- should be added
3371 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3374 // ---------- initialize flags for handling game actions --------------------
3376 // set flags for game actions to default values
3377 game.use_key_actions = TRUE;
3378 game.use_mouse_actions = FALSE;
3380 // when using Mirror Magic game engine, handle mouse events only
3381 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3383 game.use_key_actions = FALSE;
3384 game.use_mouse_actions = TRUE;
3387 // check for custom elements with mouse click events
3388 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3390 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3392 int element = EL_CUSTOM_START + i;
3394 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3395 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3396 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3397 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3398 game.use_mouse_actions = TRUE;
3403 static int get_num_special_action(int element, int action_first,
3406 int num_special_action = 0;
3409 for (i = action_first; i <= action_last; i++)
3411 boolean found = FALSE;
3413 for (j = 0; j < NUM_DIRECTIONS; j++)
3414 if (el_act_dir2img(element, i, j) !=
3415 el_act_dir2img(element, ACTION_DEFAULT, j))
3419 num_special_action++;
3424 return num_special_action;
3428 // ============================================================================
3430 // ----------------------------------------------------------------------------
3431 // initialize and start new game
3432 // ============================================================================
3434 #if DEBUG_INIT_PLAYER
3435 static void DebugPrintPlayerStatus(char *message)
3442 Debug("game:init:player", "%s:", message);
3444 for (i = 0; i < MAX_PLAYERS; i++)
3446 struct PlayerInfo *player = &stored_player[i];
3448 Debug("game:init:player",
3449 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3453 player->connected_locally,
3454 player->connected_network,
3456 (local_player == player ? " (local player)" : ""));
3463 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3464 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3465 int fade_mask = REDRAW_FIELD;
3467 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3468 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3469 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3470 int initial_move_dir = MV_DOWN;
3473 // required here to update video display before fading (FIX THIS)
3474 DrawMaskedBorder(REDRAW_DOOR_2);
3476 if (!game.restart_level)
3477 CloseDoor(DOOR_CLOSE_1);
3479 SetGameStatus(GAME_MODE_PLAYING);
3481 if (level_editor_test_game)
3482 FadeSkipNextFadeOut();
3484 FadeSetEnterScreen();
3487 fade_mask = REDRAW_ALL;
3489 FadeLevelSoundsAndMusic();
3491 ExpireSoundLoops(TRUE);
3495 if (level_editor_test_game)
3496 FadeSkipNextFadeIn();
3498 // needed if different viewport properties defined for playing
3499 ChangeViewportPropertiesIfNeeded();
3503 DrawCompleteVideoDisplay();
3505 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3508 InitGameControlValues();
3510 // initialize tape actions from game when recording tape
3513 tape.use_key_actions = game.use_key_actions;
3514 tape.use_mouse_actions = game.use_mouse_actions;
3517 // don't play tapes over network
3518 network_playing = (network.enabled && !tape.playing);
3520 for (i = 0; i < MAX_PLAYERS; i++)
3522 struct PlayerInfo *player = &stored_player[i];
3524 player->index_nr = i;
3525 player->index_bit = (1 << i);
3526 player->element_nr = EL_PLAYER_1 + i;
3528 player->present = FALSE;
3529 player->active = FALSE;
3530 player->mapped = FALSE;
3532 player->killed = FALSE;
3533 player->reanimated = FALSE;
3534 player->buried = FALSE;
3537 player->effective_action = 0;
3538 player->programmed_action = 0;
3539 player->snap_action = 0;
3541 player->mouse_action.lx = 0;
3542 player->mouse_action.ly = 0;
3543 player->mouse_action.button = 0;
3544 player->mouse_action.button_hint = 0;
3546 player->effective_mouse_action.lx = 0;
3547 player->effective_mouse_action.ly = 0;
3548 player->effective_mouse_action.button = 0;
3549 player->effective_mouse_action.button_hint = 0;
3551 for (j = 0; j < MAX_NUM_KEYS; j++)
3552 player->key[j] = FALSE;
3554 player->num_white_keys = 0;
3556 player->dynabomb_count = 0;
3557 player->dynabomb_size = 1;
3558 player->dynabombs_left = 0;
3559 player->dynabomb_xl = FALSE;
3561 player->MovDir = initial_move_dir;
3564 player->GfxDir = initial_move_dir;
3565 player->GfxAction = ACTION_DEFAULT;
3567 player->StepFrame = 0;
3569 player->initial_element = player->element_nr;
3570 player->artwork_element =
3571 (level.use_artwork_element[i] ? level.artwork_element[i] :
3572 player->element_nr);
3573 player->use_murphy = FALSE;
3575 player->block_last_field = FALSE; // initialized in InitPlayerField()
3576 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3578 player->gravity = level.initial_player_gravity[i];
3580 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3582 player->actual_frame_counter = 0;
3584 player->step_counter = 0;
3586 player->last_move_dir = initial_move_dir;
3588 player->is_active = FALSE;
3590 player->is_waiting = FALSE;
3591 player->is_moving = FALSE;
3592 player->is_auto_moving = FALSE;
3593 player->is_digging = FALSE;
3594 player->is_snapping = FALSE;
3595 player->is_collecting = FALSE;
3596 player->is_pushing = FALSE;
3597 player->is_switching = FALSE;
3598 player->is_dropping = FALSE;
3599 player->is_dropping_pressed = FALSE;
3601 player->is_bored = FALSE;
3602 player->is_sleeping = FALSE;
3604 player->was_waiting = TRUE;
3605 player->was_moving = FALSE;
3606 player->was_snapping = FALSE;
3607 player->was_dropping = FALSE;
3609 player->force_dropping = FALSE;
3611 player->frame_counter_bored = -1;
3612 player->frame_counter_sleeping = -1;
3614 player->anim_delay_counter = 0;
3615 player->post_delay_counter = 0;
3617 player->dir_waiting = initial_move_dir;
3618 player->action_waiting = ACTION_DEFAULT;
3619 player->last_action_waiting = ACTION_DEFAULT;
3620 player->special_action_bored = ACTION_DEFAULT;
3621 player->special_action_sleeping = ACTION_DEFAULT;
3623 player->switch_x = -1;
3624 player->switch_y = -1;
3626 player->drop_x = -1;
3627 player->drop_y = -1;
3629 player->show_envelope = 0;
3631 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3633 player->push_delay = -1; // initialized when pushing starts
3634 player->push_delay_value = game.initial_push_delay_value;
3636 player->drop_delay = 0;
3637 player->drop_pressed_delay = 0;
3639 player->last_jx = -1;
3640 player->last_jy = -1;
3644 player->shield_normal_time_left = 0;
3645 player->shield_deadly_time_left = 0;
3647 player->inventory_infinite_element = EL_UNDEFINED;
3648 player->inventory_size = 0;
3650 if (level.use_initial_inventory[i])
3652 for (j = 0; j < level.initial_inventory_size[i]; j++)
3654 int element = level.initial_inventory_content[i][j];
3655 int collect_count = element_info[element].collect_count_initial;
3658 if (!IS_CUSTOM_ELEMENT(element))
3661 if (collect_count == 0)
3662 player->inventory_infinite_element = element;
3664 for (k = 0; k < collect_count; k++)
3665 if (player->inventory_size < MAX_INVENTORY_SIZE)
3666 player->inventory_element[player->inventory_size++] = element;
3670 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3671 SnapField(player, 0, 0);
3673 map_player_action[i] = i;
3676 network_player_action_received = FALSE;
3678 // initial null action
3679 if (network_playing)
3680 SendToServer_MovePlayer(MV_NONE);
3685 TimeLeft = level.time;
3688 ScreenMovDir = MV_NONE;
3692 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3694 game.robot_wheel_x = -1;
3695 game.robot_wheel_y = -1;
3700 game.all_players_gone = FALSE;
3702 game.LevelSolved = FALSE;
3703 game.GameOver = FALSE;
3705 game.GamePlayed = !tape.playing;
3707 game.LevelSolved_GameWon = FALSE;
3708 game.LevelSolved_GameEnd = FALSE;
3709 game.LevelSolved_SaveTape = FALSE;
3710 game.LevelSolved_SaveScore = FALSE;
3712 game.LevelSolved_CountingTime = 0;
3713 game.LevelSolved_CountingScore = 0;
3714 game.LevelSolved_CountingHealth = 0;
3716 game.panel.active = TRUE;
3718 game.no_time_limit = (level.time == 0);
3720 game.yamyam_content_nr = 0;
3721 game.robot_wheel_active = FALSE;
3722 game.magic_wall_active = FALSE;
3723 game.magic_wall_time_left = 0;
3724 game.light_time_left = 0;
3725 game.timegate_time_left = 0;
3726 game.switchgate_pos = 0;
3727 game.wind_direction = level.wind_direction_initial;
3730 game.score_final = 0;
3732 game.health = MAX_HEALTH;
3733 game.health_final = MAX_HEALTH;
3735 game.gems_still_needed = level.gems_needed;
3736 game.sokoban_fields_still_needed = 0;
3737 game.sokoban_objects_still_needed = 0;
3738 game.lights_still_needed = 0;
3739 game.players_still_needed = 0;
3740 game.friends_still_needed = 0;
3742 game.lenses_time_left = 0;
3743 game.magnify_time_left = 0;
3745 game.ball_active = level.ball_active_initial;
3746 game.ball_content_nr = 0;
3748 game.explosions_delayed = TRUE;
3750 game.envelope_active = FALSE;
3752 for (i = 0; i < NUM_BELTS; i++)
3754 game.belt_dir[i] = MV_NONE;
3755 game.belt_dir_nr[i] = 3; // not moving, next moving left
3758 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3759 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3761 #if DEBUG_INIT_PLAYER
3762 DebugPrintPlayerStatus("Player status at level initialization");
3765 SCAN_PLAYFIELD(x, y)
3767 Tile[x][y] = Last[x][y] = level.field[x][y];
3768 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3769 ChangeDelay[x][y] = 0;
3770 ChangePage[x][y] = -1;
3771 CustomValue[x][y] = 0; // initialized in InitField()
3772 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3774 WasJustMoving[x][y] = 0;
3775 WasJustFalling[x][y] = 0;
3776 CheckCollision[x][y] = 0;
3777 CheckImpact[x][y] = 0;
3779 Pushed[x][y] = FALSE;
3781 ChangeCount[x][y] = 0;
3782 ChangeEvent[x][y] = -1;
3784 ExplodePhase[x][y] = 0;
3785 ExplodeDelay[x][y] = 0;
3786 ExplodeField[x][y] = EX_TYPE_NONE;
3788 RunnerVisit[x][y] = 0;
3789 PlayerVisit[x][y] = 0;
3792 GfxRandom[x][y] = INIT_GFX_RANDOM();
3793 GfxElement[x][y] = EL_UNDEFINED;
3794 GfxAction[x][y] = ACTION_DEFAULT;
3795 GfxDir[x][y] = MV_NONE;
3796 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3799 SCAN_PLAYFIELD(x, y)
3801 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3803 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3805 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3808 InitField(x, y, TRUE);
3810 ResetGfxAnimation(x, y);
3815 for (i = 0; i < MAX_PLAYERS; i++)
3817 struct PlayerInfo *player = &stored_player[i];
3819 // set number of special actions for bored and sleeping animation
3820 player->num_special_action_bored =
3821 get_num_special_action(player->artwork_element,
3822 ACTION_BORING_1, ACTION_BORING_LAST);
3823 player->num_special_action_sleeping =
3824 get_num_special_action(player->artwork_element,
3825 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3828 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3829 emulate_sb ? EMU_SOKOBAN :
3830 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3832 // initialize type of slippery elements
3833 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3835 if (!IS_CUSTOM_ELEMENT(i))
3837 // default: elements slip down either to the left or right randomly
3838 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3840 // SP style elements prefer to slip down on the left side
3841 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3842 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3844 // BD style elements prefer to slip down on the left side
3845 if (game.emulation == EMU_BOULDERDASH)
3846 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3850 // initialize explosion and ignition delay
3851 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3853 if (!IS_CUSTOM_ELEMENT(i))
3856 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3857 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3858 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3859 int last_phase = (num_phase + 1) * delay;
3860 int half_phase = (num_phase / 2) * delay;
3862 element_info[i].explosion_delay = last_phase - 1;
3863 element_info[i].ignition_delay = half_phase;
3865 if (i == EL_BLACK_ORB)
3866 element_info[i].ignition_delay = 1;
3870 // correct non-moving belts to start moving left
3871 for (i = 0; i < NUM_BELTS; i++)
3872 if (game.belt_dir[i] == MV_NONE)
3873 game.belt_dir_nr[i] = 3; // not moving, next moving left
3875 #if USE_NEW_PLAYER_ASSIGNMENTS
3876 // use preferred player also in local single-player mode
3877 if (!network.enabled && !game.team_mode)
3879 int new_index_nr = setup.network_player_nr;
3881 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3883 for (i = 0; i < MAX_PLAYERS; i++)
3884 stored_player[i].connected_locally = FALSE;
3886 stored_player[new_index_nr].connected_locally = TRUE;
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 stored_player[i].connected = FALSE;
3894 // in network game mode, the local player might not be the first player
3895 if (stored_player[i].connected_locally)
3896 local_player = &stored_player[i];
3899 if (!network.enabled)
3900 local_player->connected = TRUE;
3904 for (i = 0; i < MAX_PLAYERS; i++)
3905 stored_player[i].connected = tape.player_participates[i];
3907 else if (network.enabled)
3909 // add team mode players connected over the network (needed for correct
3910 // assignment of player figures from level to locally playing players)
3912 for (i = 0; i < MAX_PLAYERS; i++)
3913 if (stored_player[i].connected_network)
3914 stored_player[i].connected = TRUE;
3916 else if (game.team_mode)
3918 // try to guess locally connected team mode players (needed for correct
3919 // assignment of player figures from level to locally playing players)
3921 for (i = 0; i < MAX_PLAYERS; i++)
3922 if (setup.input[i].use_joystick ||
3923 setup.input[i].key.left != KSYM_UNDEFINED)
3924 stored_player[i].connected = TRUE;
3927 #if DEBUG_INIT_PLAYER
3928 DebugPrintPlayerStatus("Player status after level initialization");
3931 #if DEBUG_INIT_PLAYER
3932 Debug("game:init:player", "Reassigning players ...");
3935 // check if any connected player was not found in playfield
3936 for (i = 0; i < MAX_PLAYERS; i++)
3938 struct PlayerInfo *player = &stored_player[i];
3940 if (player->connected && !player->present)
3942 struct PlayerInfo *field_player = NULL;
3944 #if DEBUG_INIT_PLAYER
3945 Debug("game:init:player",
3946 "- looking for field player for player %d ...", i + 1);
3949 // assign first free player found that is present in the playfield
3951 // first try: look for unmapped playfield player that is not connected
3952 for (j = 0; j < MAX_PLAYERS; j++)
3953 if (field_player == NULL &&
3954 stored_player[j].present &&
3955 !stored_player[j].mapped &&
3956 !stored_player[j].connected)
3957 field_player = &stored_player[j];
3959 // second try: look for *any* unmapped playfield player
3960 for (j = 0; j < MAX_PLAYERS; j++)
3961 if (field_player == NULL &&
3962 stored_player[j].present &&
3963 !stored_player[j].mapped)
3964 field_player = &stored_player[j];
3966 if (field_player != NULL)
3968 int jx = field_player->jx, jy = field_player->jy;
3970 #if DEBUG_INIT_PLAYER
3971 Debug("game:init:player", "- found player %d",
3972 field_player->index_nr + 1);
3975 player->present = FALSE;
3976 player->active = FALSE;
3978 field_player->present = TRUE;
3979 field_player->active = TRUE;
3982 player->initial_element = field_player->initial_element;
3983 player->artwork_element = field_player->artwork_element;
3985 player->block_last_field = field_player->block_last_field;
3986 player->block_delay_adjustment = field_player->block_delay_adjustment;
3989 StorePlayer[jx][jy] = field_player->element_nr;
3991 field_player->jx = field_player->last_jx = jx;
3992 field_player->jy = field_player->last_jy = jy;
3994 if (local_player == player)
3995 local_player = field_player;
3997 map_player_action[field_player->index_nr] = i;
3999 field_player->mapped = TRUE;
4001 #if DEBUG_INIT_PLAYER
4002 Debug("game:init:player", "- map_player_action[%d] == %d",
4003 field_player->index_nr + 1, i + 1);
4008 if (player->connected && player->present)
4009 player->mapped = TRUE;
4012 #if DEBUG_INIT_PLAYER
4013 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4018 // check if any connected player was not found in playfield
4019 for (i = 0; i < MAX_PLAYERS; i++)
4021 struct PlayerInfo *player = &stored_player[i];
4023 if (player->connected && !player->present)
4025 for (j = 0; j < MAX_PLAYERS; j++)
4027 struct PlayerInfo *field_player = &stored_player[j];
4028 int jx = field_player->jx, jy = field_player->jy;
4030 // assign first free player found that is present in the playfield
4031 if (field_player->present && !field_player->connected)
4033 player->present = TRUE;
4034 player->active = TRUE;
4036 field_player->present = FALSE;
4037 field_player->active = FALSE;
4039 player->initial_element = field_player->initial_element;
4040 player->artwork_element = field_player->artwork_element;
4042 player->block_last_field = field_player->block_last_field;
4043 player->block_delay_adjustment = field_player->block_delay_adjustment;
4045 StorePlayer[jx][jy] = player->element_nr;
4047 player->jx = player->last_jx = jx;
4048 player->jy = player->last_jy = jy;
4058 Debug("game:init:player", "local_player->present == %d",
4059 local_player->present);
4062 // set focus to local player for network games, else to all players
4063 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4064 game.centered_player_nr_next = game.centered_player_nr;
4065 game.set_centered_player = FALSE;
4066 game.set_centered_player_wrap = FALSE;
4068 if (network_playing && tape.recording)
4070 // store client dependent player focus when recording network games
4071 tape.centered_player_nr_next = game.centered_player_nr_next;
4072 tape.set_centered_player = TRUE;
4077 // when playing a tape, eliminate all players who do not participate
4079 #if USE_NEW_PLAYER_ASSIGNMENTS
4081 if (!game.team_mode)
4083 for (i = 0; i < MAX_PLAYERS; i++)
4085 if (stored_player[i].active &&
4086 !tape.player_participates[map_player_action[i]])
4088 struct PlayerInfo *player = &stored_player[i];
4089 int jx = player->jx, jy = player->jy;
4091 #if DEBUG_INIT_PLAYER
4092 Debug("game:init:player", "Removing player %d at (%d, %d)",
4096 player->active = FALSE;
4097 StorePlayer[jx][jy] = 0;
4098 Tile[jx][jy] = EL_EMPTY;
4105 for (i = 0; i < MAX_PLAYERS; i++)
4107 if (stored_player[i].active &&
4108 !tape.player_participates[i])
4110 struct PlayerInfo *player = &stored_player[i];
4111 int jx = player->jx, jy = player->jy;
4113 player->active = FALSE;
4114 StorePlayer[jx][jy] = 0;
4115 Tile[jx][jy] = EL_EMPTY;
4120 else if (!network.enabled && !game.team_mode) // && !tape.playing
4122 // when in single player mode, eliminate all but the local player
4124 for (i = 0; i < MAX_PLAYERS; i++)
4126 struct PlayerInfo *player = &stored_player[i];
4128 if (player->active && player != local_player)
4130 int jx = player->jx, jy = player->jy;
4132 player->active = FALSE;
4133 player->present = FALSE;
4135 StorePlayer[jx][jy] = 0;
4136 Tile[jx][jy] = EL_EMPTY;
4141 for (i = 0; i < MAX_PLAYERS; i++)
4142 if (stored_player[i].active)
4143 game.players_still_needed++;
4145 if (level.solved_by_one_player)
4146 game.players_still_needed = 1;
4148 // when recording the game, store which players take part in the game
4151 #if USE_NEW_PLAYER_ASSIGNMENTS
4152 for (i = 0; i < MAX_PLAYERS; i++)
4153 if (stored_player[i].connected)
4154 tape.player_participates[i] = TRUE;
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (stored_player[i].active)
4158 tape.player_participates[i] = TRUE;
4162 #if DEBUG_INIT_PLAYER
4163 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4166 if (BorderElement == EL_EMPTY)
4169 SBX_Right = lev_fieldx - SCR_FIELDX;
4171 SBY_Lower = lev_fieldy - SCR_FIELDY;
4176 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4178 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4181 if (full_lev_fieldx <= SCR_FIELDX)
4182 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4183 if (full_lev_fieldy <= SCR_FIELDY)
4184 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4186 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4188 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4191 // if local player not found, look for custom element that might create
4192 // the player (make some assumptions about the right custom element)
4193 if (!local_player->present)
4195 int start_x = 0, start_y = 0;
4196 int found_rating = 0;
4197 int found_element = EL_UNDEFINED;
4198 int player_nr = local_player->index_nr;
4200 SCAN_PLAYFIELD(x, y)
4202 int element = Tile[x][y];
4207 if (level.use_start_element[player_nr] &&
4208 level.start_element[player_nr] == element &&
4215 found_element = element;
4218 if (!IS_CUSTOM_ELEMENT(element))
4221 if (CAN_CHANGE(element))
4223 for (i = 0; i < element_info[element].num_change_pages; i++)
4225 // check for player created from custom element as single target
4226 content = element_info[element].change_page[i].target_element;
4227 is_player = ELEM_IS_PLAYER(content);
4229 if (is_player && (found_rating < 3 ||
4230 (found_rating == 3 && element < found_element)))
4236 found_element = element;
4241 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4243 // check for player created from custom element as explosion content
4244 content = element_info[element].content.e[xx][yy];
4245 is_player = ELEM_IS_PLAYER(content);
4247 if (is_player && (found_rating < 2 ||
4248 (found_rating == 2 && element < found_element)))
4250 start_x = x + xx - 1;
4251 start_y = y + yy - 1;
4254 found_element = element;
4257 if (!CAN_CHANGE(element))
4260 for (i = 0; i < element_info[element].num_change_pages; i++)
4262 // check for player created from custom element as extended target
4264 element_info[element].change_page[i].target_content.e[xx][yy];
4266 is_player = ELEM_IS_PLAYER(content);
4268 if (is_player && (found_rating < 1 ||
4269 (found_rating == 1 && element < found_element)))
4271 start_x = x + xx - 1;
4272 start_y = y + yy - 1;
4275 found_element = element;
4281 scroll_x = SCROLL_POSITION_X(start_x);
4282 scroll_y = SCROLL_POSITION_Y(start_y);
4286 scroll_x = SCROLL_POSITION_X(local_player->jx);
4287 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4290 // !!! FIX THIS (START) !!!
4291 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4293 InitGameEngine_EM();
4295 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4297 InitGameEngine_SP();
4299 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4301 InitGameEngine_MM();
4305 DrawLevel(REDRAW_FIELD);
4308 // after drawing the level, correct some elements
4309 if (game.timegate_time_left == 0)
4310 CloseAllOpenTimegates();
4313 // blit playfield from scroll buffer to normal back buffer for fading in
4314 BlitScreenToBitmap(backbuffer);
4315 // !!! FIX THIS (END) !!!
4317 DrawMaskedBorder(fade_mask);
4322 // full screen redraw is required at this point in the following cases:
4323 // - special editor door undrawn when game was started from level editor
4324 // - drawing area (playfield) was changed and has to be removed completely
4325 redraw_mask = REDRAW_ALL;
4329 if (!game.restart_level)
4331 // copy default game door content to main double buffer
4333 // !!! CHECK AGAIN !!!
4334 SetPanelBackground();
4335 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4336 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4339 SetPanelBackground();
4340 SetDrawBackgroundMask(REDRAW_DOOR_1);
4342 UpdateAndDisplayGameControlValues();
4344 if (!game.restart_level)
4350 CreateGameButtons();
4355 // copy actual game door content to door double buffer for OpenDoor()
4356 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4358 OpenDoor(DOOR_OPEN_ALL);
4360 KeyboardAutoRepeatOffUnlessAutoplay();
4362 #if DEBUG_INIT_PLAYER
4363 DebugPrintPlayerStatus("Player status (final)");
4372 if (!game.restart_level && !tape.playing)
4374 LevelStats_incPlayed(level_nr);
4376 SaveLevelSetup_SeriesInfo();
4379 game.restart_level = FALSE;
4380 game.restart_game_message = NULL;
4381 game.request_active = FALSE;
4383 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4384 InitGameActions_MM();
4386 SaveEngineSnapshotToListInitial();
4388 if (!game.restart_level)
4390 PlaySound(SND_GAME_STARTING);
4392 if (setup.sound_music)
4397 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4398 int actual_player_x, int actual_player_y)
4400 // this is used for non-R'n'D game engines to update certain engine values
4402 // needed to determine if sounds are played within the visible screen area
4403 scroll_x = actual_scroll_x;
4404 scroll_y = actual_scroll_y;
4406 // needed to get player position for "follow finger" playing input method
4407 local_player->jx = actual_player_x;
4408 local_player->jy = actual_player_y;
4411 void InitMovDir(int x, int y)
4413 int i, element = Tile[x][y];
4414 static int xy[4][2] =
4421 static int direction[3][4] =
4423 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4424 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4425 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4434 Tile[x][y] = EL_BUG;
4435 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4438 case EL_SPACESHIP_RIGHT:
4439 case EL_SPACESHIP_UP:
4440 case EL_SPACESHIP_LEFT:
4441 case EL_SPACESHIP_DOWN:
4442 Tile[x][y] = EL_SPACESHIP;
4443 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4446 case EL_BD_BUTTERFLY_RIGHT:
4447 case EL_BD_BUTTERFLY_UP:
4448 case EL_BD_BUTTERFLY_LEFT:
4449 case EL_BD_BUTTERFLY_DOWN:
4450 Tile[x][y] = EL_BD_BUTTERFLY;
4451 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4454 case EL_BD_FIREFLY_RIGHT:
4455 case EL_BD_FIREFLY_UP:
4456 case EL_BD_FIREFLY_LEFT:
4457 case EL_BD_FIREFLY_DOWN:
4458 Tile[x][y] = EL_BD_FIREFLY;
4459 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4462 case EL_PACMAN_RIGHT:
4464 case EL_PACMAN_LEFT:
4465 case EL_PACMAN_DOWN:
4466 Tile[x][y] = EL_PACMAN;
4467 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4470 case EL_YAMYAM_LEFT:
4471 case EL_YAMYAM_RIGHT:
4473 case EL_YAMYAM_DOWN:
4474 Tile[x][y] = EL_YAMYAM;
4475 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4478 case EL_SP_SNIKSNAK:
4479 MovDir[x][y] = MV_UP;
4482 case EL_SP_ELECTRON:
4483 MovDir[x][y] = MV_LEFT;
4490 Tile[x][y] = EL_MOLE;
4491 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4494 case EL_SPRING_LEFT:
4495 case EL_SPRING_RIGHT:
4496 Tile[x][y] = EL_SPRING;
4497 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4501 if (IS_CUSTOM_ELEMENT(element))
4503 struct ElementInfo *ei = &element_info[element];
4504 int move_direction_initial = ei->move_direction_initial;
4505 int move_pattern = ei->move_pattern;
4507 if (move_direction_initial == MV_START_PREVIOUS)
4509 if (MovDir[x][y] != MV_NONE)
4512 move_direction_initial = MV_START_AUTOMATIC;
4515 if (move_direction_initial == MV_START_RANDOM)
4516 MovDir[x][y] = 1 << RND(4);
4517 else if (move_direction_initial & MV_ANY_DIRECTION)
4518 MovDir[x][y] = move_direction_initial;
4519 else if (move_pattern == MV_ALL_DIRECTIONS ||
4520 move_pattern == MV_TURNING_LEFT ||
4521 move_pattern == MV_TURNING_RIGHT ||
4522 move_pattern == MV_TURNING_LEFT_RIGHT ||
4523 move_pattern == MV_TURNING_RIGHT_LEFT ||
4524 move_pattern == MV_TURNING_RANDOM)
4525 MovDir[x][y] = 1 << RND(4);
4526 else if (move_pattern == MV_HORIZONTAL)
4527 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4528 else if (move_pattern == MV_VERTICAL)
4529 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4530 else if (move_pattern & MV_ANY_DIRECTION)
4531 MovDir[x][y] = element_info[element].move_pattern;
4532 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4533 move_pattern == MV_ALONG_RIGHT_SIDE)
4535 // use random direction as default start direction
4536 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4537 MovDir[x][y] = 1 << RND(4);
4539 for (i = 0; i < NUM_DIRECTIONS; i++)
4541 int x1 = x + xy[i][0];
4542 int y1 = y + xy[i][1];
4544 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4546 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4547 MovDir[x][y] = direction[0][i];
4549 MovDir[x][y] = direction[1][i];
4558 MovDir[x][y] = 1 << RND(4);
4560 if (element != EL_BUG &&
4561 element != EL_SPACESHIP &&
4562 element != EL_BD_BUTTERFLY &&
4563 element != EL_BD_FIREFLY)
4566 for (i = 0; i < NUM_DIRECTIONS; i++)
4568 int x1 = x + xy[i][0];
4569 int y1 = y + xy[i][1];
4571 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4573 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4575 MovDir[x][y] = direction[0][i];
4578 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4579 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4581 MovDir[x][y] = direction[1][i];
4590 GfxDir[x][y] = MovDir[x][y];
4593 void InitAmoebaNr(int x, int y)
4596 int group_nr = AmoebaNeighbourNr(x, y);
4600 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4602 if (AmoebaCnt[i] == 0)
4610 AmoebaNr[x][y] = group_nr;
4611 AmoebaCnt[group_nr]++;
4612 AmoebaCnt2[group_nr]++;
4615 static void LevelSolved(void)
4617 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4618 game.players_still_needed > 0)
4621 game.LevelSolved = TRUE;
4622 game.GameOver = TRUE;
4624 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4625 game_em.lev->score :
4626 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4629 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4630 MM_HEALTH(game_mm.laser_overload_value) :
4633 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4634 game.LevelSolved_CountingScore = game.score_final;
4635 game.LevelSolved_CountingHealth = game.health_final;
4640 static int time_count_steps;
4641 static int time, time_final;
4642 static int score, score_final;
4643 static int health, health_final;
4644 static int game_over_delay_1 = 0;
4645 static int game_over_delay_2 = 0;
4646 static int game_over_delay_3 = 0;
4647 int game_over_delay_value_1 = 50;
4648 int game_over_delay_value_2 = 25;
4649 int game_over_delay_value_3 = 50;
4651 if (!game.LevelSolved_GameWon)
4655 // do not start end game actions before the player stops moving (to exit)
4656 if (local_player->active && local_player->MovPos)
4659 game.LevelSolved_GameWon = TRUE;
4660 game.LevelSolved_SaveTape = tape.recording;
4661 game.LevelSolved_SaveScore = !tape.playing;
4665 LevelStats_incSolved(level_nr);
4667 SaveLevelSetup_SeriesInfo();
4670 if (tape.auto_play) // tape might already be stopped here
4671 tape.auto_play_level_solved = TRUE;
4675 game_over_delay_1 = 0;
4676 game_over_delay_2 = 0;
4677 game_over_delay_3 = game_over_delay_value_3;
4679 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4680 score = score_final = game.score_final;
4681 health = health_final = game.health_final;
4683 if (level.score[SC_TIME_BONUS] > 0)
4688 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4690 else if (game.no_time_limit && TimePlayed < 999)
4693 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4696 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4698 game_over_delay_1 = game_over_delay_value_1;
4700 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4703 score_final += health * level.score[SC_TIME_BONUS];
4705 game_over_delay_2 = game_over_delay_value_2;
4708 game.score_final = score_final;
4709 game.health_final = health_final;
4712 if (level_editor_test_game)
4715 score = score_final;
4717 game.LevelSolved_CountingTime = time;
4718 game.LevelSolved_CountingScore = score;
4720 game_panel_controls[GAME_PANEL_TIME].value = time;
4721 game_panel_controls[GAME_PANEL_SCORE].value = score;
4723 DisplayGameControlValues();
4726 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4728 // check if last player has left the level
4729 if (game.exit_x >= 0 &&
4732 int x = game.exit_x;
4733 int y = game.exit_y;
4734 int element = Tile[x][y];
4736 // close exit door after last player
4737 if ((game.all_players_gone &&
4738 (element == EL_EXIT_OPEN ||
4739 element == EL_SP_EXIT_OPEN ||
4740 element == EL_STEEL_EXIT_OPEN)) ||
4741 element == EL_EM_EXIT_OPEN ||
4742 element == EL_EM_STEEL_EXIT_OPEN)
4746 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4747 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4748 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4749 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4750 EL_EM_STEEL_EXIT_CLOSING);
4752 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4755 // player disappears
4756 DrawLevelField(x, y);
4759 for (i = 0; i < MAX_PLAYERS; i++)
4761 struct PlayerInfo *player = &stored_player[i];
4763 if (player->present)
4765 RemovePlayer(player);
4767 // player disappears
4768 DrawLevelField(player->jx, player->jy);
4773 PlaySound(SND_GAME_WINNING);
4776 if (game_over_delay_1 > 0)
4778 game_over_delay_1--;
4783 if (time != time_final)
4785 int time_to_go = ABS(time_final - time);
4786 int time_count_dir = (time < time_final ? +1 : -1);
4788 if (time_to_go < time_count_steps)
4789 time_count_steps = 1;
4791 time += time_count_steps * time_count_dir;
4792 score += time_count_steps * level.score[SC_TIME_BONUS];
4794 game.LevelSolved_CountingTime = time;
4795 game.LevelSolved_CountingScore = score;
4797 game_panel_controls[GAME_PANEL_TIME].value = time;
4798 game_panel_controls[GAME_PANEL_SCORE].value = score;
4800 DisplayGameControlValues();
4802 if (time == time_final)
4803 StopSound(SND_GAME_LEVELTIME_BONUS);
4804 else if (setup.sound_loops)
4805 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4807 PlaySound(SND_GAME_LEVELTIME_BONUS);
4812 if (game_over_delay_2 > 0)
4814 game_over_delay_2--;
4819 if (health != health_final)
4821 int health_count_dir = (health < health_final ? +1 : -1);
4823 health += health_count_dir;
4824 score += level.score[SC_TIME_BONUS];
4826 game.LevelSolved_CountingHealth = health;
4827 game.LevelSolved_CountingScore = score;
4829 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4830 game_panel_controls[GAME_PANEL_SCORE].value = score;
4832 DisplayGameControlValues();
4834 if (health == health_final)
4835 StopSound(SND_GAME_LEVELTIME_BONUS);
4836 else if (setup.sound_loops)
4837 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4839 PlaySound(SND_GAME_LEVELTIME_BONUS);
4844 game.panel.active = FALSE;
4846 if (game_over_delay_3 > 0)
4848 game_over_delay_3--;
4858 // used instead of "level_nr" (needed for network games)
4859 int last_level_nr = levelset.level_nr;
4862 game.LevelSolved_GameEnd = TRUE;
4864 if (game.LevelSolved_SaveTape)
4866 // make sure that request dialog to save tape does not open door again
4867 if (!global.use_envelope_request)
4868 CloseDoor(DOOR_CLOSE_1);
4870 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4873 // if no tape is to be saved, close both doors simultaneously
4874 CloseDoor(DOOR_CLOSE_ALL);
4876 if (level_editor_test_game)
4878 SetGameStatus(GAME_MODE_MAIN);
4885 if (!game.LevelSolved_SaveScore)
4887 SetGameStatus(GAME_MODE_MAIN);
4894 if (level_nr == leveldir_current->handicap_level)
4896 leveldir_current->handicap_level++;
4898 SaveLevelSetup_SeriesInfo();
4901 if (setup.increment_levels &&
4902 level_nr < leveldir_current->last_level &&
4905 level_nr++; // advance to next level
4906 TapeErase(); // start with empty tape
4908 if (setup.auto_play_next_level)
4910 LoadLevel(level_nr);
4912 SaveLevelSetup_SeriesInfo();
4916 hi_pos = NewHiScore(last_level_nr);
4918 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4920 SetGameStatus(GAME_MODE_SCORES);
4922 DrawHallOfFame(last_level_nr, hi_pos);
4924 else if (setup.auto_play_next_level && setup.increment_levels &&
4925 last_level_nr < leveldir_current->last_level &&
4928 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4932 SetGameStatus(GAME_MODE_MAIN);
4938 int NewHiScore(int level_nr)
4942 boolean one_score_entry_per_name = !program.many_scores_per_name;
4944 LoadScore(level_nr);
4946 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4947 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4950 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4952 if (game.score_final > highscore[k].Score)
4954 // player has made it to the hall of fame
4956 if (k < MAX_SCORE_ENTRIES - 1)
4958 int m = MAX_SCORE_ENTRIES - 1;
4960 if (one_score_entry_per_name)
4962 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4963 if (strEqual(setup.player_name, highscore[l].Name))
4966 if (m == k) // player's new highscore overwrites his old one
4970 for (l = m; l > k; l--)
4972 strcpy(highscore[l].Name, highscore[l - 1].Name);
4973 highscore[l].Score = highscore[l - 1].Score;
4979 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4980 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4981 highscore[k].Score = game.score_final;
4986 else if (one_score_entry_per_name &&
4987 !strncmp(setup.player_name, highscore[k].Name,
4988 MAX_PLAYER_NAME_LEN))
4989 break; // player already there with a higher score
4993 SaveScore(level_nr);
4998 static int getElementMoveStepsizeExt(int x, int y, int direction)
5000 int element = Tile[x][y];
5001 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5002 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5003 int horiz_move = (dx != 0);
5004 int sign = (horiz_move ? dx : dy);
5005 int step = sign * element_info[element].move_stepsize;
5007 // special values for move stepsize for spring and things on conveyor belt
5010 if (CAN_FALL(element) &&
5011 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5012 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5013 else if (element == EL_SPRING)
5014 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5020 static int getElementMoveStepsize(int x, int y)
5022 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5025 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5027 if (player->GfxAction != action || player->GfxDir != dir)
5029 player->GfxAction = action;
5030 player->GfxDir = dir;
5032 player->StepFrame = 0;
5036 static void ResetGfxFrame(int x, int y)
5038 // profiling showed that "autotest" spends 10~20% of its time in this function
5039 if (DrawingDeactivatedField())
5042 int element = Tile[x][y];
5043 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5045 if (graphic_info[graphic].anim_global_sync)
5046 GfxFrame[x][y] = FrameCounter;
5047 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5048 GfxFrame[x][y] = CustomValue[x][y];
5049 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5050 GfxFrame[x][y] = element_info[element].collect_score;
5051 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5052 GfxFrame[x][y] = ChangeDelay[x][y];
5055 static void ResetGfxAnimation(int x, int y)
5057 GfxAction[x][y] = ACTION_DEFAULT;
5058 GfxDir[x][y] = MovDir[x][y];
5061 ResetGfxFrame(x, y);
5064 static void ResetRandomAnimationValue(int x, int y)
5066 GfxRandom[x][y] = INIT_GFX_RANDOM();
5069 static void InitMovingField(int x, int y, int direction)
5071 int element = Tile[x][y];
5072 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5073 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5076 boolean is_moving_before, is_moving_after;
5078 // check if element was/is moving or being moved before/after mode change
5079 is_moving_before = (WasJustMoving[x][y] != 0);
5080 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5082 // reset animation only for moving elements which change direction of moving
5083 // or which just started or stopped moving
5084 // (else CEs with property "can move" / "not moving" are reset each frame)
5085 if (is_moving_before != is_moving_after ||
5086 direction != MovDir[x][y])
5087 ResetGfxAnimation(x, y);
5089 MovDir[x][y] = direction;
5090 GfxDir[x][y] = direction;
5092 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5093 direction == MV_DOWN && CAN_FALL(element) ?
5094 ACTION_FALLING : ACTION_MOVING);
5096 // this is needed for CEs with property "can move" / "not moving"
5098 if (is_moving_after)
5100 if (Tile[newx][newy] == EL_EMPTY)
5101 Tile[newx][newy] = EL_BLOCKED;
5103 MovDir[newx][newy] = MovDir[x][y];
5105 CustomValue[newx][newy] = CustomValue[x][y];
5107 GfxFrame[newx][newy] = GfxFrame[x][y];
5108 GfxRandom[newx][newy] = GfxRandom[x][y];
5109 GfxAction[newx][newy] = GfxAction[x][y];
5110 GfxDir[newx][newy] = GfxDir[x][y];
5114 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5116 int direction = MovDir[x][y];
5117 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5118 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5124 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5126 int oldx = x, oldy = y;
5127 int direction = MovDir[x][y];
5129 if (direction == MV_LEFT)
5131 else if (direction == MV_RIGHT)
5133 else if (direction == MV_UP)
5135 else if (direction == MV_DOWN)
5138 *comes_from_x = oldx;
5139 *comes_from_y = oldy;
5142 static int MovingOrBlocked2Element(int x, int y)
5144 int element = Tile[x][y];
5146 if (element == EL_BLOCKED)
5150 Blocked2Moving(x, y, &oldx, &oldy);
5151 return Tile[oldx][oldy];
5157 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5159 // like MovingOrBlocked2Element(), but if element is moving
5160 // and (x,y) is the field the moving element is just leaving,
5161 // return EL_BLOCKED instead of the element value
5162 int element = Tile[x][y];
5164 if (IS_MOVING(x, y))
5166 if (element == EL_BLOCKED)
5170 Blocked2Moving(x, y, &oldx, &oldy);
5171 return Tile[oldx][oldy];
5180 static void RemoveField(int x, int y)
5182 Tile[x][y] = EL_EMPTY;
5188 CustomValue[x][y] = 0;
5191 ChangeDelay[x][y] = 0;
5192 ChangePage[x][y] = -1;
5193 Pushed[x][y] = FALSE;
5195 GfxElement[x][y] = EL_UNDEFINED;
5196 GfxAction[x][y] = ACTION_DEFAULT;
5197 GfxDir[x][y] = MV_NONE;
5200 static void RemoveMovingField(int x, int y)
5202 int oldx = x, oldy = y, newx = x, newy = y;
5203 int element = Tile[x][y];
5204 int next_element = EL_UNDEFINED;
5206 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5209 if (IS_MOVING(x, y))
5211 Moving2Blocked(x, y, &newx, &newy);
5213 if (Tile[newx][newy] != EL_BLOCKED)
5215 // element is moving, but target field is not free (blocked), but
5216 // already occupied by something different (example: acid pool);
5217 // in this case, only remove the moving field, but not the target
5219 RemoveField(oldx, oldy);
5221 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5223 TEST_DrawLevelField(oldx, oldy);
5228 else if (element == EL_BLOCKED)
5230 Blocked2Moving(x, y, &oldx, &oldy);
5231 if (!IS_MOVING(oldx, oldy))
5235 if (element == EL_BLOCKED &&
5236 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5237 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5238 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5239 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5240 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5241 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5242 next_element = get_next_element(Tile[oldx][oldy]);
5244 RemoveField(oldx, oldy);
5245 RemoveField(newx, newy);
5247 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5249 if (next_element != EL_UNDEFINED)
5250 Tile[oldx][oldy] = next_element;
5252 TEST_DrawLevelField(oldx, oldy);
5253 TEST_DrawLevelField(newx, newy);
5256 void DrawDynamite(int x, int y)
5258 int sx = SCREENX(x), sy = SCREENY(y);
5259 int graphic = el2img(Tile[x][y]);
5262 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5265 if (IS_WALKABLE_INSIDE(Back[x][y]))
5269 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5270 else if (Store[x][y])
5271 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5273 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5275 if (Back[x][y] || Store[x][y])
5276 DrawGraphicThruMask(sx, sy, graphic, frame);
5278 DrawGraphic(sx, sy, graphic, frame);
5281 static void CheckDynamite(int x, int y)
5283 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5287 if (MovDelay[x][y] != 0)
5290 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5296 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5301 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5303 boolean num_checked_players = 0;
5306 for (i = 0; i < MAX_PLAYERS; i++)
5308 if (stored_player[i].active)
5310 int sx = stored_player[i].jx;
5311 int sy = stored_player[i].jy;
5313 if (num_checked_players == 0)
5320 *sx1 = MIN(*sx1, sx);
5321 *sy1 = MIN(*sy1, sy);
5322 *sx2 = MAX(*sx2, sx);
5323 *sy2 = MAX(*sy2, sy);
5326 num_checked_players++;
5331 static boolean checkIfAllPlayersFitToScreen_RND(void)
5333 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5335 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5337 return (sx2 - sx1 < SCR_FIELDX &&
5338 sy2 - sy1 < SCR_FIELDY);
5341 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5343 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5345 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5347 *sx = (sx1 + sx2) / 2;
5348 *sy = (sy1 + sy2) / 2;
5351 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5352 boolean center_screen, boolean quick_relocation)
5354 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5355 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5356 boolean no_delay = (tape.warp_forward);
5357 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5358 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5359 int new_scroll_x, new_scroll_y;
5361 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5363 // case 1: quick relocation inside visible screen (without scrolling)
5370 if (!level.shifted_relocation || center_screen)
5372 // relocation _with_ centering of screen
5374 new_scroll_x = SCROLL_POSITION_X(x);
5375 new_scroll_y = SCROLL_POSITION_Y(y);
5379 // relocation _without_ centering of screen
5381 int center_scroll_x = SCROLL_POSITION_X(old_x);
5382 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5383 int offset_x = x + (scroll_x - center_scroll_x);
5384 int offset_y = y + (scroll_y - center_scroll_y);
5386 // for new screen position, apply previous offset to center position
5387 new_scroll_x = SCROLL_POSITION_X(offset_x);
5388 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5391 if (quick_relocation)
5393 // case 2: quick relocation (redraw without visible scrolling)
5395 scroll_x = new_scroll_x;
5396 scroll_y = new_scroll_y;
5403 // case 3: visible relocation (with scrolling to new position)
5405 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5407 SetVideoFrameDelay(wait_delay_value);
5409 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5411 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5412 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5414 if (dx == 0 && dy == 0) // no scrolling needed at all
5420 // set values for horizontal/vertical screen scrolling (half tile size)
5421 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5422 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5423 int pos_x = dx * TILEX / 2;
5424 int pos_y = dy * TILEY / 2;
5425 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5426 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5428 ScrollLevel(dx, dy);
5431 // scroll in two steps of half tile size to make things smoother
5432 BlitScreenToBitmapExt_RND(window, fx, fy);
5434 // scroll second step to align at full tile size
5435 BlitScreenToBitmap(window);
5441 SetVideoFrameDelay(frame_delay_value_old);
5444 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5446 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5447 int player_nr = GET_PLAYER_NR(el_player);
5448 struct PlayerInfo *player = &stored_player[player_nr];
5449 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5450 boolean no_delay = (tape.warp_forward);
5451 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5452 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5453 int old_jx = player->jx;
5454 int old_jy = player->jy;
5455 int old_element = Tile[old_jx][old_jy];
5456 int element = Tile[jx][jy];
5457 boolean player_relocated = (old_jx != jx || old_jy != jy);
5459 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5460 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5461 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5462 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5463 int leave_side_horiz = move_dir_horiz;
5464 int leave_side_vert = move_dir_vert;
5465 int enter_side = enter_side_horiz | enter_side_vert;
5466 int leave_side = leave_side_horiz | leave_side_vert;
5468 if (player->buried) // do not reanimate dead player
5471 if (!player_relocated) // no need to relocate the player
5474 if (IS_PLAYER(jx, jy)) // player already placed at new position
5476 RemoveField(jx, jy); // temporarily remove newly placed player
5477 DrawLevelField(jx, jy);
5480 if (player->present)
5482 while (player->MovPos)
5484 ScrollPlayer(player, SCROLL_GO_ON);
5485 ScrollScreen(NULL, SCROLL_GO_ON);
5487 AdvanceFrameAndPlayerCounters(player->index_nr);
5491 BackToFront_WithFrameDelay(wait_delay_value);
5494 DrawPlayer(player); // needed here only to cleanup last field
5495 DrawLevelField(player->jx, player->jy); // remove player graphic
5497 player->is_moving = FALSE;
5500 if (IS_CUSTOM_ELEMENT(old_element))
5501 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5503 player->index_bit, leave_side);
5505 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5507 player->index_bit, leave_side);
5509 Tile[jx][jy] = el_player;
5510 InitPlayerField(jx, jy, el_player, TRUE);
5512 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5513 possible that the relocation target field did not contain a player element,
5514 but a walkable element, to which the new player was relocated -- in this
5515 case, restore that (already initialized!) element on the player field */
5516 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5518 Tile[jx][jy] = element; // restore previously existing element
5521 // only visually relocate centered player
5522 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5523 FALSE, level.instant_relocation);
5525 TestIfPlayerTouchesBadThing(jx, jy);
5526 TestIfPlayerTouchesCustomElement(jx, jy);
5528 if (IS_CUSTOM_ELEMENT(element))
5529 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5530 player->index_bit, enter_side);
5532 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5533 player->index_bit, enter_side);
5535 if (player->is_switching)
5537 /* ensure that relocation while still switching an element does not cause
5538 a new element to be treated as also switched directly after relocation
5539 (this is important for teleporter switches that teleport the player to
5540 a place where another teleporter switch is in the same direction, which
5541 would then incorrectly be treated as immediately switched before the
5542 direction key that caused the switch was released) */
5544 player->switch_x += jx - old_jx;
5545 player->switch_y += jy - old_jy;
5549 static void Explode(int ex, int ey, int phase, int mode)
5555 // !!! eliminate this variable !!!
5556 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5558 if (game.explosions_delayed)
5560 ExplodeField[ex][ey] = mode;
5564 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5566 int center_element = Tile[ex][ey];
5567 int artwork_element, explosion_element; // set these values later
5569 // remove things displayed in background while burning dynamite
5570 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5573 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5575 // put moving element to center field (and let it explode there)
5576 center_element = MovingOrBlocked2Element(ex, ey);
5577 RemoveMovingField(ex, ey);
5578 Tile[ex][ey] = center_element;
5581 // now "center_element" is finally determined -- set related values now
5582 artwork_element = center_element; // for custom player artwork
5583 explosion_element = center_element; // for custom player artwork
5585 if (IS_PLAYER(ex, ey))
5587 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5589 artwork_element = stored_player[player_nr].artwork_element;
5591 if (level.use_explosion_element[player_nr])
5593 explosion_element = level.explosion_element[player_nr];
5594 artwork_element = explosion_element;
5598 if (mode == EX_TYPE_NORMAL ||
5599 mode == EX_TYPE_CENTER ||
5600 mode == EX_TYPE_CROSS)
5601 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5603 last_phase = element_info[explosion_element].explosion_delay + 1;
5605 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5607 int xx = x - ex + 1;
5608 int yy = y - ey + 1;
5611 if (!IN_LEV_FIELD(x, y) ||
5612 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5613 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5616 element = Tile[x][y];
5618 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5620 element = MovingOrBlocked2Element(x, y);
5622 if (!IS_EXPLOSION_PROOF(element))
5623 RemoveMovingField(x, y);
5626 // indestructible elements can only explode in center (but not flames)
5627 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5628 mode == EX_TYPE_BORDER)) ||
5629 element == EL_FLAMES)
5632 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5633 behaviour, for example when touching a yamyam that explodes to rocks
5634 with active deadly shield, a rock is created under the player !!! */
5635 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5637 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5638 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5639 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5641 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5644 if (IS_ACTIVE_BOMB(element))
5646 // re-activate things under the bomb like gate or penguin
5647 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5654 // save walkable background elements while explosion on same tile
5655 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5656 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5657 Back[x][y] = element;
5659 // ignite explodable elements reached by other explosion
5660 if (element == EL_EXPLOSION)
5661 element = Store2[x][y];
5663 if (AmoebaNr[x][y] &&
5664 (element == EL_AMOEBA_FULL ||
5665 element == EL_BD_AMOEBA ||
5666 element == EL_AMOEBA_GROWING))
5668 AmoebaCnt[AmoebaNr[x][y]]--;
5669 AmoebaCnt2[AmoebaNr[x][y]]--;
5674 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5676 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5678 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5680 if (PLAYERINFO(ex, ey)->use_murphy)
5681 Store[x][y] = EL_EMPTY;
5684 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5685 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5686 else if (ELEM_IS_PLAYER(center_element))
5687 Store[x][y] = EL_EMPTY;
5688 else if (center_element == EL_YAMYAM)
5689 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5690 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5691 Store[x][y] = element_info[center_element].content.e[xx][yy];
5693 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5694 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5695 // otherwise) -- FIX THIS !!!
5696 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5697 Store[x][y] = element_info[element].content.e[1][1];
5699 else if (!CAN_EXPLODE(element))
5700 Store[x][y] = element_info[element].content.e[1][1];
5703 Store[x][y] = EL_EMPTY;
5705 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5706 center_element == EL_AMOEBA_TO_DIAMOND)
5707 Store2[x][y] = element;
5709 Tile[x][y] = EL_EXPLOSION;
5710 GfxElement[x][y] = artwork_element;
5712 ExplodePhase[x][y] = 1;
5713 ExplodeDelay[x][y] = last_phase;
5718 if (center_element == EL_YAMYAM)
5719 game.yamyam_content_nr =
5720 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5732 GfxFrame[x][y] = 0; // restart explosion animation
5734 last_phase = ExplodeDelay[x][y];
5736 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5738 // this can happen if the player leaves an explosion just in time
5739 if (GfxElement[x][y] == EL_UNDEFINED)
5740 GfxElement[x][y] = EL_EMPTY;
5742 border_element = Store2[x][y];
5743 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5744 border_element = StorePlayer[x][y];
5746 if (phase == element_info[border_element].ignition_delay ||
5747 phase == last_phase)
5749 boolean border_explosion = FALSE;
5751 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5752 !PLAYER_EXPLOSION_PROTECTED(x, y))
5754 KillPlayerUnlessExplosionProtected(x, y);
5755 border_explosion = TRUE;
5757 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5759 Tile[x][y] = Store2[x][y];
5762 border_explosion = TRUE;
5764 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5766 AmoebaToDiamond(x, y);
5768 border_explosion = TRUE;
5771 // if an element just explodes due to another explosion (chain-reaction),
5772 // do not immediately end the new explosion when it was the last frame of
5773 // the explosion (as it would be done in the following "if"-statement!)
5774 if (border_explosion && phase == last_phase)
5778 if (phase == last_phase)
5782 element = Tile[x][y] = Store[x][y];
5783 Store[x][y] = Store2[x][y] = 0;
5784 GfxElement[x][y] = EL_UNDEFINED;
5786 // player can escape from explosions and might therefore be still alive
5787 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5788 element <= EL_PLAYER_IS_EXPLODING_4)
5790 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5791 int explosion_element = EL_PLAYER_1 + player_nr;
5792 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5793 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5795 if (level.use_explosion_element[player_nr])
5796 explosion_element = level.explosion_element[player_nr];
5798 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5799 element_info[explosion_element].content.e[xx][yy]);
5802 // restore probably existing indestructible background element
5803 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5804 element = Tile[x][y] = Back[x][y];
5807 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5808 GfxDir[x][y] = MV_NONE;
5809 ChangeDelay[x][y] = 0;
5810 ChangePage[x][y] = -1;
5812 CustomValue[x][y] = 0;
5814 InitField_WithBug2(x, y, FALSE);
5816 TEST_DrawLevelField(x, y);
5818 TestIfElementTouchesCustomElement(x, y);
5820 if (GFX_CRUMBLED(element))
5821 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5823 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5824 StorePlayer[x][y] = 0;
5826 if (ELEM_IS_PLAYER(element))
5827 RelocatePlayer(x, y, element);
5829 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5831 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5832 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5835 TEST_DrawLevelFieldCrumbled(x, y);
5837 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5839 DrawLevelElement(x, y, Back[x][y]);
5840 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5842 else if (IS_WALKABLE_UNDER(Back[x][y]))
5844 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5845 DrawLevelElementThruMask(x, y, Back[x][y]);
5847 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5848 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5852 static void DynaExplode(int ex, int ey)
5855 int dynabomb_element = Tile[ex][ey];
5856 int dynabomb_size = 1;
5857 boolean dynabomb_xl = FALSE;
5858 struct PlayerInfo *player;
5859 static int xy[4][2] =
5867 if (IS_ACTIVE_BOMB(dynabomb_element))
5869 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5870 dynabomb_size = player->dynabomb_size;
5871 dynabomb_xl = player->dynabomb_xl;
5872 player->dynabombs_left++;
5875 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5877 for (i = 0; i < NUM_DIRECTIONS; i++)
5879 for (j = 1; j <= dynabomb_size; j++)
5881 int x = ex + j * xy[i][0];
5882 int y = ey + j * xy[i][1];
5885 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5888 element = Tile[x][y];
5890 // do not restart explosions of fields with active bombs
5891 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5894 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5896 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5897 !IS_DIGGABLE(element) && !dynabomb_xl)
5903 void Bang(int x, int y)
5905 int element = MovingOrBlocked2Element(x, y);
5906 int explosion_type = EX_TYPE_NORMAL;
5908 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5910 struct PlayerInfo *player = PLAYERINFO(x, y);
5912 element = Tile[x][y] = player->initial_element;
5914 if (level.use_explosion_element[player->index_nr])
5916 int explosion_element = level.explosion_element[player->index_nr];
5918 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5919 explosion_type = EX_TYPE_CROSS;
5920 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5921 explosion_type = EX_TYPE_CENTER;
5929 case EL_BD_BUTTERFLY:
5932 case EL_DARK_YAMYAM:
5936 RaiseScoreElement(element);
5939 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5940 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5941 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5942 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5943 case EL_DYNABOMB_INCREASE_NUMBER:
5944 case EL_DYNABOMB_INCREASE_SIZE:
5945 case EL_DYNABOMB_INCREASE_POWER:
5946 explosion_type = EX_TYPE_DYNA;
5949 case EL_DC_LANDMINE:
5950 explosion_type = EX_TYPE_CENTER;
5955 case EL_LAMP_ACTIVE:
5956 case EL_AMOEBA_TO_DIAMOND:
5957 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5958 explosion_type = EX_TYPE_CENTER;
5962 if (element_info[element].explosion_type == EXPLODES_CROSS)
5963 explosion_type = EX_TYPE_CROSS;
5964 else if (element_info[element].explosion_type == EXPLODES_1X1)
5965 explosion_type = EX_TYPE_CENTER;
5969 if (explosion_type == EX_TYPE_DYNA)
5972 Explode(x, y, EX_PHASE_START, explosion_type);
5974 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5977 static void SplashAcid(int x, int y)
5979 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5980 (!IN_LEV_FIELD(x - 1, y - 2) ||
5981 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5982 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5984 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5985 (!IN_LEV_FIELD(x + 1, y - 2) ||
5986 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5987 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5989 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5992 static void InitBeltMovement(void)
5994 static int belt_base_element[4] =
5996 EL_CONVEYOR_BELT_1_LEFT,
5997 EL_CONVEYOR_BELT_2_LEFT,
5998 EL_CONVEYOR_BELT_3_LEFT,
5999 EL_CONVEYOR_BELT_4_LEFT
6001 static int belt_base_active_element[4] =
6003 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6004 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6005 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6006 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6011 // set frame order for belt animation graphic according to belt direction
6012 for (i = 0; i < NUM_BELTS; i++)
6016 for (j = 0; j < NUM_BELT_PARTS; j++)
6018 int element = belt_base_active_element[belt_nr] + j;
6019 int graphic_1 = el2img(element);
6020 int graphic_2 = el2panelimg(element);
6022 if (game.belt_dir[i] == MV_LEFT)
6024 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6025 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6029 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6030 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6035 SCAN_PLAYFIELD(x, y)
6037 int element = Tile[x][y];
6039 for (i = 0; i < NUM_BELTS; i++)
6041 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6043 int e_belt_nr = getBeltNrFromBeltElement(element);
6046 if (e_belt_nr == belt_nr)
6048 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6050 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6057 static void ToggleBeltSwitch(int x, int y)
6059 static int belt_base_element[4] =
6061 EL_CONVEYOR_BELT_1_LEFT,
6062 EL_CONVEYOR_BELT_2_LEFT,
6063 EL_CONVEYOR_BELT_3_LEFT,
6064 EL_CONVEYOR_BELT_4_LEFT
6066 static int belt_base_active_element[4] =
6068 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6069 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6070 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6071 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6073 static int belt_base_switch_element[4] =
6075 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6076 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6077 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6078 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6080 static int belt_move_dir[4] =
6088 int element = Tile[x][y];
6089 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6090 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6091 int belt_dir = belt_move_dir[belt_dir_nr];
6094 if (!IS_BELT_SWITCH(element))
6097 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6098 game.belt_dir[belt_nr] = belt_dir;
6100 if (belt_dir_nr == 3)
6103 // set frame order for belt animation graphic according to belt direction
6104 for (i = 0; i < NUM_BELT_PARTS; i++)
6106 int element = belt_base_active_element[belt_nr] + i;
6107 int graphic_1 = el2img(element);
6108 int graphic_2 = el2panelimg(element);
6110 if (belt_dir == MV_LEFT)
6112 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6113 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6117 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6118 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6122 SCAN_PLAYFIELD(xx, yy)
6124 int element = Tile[xx][yy];
6126 if (IS_BELT_SWITCH(element))
6128 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6130 if (e_belt_nr == belt_nr)
6132 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6133 TEST_DrawLevelField(xx, yy);
6136 else if (IS_BELT(element) && belt_dir != MV_NONE)
6138 int e_belt_nr = getBeltNrFromBeltElement(element);
6140 if (e_belt_nr == belt_nr)
6142 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6144 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6145 TEST_DrawLevelField(xx, yy);
6148 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6150 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6152 if (e_belt_nr == belt_nr)
6154 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6156 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6157 TEST_DrawLevelField(xx, yy);
6163 static void ToggleSwitchgateSwitch(int x, int y)
6167 game.switchgate_pos = !game.switchgate_pos;
6169 SCAN_PLAYFIELD(xx, yy)
6171 int element = Tile[xx][yy];
6173 if (element == EL_SWITCHGATE_SWITCH_UP)
6175 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6176 TEST_DrawLevelField(xx, yy);
6178 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6180 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6181 TEST_DrawLevelField(xx, yy);
6183 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6185 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6186 TEST_DrawLevelField(xx, yy);
6188 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6190 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6191 TEST_DrawLevelField(xx, yy);
6193 else if (element == EL_SWITCHGATE_OPEN ||
6194 element == EL_SWITCHGATE_OPENING)
6196 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6198 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6200 else if (element == EL_SWITCHGATE_CLOSED ||
6201 element == EL_SWITCHGATE_CLOSING)
6203 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6205 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6210 static int getInvisibleActiveFromInvisibleElement(int element)
6212 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6213 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6214 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6218 static int getInvisibleFromInvisibleActiveElement(int element)
6220 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6221 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6222 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6226 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6230 SCAN_PLAYFIELD(x, y)
6232 int element = Tile[x][y];
6234 if (element == EL_LIGHT_SWITCH &&
6235 game.light_time_left > 0)
6237 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6238 TEST_DrawLevelField(x, y);
6240 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6241 game.light_time_left == 0)
6243 Tile[x][y] = EL_LIGHT_SWITCH;
6244 TEST_DrawLevelField(x, y);
6246 else if (element == EL_EMC_DRIPPER &&
6247 game.light_time_left > 0)
6249 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6250 TEST_DrawLevelField(x, y);
6252 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6253 game.light_time_left == 0)
6255 Tile[x][y] = EL_EMC_DRIPPER;
6256 TEST_DrawLevelField(x, y);
6258 else if (element == EL_INVISIBLE_STEELWALL ||
6259 element == EL_INVISIBLE_WALL ||
6260 element == EL_INVISIBLE_SAND)
6262 if (game.light_time_left > 0)
6263 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6265 TEST_DrawLevelField(x, y);
6267 // uncrumble neighbour fields, if needed
6268 if (element == EL_INVISIBLE_SAND)
6269 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6271 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6272 element == EL_INVISIBLE_WALL_ACTIVE ||
6273 element == EL_INVISIBLE_SAND_ACTIVE)
6275 if (game.light_time_left == 0)
6276 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6278 TEST_DrawLevelField(x, y);
6280 // re-crumble neighbour fields, if needed
6281 if (element == EL_INVISIBLE_SAND)
6282 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6287 static void RedrawAllInvisibleElementsForLenses(void)
6291 SCAN_PLAYFIELD(x, y)
6293 int element = Tile[x][y];
6295 if (element == EL_EMC_DRIPPER &&
6296 game.lenses_time_left > 0)
6298 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6299 TEST_DrawLevelField(x, y);
6301 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6302 game.lenses_time_left == 0)
6304 Tile[x][y] = EL_EMC_DRIPPER;
6305 TEST_DrawLevelField(x, y);
6307 else if (element == EL_INVISIBLE_STEELWALL ||
6308 element == EL_INVISIBLE_WALL ||
6309 element == EL_INVISIBLE_SAND)
6311 if (game.lenses_time_left > 0)
6312 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6314 TEST_DrawLevelField(x, y);
6316 // uncrumble neighbour fields, if needed
6317 if (element == EL_INVISIBLE_SAND)
6318 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6320 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6321 element == EL_INVISIBLE_WALL_ACTIVE ||
6322 element == EL_INVISIBLE_SAND_ACTIVE)
6324 if (game.lenses_time_left == 0)
6325 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6327 TEST_DrawLevelField(x, y);
6329 // re-crumble neighbour fields, if needed
6330 if (element == EL_INVISIBLE_SAND)
6331 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6336 static void RedrawAllInvisibleElementsForMagnifier(void)
6340 SCAN_PLAYFIELD(x, y)
6342 int element = Tile[x][y];
6344 if (element == EL_EMC_FAKE_GRASS &&
6345 game.magnify_time_left > 0)
6347 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6348 TEST_DrawLevelField(x, y);
6350 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6351 game.magnify_time_left == 0)
6353 Tile[x][y] = EL_EMC_FAKE_GRASS;
6354 TEST_DrawLevelField(x, y);
6356 else if (IS_GATE_GRAY(element) &&
6357 game.magnify_time_left > 0)
6359 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6360 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6361 IS_EM_GATE_GRAY(element) ?
6362 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6363 IS_EMC_GATE_GRAY(element) ?
6364 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6365 IS_DC_GATE_GRAY(element) ?
6366 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6368 TEST_DrawLevelField(x, y);
6370 else if (IS_GATE_GRAY_ACTIVE(element) &&
6371 game.magnify_time_left == 0)
6373 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6374 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6375 IS_EM_GATE_GRAY_ACTIVE(element) ?
6376 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6377 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6378 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6379 IS_DC_GATE_GRAY_ACTIVE(element) ?
6380 EL_DC_GATE_WHITE_GRAY :
6382 TEST_DrawLevelField(x, y);
6387 static void ToggleLightSwitch(int x, int y)
6389 int element = Tile[x][y];
6391 game.light_time_left =
6392 (element == EL_LIGHT_SWITCH ?
6393 level.time_light * FRAMES_PER_SECOND : 0);
6395 RedrawAllLightSwitchesAndInvisibleElements();
6398 static void ActivateTimegateSwitch(int x, int y)
6402 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6404 SCAN_PLAYFIELD(xx, yy)
6406 int element = Tile[xx][yy];
6408 if (element == EL_TIMEGATE_CLOSED ||
6409 element == EL_TIMEGATE_CLOSING)
6411 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6412 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6416 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6418 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6419 TEST_DrawLevelField(xx, yy);
6425 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6426 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6429 static void Impact(int x, int y)
6431 boolean last_line = (y == lev_fieldy - 1);
6432 boolean object_hit = FALSE;
6433 boolean impact = (last_line || object_hit);
6434 int element = Tile[x][y];
6435 int smashed = EL_STEELWALL;
6437 if (!last_line) // check if element below was hit
6439 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6442 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6443 MovDir[x][y + 1] != MV_DOWN ||
6444 MovPos[x][y + 1] <= TILEY / 2));
6446 // do not smash moving elements that left the smashed field in time
6447 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6448 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6451 #if USE_QUICKSAND_IMPACT_BUGFIX
6452 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6454 RemoveMovingField(x, y + 1);
6455 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6456 Tile[x][y + 2] = EL_ROCK;
6457 TEST_DrawLevelField(x, y + 2);
6462 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6464 RemoveMovingField(x, y + 1);
6465 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6466 Tile[x][y + 2] = EL_ROCK;
6467 TEST_DrawLevelField(x, y + 2);
6474 smashed = MovingOrBlocked2Element(x, y + 1);
6476 impact = (last_line || object_hit);
6479 if (!last_line && smashed == EL_ACID) // element falls into acid
6481 SplashAcid(x, y + 1);
6485 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6486 // only reset graphic animation if graphic really changes after impact
6488 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6490 ResetGfxAnimation(x, y);
6491 TEST_DrawLevelField(x, y);
6494 if (impact && CAN_EXPLODE_IMPACT(element))
6499 else if (impact && element == EL_PEARL &&
6500 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6502 ResetGfxAnimation(x, y);
6504 Tile[x][y] = EL_PEARL_BREAKING;
6505 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6508 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6510 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6515 if (impact && element == EL_AMOEBA_DROP)
6517 if (object_hit && IS_PLAYER(x, y + 1))
6518 KillPlayerUnlessEnemyProtected(x, y + 1);
6519 else if (object_hit && smashed == EL_PENGUIN)
6523 Tile[x][y] = EL_AMOEBA_GROWING;
6524 Store[x][y] = EL_AMOEBA_WET;
6526 ResetRandomAnimationValue(x, y);
6531 if (object_hit) // check which object was hit
6533 if ((CAN_PASS_MAGIC_WALL(element) &&
6534 (smashed == EL_MAGIC_WALL ||
6535 smashed == EL_BD_MAGIC_WALL)) ||
6536 (CAN_PASS_DC_MAGIC_WALL(element) &&
6537 smashed == EL_DC_MAGIC_WALL))
6540 int activated_magic_wall =
6541 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6542 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6543 EL_DC_MAGIC_WALL_ACTIVE);
6545 // activate magic wall / mill
6546 SCAN_PLAYFIELD(xx, yy)
6548 if (Tile[xx][yy] == smashed)
6549 Tile[xx][yy] = activated_magic_wall;
6552 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6553 game.magic_wall_active = TRUE;
6555 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6556 SND_MAGIC_WALL_ACTIVATING :
6557 smashed == EL_BD_MAGIC_WALL ?
6558 SND_BD_MAGIC_WALL_ACTIVATING :
6559 SND_DC_MAGIC_WALL_ACTIVATING));
6562 if (IS_PLAYER(x, y + 1))
6564 if (CAN_SMASH_PLAYER(element))
6566 KillPlayerUnlessEnemyProtected(x, y + 1);
6570 else if (smashed == EL_PENGUIN)
6572 if (CAN_SMASH_PLAYER(element))
6578 else if (element == EL_BD_DIAMOND)
6580 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6586 else if (((element == EL_SP_INFOTRON ||
6587 element == EL_SP_ZONK) &&
6588 (smashed == EL_SP_SNIKSNAK ||
6589 smashed == EL_SP_ELECTRON ||
6590 smashed == EL_SP_DISK_ORANGE)) ||
6591 (element == EL_SP_INFOTRON &&
6592 smashed == EL_SP_DISK_YELLOW))
6597 else if (CAN_SMASH_EVERYTHING(element))
6599 if (IS_CLASSIC_ENEMY(smashed) ||
6600 CAN_EXPLODE_SMASHED(smashed))
6605 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6607 if (smashed == EL_LAMP ||
6608 smashed == EL_LAMP_ACTIVE)
6613 else if (smashed == EL_NUT)
6615 Tile[x][y + 1] = EL_NUT_BREAKING;
6616 PlayLevelSound(x, y, SND_NUT_BREAKING);
6617 RaiseScoreElement(EL_NUT);
6620 else if (smashed == EL_PEARL)
6622 ResetGfxAnimation(x, y);
6624 Tile[x][y + 1] = EL_PEARL_BREAKING;
6625 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6628 else if (smashed == EL_DIAMOND)
6630 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6631 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6634 else if (IS_BELT_SWITCH(smashed))
6636 ToggleBeltSwitch(x, y + 1);
6638 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6639 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6640 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6641 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6643 ToggleSwitchgateSwitch(x, y + 1);
6645 else if (smashed == EL_LIGHT_SWITCH ||
6646 smashed == EL_LIGHT_SWITCH_ACTIVE)
6648 ToggleLightSwitch(x, y + 1);
6652 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6654 CheckElementChangeBySide(x, y + 1, smashed, element,
6655 CE_SWITCHED, CH_SIDE_TOP);
6656 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6662 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6667 // play sound of magic wall / mill
6669 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6670 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6671 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6673 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6674 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6675 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6676 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6677 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6678 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6683 // play sound of object that hits the ground
6684 if (last_line || object_hit)
6685 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6688 static void TurnRoundExt(int x, int y)
6700 { 0, 0 }, { 0, 0 }, { 0, 0 },
6705 int left, right, back;
6709 { MV_DOWN, MV_UP, MV_RIGHT },
6710 { MV_UP, MV_DOWN, MV_LEFT },
6712 { MV_LEFT, MV_RIGHT, MV_DOWN },
6716 { MV_RIGHT, MV_LEFT, MV_UP }
6719 int element = Tile[x][y];
6720 int move_pattern = element_info[element].move_pattern;
6722 int old_move_dir = MovDir[x][y];
6723 int left_dir = turn[old_move_dir].left;
6724 int right_dir = turn[old_move_dir].right;
6725 int back_dir = turn[old_move_dir].back;
6727 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6728 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6729 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6730 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6732 int left_x = x + left_dx, left_y = y + left_dy;
6733 int right_x = x + right_dx, right_y = y + right_dy;
6734 int move_x = x + move_dx, move_y = y + move_dy;
6738 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6740 TestIfBadThingTouchesOtherBadThing(x, y);
6742 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6743 MovDir[x][y] = right_dir;
6744 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6745 MovDir[x][y] = left_dir;
6747 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6749 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6752 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6754 TestIfBadThingTouchesOtherBadThing(x, y);
6756 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6757 MovDir[x][y] = left_dir;
6758 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6759 MovDir[x][y] = right_dir;
6761 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6763 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6766 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6768 TestIfBadThingTouchesOtherBadThing(x, y);
6770 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6771 MovDir[x][y] = left_dir;
6772 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6773 MovDir[x][y] = right_dir;
6775 if (MovDir[x][y] != old_move_dir)
6778 else if (element == EL_YAMYAM)
6780 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6781 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6783 if (can_turn_left && can_turn_right)
6784 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6785 else if (can_turn_left)
6786 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6787 else if (can_turn_right)
6788 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6790 MovDir[x][y] = back_dir;
6792 MovDelay[x][y] = 16 + 16 * RND(3);
6794 else if (element == EL_DARK_YAMYAM)
6796 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6798 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6801 if (can_turn_left && can_turn_right)
6802 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6803 else if (can_turn_left)
6804 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6805 else if (can_turn_right)
6806 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6808 MovDir[x][y] = back_dir;
6810 MovDelay[x][y] = 16 + 16 * RND(3);
6812 else if (element == EL_PACMAN)
6814 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6815 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6817 if (can_turn_left && can_turn_right)
6818 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6819 else if (can_turn_left)
6820 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6821 else if (can_turn_right)
6822 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6824 MovDir[x][y] = back_dir;
6826 MovDelay[x][y] = 6 + RND(40);
6828 else if (element == EL_PIG)
6830 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6831 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6832 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6833 boolean should_turn_left, should_turn_right, should_move_on;
6835 int rnd = RND(rnd_value);
6837 should_turn_left = (can_turn_left &&
6839 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6840 y + back_dy + left_dy)));
6841 should_turn_right = (can_turn_right &&
6843 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6844 y + back_dy + right_dy)));
6845 should_move_on = (can_move_on &&
6848 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6849 y + move_dy + left_dy) ||
6850 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6851 y + move_dy + right_dy)));
6853 if (should_turn_left || should_turn_right || should_move_on)
6855 if (should_turn_left && should_turn_right && should_move_on)
6856 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6857 rnd < 2 * rnd_value / 3 ? right_dir :
6859 else if (should_turn_left && should_turn_right)
6860 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6861 else if (should_turn_left && should_move_on)
6862 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6863 else if (should_turn_right && should_move_on)
6864 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6865 else if (should_turn_left)
6866 MovDir[x][y] = left_dir;
6867 else if (should_turn_right)
6868 MovDir[x][y] = right_dir;
6869 else if (should_move_on)
6870 MovDir[x][y] = old_move_dir;
6872 else if (can_move_on && rnd > rnd_value / 8)
6873 MovDir[x][y] = old_move_dir;
6874 else if (can_turn_left && can_turn_right)
6875 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6876 else if (can_turn_left && rnd > rnd_value / 8)
6877 MovDir[x][y] = left_dir;
6878 else if (can_turn_right && rnd > rnd_value/8)
6879 MovDir[x][y] = right_dir;
6881 MovDir[x][y] = back_dir;
6883 xx = x + move_xy[MovDir[x][y]].dx;
6884 yy = y + move_xy[MovDir[x][y]].dy;
6886 if (!IN_LEV_FIELD(xx, yy) ||
6887 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6888 MovDir[x][y] = old_move_dir;
6892 else if (element == EL_DRAGON)
6894 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6895 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6896 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6898 int rnd = RND(rnd_value);
6900 if (can_move_on && rnd > rnd_value / 8)
6901 MovDir[x][y] = old_move_dir;
6902 else if (can_turn_left && can_turn_right)
6903 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6904 else if (can_turn_left && rnd > rnd_value / 8)
6905 MovDir[x][y] = left_dir;
6906 else if (can_turn_right && rnd > rnd_value / 8)
6907 MovDir[x][y] = right_dir;
6909 MovDir[x][y] = back_dir;
6911 xx = x + move_xy[MovDir[x][y]].dx;
6912 yy = y + move_xy[MovDir[x][y]].dy;
6914 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6915 MovDir[x][y] = old_move_dir;
6919 else if (element == EL_MOLE)
6921 boolean can_move_on =
6922 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6923 IS_AMOEBOID(Tile[move_x][move_y]) ||
6924 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6927 boolean can_turn_left =
6928 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6929 IS_AMOEBOID(Tile[left_x][left_y])));
6931 boolean can_turn_right =
6932 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6933 IS_AMOEBOID(Tile[right_x][right_y])));
6935 if (can_turn_left && can_turn_right)
6936 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6937 else if (can_turn_left)
6938 MovDir[x][y] = left_dir;
6940 MovDir[x][y] = right_dir;
6943 if (MovDir[x][y] != old_move_dir)
6946 else if (element == EL_BALLOON)
6948 MovDir[x][y] = game.wind_direction;
6951 else if (element == EL_SPRING)
6953 if (MovDir[x][y] & MV_HORIZONTAL)
6955 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6956 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6958 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6959 ResetGfxAnimation(move_x, move_y);
6960 TEST_DrawLevelField(move_x, move_y);
6962 MovDir[x][y] = back_dir;
6964 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6965 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6966 MovDir[x][y] = MV_NONE;
6971 else if (element == EL_ROBOT ||
6972 element == EL_SATELLITE ||
6973 element == EL_PENGUIN ||
6974 element == EL_EMC_ANDROID)
6976 int attr_x = -1, attr_y = -1;
6978 if (game.all_players_gone)
6980 attr_x = game.exit_x;
6981 attr_y = game.exit_y;
6987 for (i = 0; i < MAX_PLAYERS; i++)
6989 struct PlayerInfo *player = &stored_player[i];
6990 int jx = player->jx, jy = player->jy;
6992 if (!player->active)
6996 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7004 if (element == EL_ROBOT &&
7005 game.robot_wheel_x >= 0 &&
7006 game.robot_wheel_y >= 0 &&
7007 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7008 game.engine_version < VERSION_IDENT(3,1,0,0)))
7010 attr_x = game.robot_wheel_x;
7011 attr_y = game.robot_wheel_y;
7014 if (element == EL_PENGUIN)
7017 static int xy[4][2] =
7025 for (i = 0; i < NUM_DIRECTIONS; i++)
7027 int ex = x + xy[i][0];
7028 int ey = y + xy[i][1];
7030 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7031 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7032 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7033 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7042 MovDir[x][y] = MV_NONE;
7044 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7045 else if (attr_x > x)
7046 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7048 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7049 else if (attr_y > y)
7050 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7052 if (element == EL_ROBOT)
7056 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7057 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7058 Moving2Blocked(x, y, &newx, &newy);
7060 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7061 MovDelay[x][y] = 8 + 8 * !RND(3);
7063 MovDelay[x][y] = 16;
7065 else if (element == EL_PENGUIN)
7071 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7073 boolean first_horiz = RND(2);
7074 int new_move_dir = MovDir[x][y];
7077 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7078 Moving2Blocked(x, y, &newx, &newy);
7080 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7084 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7085 Moving2Blocked(x, y, &newx, &newy);
7087 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7090 MovDir[x][y] = old_move_dir;
7094 else if (element == EL_SATELLITE)
7100 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7102 boolean first_horiz = RND(2);
7103 int new_move_dir = MovDir[x][y];
7106 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7107 Moving2Blocked(x, y, &newx, &newy);
7109 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7113 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7114 Moving2Blocked(x, y, &newx, &newy);
7116 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7119 MovDir[x][y] = old_move_dir;
7123 else if (element == EL_EMC_ANDROID)
7125 static int check_pos[16] =
7127 -1, // 0 => (invalid)
7130 -1, // 3 => (invalid)
7132 0, // 5 => MV_LEFT | MV_UP
7133 2, // 6 => MV_RIGHT | MV_UP
7134 -1, // 7 => (invalid)
7136 6, // 9 => MV_LEFT | MV_DOWN
7137 4, // 10 => MV_RIGHT | MV_DOWN
7138 -1, // 11 => (invalid)
7139 -1, // 12 => (invalid)
7140 -1, // 13 => (invalid)
7141 -1, // 14 => (invalid)
7142 -1, // 15 => (invalid)
7150 { -1, -1, MV_LEFT | MV_UP },
7152 { +1, -1, MV_RIGHT | MV_UP },
7153 { +1, 0, MV_RIGHT },
7154 { +1, +1, MV_RIGHT | MV_DOWN },
7156 { -1, +1, MV_LEFT | MV_DOWN },
7159 int start_pos, check_order;
7160 boolean can_clone = FALSE;
7163 // check if there is any free field around current position
7164 for (i = 0; i < 8; i++)
7166 int newx = x + check_xy[i].dx;
7167 int newy = y + check_xy[i].dy;
7169 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7177 if (can_clone) // randomly find an element to clone
7181 start_pos = check_pos[RND(8)];
7182 check_order = (RND(2) ? -1 : +1);
7184 for (i = 0; i < 8; i++)
7186 int pos_raw = start_pos + i * check_order;
7187 int pos = (pos_raw + 8) % 8;
7188 int newx = x + check_xy[pos].dx;
7189 int newy = y + check_xy[pos].dy;
7191 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7193 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7194 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7196 Store[x][y] = Tile[newx][newy];
7205 if (can_clone) // randomly find a direction to move
7209 start_pos = check_pos[RND(8)];
7210 check_order = (RND(2) ? -1 : +1);
7212 for (i = 0; i < 8; i++)
7214 int pos_raw = start_pos + i * check_order;
7215 int pos = (pos_raw + 8) % 8;
7216 int newx = x + check_xy[pos].dx;
7217 int newy = y + check_xy[pos].dy;
7218 int new_move_dir = check_xy[pos].dir;
7220 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7222 MovDir[x][y] = new_move_dir;
7223 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7232 if (can_clone) // cloning and moving successful
7235 // cannot clone -- try to move towards player
7237 start_pos = check_pos[MovDir[x][y] & 0x0f];
7238 check_order = (RND(2) ? -1 : +1);
7240 for (i = 0; i < 3; i++)
7242 // first check start_pos, then previous/next or (next/previous) pos
7243 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7244 int pos = (pos_raw + 8) % 8;
7245 int newx = x + check_xy[pos].dx;
7246 int newy = y + check_xy[pos].dy;
7247 int new_move_dir = check_xy[pos].dir;
7249 if (IS_PLAYER(newx, newy))
7252 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7254 MovDir[x][y] = new_move_dir;
7255 MovDelay[x][y] = level.android_move_time * 8 + 1;
7262 else if (move_pattern == MV_TURNING_LEFT ||
7263 move_pattern == MV_TURNING_RIGHT ||
7264 move_pattern == MV_TURNING_LEFT_RIGHT ||
7265 move_pattern == MV_TURNING_RIGHT_LEFT ||
7266 move_pattern == MV_TURNING_RANDOM ||
7267 move_pattern == MV_ALL_DIRECTIONS)
7269 boolean can_turn_left =
7270 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7271 boolean can_turn_right =
7272 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7274 if (element_info[element].move_stepsize == 0) // "not moving"
7277 if (move_pattern == MV_TURNING_LEFT)
7278 MovDir[x][y] = left_dir;
7279 else if (move_pattern == MV_TURNING_RIGHT)
7280 MovDir[x][y] = right_dir;
7281 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7282 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7283 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7284 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7285 else if (move_pattern == MV_TURNING_RANDOM)
7286 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7287 can_turn_right && !can_turn_left ? right_dir :
7288 RND(2) ? left_dir : right_dir);
7289 else if (can_turn_left && can_turn_right)
7290 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7291 else if (can_turn_left)
7292 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7293 else if (can_turn_right)
7294 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7296 MovDir[x][y] = back_dir;
7298 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7300 else if (move_pattern == MV_HORIZONTAL ||
7301 move_pattern == MV_VERTICAL)
7303 if (move_pattern & old_move_dir)
7304 MovDir[x][y] = back_dir;
7305 else if (move_pattern == MV_HORIZONTAL)
7306 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7307 else if (move_pattern == MV_VERTICAL)
7308 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7310 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7312 else if (move_pattern & MV_ANY_DIRECTION)
7314 MovDir[x][y] = move_pattern;
7315 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7317 else if (move_pattern & MV_WIND_DIRECTION)
7319 MovDir[x][y] = game.wind_direction;
7320 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7322 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7324 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7325 MovDir[x][y] = left_dir;
7326 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7327 MovDir[x][y] = right_dir;
7329 if (MovDir[x][y] != old_move_dir)
7330 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7332 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7334 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7335 MovDir[x][y] = right_dir;
7336 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7337 MovDir[x][y] = left_dir;
7339 if (MovDir[x][y] != old_move_dir)
7340 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7342 else if (move_pattern == MV_TOWARDS_PLAYER ||
7343 move_pattern == MV_AWAY_FROM_PLAYER)
7345 int attr_x = -1, attr_y = -1;
7347 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7349 if (game.all_players_gone)
7351 attr_x = game.exit_x;
7352 attr_y = game.exit_y;
7358 for (i = 0; i < MAX_PLAYERS; i++)
7360 struct PlayerInfo *player = &stored_player[i];
7361 int jx = player->jx, jy = player->jy;
7363 if (!player->active)
7367 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7375 MovDir[x][y] = MV_NONE;
7377 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7378 else if (attr_x > x)
7379 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7381 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7382 else if (attr_y > y)
7383 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7385 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7387 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7389 boolean first_horiz = RND(2);
7390 int new_move_dir = MovDir[x][y];
7392 if (element_info[element].move_stepsize == 0) // "not moving"
7394 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7395 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7401 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7402 Moving2Blocked(x, y, &newx, &newy);
7404 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7408 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7409 Moving2Blocked(x, y, &newx, &newy);
7411 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7414 MovDir[x][y] = old_move_dir;
7417 else if (move_pattern == MV_WHEN_PUSHED ||
7418 move_pattern == MV_WHEN_DROPPED)
7420 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7421 MovDir[x][y] = MV_NONE;
7425 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7427 static int test_xy[7][2] =
7437 static int test_dir[7] =
7447 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7448 int move_preference = -1000000; // start with very low preference
7449 int new_move_dir = MV_NONE;
7450 int start_test = RND(4);
7453 for (i = 0; i < NUM_DIRECTIONS; i++)
7455 int move_dir = test_dir[start_test + i];
7456 int move_dir_preference;
7458 xx = x + test_xy[start_test + i][0];
7459 yy = y + test_xy[start_test + i][1];
7461 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7462 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7464 new_move_dir = move_dir;
7469 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7472 move_dir_preference = -1 * RunnerVisit[xx][yy];
7473 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7474 move_dir_preference = PlayerVisit[xx][yy];
7476 if (move_dir_preference > move_preference)
7478 // prefer field that has not been visited for the longest time
7479 move_preference = move_dir_preference;
7480 new_move_dir = move_dir;
7482 else if (move_dir_preference == move_preference &&
7483 move_dir == old_move_dir)
7485 // prefer last direction when all directions are preferred equally
7486 move_preference = move_dir_preference;
7487 new_move_dir = move_dir;
7491 MovDir[x][y] = new_move_dir;
7492 if (old_move_dir != new_move_dir)
7493 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7497 static void TurnRound(int x, int y)
7499 int direction = MovDir[x][y];
7503 GfxDir[x][y] = MovDir[x][y];
7505 if (direction != MovDir[x][y])
7509 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7511 ResetGfxFrame(x, y);
7514 static boolean JustBeingPushed(int x, int y)
7518 for (i = 0; i < MAX_PLAYERS; i++)
7520 struct PlayerInfo *player = &stored_player[i];
7522 if (player->active && player->is_pushing && player->MovPos)
7524 int next_jx = player->jx + (player->jx - player->last_jx);
7525 int next_jy = player->jy + (player->jy - player->last_jy);
7527 if (x == next_jx && y == next_jy)
7535 static void StartMoving(int x, int y)
7537 boolean started_moving = FALSE; // some elements can fall _and_ move
7538 int element = Tile[x][y];
7543 if (MovDelay[x][y] == 0)
7544 GfxAction[x][y] = ACTION_DEFAULT;
7546 if (CAN_FALL(element) && y < lev_fieldy - 1)
7548 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7549 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7550 if (JustBeingPushed(x, y))
7553 if (element == EL_QUICKSAND_FULL)
7555 if (IS_FREE(x, y + 1))
7557 InitMovingField(x, y, MV_DOWN);
7558 started_moving = TRUE;
7560 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7561 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7562 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7563 Store[x][y] = EL_ROCK;
7565 Store[x][y] = EL_ROCK;
7568 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7570 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7572 if (!MovDelay[x][y])
7574 MovDelay[x][y] = TILEY + 1;
7576 ResetGfxAnimation(x, y);
7577 ResetGfxAnimation(x, y + 1);
7582 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7583 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7590 Tile[x][y] = EL_QUICKSAND_EMPTY;
7591 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7592 Store[x][y + 1] = Store[x][y];
7595 PlayLevelSoundAction(x, y, ACTION_FILLING);
7597 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7599 if (!MovDelay[x][y])
7601 MovDelay[x][y] = TILEY + 1;
7603 ResetGfxAnimation(x, y);
7604 ResetGfxAnimation(x, y + 1);
7609 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7610 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7617 Tile[x][y] = EL_QUICKSAND_EMPTY;
7618 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7619 Store[x][y + 1] = Store[x][y];
7622 PlayLevelSoundAction(x, y, ACTION_FILLING);
7625 else if (element == EL_QUICKSAND_FAST_FULL)
7627 if (IS_FREE(x, y + 1))
7629 InitMovingField(x, y, MV_DOWN);
7630 started_moving = TRUE;
7632 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7633 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7634 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7635 Store[x][y] = EL_ROCK;
7637 Store[x][y] = EL_ROCK;
7640 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7642 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7644 if (!MovDelay[x][y])
7646 MovDelay[x][y] = TILEY + 1;
7648 ResetGfxAnimation(x, y);
7649 ResetGfxAnimation(x, y + 1);
7654 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7655 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7662 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7663 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7664 Store[x][y + 1] = Store[x][y];
7667 PlayLevelSoundAction(x, y, ACTION_FILLING);
7669 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7671 if (!MovDelay[x][y])
7673 MovDelay[x][y] = TILEY + 1;
7675 ResetGfxAnimation(x, y);
7676 ResetGfxAnimation(x, y + 1);
7681 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7682 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7689 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7690 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7691 Store[x][y + 1] = Store[x][y];
7694 PlayLevelSoundAction(x, y, ACTION_FILLING);
7697 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7698 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7700 InitMovingField(x, y, MV_DOWN);
7701 started_moving = TRUE;
7703 Tile[x][y] = EL_QUICKSAND_FILLING;
7704 Store[x][y] = element;
7706 PlayLevelSoundAction(x, y, ACTION_FILLING);
7708 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7709 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7711 InitMovingField(x, y, MV_DOWN);
7712 started_moving = TRUE;
7714 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7715 Store[x][y] = element;
7717 PlayLevelSoundAction(x, y, ACTION_FILLING);
7719 else if (element == EL_MAGIC_WALL_FULL)
7721 if (IS_FREE(x, y + 1))
7723 InitMovingField(x, y, MV_DOWN);
7724 started_moving = TRUE;
7726 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7727 Store[x][y] = EL_CHANGED(Store[x][y]);
7729 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7731 if (!MovDelay[x][y])
7732 MovDelay[x][y] = TILEY / 4 + 1;
7741 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7742 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7743 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7747 else if (element == EL_BD_MAGIC_WALL_FULL)
7749 if (IS_FREE(x, y + 1))
7751 InitMovingField(x, y, MV_DOWN);
7752 started_moving = TRUE;
7754 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7755 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7757 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7759 if (!MovDelay[x][y])
7760 MovDelay[x][y] = TILEY / 4 + 1;
7769 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7770 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7771 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7775 else if (element == EL_DC_MAGIC_WALL_FULL)
7777 if (IS_FREE(x, y + 1))
7779 InitMovingField(x, y, MV_DOWN);
7780 started_moving = TRUE;
7782 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7783 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7785 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7787 if (!MovDelay[x][y])
7788 MovDelay[x][y] = TILEY / 4 + 1;
7797 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7798 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7799 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7803 else if ((CAN_PASS_MAGIC_WALL(element) &&
7804 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7805 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7806 (CAN_PASS_DC_MAGIC_WALL(element) &&
7807 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7810 InitMovingField(x, y, MV_DOWN);
7811 started_moving = TRUE;
7814 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7815 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7816 EL_DC_MAGIC_WALL_FILLING);
7817 Store[x][y] = element;
7819 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7821 SplashAcid(x, y + 1);
7823 InitMovingField(x, y, MV_DOWN);
7824 started_moving = TRUE;
7826 Store[x][y] = EL_ACID;
7829 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7830 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7831 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7832 CAN_FALL(element) && WasJustFalling[x][y] &&
7833 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7835 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7836 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7837 (Tile[x][y + 1] == EL_BLOCKED)))
7839 /* this is needed for a special case not covered by calling "Impact()"
7840 from "ContinueMoving()": if an element moves to a tile directly below
7841 another element which was just falling on that tile (which was empty
7842 in the previous frame), the falling element above would just stop
7843 instead of smashing the element below (in previous version, the above
7844 element was just checked for "moving" instead of "falling", resulting
7845 in incorrect smashes caused by horizontal movement of the above
7846 element; also, the case of the player being the element to smash was
7847 simply not covered here... :-/ ) */
7849 CheckCollision[x][y] = 0;
7850 CheckImpact[x][y] = 0;
7854 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7856 if (MovDir[x][y] == MV_NONE)
7858 InitMovingField(x, y, MV_DOWN);
7859 started_moving = TRUE;
7862 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7864 if (WasJustFalling[x][y]) // prevent animation from being restarted
7865 MovDir[x][y] = MV_DOWN;
7867 InitMovingField(x, y, MV_DOWN);
7868 started_moving = TRUE;
7870 else if (element == EL_AMOEBA_DROP)
7872 Tile[x][y] = EL_AMOEBA_GROWING;
7873 Store[x][y] = EL_AMOEBA_WET;
7875 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7876 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7877 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7878 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7880 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7881 (IS_FREE(x - 1, y + 1) ||
7882 Tile[x - 1][y + 1] == EL_ACID));
7883 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7884 (IS_FREE(x + 1, y + 1) ||
7885 Tile[x + 1][y + 1] == EL_ACID));
7886 boolean can_fall_any = (can_fall_left || can_fall_right);
7887 boolean can_fall_both = (can_fall_left && can_fall_right);
7888 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7890 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7892 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7893 can_fall_right = FALSE;
7894 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7895 can_fall_left = FALSE;
7896 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7897 can_fall_right = FALSE;
7898 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7899 can_fall_left = FALSE;
7901 can_fall_any = (can_fall_left || can_fall_right);
7902 can_fall_both = FALSE;
7907 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7908 can_fall_right = FALSE; // slip down on left side
7910 can_fall_left = !(can_fall_right = RND(2));
7912 can_fall_both = FALSE;
7917 // if not determined otherwise, prefer left side for slipping down
7918 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7919 started_moving = TRUE;
7922 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7924 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7925 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7926 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7927 int belt_dir = game.belt_dir[belt_nr];
7929 if ((belt_dir == MV_LEFT && left_is_free) ||
7930 (belt_dir == MV_RIGHT && right_is_free))
7932 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7934 InitMovingField(x, y, belt_dir);
7935 started_moving = TRUE;
7937 Pushed[x][y] = TRUE;
7938 Pushed[nextx][y] = TRUE;
7940 GfxAction[x][y] = ACTION_DEFAULT;
7944 MovDir[x][y] = 0; // if element was moving, stop it
7949 // not "else if" because of elements that can fall and move (EL_SPRING)
7950 if (CAN_MOVE(element) && !started_moving)
7952 int move_pattern = element_info[element].move_pattern;
7955 Moving2Blocked(x, y, &newx, &newy);
7957 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7960 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7961 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7963 WasJustMoving[x][y] = 0;
7964 CheckCollision[x][y] = 0;
7966 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7968 if (Tile[x][y] != element) // element has changed
7972 if (!MovDelay[x][y]) // start new movement phase
7974 // all objects that can change their move direction after each step
7975 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7977 if (element != EL_YAMYAM &&
7978 element != EL_DARK_YAMYAM &&
7979 element != EL_PACMAN &&
7980 !(move_pattern & MV_ANY_DIRECTION) &&
7981 move_pattern != MV_TURNING_LEFT &&
7982 move_pattern != MV_TURNING_RIGHT &&
7983 move_pattern != MV_TURNING_LEFT_RIGHT &&
7984 move_pattern != MV_TURNING_RIGHT_LEFT &&
7985 move_pattern != MV_TURNING_RANDOM)
7989 if (MovDelay[x][y] && (element == EL_BUG ||
7990 element == EL_SPACESHIP ||
7991 element == EL_SP_SNIKSNAK ||
7992 element == EL_SP_ELECTRON ||
7993 element == EL_MOLE))
7994 TEST_DrawLevelField(x, y);
7998 if (MovDelay[x][y]) // wait some time before next movement
8002 if (element == EL_ROBOT ||
8003 element == EL_YAMYAM ||
8004 element == EL_DARK_YAMYAM)
8006 DrawLevelElementAnimationIfNeeded(x, y, element);
8007 PlayLevelSoundAction(x, y, ACTION_WAITING);
8009 else if (element == EL_SP_ELECTRON)
8010 DrawLevelElementAnimationIfNeeded(x, y, element);
8011 else if (element == EL_DRAGON)
8014 int dir = MovDir[x][y];
8015 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8016 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8017 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8018 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8019 dir == MV_UP ? IMG_FLAMES_1_UP :
8020 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8021 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8023 GfxAction[x][y] = ACTION_ATTACKING;
8025 if (IS_PLAYER(x, y))
8026 DrawPlayerField(x, y);
8028 TEST_DrawLevelField(x, y);
8030 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8032 for (i = 1; i <= 3; i++)
8034 int xx = x + i * dx;
8035 int yy = y + i * dy;
8036 int sx = SCREENX(xx);
8037 int sy = SCREENY(yy);
8038 int flame_graphic = graphic + (i - 1);
8040 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8045 int flamed = MovingOrBlocked2Element(xx, yy);
8047 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8050 RemoveMovingField(xx, yy);
8052 ChangeDelay[xx][yy] = 0;
8054 Tile[xx][yy] = EL_FLAMES;
8056 if (IN_SCR_FIELD(sx, sy))
8058 TEST_DrawLevelFieldCrumbled(xx, yy);
8059 DrawGraphic(sx, sy, flame_graphic, frame);
8064 if (Tile[xx][yy] == EL_FLAMES)
8065 Tile[xx][yy] = EL_EMPTY;
8066 TEST_DrawLevelField(xx, yy);
8071 if (MovDelay[x][y]) // element still has to wait some time
8073 PlayLevelSoundAction(x, y, ACTION_WAITING);
8079 // now make next step
8081 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8083 if (DONT_COLLIDE_WITH(element) &&
8084 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8085 !PLAYER_ENEMY_PROTECTED(newx, newy))
8087 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8092 else if (CAN_MOVE_INTO_ACID(element) &&
8093 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8094 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8095 (MovDir[x][y] == MV_DOWN ||
8096 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8098 SplashAcid(newx, newy);
8099 Store[x][y] = EL_ACID;
8101 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8103 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8104 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8105 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8106 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8109 TEST_DrawLevelField(x, y);
8111 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8112 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8113 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8115 game.friends_still_needed--;
8116 if (!game.friends_still_needed &&
8118 game.all_players_gone)
8123 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8125 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8126 TEST_DrawLevelField(newx, newy);
8128 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8130 else if (!IS_FREE(newx, newy))
8132 GfxAction[x][y] = ACTION_WAITING;
8134 if (IS_PLAYER(x, y))
8135 DrawPlayerField(x, y);
8137 TEST_DrawLevelField(x, y);
8142 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8144 if (IS_FOOD_PIG(Tile[newx][newy]))
8146 if (IS_MOVING(newx, newy))
8147 RemoveMovingField(newx, newy);
8150 Tile[newx][newy] = EL_EMPTY;
8151 TEST_DrawLevelField(newx, newy);
8154 PlayLevelSound(x, y, SND_PIG_DIGGING);
8156 else if (!IS_FREE(newx, newy))
8158 if (IS_PLAYER(x, y))
8159 DrawPlayerField(x, y);
8161 TEST_DrawLevelField(x, y);
8166 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8168 if (Store[x][y] != EL_EMPTY)
8170 boolean can_clone = FALSE;
8173 // check if element to clone is still there
8174 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8176 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8184 // cannot clone or target field not free anymore -- do not clone
8185 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8186 Store[x][y] = EL_EMPTY;
8189 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8191 if (IS_MV_DIAGONAL(MovDir[x][y]))
8193 int diagonal_move_dir = MovDir[x][y];
8194 int stored = Store[x][y];
8195 int change_delay = 8;
8198 // android is moving diagonally
8200 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8202 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8203 GfxElement[x][y] = EL_EMC_ANDROID;
8204 GfxAction[x][y] = ACTION_SHRINKING;
8205 GfxDir[x][y] = diagonal_move_dir;
8206 ChangeDelay[x][y] = change_delay;
8208 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8211 DrawLevelGraphicAnimation(x, y, graphic);
8212 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8214 if (Tile[newx][newy] == EL_ACID)
8216 SplashAcid(newx, newy);
8221 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8223 Store[newx][newy] = EL_EMC_ANDROID;
8224 GfxElement[newx][newy] = EL_EMC_ANDROID;
8225 GfxAction[newx][newy] = ACTION_GROWING;
8226 GfxDir[newx][newy] = diagonal_move_dir;
8227 ChangeDelay[newx][newy] = change_delay;
8229 graphic = el_act_dir2img(GfxElement[newx][newy],
8230 GfxAction[newx][newy], GfxDir[newx][newy]);
8232 DrawLevelGraphicAnimation(newx, newy, graphic);
8233 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8239 Tile[newx][newy] = EL_EMPTY;
8240 TEST_DrawLevelField(newx, newy);
8242 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8245 else if (!IS_FREE(newx, newy))
8250 else if (IS_CUSTOM_ELEMENT(element) &&
8251 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8253 if (!DigFieldByCE(newx, newy, element))
8256 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8258 RunnerVisit[x][y] = FrameCounter;
8259 PlayerVisit[x][y] /= 8; // expire player visit path
8262 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8264 if (!IS_FREE(newx, newy))
8266 if (IS_PLAYER(x, y))
8267 DrawPlayerField(x, y);
8269 TEST_DrawLevelField(x, y);
8275 boolean wanna_flame = !RND(10);
8276 int dx = newx - x, dy = newy - y;
8277 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8278 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8279 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8280 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8281 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8282 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8285 IS_CLASSIC_ENEMY(element1) ||
8286 IS_CLASSIC_ENEMY(element2)) &&
8287 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8288 element1 != EL_FLAMES && element2 != EL_FLAMES)
8290 ResetGfxAnimation(x, y);
8291 GfxAction[x][y] = ACTION_ATTACKING;
8293 if (IS_PLAYER(x, y))
8294 DrawPlayerField(x, y);
8296 TEST_DrawLevelField(x, y);
8298 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8300 MovDelay[x][y] = 50;
8302 Tile[newx][newy] = EL_FLAMES;
8303 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8304 Tile[newx1][newy1] = EL_FLAMES;
8305 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8306 Tile[newx2][newy2] = EL_FLAMES;
8312 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8313 Tile[newx][newy] == EL_DIAMOND)
8315 if (IS_MOVING(newx, newy))
8316 RemoveMovingField(newx, newy);
8319 Tile[newx][newy] = EL_EMPTY;
8320 TEST_DrawLevelField(newx, newy);
8323 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8325 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8326 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8328 if (AmoebaNr[newx][newy])
8330 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8331 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8332 Tile[newx][newy] == EL_BD_AMOEBA)
8333 AmoebaCnt[AmoebaNr[newx][newy]]--;
8336 if (IS_MOVING(newx, newy))
8338 RemoveMovingField(newx, newy);
8342 Tile[newx][newy] = EL_EMPTY;
8343 TEST_DrawLevelField(newx, newy);
8346 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8348 else if ((element == EL_PACMAN || element == EL_MOLE)
8349 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8351 if (AmoebaNr[newx][newy])
8353 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8354 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8355 Tile[newx][newy] == EL_BD_AMOEBA)
8356 AmoebaCnt[AmoebaNr[newx][newy]]--;
8359 if (element == EL_MOLE)
8361 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8362 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8364 ResetGfxAnimation(x, y);
8365 GfxAction[x][y] = ACTION_DIGGING;
8366 TEST_DrawLevelField(x, y);
8368 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8370 return; // wait for shrinking amoeba
8372 else // element == EL_PACMAN
8374 Tile[newx][newy] = EL_EMPTY;
8375 TEST_DrawLevelField(newx, newy);
8376 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8379 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8380 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8381 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8383 // wait for shrinking amoeba to completely disappear
8386 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8388 // object was running against a wall
8392 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8393 DrawLevelElementAnimation(x, y, element);
8395 if (DONT_TOUCH(element))
8396 TestIfBadThingTouchesPlayer(x, y);
8401 InitMovingField(x, y, MovDir[x][y]);
8403 PlayLevelSoundAction(x, y, ACTION_MOVING);
8407 ContinueMoving(x, y);
8410 void ContinueMoving(int x, int y)
8412 int element = Tile[x][y];
8413 struct ElementInfo *ei = &element_info[element];
8414 int direction = MovDir[x][y];
8415 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8416 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8417 int newx = x + dx, newy = y + dy;
8418 int stored = Store[x][y];
8419 int stored_new = Store[newx][newy];
8420 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8421 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8422 boolean last_line = (newy == lev_fieldy - 1);
8424 MovPos[x][y] += getElementMoveStepsize(x, y);
8426 if (pushed_by_player) // special case: moving object pushed by player
8427 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8429 if (ABS(MovPos[x][y]) < TILEX)
8431 TEST_DrawLevelField(x, y);
8433 return; // element is still moving
8436 // element reached destination field
8438 Tile[x][y] = EL_EMPTY;
8439 Tile[newx][newy] = element;
8440 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8442 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8444 element = Tile[newx][newy] = EL_ACID;
8446 else if (element == EL_MOLE)
8448 Tile[x][y] = EL_SAND;
8450 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8452 else if (element == EL_QUICKSAND_FILLING)
8454 element = Tile[newx][newy] = get_next_element(element);
8455 Store[newx][newy] = Store[x][y];
8457 else if (element == EL_QUICKSAND_EMPTYING)
8459 Tile[x][y] = get_next_element(element);
8460 element = Tile[newx][newy] = Store[x][y];
8462 else if (element == EL_QUICKSAND_FAST_FILLING)
8464 element = Tile[newx][newy] = get_next_element(element);
8465 Store[newx][newy] = Store[x][y];
8467 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8469 Tile[x][y] = get_next_element(element);
8470 element = Tile[newx][newy] = Store[x][y];
8472 else if (element == EL_MAGIC_WALL_FILLING)
8474 element = Tile[newx][newy] = get_next_element(element);
8475 if (!game.magic_wall_active)
8476 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8477 Store[newx][newy] = Store[x][y];
8479 else if (element == EL_MAGIC_WALL_EMPTYING)
8481 Tile[x][y] = get_next_element(element);
8482 if (!game.magic_wall_active)
8483 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8484 element = Tile[newx][newy] = Store[x][y];
8486 InitField(newx, newy, FALSE);
8488 else if (element == EL_BD_MAGIC_WALL_FILLING)
8490 element = Tile[newx][newy] = get_next_element(element);
8491 if (!game.magic_wall_active)
8492 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8493 Store[newx][newy] = Store[x][y];
8495 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8497 Tile[x][y] = get_next_element(element);
8498 if (!game.magic_wall_active)
8499 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8500 element = Tile[newx][newy] = Store[x][y];
8502 InitField(newx, newy, FALSE);
8504 else if (element == EL_DC_MAGIC_WALL_FILLING)
8506 element = Tile[newx][newy] = get_next_element(element);
8507 if (!game.magic_wall_active)
8508 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8509 Store[newx][newy] = Store[x][y];
8511 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8513 Tile[x][y] = get_next_element(element);
8514 if (!game.magic_wall_active)
8515 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8516 element = Tile[newx][newy] = Store[x][y];
8518 InitField(newx, newy, FALSE);
8520 else if (element == EL_AMOEBA_DROPPING)
8522 Tile[x][y] = get_next_element(element);
8523 element = Tile[newx][newy] = Store[x][y];
8525 else if (element == EL_SOKOBAN_OBJECT)
8528 Tile[x][y] = Back[x][y];
8530 if (Back[newx][newy])
8531 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8533 Back[x][y] = Back[newx][newy] = 0;
8536 Store[x][y] = EL_EMPTY;
8541 MovDelay[newx][newy] = 0;
8543 if (CAN_CHANGE_OR_HAS_ACTION(element))
8545 // copy element change control values to new field
8546 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8547 ChangePage[newx][newy] = ChangePage[x][y];
8548 ChangeCount[newx][newy] = ChangeCount[x][y];
8549 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8552 CustomValue[newx][newy] = CustomValue[x][y];
8554 ChangeDelay[x][y] = 0;
8555 ChangePage[x][y] = -1;
8556 ChangeCount[x][y] = 0;
8557 ChangeEvent[x][y] = -1;
8559 CustomValue[x][y] = 0;
8561 // copy animation control values to new field
8562 GfxFrame[newx][newy] = GfxFrame[x][y];
8563 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8564 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8565 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8567 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8569 // some elements can leave other elements behind after moving
8570 if (ei->move_leave_element != EL_EMPTY &&
8571 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8572 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8574 int move_leave_element = ei->move_leave_element;
8576 // this makes it possible to leave the removed element again
8577 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8578 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8580 Tile[x][y] = move_leave_element;
8582 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8583 MovDir[x][y] = direction;
8585 InitField(x, y, FALSE);
8587 if (GFX_CRUMBLED(Tile[x][y]))
8588 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8590 if (ELEM_IS_PLAYER(move_leave_element))
8591 RelocatePlayer(x, y, move_leave_element);
8594 // do this after checking for left-behind element
8595 ResetGfxAnimation(x, y); // reset animation values for old field
8597 if (!CAN_MOVE(element) ||
8598 (CAN_FALL(element) && direction == MV_DOWN &&
8599 (element == EL_SPRING ||
8600 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8601 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8602 GfxDir[x][y] = MovDir[newx][newy] = 0;
8604 TEST_DrawLevelField(x, y);
8605 TEST_DrawLevelField(newx, newy);
8607 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8609 // prevent pushed element from moving on in pushed direction
8610 if (pushed_by_player && CAN_MOVE(element) &&
8611 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8612 !(element_info[element].move_pattern & direction))
8613 TurnRound(newx, newy);
8615 // prevent elements on conveyor belt from moving on in last direction
8616 if (pushed_by_conveyor && CAN_FALL(element) &&
8617 direction & MV_HORIZONTAL)
8618 MovDir[newx][newy] = 0;
8620 if (!pushed_by_player)
8622 int nextx = newx + dx, nexty = newy + dy;
8623 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8625 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8627 if (CAN_FALL(element) && direction == MV_DOWN)
8628 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8630 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8631 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8633 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8634 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8637 if (DONT_TOUCH(element)) // object may be nasty to player or others
8639 TestIfBadThingTouchesPlayer(newx, newy);
8640 TestIfBadThingTouchesFriend(newx, newy);
8642 if (!IS_CUSTOM_ELEMENT(element))
8643 TestIfBadThingTouchesOtherBadThing(newx, newy);
8645 else if (element == EL_PENGUIN)
8646 TestIfFriendTouchesBadThing(newx, newy);
8648 if (DONT_GET_HIT_BY(element))
8650 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8653 // give the player one last chance (one more frame) to move away
8654 if (CAN_FALL(element) && direction == MV_DOWN &&
8655 (last_line || (!IS_FREE(x, newy + 1) &&
8656 (!IS_PLAYER(x, newy + 1) ||
8657 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8660 if (pushed_by_player && !game.use_change_when_pushing_bug)
8662 int push_side = MV_DIR_OPPOSITE(direction);
8663 struct PlayerInfo *player = PLAYERINFO(x, y);
8665 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8666 player->index_bit, push_side);
8667 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8668 player->index_bit, push_side);
8671 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8672 MovDelay[newx][newy] = 1;
8674 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8676 TestIfElementTouchesCustomElement(x, y); // empty or new element
8677 TestIfElementHitsCustomElement(newx, newy, direction);
8678 TestIfPlayerTouchesCustomElement(newx, newy);
8679 TestIfElementTouchesCustomElement(newx, newy);
8681 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8682 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8683 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8684 MV_DIR_OPPOSITE(direction));
8687 int AmoebaNeighbourNr(int ax, int ay)
8690 int element = Tile[ax][ay];
8692 static int xy[4][2] =
8700 for (i = 0; i < NUM_DIRECTIONS; i++)
8702 int x = ax + xy[i][0];
8703 int y = ay + xy[i][1];
8705 if (!IN_LEV_FIELD(x, y))
8708 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8709 group_nr = AmoebaNr[x][y];
8715 static void AmoebaMerge(int ax, int ay)
8717 int i, x, y, xx, yy;
8718 int new_group_nr = AmoebaNr[ax][ay];
8719 static int xy[4][2] =
8727 if (new_group_nr == 0)
8730 for (i = 0; i < NUM_DIRECTIONS; i++)
8735 if (!IN_LEV_FIELD(x, y))
8738 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8739 Tile[x][y] == EL_BD_AMOEBA ||
8740 Tile[x][y] == EL_AMOEBA_DEAD) &&
8741 AmoebaNr[x][y] != new_group_nr)
8743 int old_group_nr = AmoebaNr[x][y];
8745 if (old_group_nr == 0)
8748 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8749 AmoebaCnt[old_group_nr] = 0;
8750 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8751 AmoebaCnt2[old_group_nr] = 0;
8753 SCAN_PLAYFIELD(xx, yy)
8755 if (AmoebaNr[xx][yy] == old_group_nr)
8756 AmoebaNr[xx][yy] = new_group_nr;
8762 void AmoebaToDiamond(int ax, int ay)
8766 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8768 int group_nr = AmoebaNr[ax][ay];
8773 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8774 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8780 SCAN_PLAYFIELD(x, y)
8782 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8785 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8789 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8790 SND_AMOEBA_TURNING_TO_GEM :
8791 SND_AMOEBA_TURNING_TO_ROCK));
8796 static int xy[4][2] =
8804 for (i = 0; i < NUM_DIRECTIONS; i++)
8809 if (!IN_LEV_FIELD(x, y))
8812 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8814 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8815 SND_AMOEBA_TURNING_TO_GEM :
8816 SND_AMOEBA_TURNING_TO_ROCK));
8823 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8826 int group_nr = AmoebaNr[ax][ay];
8827 boolean done = FALSE;
8832 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8833 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8839 SCAN_PLAYFIELD(x, y)
8841 if (AmoebaNr[x][y] == group_nr &&
8842 (Tile[x][y] == EL_AMOEBA_DEAD ||
8843 Tile[x][y] == EL_BD_AMOEBA ||
8844 Tile[x][y] == EL_AMOEBA_GROWING))
8847 Tile[x][y] = new_element;
8848 InitField(x, y, FALSE);
8849 TEST_DrawLevelField(x, y);
8855 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8856 SND_BD_AMOEBA_TURNING_TO_ROCK :
8857 SND_BD_AMOEBA_TURNING_TO_GEM));
8860 static void AmoebaGrowing(int x, int y)
8862 static unsigned int sound_delay = 0;
8863 static unsigned int sound_delay_value = 0;
8865 if (!MovDelay[x][y]) // start new growing cycle
8869 if (DelayReached(&sound_delay, sound_delay_value))
8871 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8872 sound_delay_value = 30;
8876 if (MovDelay[x][y]) // wait some time before growing bigger
8879 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8881 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8882 6 - MovDelay[x][y]);
8884 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8887 if (!MovDelay[x][y])
8889 Tile[x][y] = Store[x][y];
8891 TEST_DrawLevelField(x, y);
8896 static void AmoebaShrinking(int x, int y)
8898 static unsigned int sound_delay = 0;
8899 static unsigned int sound_delay_value = 0;
8901 if (!MovDelay[x][y]) // start new shrinking cycle
8905 if (DelayReached(&sound_delay, sound_delay_value))
8906 sound_delay_value = 30;
8909 if (MovDelay[x][y]) // wait some time before shrinking
8912 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8914 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8915 6 - MovDelay[x][y]);
8917 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8920 if (!MovDelay[x][y])
8922 Tile[x][y] = EL_EMPTY;
8923 TEST_DrawLevelField(x, y);
8925 // don't let mole enter this field in this cycle;
8926 // (give priority to objects falling to this field from above)
8932 static void AmoebaReproduce(int ax, int ay)
8935 int element = Tile[ax][ay];
8936 int graphic = el2img(element);
8937 int newax = ax, neway = ay;
8938 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8939 static int xy[4][2] =
8947 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8949 Tile[ax][ay] = EL_AMOEBA_DEAD;
8950 TEST_DrawLevelField(ax, ay);
8954 if (IS_ANIMATED(graphic))
8955 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8957 if (!MovDelay[ax][ay]) // start making new amoeba field
8958 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8960 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8963 if (MovDelay[ax][ay])
8967 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8970 int x = ax + xy[start][0];
8971 int y = ay + xy[start][1];
8973 if (!IN_LEV_FIELD(x, y))
8976 if (IS_FREE(x, y) ||
8977 CAN_GROW_INTO(Tile[x][y]) ||
8978 Tile[x][y] == EL_QUICKSAND_EMPTY ||
8979 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
8985 if (newax == ax && neway == ay)
8988 else // normal or "filled" (BD style) amoeba
8991 boolean waiting_for_player = FALSE;
8993 for (i = 0; i < NUM_DIRECTIONS; i++)
8995 int j = (start + i) % 4;
8996 int x = ax + xy[j][0];
8997 int y = ay + xy[j][1];
8999 if (!IN_LEV_FIELD(x, y))
9002 if (IS_FREE(x, y) ||
9003 CAN_GROW_INTO(Tile[x][y]) ||
9004 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9005 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9011 else if (IS_PLAYER(x, y))
9012 waiting_for_player = TRUE;
9015 if (newax == ax && neway == ay) // amoeba cannot grow
9017 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9019 Tile[ax][ay] = EL_AMOEBA_DEAD;
9020 TEST_DrawLevelField(ax, ay);
9021 AmoebaCnt[AmoebaNr[ax][ay]]--;
9023 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9025 if (element == EL_AMOEBA_FULL)
9026 AmoebaToDiamond(ax, ay);
9027 else if (element == EL_BD_AMOEBA)
9028 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9033 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9035 // amoeba gets larger by growing in some direction
9037 int new_group_nr = AmoebaNr[ax][ay];
9040 if (new_group_nr == 0)
9042 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9044 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9050 AmoebaNr[newax][neway] = new_group_nr;
9051 AmoebaCnt[new_group_nr]++;
9052 AmoebaCnt2[new_group_nr]++;
9054 // if amoeba touches other amoeba(s) after growing, unify them
9055 AmoebaMerge(newax, neway);
9057 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9059 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9065 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9066 (neway == lev_fieldy - 1 && newax != ax))
9068 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9069 Store[newax][neway] = element;
9071 else if (neway == ay || element == EL_EMC_DRIPPER)
9073 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9075 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9079 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9080 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9081 Store[ax][ay] = EL_AMOEBA_DROP;
9082 ContinueMoving(ax, ay);
9086 TEST_DrawLevelField(newax, neway);
9089 static void Life(int ax, int ay)
9093 int element = Tile[ax][ay];
9094 int graphic = el2img(element);
9095 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9097 boolean changed = FALSE;
9099 if (IS_ANIMATED(graphic))
9100 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9105 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9106 MovDelay[ax][ay] = life_time;
9108 if (MovDelay[ax][ay]) // wait some time before next cycle
9111 if (MovDelay[ax][ay])
9115 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9117 int xx = ax+x1, yy = ay+y1;
9118 int old_element = Tile[xx][yy];
9119 int num_neighbours = 0;
9121 if (!IN_LEV_FIELD(xx, yy))
9124 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9126 int x = xx+x2, y = yy+y2;
9128 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9131 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9132 boolean is_neighbour = FALSE;
9134 if (level.use_life_bugs)
9136 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9137 (IS_FREE(x, y) && Stop[x][y]));
9140 (Last[x][y] == element || is_player_cell);
9146 boolean is_free = FALSE;
9148 if (level.use_life_bugs)
9149 is_free = (IS_FREE(xx, yy));
9151 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9153 if (xx == ax && yy == ay) // field in the middle
9155 if (num_neighbours < life_parameter[0] ||
9156 num_neighbours > life_parameter[1])
9158 Tile[xx][yy] = EL_EMPTY;
9159 if (Tile[xx][yy] != old_element)
9160 TEST_DrawLevelField(xx, yy);
9161 Stop[xx][yy] = TRUE;
9165 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9166 { // free border field
9167 if (num_neighbours >= life_parameter[2] &&
9168 num_neighbours <= life_parameter[3])
9170 Tile[xx][yy] = element;
9171 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9172 if (Tile[xx][yy] != old_element)
9173 TEST_DrawLevelField(xx, yy);
9174 Stop[xx][yy] = TRUE;
9181 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9182 SND_GAME_OF_LIFE_GROWING);
9185 static void InitRobotWheel(int x, int y)
9187 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9190 static void RunRobotWheel(int x, int y)
9192 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9195 static void StopRobotWheel(int x, int y)
9197 if (game.robot_wheel_x == x &&
9198 game.robot_wheel_y == y)
9200 game.robot_wheel_x = -1;
9201 game.robot_wheel_y = -1;
9202 game.robot_wheel_active = FALSE;
9206 static void InitTimegateWheel(int x, int y)
9208 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9211 static void RunTimegateWheel(int x, int y)
9213 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9216 static void InitMagicBallDelay(int x, int y)
9218 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9221 static void ActivateMagicBall(int bx, int by)
9225 if (level.ball_random)
9227 int pos_border = RND(8); // select one of the eight border elements
9228 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9229 int xx = pos_content % 3;
9230 int yy = pos_content / 3;
9235 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9236 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9240 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9242 int xx = x - bx + 1;
9243 int yy = y - by + 1;
9245 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9246 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9250 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9253 static void CheckExit(int x, int y)
9255 if (game.gems_still_needed > 0 ||
9256 game.sokoban_fields_still_needed > 0 ||
9257 game.sokoban_objects_still_needed > 0 ||
9258 game.lights_still_needed > 0)
9260 int element = Tile[x][y];
9261 int graphic = el2img(element);
9263 if (IS_ANIMATED(graphic))
9264 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9269 // do not re-open exit door closed after last player
9270 if (game.all_players_gone)
9273 Tile[x][y] = EL_EXIT_OPENING;
9275 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9278 static void CheckExitEM(int x, int y)
9280 if (game.gems_still_needed > 0 ||
9281 game.sokoban_fields_still_needed > 0 ||
9282 game.sokoban_objects_still_needed > 0 ||
9283 game.lights_still_needed > 0)
9285 int element = Tile[x][y];
9286 int graphic = el2img(element);
9288 if (IS_ANIMATED(graphic))
9289 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9294 // do not re-open exit door closed after last player
9295 if (game.all_players_gone)
9298 Tile[x][y] = EL_EM_EXIT_OPENING;
9300 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9303 static void CheckExitSteel(int x, int y)
9305 if (game.gems_still_needed > 0 ||
9306 game.sokoban_fields_still_needed > 0 ||
9307 game.sokoban_objects_still_needed > 0 ||
9308 game.lights_still_needed > 0)
9310 int element = Tile[x][y];
9311 int graphic = el2img(element);
9313 if (IS_ANIMATED(graphic))
9314 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9319 // do not re-open exit door closed after last player
9320 if (game.all_players_gone)
9323 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9325 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9328 static void CheckExitSteelEM(int x, int y)
9330 if (game.gems_still_needed > 0 ||
9331 game.sokoban_fields_still_needed > 0 ||
9332 game.sokoban_objects_still_needed > 0 ||
9333 game.lights_still_needed > 0)
9335 int element = Tile[x][y];
9336 int graphic = el2img(element);
9338 if (IS_ANIMATED(graphic))
9339 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9344 // do not re-open exit door closed after last player
9345 if (game.all_players_gone)
9348 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9350 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9353 static void CheckExitSP(int x, int y)
9355 if (game.gems_still_needed > 0)
9357 int element = Tile[x][y];
9358 int graphic = el2img(element);
9360 if (IS_ANIMATED(graphic))
9361 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9366 // do not re-open exit door closed after last player
9367 if (game.all_players_gone)
9370 Tile[x][y] = EL_SP_EXIT_OPENING;
9372 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9375 static void CloseAllOpenTimegates(void)
9379 SCAN_PLAYFIELD(x, y)
9381 int element = Tile[x][y];
9383 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9385 Tile[x][y] = EL_TIMEGATE_CLOSING;
9387 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9392 static void DrawTwinkleOnField(int x, int y)
9394 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9397 if (Tile[x][y] == EL_BD_DIAMOND)
9400 if (MovDelay[x][y] == 0) // next animation frame
9401 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9403 if (MovDelay[x][y] != 0) // wait some time before next frame
9407 DrawLevelElementAnimation(x, y, Tile[x][y]);
9409 if (MovDelay[x][y] != 0)
9411 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9412 10 - MovDelay[x][y]);
9414 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9419 static void MauerWaechst(int x, int y)
9423 if (!MovDelay[x][y]) // next animation frame
9424 MovDelay[x][y] = 3 * delay;
9426 if (MovDelay[x][y]) // wait some time before next frame
9430 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9432 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9433 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9435 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9438 if (!MovDelay[x][y])
9440 if (MovDir[x][y] == MV_LEFT)
9442 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9443 TEST_DrawLevelField(x - 1, y);
9445 else if (MovDir[x][y] == MV_RIGHT)
9447 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9448 TEST_DrawLevelField(x + 1, y);
9450 else if (MovDir[x][y] == MV_UP)
9452 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9453 TEST_DrawLevelField(x, y - 1);
9457 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9458 TEST_DrawLevelField(x, y + 1);
9461 Tile[x][y] = Store[x][y];
9463 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9464 TEST_DrawLevelField(x, y);
9469 static void MauerAbleger(int ax, int ay)
9471 int element = Tile[ax][ay];
9472 int graphic = el2img(element);
9473 boolean oben_frei = FALSE, unten_frei = FALSE;
9474 boolean links_frei = FALSE, rechts_frei = FALSE;
9475 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9476 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9477 boolean new_wall = FALSE;
9479 if (IS_ANIMATED(graphic))
9480 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9482 if (!MovDelay[ax][ay]) // start building new wall
9483 MovDelay[ax][ay] = 6;
9485 if (MovDelay[ax][ay]) // wait some time before building new wall
9488 if (MovDelay[ax][ay])
9492 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9494 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9496 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9498 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9501 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9502 element == EL_EXPANDABLE_WALL_ANY)
9506 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9507 Store[ax][ay-1] = element;
9508 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9509 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9510 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9511 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9516 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9517 Store[ax][ay+1] = element;
9518 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9519 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9520 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9521 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9526 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9527 element == EL_EXPANDABLE_WALL_ANY ||
9528 element == EL_EXPANDABLE_WALL ||
9529 element == EL_BD_EXPANDABLE_WALL)
9533 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9534 Store[ax-1][ay] = element;
9535 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9536 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9537 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9538 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9544 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9545 Store[ax+1][ay] = element;
9546 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9547 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9548 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9549 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9554 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9555 TEST_DrawLevelField(ax, ay);
9557 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9559 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9560 unten_massiv = TRUE;
9561 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9562 links_massiv = TRUE;
9563 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9564 rechts_massiv = TRUE;
9566 if (((oben_massiv && unten_massiv) ||
9567 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9568 element == EL_EXPANDABLE_WALL) &&
9569 ((links_massiv && rechts_massiv) ||
9570 element == EL_EXPANDABLE_WALL_VERTICAL))
9571 Tile[ax][ay] = EL_WALL;
9574 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9577 static void MauerAblegerStahl(int ax, int ay)
9579 int element = Tile[ax][ay];
9580 int graphic = el2img(element);
9581 boolean oben_frei = FALSE, unten_frei = FALSE;
9582 boolean links_frei = FALSE, rechts_frei = FALSE;
9583 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9584 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9585 boolean new_wall = FALSE;
9587 if (IS_ANIMATED(graphic))
9588 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9590 if (!MovDelay[ax][ay]) // start building new wall
9591 MovDelay[ax][ay] = 6;
9593 if (MovDelay[ax][ay]) // wait some time before building new wall
9596 if (MovDelay[ax][ay])
9600 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9602 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9604 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9606 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9609 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9610 element == EL_EXPANDABLE_STEELWALL_ANY)
9614 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9615 Store[ax][ay-1] = element;
9616 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9617 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9618 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9619 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9624 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9625 Store[ax][ay+1] = element;
9626 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9627 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9628 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9629 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9634 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9635 element == EL_EXPANDABLE_STEELWALL_ANY)
9639 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9640 Store[ax-1][ay] = element;
9641 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9642 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9643 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9644 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9650 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9651 Store[ax+1][ay] = element;
9652 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9653 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9654 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9655 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9660 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9662 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9663 unten_massiv = TRUE;
9664 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9665 links_massiv = TRUE;
9666 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9667 rechts_massiv = TRUE;
9669 if (((oben_massiv && unten_massiv) ||
9670 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9671 ((links_massiv && rechts_massiv) ||
9672 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9673 Tile[ax][ay] = EL_STEELWALL;
9676 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9679 static void CheckForDragon(int x, int y)
9682 boolean dragon_found = FALSE;
9683 static int xy[4][2] =
9691 for (i = 0; i < NUM_DIRECTIONS; i++)
9693 for (j = 0; j < 4; j++)
9695 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9697 if (IN_LEV_FIELD(xx, yy) &&
9698 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9700 if (Tile[xx][yy] == EL_DRAGON)
9701 dragon_found = TRUE;
9710 for (i = 0; i < NUM_DIRECTIONS; i++)
9712 for (j = 0; j < 3; j++)
9714 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9716 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9718 Tile[xx][yy] = EL_EMPTY;
9719 TEST_DrawLevelField(xx, yy);
9728 static void InitBuggyBase(int x, int y)
9730 int element = Tile[x][y];
9731 int activating_delay = FRAMES_PER_SECOND / 4;
9734 (element == EL_SP_BUGGY_BASE ?
9735 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9736 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9738 element == EL_SP_BUGGY_BASE_ACTIVE ?
9739 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9742 static void WarnBuggyBase(int x, int y)
9745 static int xy[4][2] =
9753 for (i = 0; i < NUM_DIRECTIONS; i++)
9755 int xx = x + xy[i][0];
9756 int yy = y + xy[i][1];
9758 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9760 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9767 static void InitTrap(int x, int y)
9769 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9772 static void ActivateTrap(int x, int y)
9774 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9777 static void ChangeActiveTrap(int x, int y)
9779 int graphic = IMG_TRAP_ACTIVE;
9781 // if new animation frame was drawn, correct crumbled sand border
9782 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9783 TEST_DrawLevelFieldCrumbled(x, y);
9786 static int getSpecialActionElement(int element, int number, int base_element)
9788 return (element != EL_EMPTY ? element :
9789 number != -1 ? base_element + number - 1 :
9793 static int getModifiedActionNumber(int value_old, int operator, int operand,
9794 int value_min, int value_max)
9796 int value_new = (operator == CA_MODE_SET ? operand :
9797 operator == CA_MODE_ADD ? value_old + operand :
9798 operator == CA_MODE_SUBTRACT ? value_old - operand :
9799 operator == CA_MODE_MULTIPLY ? value_old * operand :
9800 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9801 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9804 return (value_new < value_min ? value_min :
9805 value_new > value_max ? value_max :
9809 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9811 struct ElementInfo *ei = &element_info[element];
9812 struct ElementChangeInfo *change = &ei->change_page[page];
9813 int target_element = change->target_element;
9814 int action_type = change->action_type;
9815 int action_mode = change->action_mode;
9816 int action_arg = change->action_arg;
9817 int action_element = change->action_element;
9820 if (!change->has_action)
9823 // ---------- determine action paramater values -----------------------------
9825 int level_time_value =
9826 (level.time > 0 ? TimeLeft :
9829 int action_arg_element_raw =
9830 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9831 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9832 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9833 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9834 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9835 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9836 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9838 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9840 int action_arg_direction =
9841 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9842 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9843 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9844 change->actual_trigger_side :
9845 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9846 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9849 int action_arg_number_min =
9850 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9853 int action_arg_number_max =
9854 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9855 action_type == CA_SET_LEVEL_GEMS ? 999 :
9856 action_type == CA_SET_LEVEL_TIME ? 9999 :
9857 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9858 action_type == CA_SET_CE_VALUE ? 9999 :
9859 action_type == CA_SET_CE_SCORE ? 9999 :
9862 int action_arg_number_reset =
9863 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9864 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9865 action_type == CA_SET_LEVEL_TIME ? level.time :
9866 action_type == CA_SET_LEVEL_SCORE ? 0 :
9867 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9868 action_type == CA_SET_CE_SCORE ? 0 :
9871 int action_arg_number =
9872 (action_arg <= CA_ARG_MAX ? action_arg :
9873 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9874 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9875 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9876 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9877 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9878 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9879 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9880 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9881 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9882 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9883 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9884 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9885 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9886 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9887 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9888 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9889 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9890 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9891 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9892 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9893 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9896 int action_arg_number_old =
9897 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9898 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9899 action_type == CA_SET_LEVEL_SCORE ? game.score :
9900 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9901 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9904 int action_arg_number_new =
9905 getModifiedActionNumber(action_arg_number_old,
9906 action_mode, action_arg_number,
9907 action_arg_number_min, action_arg_number_max);
9909 int trigger_player_bits =
9910 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9911 change->actual_trigger_player_bits : change->trigger_player);
9913 int action_arg_player_bits =
9914 (action_arg >= CA_ARG_PLAYER_1 &&
9915 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9916 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9917 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9920 // ---------- execute action -----------------------------------------------
9922 switch (action_type)
9929 // ---------- level actions ----------------------------------------------
9931 case CA_RESTART_LEVEL:
9933 game.restart_level = TRUE;
9938 case CA_SHOW_ENVELOPE:
9940 int element = getSpecialActionElement(action_arg_element,
9941 action_arg_number, EL_ENVELOPE_1);
9943 if (IS_ENVELOPE(element))
9944 local_player->show_envelope = element;
9949 case CA_SET_LEVEL_TIME:
9951 if (level.time > 0) // only modify limited time value
9953 TimeLeft = action_arg_number_new;
9955 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9957 DisplayGameControlValues();
9959 if (!TimeLeft && setup.time_limit)
9960 for (i = 0; i < MAX_PLAYERS; i++)
9961 KillPlayer(&stored_player[i]);
9967 case CA_SET_LEVEL_SCORE:
9969 game.score = action_arg_number_new;
9971 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9973 DisplayGameControlValues();
9978 case CA_SET_LEVEL_GEMS:
9980 game.gems_still_needed = action_arg_number_new;
9982 game.snapshot.collected_item = TRUE;
9984 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9986 DisplayGameControlValues();
9991 case CA_SET_LEVEL_WIND:
9993 game.wind_direction = action_arg_direction;
9998 case CA_SET_LEVEL_RANDOM_SEED:
10000 // ensure that setting a new random seed while playing is predictable
10001 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10006 // ---------- player actions ---------------------------------------------
10008 case CA_MOVE_PLAYER:
10009 case CA_MOVE_PLAYER_NEW:
10011 // automatically move to the next field in specified direction
10012 for (i = 0; i < MAX_PLAYERS; i++)
10013 if (trigger_player_bits & (1 << i))
10014 if (action_type == CA_MOVE_PLAYER ||
10015 stored_player[i].MovPos == 0)
10016 stored_player[i].programmed_action = action_arg_direction;
10021 case CA_EXIT_PLAYER:
10023 for (i = 0; i < MAX_PLAYERS; i++)
10024 if (action_arg_player_bits & (1 << i))
10025 ExitPlayer(&stored_player[i]);
10027 if (game.players_still_needed == 0)
10033 case CA_KILL_PLAYER:
10035 for (i = 0; i < MAX_PLAYERS; i++)
10036 if (action_arg_player_bits & (1 << i))
10037 KillPlayer(&stored_player[i]);
10042 case CA_SET_PLAYER_KEYS:
10044 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10045 int element = getSpecialActionElement(action_arg_element,
10046 action_arg_number, EL_KEY_1);
10048 if (IS_KEY(element))
10050 for (i = 0; i < MAX_PLAYERS; i++)
10052 if (trigger_player_bits & (1 << i))
10054 stored_player[i].key[KEY_NR(element)] = key_state;
10056 DrawGameDoorValues();
10064 case CA_SET_PLAYER_SPEED:
10066 for (i = 0; i < MAX_PLAYERS; i++)
10068 if (trigger_player_bits & (1 << i))
10070 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10072 if (action_arg == CA_ARG_SPEED_FASTER &&
10073 stored_player[i].cannot_move)
10075 action_arg_number = STEPSIZE_VERY_SLOW;
10077 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10078 action_arg == CA_ARG_SPEED_FASTER)
10080 action_arg_number = 2;
10081 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10084 else if (action_arg == CA_ARG_NUMBER_RESET)
10086 action_arg_number = level.initial_player_stepsize[i];
10090 getModifiedActionNumber(move_stepsize,
10093 action_arg_number_min,
10094 action_arg_number_max);
10096 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10103 case CA_SET_PLAYER_SHIELD:
10105 for (i = 0; i < MAX_PLAYERS; i++)
10107 if (trigger_player_bits & (1 << i))
10109 if (action_arg == CA_ARG_SHIELD_OFF)
10111 stored_player[i].shield_normal_time_left = 0;
10112 stored_player[i].shield_deadly_time_left = 0;
10114 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10116 stored_player[i].shield_normal_time_left = 999999;
10118 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10120 stored_player[i].shield_normal_time_left = 999999;
10121 stored_player[i].shield_deadly_time_left = 999999;
10129 case CA_SET_PLAYER_GRAVITY:
10131 for (i = 0; i < MAX_PLAYERS; i++)
10133 if (trigger_player_bits & (1 << i))
10135 stored_player[i].gravity =
10136 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10137 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10138 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10139 stored_player[i].gravity);
10146 case CA_SET_PLAYER_ARTWORK:
10148 for (i = 0; i < MAX_PLAYERS; i++)
10150 if (trigger_player_bits & (1 << i))
10152 int artwork_element = action_arg_element;
10154 if (action_arg == CA_ARG_ELEMENT_RESET)
10156 (level.use_artwork_element[i] ? level.artwork_element[i] :
10157 stored_player[i].element_nr);
10159 if (stored_player[i].artwork_element != artwork_element)
10160 stored_player[i].Frame = 0;
10162 stored_player[i].artwork_element = artwork_element;
10164 SetPlayerWaiting(&stored_player[i], FALSE);
10166 // set number of special actions for bored and sleeping animation
10167 stored_player[i].num_special_action_bored =
10168 get_num_special_action(artwork_element,
10169 ACTION_BORING_1, ACTION_BORING_LAST);
10170 stored_player[i].num_special_action_sleeping =
10171 get_num_special_action(artwork_element,
10172 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10179 case CA_SET_PLAYER_INVENTORY:
10181 for (i = 0; i < MAX_PLAYERS; i++)
10183 struct PlayerInfo *player = &stored_player[i];
10186 if (trigger_player_bits & (1 << i))
10188 int inventory_element = action_arg_element;
10190 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10191 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10192 action_arg == CA_ARG_ELEMENT_ACTION)
10194 int element = inventory_element;
10195 int collect_count = element_info[element].collect_count_initial;
10197 if (!IS_CUSTOM_ELEMENT(element))
10200 if (collect_count == 0)
10201 player->inventory_infinite_element = element;
10203 for (k = 0; k < collect_count; k++)
10204 if (player->inventory_size < MAX_INVENTORY_SIZE)
10205 player->inventory_element[player->inventory_size++] =
10208 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10209 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10210 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10212 if (player->inventory_infinite_element != EL_UNDEFINED &&
10213 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10214 action_arg_element_raw))
10215 player->inventory_infinite_element = EL_UNDEFINED;
10217 for (k = 0, j = 0; j < player->inventory_size; j++)
10219 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10220 action_arg_element_raw))
10221 player->inventory_element[k++] = player->inventory_element[j];
10224 player->inventory_size = k;
10226 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10228 if (player->inventory_size > 0)
10230 for (j = 0; j < player->inventory_size - 1; j++)
10231 player->inventory_element[j] = player->inventory_element[j + 1];
10233 player->inventory_size--;
10236 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10238 if (player->inventory_size > 0)
10239 player->inventory_size--;
10241 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10243 player->inventory_infinite_element = EL_UNDEFINED;
10244 player->inventory_size = 0;
10246 else if (action_arg == CA_ARG_INVENTORY_RESET)
10248 player->inventory_infinite_element = EL_UNDEFINED;
10249 player->inventory_size = 0;
10251 if (level.use_initial_inventory[i])
10253 for (j = 0; j < level.initial_inventory_size[i]; j++)
10255 int element = level.initial_inventory_content[i][j];
10256 int collect_count = element_info[element].collect_count_initial;
10258 if (!IS_CUSTOM_ELEMENT(element))
10261 if (collect_count == 0)
10262 player->inventory_infinite_element = element;
10264 for (k = 0; k < collect_count; k++)
10265 if (player->inventory_size < MAX_INVENTORY_SIZE)
10266 player->inventory_element[player->inventory_size++] =
10277 // ---------- CE actions -------------------------------------------------
10279 case CA_SET_CE_VALUE:
10281 int last_ce_value = CustomValue[x][y];
10283 CustomValue[x][y] = action_arg_number_new;
10285 if (CustomValue[x][y] != last_ce_value)
10287 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10288 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10290 if (CustomValue[x][y] == 0)
10292 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10293 ChangeCount[x][y] = 0; // allow at least one more change
10295 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10296 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10303 case CA_SET_CE_SCORE:
10305 int last_ce_score = ei->collect_score;
10307 ei->collect_score = action_arg_number_new;
10309 if (ei->collect_score != last_ce_score)
10311 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10312 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10314 if (ei->collect_score == 0)
10318 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10319 ChangeCount[x][y] = 0; // allow at least one more change
10321 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10322 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10325 This is a very special case that seems to be a mixture between
10326 CheckElementChange() and CheckTriggeredElementChange(): while
10327 the first one only affects single elements that are triggered
10328 directly, the second one affects multiple elements in the playfield
10329 that are triggered indirectly by another element. This is a third
10330 case: Changing the CE score always affects multiple identical CEs,
10331 so every affected CE must be checked, not only the single CE for
10332 which the CE score was changed in the first place (as every instance
10333 of that CE shares the same CE score, and therefore also can change)!
10335 SCAN_PLAYFIELD(xx, yy)
10337 if (Tile[xx][yy] == element)
10338 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10339 CE_SCORE_GETS_ZERO);
10347 case CA_SET_CE_ARTWORK:
10349 int artwork_element = action_arg_element;
10350 boolean reset_frame = FALSE;
10353 if (action_arg == CA_ARG_ELEMENT_RESET)
10354 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10357 if (ei->gfx_element != artwork_element)
10358 reset_frame = TRUE;
10360 ei->gfx_element = artwork_element;
10362 SCAN_PLAYFIELD(xx, yy)
10364 if (Tile[xx][yy] == element)
10368 ResetGfxAnimation(xx, yy);
10369 ResetRandomAnimationValue(xx, yy);
10372 TEST_DrawLevelField(xx, yy);
10379 // ---------- engine actions ---------------------------------------------
10381 case CA_SET_ENGINE_SCAN_MODE:
10383 InitPlayfieldScanMode(action_arg);
10393 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10395 int old_element = Tile[x][y];
10396 int new_element = GetElementFromGroupElement(element);
10397 int previous_move_direction = MovDir[x][y];
10398 int last_ce_value = CustomValue[x][y];
10399 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10400 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10401 boolean add_player_onto_element = (new_element_is_player &&
10402 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10403 IS_WALKABLE(old_element));
10405 if (!add_player_onto_element)
10407 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10408 RemoveMovingField(x, y);
10412 Tile[x][y] = new_element;
10414 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10415 MovDir[x][y] = previous_move_direction;
10417 if (element_info[new_element].use_last_ce_value)
10418 CustomValue[x][y] = last_ce_value;
10420 InitField_WithBug1(x, y, FALSE);
10422 new_element = Tile[x][y]; // element may have changed
10424 ResetGfxAnimation(x, y);
10425 ResetRandomAnimationValue(x, y);
10427 TEST_DrawLevelField(x, y);
10429 if (GFX_CRUMBLED(new_element))
10430 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10433 // check if element under the player changes from accessible to unaccessible
10434 // (needed for special case of dropping element which then changes)
10435 // (must be checked after creating new element for walkable group elements)
10436 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10437 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10444 // "ChangeCount" not set yet to allow "entered by player" change one time
10445 if (new_element_is_player)
10446 RelocatePlayer(x, y, new_element);
10449 ChangeCount[x][y]++; // count number of changes in the same frame
10451 TestIfBadThingTouchesPlayer(x, y);
10452 TestIfPlayerTouchesCustomElement(x, y);
10453 TestIfElementTouchesCustomElement(x, y);
10456 static void CreateField(int x, int y, int element)
10458 CreateFieldExt(x, y, element, FALSE);
10461 static void CreateElementFromChange(int x, int y, int element)
10463 element = GET_VALID_RUNTIME_ELEMENT(element);
10465 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10467 int old_element = Tile[x][y];
10469 // prevent changed element from moving in same engine frame
10470 // unless both old and new element can either fall or move
10471 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10472 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10476 CreateFieldExt(x, y, element, TRUE);
10479 static boolean ChangeElement(int x, int y, int element, int page)
10481 struct ElementInfo *ei = &element_info[element];
10482 struct ElementChangeInfo *change = &ei->change_page[page];
10483 int ce_value = CustomValue[x][y];
10484 int ce_score = ei->collect_score;
10485 int target_element;
10486 int old_element = Tile[x][y];
10488 // always use default change event to prevent running into a loop
10489 if (ChangeEvent[x][y] == -1)
10490 ChangeEvent[x][y] = CE_DELAY;
10492 if (ChangeEvent[x][y] == CE_DELAY)
10494 // reset actual trigger element, trigger player and action element
10495 change->actual_trigger_element = EL_EMPTY;
10496 change->actual_trigger_player = EL_EMPTY;
10497 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10498 change->actual_trigger_side = CH_SIDE_NONE;
10499 change->actual_trigger_ce_value = 0;
10500 change->actual_trigger_ce_score = 0;
10503 // do not change elements more than a specified maximum number of changes
10504 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10507 ChangeCount[x][y]++; // count number of changes in the same frame
10509 if (change->explode)
10516 if (change->use_target_content)
10518 boolean complete_replace = TRUE;
10519 boolean can_replace[3][3];
10522 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10525 boolean is_walkable;
10526 boolean is_diggable;
10527 boolean is_collectible;
10528 boolean is_removable;
10529 boolean is_destructible;
10530 int ex = x + xx - 1;
10531 int ey = y + yy - 1;
10532 int content_element = change->target_content.e[xx][yy];
10535 can_replace[xx][yy] = TRUE;
10537 if (ex == x && ey == y) // do not check changing element itself
10540 if (content_element == EL_EMPTY_SPACE)
10542 can_replace[xx][yy] = FALSE; // do not replace border with space
10547 if (!IN_LEV_FIELD(ex, ey))
10549 can_replace[xx][yy] = FALSE;
10550 complete_replace = FALSE;
10557 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10558 e = MovingOrBlocked2Element(ex, ey);
10560 is_empty = (IS_FREE(ex, ey) ||
10561 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10563 is_walkable = (is_empty || IS_WALKABLE(e));
10564 is_diggable = (is_empty || IS_DIGGABLE(e));
10565 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10566 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10567 is_removable = (is_diggable || is_collectible);
10569 can_replace[xx][yy] =
10570 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10571 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10572 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10573 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10574 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10575 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10576 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10578 if (!can_replace[xx][yy])
10579 complete_replace = FALSE;
10582 if (!change->only_if_complete || complete_replace)
10584 boolean something_has_changed = FALSE;
10586 if (change->only_if_complete && change->use_random_replace &&
10587 RND(100) < change->random_percentage)
10590 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10592 int ex = x + xx - 1;
10593 int ey = y + yy - 1;
10594 int content_element;
10596 if (can_replace[xx][yy] && (!change->use_random_replace ||
10597 RND(100) < change->random_percentage))
10599 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10600 RemoveMovingField(ex, ey);
10602 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10604 content_element = change->target_content.e[xx][yy];
10605 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10606 ce_value, ce_score);
10608 CreateElementFromChange(ex, ey, target_element);
10610 something_has_changed = TRUE;
10612 // for symmetry reasons, freeze newly created border elements
10613 if (ex != x || ey != y)
10614 Stop[ex][ey] = TRUE; // no more moving in this frame
10618 if (something_has_changed)
10620 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10621 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10627 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10628 ce_value, ce_score);
10630 if (element == EL_DIAGONAL_GROWING ||
10631 element == EL_DIAGONAL_SHRINKING)
10633 target_element = Store[x][y];
10635 Store[x][y] = EL_EMPTY;
10638 CreateElementFromChange(x, y, target_element);
10640 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10641 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10644 // this uses direct change before indirect change
10645 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10650 static void HandleElementChange(int x, int y, int page)
10652 int element = MovingOrBlocked2Element(x, y);
10653 struct ElementInfo *ei = &element_info[element];
10654 struct ElementChangeInfo *change = &ei->change_page[page];
10655 boolean handle_action_before_change = FALSE;
10658 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10659 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10661 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10662 x, y, element, element_info[element].token_name);
10663 Debug("game:playing:HandleElementChange", "This should never happen!");
10667 // this can happen with classic bombs on walkable, changing elements
10668 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10673 if (ChangeDelay[x][y] == 0) // initialize element change
10675 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10677 if (change->can_change)
10679 // !!! not clear why graphic animation should be reset at all here !!!
10680 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10681 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10684 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10686 When using an animation frame delay of 1 (this only happens with
10687 "sp_zonk.moving.left/right" in the classic graphics), the default
10688 (non-moving) animation shows wrong animation frames (while the
10689 moving animation, like "sp_zonk.moving.left/right", is correct,
10690 so this graphical bug never shows up with the classic graphics).
10691 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10692 be drawn instead of the correct frames 0,1,2,3. This is caused by
10693 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10694 an element change: First when the change delay ("ChangeDelay[][]")
10695 counter has reached zero after decrementing, then a second time in
10696 the next frame (after "GfxFrame[][]" was already incremented) when
10697 "ChangeDelay[][]" is reset to the initial delay value again.
10699 This causes frame 0 to be drawn twice, while the last frame won't
10700 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10702 As some animations may already be cleverly designed around this bug
10703 (at least the "Snake Bite" snake tail animation does this), it cannot
10704 simply be fixed here without breaking such existing animations.
10705 Unfortunately, it cannot easily be detected if a graphics set was
10706 designed "before" or "after" the bug was fixed. As a workaround,
10707 a new graphics set option "game.graphics_engine_version" was added
10708 to be able to specify the game's major release version for which the
10709 graphics set was designed, which can then be used to decide if the
10710 bugfix should be used (version 4 and above) or not (version 3 or
10711 below, or if no version was specified at all, as with old sets).
10713 (The wrong/fixed animation frames can be tested with the test level set
10714 "test_gfxframe" and level "000", which contains a specially prepared
10715 custom element at level position (x/y) == (11/9) which uses the zonk
10716 animation mentioned above. Using "game.graphics_engine_version: 4"
10717 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10718 This can also be seen from the debug output for this test element.)
10721 // when a custom element is about to change (for example by change delay),
10722 // do not reset graphic animation when the custom element is moving
10723 if (game.graphics_engine_version < 4 &&
10726 ResetGfxAnimation(x, y);
10727 ResetRandomAnimationValue(x, y);
10730 if (change->pre_change_function)
10731 change->pre_change_function(x, y);
10735 ChangeDelay[x][y]--;
10737 if (ChangeDelay[x][y] != 0) // continue element change
10739 if (change->can_change)
10741 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10743 if (IS_ANIMATED(graphic))
10744 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10746 if (change->change_function)
10747 change->change_function(x, y);
10750 else // finish element change
10752 if (ChangePage[x][y] != -1) // remember page from delayed change
10754 page = ChangePage[x][y];
10755 ChangePage[x][y] = -1;
10757 change = &ei->change_page[page];
10760 if (IS_MOVING(x, y)) // never change a running system ;-)
10762 ChangeDelay[x][y] = 1; // try change after next move step
10763 ChangePage[x][y] = page; // remember page to use for change
10768 // special case: set new level random seed before changing element
10769 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10770 handle_action_before_change = TRUE;
10772 if (change->has_action && handle_action_before_change)
10773 ExecuteCustomElementAction(x, y, element, page);
10775 if (change->can_change)
10777 if (ChangeElement(x, y, element, page))
10779 if (change->post_change_function)
10780 change->post_change_function(x, y);
10784 if (change->has_action && !handle_action_before_change)
10785 ExecuteCustomElementAction(x, y, element, page);
10789 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10790 int trigger_element,
10792 int trigger_player,
10796 boolean change_done_any = FALSE;
10797 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10800 if (!(trigger_events[trigger_element][trigger_event]))
10803 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10805 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10807 int element = EL_CUSTOM_START + i;
10808 boolean change_done = FALSE;
10811 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10812 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10815 for (p = 0; p < element_info[element].num_change_pages; p++)
10817 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10819 if (change->can_change_or_has_action &&
10820 change->has_event[trigger_event] &&
10821 change->trigger_side & trigger_side &&
10822 change->trigger_player & trigger_player &&
10823 change->trigger_page & trigger_page_bits &&
10824 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10826 change->actual_trigger_element = trigger_element;
10827 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10828 change->actual_trigger_player_bits = trigger_player;
10829 change->actual_trigger_side = trigger_side;
10830 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10831 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10833 if ((change->can_change && !change_done) || change->has_action)
10837 SCAN_PLAYFIELD(x, y)
10839 if (Tile[x][y] == element)
10841 if (change->can_change && !change_done)
10843 // if element already changed in this frame, not only prevent
10844 // another element change (checked in ChangeElement()), but
10845 // also prevent additional element actions for this element
10847 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10848 !level.use_action_after_change_bug)
10851 ChangeDelay[x][y] = 1;
10852 ChangeEvent[x][y] = trigger_event;
10854 HandleElementChange(x, y, p);
10856 else if (change->has_action)
10858 // if element already changed in this frame, not only prevent
10859 // another element change (checked in ChangeElement()), but
10860 // also prevent additional element actions for this element
10862 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10863 !level.use_action_after_change_bug)
10866 ExecuteCustomElementAction(x, y, element, p);
10867 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10872 if (change->can_change)
10874 change_done = TRUE;
10875 change_done_any = TRUE;
10882 RECURSION_LOOP_DETECTION_END();
10884 return change_done_any;
10887 static boolean CheckElementChangeExt(int x, int y,
10889 int trigger_element,
10891 int trigger_player,
10894 boolean change_done = FALSE;
10897 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10898 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10901 if (Tile[x][y] == EL_BLOCKED)
10903 Blocked2Moving(x, y, &x, &y);
10904 element = Tile[x][y];
10907 // check if element has already changed or is about to change after moving
10908 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10909 Tile[x][y] != element) ||
10911 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10912 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10913 ChangePage[x][y] != -1)))
10916 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10918 for (p = 0; p < element_info[element].num_change_pages; p++)
10920 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10922 /* check trigger element for all events where the element that is checked
10923 for changing interacts with a directly adjacent element -- this is
10924 different to element changes that affect other elements to change on the
10925 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10926 boolean check_trigger_element =
10927 (trigger_event == CE_TOUCHING_X ||
10928 trigger_event == CE_HITTING_X ||
10929 trigger_event == CE_HIT_BY_X ||
10930 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10932 if (change->can_change_or_has_action &&
10933 change->has_event[trigger_event] &&
10934 change->trigger_side & trigger_side &&
10935 change->trigger_player & trigger_player &&
10936 (!check_trigger_element ||
10937 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10939 change->actual_trigger_element = trigger_element;
10940 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10941 change->actual_trigger_player_bits = trigger_player;
10942 change->actual_trigger_side = trigger_side;
10943 change->actual_trigger_ce_value = CustomValue[x][y];
10944 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10946 // special case: trigger element not at (x,y) position for some events
10947 if (check_trigger_element)
10959 { 0, 0 }, { 0, 0 }, { 0, 0 },
10963 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10964 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10966 change->actual_trigger_ce_value = CustomValue[xx][yy];
10967 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10970 if (change->can_change && !change_done)
10972 ChangeDelay[x][y] = 1;
10973 ChangeEvent[x][y] = trigger_event;
10975 HandleElementChange(x, y, p);
10977 change_done = TRUE;
10979 else if (change->has_action)
10981 ExecuteCustomElementAction(x, y, element, p);
10982 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10987 RECURSION_LOOP_DETECTION_END();
10989 return change_done;
10992 static void PlayPlayerSound(struct PlayerInfo *player)
10994 int jx = player->jx, jy = player->jy;
10995 int sound_element = player->artwork_element;
10996 int last_action = player->last_action_waiting;
10997 int action = player->action_waiting;
10999 if (player->is_waiting)
11001 if (action != last_action)
11002 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11004 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11008 if (action != last_action)
11009 StopSound(element_info[sound_element].sound[last_action]);
11011 if (last_action == ACTION_SLEEPING)
11012 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11016 static void PlayAllPlayersSound(void)
11020 for (i = 0; i < MAX_PLAYERS; i++)
11021 if (stored_player[i].active)
11022 PlayPlayerSound(&stored_player[i]);
11025 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11027 boolean last_waiting = player->is_waiting;
11028 int move_dir = player->MovDir;
11030 player->dir_waiting = move_dir;
11031 player->last_action_waiting = player->action_waiting;
11035 if (!last_waiting) // not waiting -> waiting
11037 player->is_waiting = TRUE;
11039 player->frame_counter_bored =
11041 game.player_boring_delay_fixed +
11042 GetSimpleRandom(game.player_boring_delay_random);
11043 player->frame_counter_sleeping =
11045 game.player_sleeping_delay_fixed +
11046 GetSimpleRandom(game.player_sleeping_delay_random);
11048 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11051 if (game.player_sleeping_delay_fixed +
11052 game.player_sleeping_delay_random > 0 &&
11053 player->anim_delay_counter == 0 &&
11054 player->post_delay_counter == 0 &&
11055 FrameCounter >= player->frame_counter_sleeping)
11056 player->is_sleeping = TRUE;
11057 else if (game.player_boring_delay_fixed +
11058 game.player_boring_delay_random > 0 &&
11059 FrameCounter >= player->frame_counter_bored)
11060 player->is_bored = TRUE;
11062 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11063 player->is_bored ? ACTION_BORING :
11066 if (player->is_sleeping && player->use_murphy)
11068 // special case for sleeping Murphy when leaning against non-free tile
11070 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11071 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11072 !IS_MOVING(player->jx - 1, player->jy)))
11073 move_dir = MV_LEFT;
11074 else 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_RIGHT;
11079 player->is_sleeping = FALSE;
11081 player->dir_waiting = move_dir;
11084 if (player->is_sleeping)
11086 if (player->num_special_action_sleeping > 0)
11088 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11090 int last_special_action = player->special_action_sleeping;
11091 int num_special_action = player->num_special_action_sleeping;
11092 int special_action =
11093 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11094 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11095 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11096 last_special_action + 1 : ACTION_SLEEPING);
11097 int special_graphic =
11098 el_act_dir2img(player->artwork_element, special_action, move_dir);
11100 player->anim_delay_counter =
11101 graphic_info[special_graphic].anim_delay_fixed +
11102 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11103 player->post_delay_counter =
11104 graphic_info[special_graphic].post_delay_fixed +
11105 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11107 player->special_action_sleeping = special_action;
11110 if (player->anim_delay_counter > 0)
11112 player->action_waiting = player->special_action_sleeping;
11113 player->anim_delay_counter--;
11115 else if (player->post_delay_counter > 0)
11117 player->post_delay_counter--;
11121 else if (player->is_bored)
11123 if (player->num_special_action_bored > 0)
11125 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11127 int special_action =
11128 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11129 int special_graphic =
11130 el_act_dir2img(player->artwork_element, special_action, move_dir);
11132 player->anim_delay_counter =
11133 graphic_info[special_graphic].anim_delay_fixed +
11134 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11135 player->post_delay_counter =
11136 graphic_info[special_graphic].post_delay_fixed +
11137 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11139 player->special_action_bored = special_action;
11142 if (player->anim_delay_counter > 0)
11144 player->action_waiting = player->special_action_bored;
11145 player->anim_delay_counter--;
11147 else if (player->post_delay_counter > 0)
11149 player->post_delay_counter--;
11154 else if (last_waiting) // waiting -> not waiting
11156 player->is_waiting = FALSE;
11157 player->is_bored = FALSE;
11158 player->is_sleeping = FALSE;
11160 player->frame_counter_bored = -1;
11161 player->frame_counter_sleeping = -1;
11163 player->anim_delay_counter = 0;
11164 player->post_delay_counter = 0;
11166 player->dir_waiting = player->MovDir;
11167 player->action_waiting = ACTION_DEFAULT;
11169 player->special_action_bored = ACTION_DEFAULT;
11170 player->special_action_sleeping = ACTION_DEFAULT;
11174 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11176 if ((!player->is_moving && player->was_moving) ||
11177 (player->MovPos == 0 && player->was_moving) ||
11178 (player->is_snapping && !player->was_snapping) ||
11179 (player->is_dropping && !player->was_dropping))
11181 if (!CheckSaveEngineSnapshotToList())
11184 player->was_moving = FALSE;
11185 player->was_snapping = TRUE;
11186 player->was_dropping = TRUE;
11190 if (player->is_moving)
11191 player->was_moving = TRUE;
11193 if (!player->is_snapping)
11194 player->was_snapping = FALSE;
11196 if (!player->is_dropping)
11197 player->was_dropping = FALSE;
11201 static void CheckSingleStepMode(struct PlayerInfo *player)
11203 if (tape.single_step && tape.recording && !tape.pausing)
11205 /* as it is called "single step mode", just return to pause mode when the
11206 player stopped moving after one tile (or never starts moving at all) */
11207 if (!player->is_moving &&
11208 !player->is_pushing &&
11209 !player->is_dropping_pressed)
11210 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11213 CheckSaveEngineSnapshot(player);
11216 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11218 int left = player_action & JOY_LEFT;
11219 int right = player_action & JOY_RIGHT;
11220 int up = player_action & JOY_UP;
11221 int down = player_action & JOY_DOWN;
11222 int button1 = player_action & JOY_BUTTON_1;
11223 int button2 = player_action & JOY_BUTTON_2;
11224 int dx = (left ? -1 : right ? 1 : 0);
11225 int dy = (up ? -1 : down ? 1 : 0);
11227 if (!player->active || tape.pausing)
11233 SnapField(player, dx, dy);
11237 DropElement(player);
11239 MovePlayer(player, dx, dy);
11242 CheckSingleStepMode(player);
11244 SetPlayerWaiting(player, FALSE);
11246 return player_action;
11250 // no actions for this player (no input at player's configured device)
11252 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11253 SnapField(player, 0, 0);
11254 CheckGravityMovementWhenNotMoving(player);
11256 if (player->MovPos == 0)
11257 SetPlayerWaiting(player, TRUE);
11259 if (player->MovPos == 0) // needed for tape.playing
11260 player->is_moving = FALSE;
11262 player->is_dropping = FALSE;
11263 player->is_dropping_pressed = FALSE;
11264 player->drop_pressed_delay = 0;
11266 CheckSingleStepMode(player);
11272 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11275 if (!tape.use_mouse_actions)
11278 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11279 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11280 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11283 static void SetTapeActionFromMouseAction(byte *tape_action,
11284 struct MouseActionInfo *mouse_action)
11286 if (!tape.use_mouse_actions)
11289 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11290 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11291 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11294 static void CheckLevelSolved(void)
11296 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11298 if (game_em.level_solved &&
11299 !game_em.game_over) // game won
11303 game_em.game_over = TRUE;
11305 game.all_players_gone = TRUE;
11308 if (game_em.game_over) // game lost
11309 game.all_players_gone = TRUE;
11311 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11313 if (game_sp.level_solved &&
11314 !game_sp.game_over) // game won
11318 game_sp.game_over = TRUE;
11320 game.all_players_gone = TRUE;
11323 if (game_sp.game_over) // game lost
11324 game.all_players_gone = TRUE;
11326 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11328 if (game_mm.level_solved &&
11329 !game_mm.game_over) // game won
11333 game_mm.game_over = TRUE;
11335 game.all_players_gone = TRUE;
11338 if (game_mm.game_over) // game lost
11339 game.all_players_gone = TRUE;
11343 static void CheckLevelTime(void)
11347 if (TimeFrames >= FRAMES_PER_SECOND)
11352 for (i = 0; i < MAX_PLAYERS; i++)
11354 struct PlayerInfo *player = &stored_player[i];
11356 if (SHIELD_ON(player))
11358 player->shield_normal_time_left--;
11360 if (player->shield_deadly_time_left > 0)
11361 player->shield_deadly_time_left--;
11365 if (!game.LevelSolved && !level.use_step_counter)
11373 if (TimeLeft <= 10 && setup.time_limit)
11374 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11376 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11377 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11379 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11381 if (!TimeLeft && setup.time_limit)
11383 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11384 game_em.lev->killed_out_of_time = TRUE;
11386 for (i = 0; i < MAX_PLAYERS; i++)
11387 KillPlayer(&stored_player[i]);
11390 else if (game.no_time_limit && !game.all_players_gone)
11392 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11395 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11398 if (tape.recording || tape.playing)
11399 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11402 if (tape.recording || tape.playing)
11403 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11405 UpdateAndDisplayGameControlValues();
11408 void AdvanceFrameAndPlayerCounters(int player_nr)
11412 // advance frame counters (global frame counter and time frame counter)
11416 // advance player counters (counters for move delay, move animation etc.)
11417 for (i = 0; i < MAX_PLAYERS; i++)
11419 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11420 int move_delay_value = stored_player[i].move_delay_value;
11421 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11423 if (!advance_player_counters) // not all players may be affected
11426 if (move_frames == 0) // less than one move per game frame
11428 int stepsize = TILEX / move_delay_value;
11429 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11430 int count = (stored_player[i].is_moving ?
11431 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11433 if (count % delay == 0)
11437 stored_player[i].Frame += move_frames;
11439 if (stored_player[i].MovPos != 0)
11440 stored_player[i].StepFrame += move_frames;
11442 if (stored_player[i].move_delay > 0)
11443 stored_player[i].move_delay--;
11445 // due to bugs in previous versions, counter must count up, not down
11446 if (stored_player[i].push_delay != -1)
11447 stored_player[i].push_delay++;
11449 if (stored_player[i].drop_delay > 0)
11450 stored_player[i].drop_delay--;
11452 if (stored_player[i].is_dropping_pressed)
11453 stored_player[i].drop_pressed_delay++;
11457 void StartGameActions(boolean init_network_game, boolean record_tape,
11460 unsigned int new_random_seed = InitRND(random_seed);
11463 TapeStartRecording(new_random_seed);
11465 if (init_network_game)
11467 SendToServer_LevelFile();
11468 SendToServer_StartPlaying();
11476 static void GameActionsExt(void)
11479 static unsigned int game_frame_delay = 0;
11481 unsigned int game_frame_delay_value;
11482 byte *recorded_player_action;
11483 byte summarized_player_action = 0;
11484 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11487 // detect endless loops, caused by custom element programming
11488 if (recursion_loop_detected && recursion_loop_depth == 0)
11490 char *message = getStringCat3("Internal Error! Element ",
11491 EL_NAME(recursion_loop_element),
11492 " caused endless loop! Quit the game?");
11494 Warn("element '%s' caused endless loop in game engine",
11495 EL_NAME(recursion_loop_element));
11497 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11499 recursion_loop_detected = FALSE; // if game should be continued
11506 if (game.restart_level)
11507 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11509 CheckLevelSolved();
11511 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11514 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11517 if (game_status != GAME_MODE_PLAYING) // status might have changed
11520 game_frame_delay_value =
11521 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11523 if (tape.playing && tape.warp_forward && !tape.pausing)
11524 game_frame_delay_value = 0;
11526 SetVideoFrameDelay(game_frame_delay_value);
11528 // (de)activate virtual buttons depending on current game status
11529 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11531 if (game.all_players_gone) // if no players there to be controlled anymore
11532 SetOverlayActive(FALSE);
11533 else if (!tape.playing) // if game continues after tape stopped playing
11534 SetOverlayActive(TRUE);
11539 // ---------- main game synchronization point ----------
11541 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11543 Debug("game:playing:skip", "skip == %d", skip);
11546 // ---------- main game synchronization point ----------
11548 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11552 if (network_playing && !network_player_action_received)
11554 // try to get network player actions in time
11556 // last chance to get network player actions without main loop delay
11557 HandleNetworking();
11559 // game was quit by network peer
11560 if (game_status != GAME_MODE_PLAYING)
11563 // check if network player actions still missing and game still running
11564 if (!network_player_action_received && !checkGameEnded())
11565 return; // failed to get network player actions in time
11567 // do not yet reset "network_player_action_received" (for tape.pausing)
11573 // at this point we know that we really continue executing the game
11575 network_player_action_received = FALSE;
11577 // when playing tape, read previously recorded player input from tape data
11578 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11580 local_player->effective_mouse_action = local_player->mouse_action;
11582 if (recorded_player_action != NULL)
11583 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11584 recorded_player_action);
11586 // TapePlayAction() may return NULL when toggling to "pause before death"
11590 if (tape.set_centered_player)
11592 game.centered_player_nr_next = tape.centered_player_nr_next;
11593 game.set_centered_player = TRUE;
11596 for (i = 0; i < MAX_PLAYERS; i++)
11598 summarized_player_action |= stored_player[i].action;
11600 if (!network_playing && (game.team_mode || tape.playing))
11601 stored_player[i].effective_action = stored_player[i].action;
11604 if (network_playing && !checkGameEnded())
11605 SendToServer_MovePlayer(summarized_player_action);
11607 // summarize all actions at local players mapped input device position
11608 // (this allows using different input devices in single player mode)
11609 if (!network.enabled && !game.team_mode)
11610 stored_player[map_player_action[local_player->index_nr]].effective_action =
11611 summarized_player_action;
11613 // summarize all actions at centered player in local team mode
11614 if (tape.recording &&
11615 setup.team_mode && !network.enabled &&
11616 setup.input_on_focus &&
11617 game.centered_player_nr != -1)
11619 for (i = 0; i < MAX_PLAYERS; i++)
11620 stored_player[map_player_action[i]].effective_action =
11621 (i == game.centered_player_nr ? summarized_player_action : 0);
11624 if (recorded_player_action != NULL)
11625 for (i = 0; i < MAX_PLAYERS; i++)
11626 stored_player[i].effective_action = recorded_player_action[i];
11628 for (i = 0; i < MAX_PLAYERS; i++)
11630 tape_action[i] = stored_player[i].effective_action;
11632 /* (this may happen in the RND game engine if a player was not present on
11633 the playfield on level start, but appeared later from a custom element */
11634 if (setup.team_mode &&
11637 !tape.player_participates[i])
11638 tape.player_participates[i] = TRUE;
11641 SetTapeActionFromMouseAction(tape_action,
11642 &local_player->effective_mouse_action);
11644 // only record actions from input devices, but not programmed actions
11645 if (tape.recording)
11646 TapeRecordAction(tape_action);
11648 // remember if game was played (especially after tape stopped playing)
11649 if (!tape.playing && summarized_player_action)
11650 game.GamePlayed = TRUE;
11652 #if USE_NEW_PLAYER_ASSIGNMENTS
11653 // !!! also map player actions in single player mode !!!
11654 // if (game.team_mode)
11657 byte mapped_action[MAX_PLAYERS];
11659 #if DEBUG_PLAYER_ACTIONS
11661 for (i = 0; i < MAX_PLAYERS; i++)
11662 printf(" %d, ", stored_player[i].effective_action);
11665 for (i = 0; i < MAX_PLAYERS; i++)
11666 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11668 for (i = 0; i < MAX_PLAYERS; i++)
11669 stored_player[i].effective_action = mapped_action[i];
11671 #if DEBUG_PLAYER_ACTIONS
11673 for (i = 0; i < MAX_PLAYERS; i++)
11674 printf(" %d, ", stored_player[i].effective_action);
11678 #if DEBUG_PLAYER_ACTIONS
11682 for (i = 0; i < MAX_PLAYERS; i++)
11683 printf(" %d, ", stored_player[i].effective_action);
11689 for (i = 0; i < MAX_PLAYERS; i++)
11691 // allow engine snapshot in case of changed movement attempt
11692 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11693 (stored_player[i].effective_action & KEY_MOTION))
11694 game.snapshot.changed_action = TRUE;
11696 // allow engine snapshot in case of snapping/dropping attempt
11697 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11698 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11699 game.snapshot.changed_action = TRUE;
11701 game.snapshot.last_action[i] = stored_player[i].effective_action;
11704 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11706 GameActions_EM_Main();
11708 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11710 GameActions_SP_Main();
11712 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11714 GameActions_MM_Main();
11718 GameActions_RND_Main();
11721 BlitScreenToBitmap(backbuffer);
11723 CheckLevelSolved();
11726 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11728 if (global.show_frames_per_second)
11730 static unsigned int fps_counter = 0;
11731 static int fps_frames = 0;
11732 unsigned int fps_delay_ms = Counter() - fps_counter;
11736 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11738 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11741 fps_counter = Counter();
11743 // always draw FPS to screen after FPS value was updated
11744 redraw_mask |= REDRAW_FPS;
11747 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11748 if (GetDrawDeactivationMask() == REDRAW_NONE)
11749 redraw_mask |= REDRAW_FPS;
11753 static void GameActions_CheckSaveEngineSnapshot(void)
11755 if (!game.snapshot.save_snapshot)
11758 // clear flag for saving snapshot _before_ saving snapshot
11759 game.snapshot.save_snapshot = FALSE;
11761 SaveEngineSnapshotToList();
11764 void GameActions(void)
11768 GameActions_CheckSaveEngineSnapshot();
11771 void GameActions_EM_Main(void)
11773 byte effective_action[MAX_PLAYERS];
11774 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11777 for (i = 0; i < MAX_PLAYERS; i++)
11778 effective_action[i] = stored_player[i].effective_action;
11780 GameActions_EM(effective_action, warp_mode);
11783 void GameActions_SP_Main(void)
11785 byte effective_action[MAX_PLAYERS];
11786 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11789 for (i = 0; i < MAX_PLAYERS; i++)
11790 effective_action[i] = stored_player[i].effective_action;
11792 GameActions_SP(effective_action, warp_mode);
11794 for (i = 0; i < MAX_PLAYERS; i++)
11796 if (stored_player[i].force_dropping)
11797 stored_player[i].action |= KEY_BUTTON_DROP;
11799 stored_player[i].force_dropping = FALSE;
11803 void GameActions_MM_Main(void)
11805 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11807 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11810 void GameActions_RND_Main(void)
11815 void GameActions_RND(void)
11817 static struct MouseActionInfo mouse_action_last = { 0 };
11818 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11819 int magic_wall_x = 0, magic_wall_y = 0;
11820 int i, x, y, element, graphic, last_gfx_frame;
11822 InitPlayfieldScanModeVars();
11824 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11826 SCAN_PLAYFIELD(x, y)
11828 ChangeCount[x][y] = 0;
11829 ChangeEvent[x][y] = -1;
11833 if (game.set_centered_player)
11835 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11837 // switching to "all players" only possible if all players fit to screen
11838 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11840 game.centered_player_nr_next = game.centered_player_nr;
11841 game.set_centered_player = FALSE;
11844 // do not switch focus to non-existing (or non-active) player
11845 if (game.centered_player_nr_next >= 0 &&
11846 !stored_player[game.centered_player_nr_next].active)
11848 game.centered_player_nr_next = game.centered_player_nr;
11849 game.set_centered_player = FALSE;
11853 if (game.set_centered_player &&
11854 ScreenMovPos == 0) // screen currently aligned at tile position
11858 if (game.centered_player_nr_next == -1)
11860 setScreenCenteredToAllPlayers(&sx, &sy);
11864 sx = stored_player[game.centered_player_nr_next].jx;
11865 sy = stored_player[game.centered_player_nr_next].jy;
11868 game.centered_player_nr = game.centered_player_nr_next;
11869 game.set_centered_player = FALSE;
11871 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11872 DrawGameDoorValues();
11875 for (i = 0; i < MAX_PLAYERS; i++)
11877 int actual_player_action = stored_player[i].effective_action;
11880 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11881 - rnd_equinox_tetrachloride 048
11882 - rnd_equinox_tetrachloride_ii 096
11883 - rnd_emanuel_schmieg 002
11884 - doctor_sloan_ww 001, 020
11886 if (stored_player[i].MovPos == 0)
11887 CheckGravityMovement(&stored_player[i]);
11890 // overwrite programmed action with tape action
11891 if (stored_player[i].programmed_action)
11892 actual_player_action = stored_player[i].programmed_action;
11894 PlayerActions(&stored_player[i], actual_player_action);
11896 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11899 ScrollScreen(NULL, SCROLL_GO_ON);
11901 /* for backwards compatibility, the following code emulates a fixed bug that
11902 occured when pushing elements (causing elements that just made their last
11903 pushing step to already (if possible) make their first falling step in the
11904 same game frame, which is bad); this code is also needed to use the famous
11905 "spring push bug" which is used in older levels and might be wanted to be
11906 used also in newer levels, but in this case the buggy pushing code is only
11907 affecting the "spring" element and no other elements */
11909 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11911 for (i = 0; i < MAX_PLAYERS; i++)
11913 struct PlayerInfo *player = &stored_player[i];
11914 int x = player->jx;
11915 int y = player->jy;
11917 if (player->active && player->is_pushing && player->is_moving &&
11919 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11920 Tile[x][y] == EL_SPRING))
11922 ContinueMoving(x, y);
11924 // continue moving after pushing (this is actually a bug)
11925 if (!IS_MOVING(x, y))
11926 Stop[x][y] = FALSE;
11931 SCAN_PLAYFIELD(x, y)
11933 Last[x][y] = Tile[x][y];
11935 ChangeCount[x][y] = 0;
11936 ChangeEvent[x][y] = -1;
11938 // this must be handled before main playfield loop
11939 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11942 if (MovDelay[x][y] <= 0)
11946 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
11949 if (MovDelay[x][y] <= 0)
11952 TEST_DrawLevelField(x, y);
11954 TestIfElementTouchesCustomElement(x, y); // for empty space
11959 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11961 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
11963 Debug("game:playing:GameActions_RND", "This should never happen!");
11965 ChangePage[x][y] = -1;
11969 Stop[x][y] = FALSE;
11970 if (WasJustMoving[x][y] > 0)
11971 WasJustMoving[x][y]--;
11972 if (WasJustFalling[x][y] > 0)
11973 WasJustFalling[x][y]--;
11974 if (CheckCollision[x][y] > 0)
11975 CheckCollision[x][y]--;
11976 if (CheckImpact[x][y] > 0)
11977 CheckImpact[x][y]--;
11981 /* reset finished pushing action (not done in ContinueMoving() to allow
11982 continuous pushing animation for elements with zero push delay) */
11983 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11985 ResetGfxAnimation(x, y);
11986 TEST_DrawLevelField(x, y);
11990 if (IS_BLOCKED(x, y))
11994 Blocked2Moving(x, y, &oldx, &oldy);
11995 if (!IS_MOVING(oldx, oldy))
11997 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
11998 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
11999 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12000 Debug("game:playing:GameActions_RND", "This should never happen!");
12006 if (mouse_action.button)
12008 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12010 x = mouse_action.lx;
12011 y = mouse_action.ly;
12012 element = Tile[x][y];
12016 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12017 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12020 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12021 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12024 SCAN_PLAYFIELD(x, y)
12026 element = Tile[x][y];
12027 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12028 last_gfx_frame = GfxFrame[x][y];
12030 ResetGfxFrame(x, y);
12032 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12033 DrawLevelGraphicAnimation(x, y, graphic);
12035 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12036 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12037 ResetRandomAnimationValue(x, y);
12039 SetRandomAnimationValue(x, y);
12041 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12043 if (IS_INACTIVE(element))
12045 if (IS_ANIMATED(graphic))
12046 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12051 // this may take place after moving, so 'element' may have changed
12052 if (IS_CHANGING(x, y) &&
12053 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12055 int page = element_info[element].event_page_nr[CE_DELAY];
12057 HandleElementChange(x, y, page);
12059 element = Tile[x][y];
12060 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12063 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12067 element = Tile[x][y];
12068 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12070 if (IS_ANIMATED(graphic) &&
12071 !IS_MOVING(x, y) &&
12073 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12075 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12076 TEST_DrawTwinkleOnField(x, y);
12078 else if (element == EL_ACID)
12081 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12083 else if ((element == EL_EXIT_OPEN ||
12084 element == EL_EM_EXIT_OPEN ||
12085 element == EL_SP_EXIT_OPEN ||
12086 element == EL_STEEL_EXIT_OPEN ||
12087 element == EL_EM_STEEL_EXIT_OPEN ||
12088 element == EL_SP_TERMINAL ||
12089 element == EL_SP_TERMINAL_ACTIVE ||
12090 element == EL_EXTRA_TIME ||
12091 element == EL_SHIELD_NORMAL ||
12092 element == EL_SHIELD_DEADLY) &&
12093 IS_ANIMATED(graphic))
12094 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12095 else if (IS_MOVING(x, y))
12096 ContinueMoving(x, y);
12097 else if (IS_ACTIVE_BOMB(element))
12098 CheckDynamite(x, y);
12099 else if (element == EL_AMOEBA_GROWING)
12100 AmoebaGrowing(x, y);
12101 else if (element == EL_AMOEBA_SHRINKING)
12102 AmoebaShrinking(x, y);
12104 #if !USE_NEW_AMOEBA_CODE
12105 else if (IS_AMOEBALIVE(element))
12106 AmoebaReproduce(x, y);
12109 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12111 else if (element == EL_EXIT_CLOSED)
12113 else if (element == EL_EM_EXIT_CLOSED)
12115 else if (element == EL_STEEL_EXIT_CLOSED)
12116 CheckExitSteel(x, y);
12117 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12118 CheckExitSteelEM(x, y);
12119 else if (element == EL_SP_EXIT_CLOSED)
12121 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12122 element == EL_EXPANDABLE_STEELWALL_GROWING)
12123 MauerWaechst(x, y);
12124 else if (element == EL_EXPANDABLE_WALL ||
12125 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12126 element == EL_EXPANDABLE_WALL_VERTICAL ||
12127 element == EL_EXPANDABLE_WALL_ANY ||
12128 element == EL_BD_EXPANDABLE_WALL)
12129 MauerAbleger(x, y);
12130 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12131 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12132 element == EL_EXPANDABLE_STEELWALL_ANY)
12133 MauerAblegerStahl(x, y);
12134 else if (element == EL_FLAMES)
12135 CheckForDragon(x, y);
12136 else if (element == EL_EXPLOSION)
12137 ; // drawing of correct explosion animation is handled separately
12138 else if (element == EL_ELEMENT_SNAPPING ||
12139 element == EL_DIAGONAL_SHRINKING ||
12140 element == EL_DIAGONAL_GROWING)
12142 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12144 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12146 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12147 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12149 if (IS_BELT_ACTIVE(element))
12150 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12152 if (game.magic_wall_active)
12154 int jx = local_player->jx, jy = local_player->jy;
12156 // play the element sound at the position nearest to the player
12157 if ((element == EL_MAGIC_WALL_FULL ||
12158 element == EL_MAGIC_WALL_ACTIVE ||
12159 element == EL_MAGIC_WALL_EMPTYING ||
12160 element == EL_BD_MAGIC_WALL_FULL ||
12161 element == EL_BD_MAGIC_WALL_ACTIVE ||
12162 element == EL_BD_MAGIC_WALL_EMPTYING ||
12163 element == EL_DC_MAGIC_WALL_FULL ||
12164 element == EL_DC_MAGIC_WALL_ACTIVE ||
12165 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12166 ABS(x - jx) + ABS(y - jy) <
12167 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12175 #if USE_NEW_AMOEBA_CODE
12176 // new experimental amoeba growth stuff
12177 if (!(FrameCounter % 8))
12179 static unsigned int random = 1684108901;
12181 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12183 x = RND(lev_fieldx);
12184 y = RND(lev_fieldy);
12185 element = Tile[x][y];
12187 if (!IS_PLAYER(x,y) &&
12188 (element == EL_EMPTY ||
12189 CAN_GROW_INTO(element) ||
12190 element == EL_QUICKSAND_EMPTY ||
12191 element == EL_QUICKSAND_FAST_EMPTY ||
12192 element == EL_ACID_SPLASH_LEFT ||
12193 element == EL_ACID_SPLASH_RIGHT))
12195 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12196 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12197 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12198 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12199 Tile[x][y] = EL_AMOEBA_DROP;
12202 random = random * 129 + 1;
12207 game.explosions_delayed = FALSE;
12209 SCAN_PLAYFIELD(x, y)
12211 element = Tile[x][y];
12213 if (ExplodeField[x][y])
12214 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12215 else if (element == EL_EXPLOSION)
12216 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12218 ExplodeField[x][y] = EX_TYPE_NONE;
12221 game.explosions_delayed = TRUE;
12223 if (game.magic_wall_active)
12225 if (!(game.magic_wall_time_left % 4))
12227 int element = Tile[magic_wall_x][magic_wall_y];
12229 if (element == EL_BD_MAGIC_WALL_FULL ||
12230 element == EL_BD_MAGIC_WALL_ACTIVE ||
12231 element == EL_BD_MAGIC_WALL_EMPTYING)
12232 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12233 else if (element == EL_DC_MAGIC_WALL_FULL ||
12234 element == EL_DC_MAGIC_WALL_ACTIVE ||
12235 element == EL_DC_MAGIC_WALL_EMPTYING)
12236 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12238 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12241 if (game.magic_wall_time_left > 0)
12243 game.magic_wall_time_left--;
12245 if (!game.magic_wall_time_left)
12247 SCAN_PLAYFIELD(x, y)
12249 element = Tile[x][y];
12251 if (element == EL_MAGIC_WALL_ACTIVE ||
12252 element == EL_MAGIC_WALL_FULL)
12254 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12255 TEST_DrawLevelField(x, y);
12257 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12258 element == EL_BD_MAGIC_WALL_FULL)
12260 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12261 TEST_DrawLevelField(x, y);
12263 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12264 element == EL_DC_MAGIC_WALL_FULL)
12266 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12267 TEST_DrawLevelField(x, y);
12271 game.magic_wall_active = FALSE;
12276 if (game.light_time_left > 0)
12278 game.light_time_left--;
12280 if (game.light_time_left == 0)
12281 RedrawAllLightSwitchesAndInvisibleElements();
12284 if (game.timegate_time_left > 0)
12286 game.timegate_time_left--;
12288 if (game.timegate_time_left == 0)
12289 CloseAllOpenTimegates();
12292 if (game.lenses_time_left > 0)
12294 game.lenses_time_left--;
12296 if (game.lenses_time_left == 0)
12297 RedrawAllInvisibleElementsForLenses();
12300 if (game.magnify_time_left > 0)
12302 game.magnify_time_left--;
12304 if (game.magnify_time_left == 0)
12305 RedrawAllInvisibleElementsForMagnifier();
12308 for (i = 0; i < MAX_PLAYERS; i++)
12310 struct PlayerInfo *player = &stored_player[i];
12312 if (SHIELD_ON(player))
12314 if (player->shield_deadly_time_left)
12315 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12316 else if (player->shield_normal_time_left)
12317 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12321 #if USE_DELAYED_GFX_REDRAW
12322 SCAN_PLAYFIELD(x, y)
12324 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12326 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12327 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12329 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12330 DrawLevelField(x, y);
12332 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12333 DrawLevelFieldCrumbled(x, y);
12335 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12336 DrawLevelFieldCrumbledNeighbours(x, y);
12338 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12339 DrawTwinkleOnField(x, y);
12342 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12347 PlayAllPlayersSound();
12349 for (i = 0; i < MAX_PLAYERS; i++)
12351 struct PlayerInfo *player = &stored_player[i];
12353 if (player->show_envelope != 0 && (!player->active ||
12354 player->MovPos == 0))
12356 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12358 player->show_envelope = 0;
12362 // use random number generator in every frame to make it less predictable
12363 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12366 mouse_action_last = mouse_action;
12369 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12371 int min_x = x, min_y = y, max_x = x, max_y = y;
12374 for (i = 0; i < MAX_PLAYERS; i++)
12376 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12378 if (!stored_player[i].active || &stored_player[i] == player)
12381 min_x = MIN(min_x, jx);
12382 min_y = MIN(min_y, jy);
12383 max_x = MAX(max_x, jx);
12384 max_y = MAX(max_y, jy);
12387 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12390 static boolean AllPlayersInVisibleScreen(void)
12394 for (i = 0; i < MAX_PLAYERS; i++)
12396 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12398 if (!stored_player[i].active)
12401 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12408 void ScrollLevel(int dx, int dy)
12410 int scroll_offset = 2 * TILEX_VAR;
12413 BlitBitmap(drawto_field, drawto_field,
12414 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12415 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12416 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12417 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12418 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12419 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12423 x = (dx == 1 ? BX1 : BX2);
12424 for (y = BY1; y <= BY2; y++)
12425 DrawScreenField(x, y);
12430 y = (dy == 1 ? BY1 : BY2);
12431 for (x = BX1; x <= BX2; x++)
12432 DrawScreenField(x, y);
12435 redraw_mask |= REDRAW_FIELD;
12438 static boolean canFallDown(struct PlayerInfo *player)
12440 int jx = player->jx, jy = player->jy;
12442 return (IN_LEV_FIELD(jx, jy + 1) &&
12443 (IS_FREE(jx, jy + 1) ||
12444 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12445 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12446 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12449 static boolean canPassField(int x, int y, int move_dir)
12451 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12452 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12453 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12454 int nextx = x + dx;
12455 int nexty = y + dy;
12456 int element = Tile[x][y];
12458 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12459 !CAN_MOVE(element) &&
12460 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12461 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12462 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12465 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12467 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12468 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12469 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12473 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12474 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12475 (IS_DIGGABLE(Tile[newx][newy]) ||
12476 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12477 canPassField(newx, newy, move_dir)));
12480 static void CheckGravityMovement(struct PlayerInfo *player)
12482 if (player->gravity && !player->programmed_action)
12484 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12485 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12486 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12487 int jx = player->jx, jy = player->jy;
12488 boolean player_is_moving_to_valid_field =
12489 (!player_is_snapping &&
12490 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12491 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12492 boolean player_can_fall_down = canFallDown(player);
12494 if (player_can_fall_down &&
12495 !player_is_moving_to_valid_field)
12496 player->programmed_action = MV_DOWN;
12500 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12502 return CheckGravityMovement(player);
12504 if (player->gravity && !player->programmed_action)
12506 int jx = player->jx, jy = player->jy;
12507 boolean field_under_player_is_free =
12508 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12509 boolean player_is_standing_on_valid_field =
12510 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12511 (IS_WALKABLE(Tile[jx][jy]) &&
12512 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12514 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12515 player->programmed_action = MV_DOWN;
12520 MovePlayerOneStep()
12521 -----------------------------------------------------------------------------
12522 dx, dy: direction (non-diagonal) to try to move the player to
12523 real_dx, real_dy: direction as read from input device (can be diagonal)
12526 boolean MovePlayerOneStep(struct PlayerInfo *player,
12527 int dx, int dy, int real_dx, int real_dy)
12529 int jx = player->jx, jy = player->jy;
12530 int new_jx = jx + dx, new_jy = jy + dy;
12532 boolean player_can_move = !player->cannot_move;
12534 if (!player->active || (!dx && !dy))
12535 return MP_NO_ACTION;
12537 player->MovDir = (dx < 0 ? MV_LEFT :
12538 dx > 0 ? MV_RIGHT :
12540 dy > 0 ? MV_DOWN : MV_NONE);
12542 if (!IN_LEV_FIELD(new_jx, new_jy))
12543 return MP_NO_ACTION;
12545 if (!player_can_move)
12547 if (player->MovPos == 0)
12549 player->is_moving = FALSE;
12550 player->is_digging = FALSE;
12551 player->is_collecting = FALSE;
12552 player->is_snapping = FALSE;
12553 player->is_pushing = FALSE;
12557 if (!network.enabled && game.centered_player_nr == -1 &&
12558 !AllPlayersInSight(player, new_jx, new_jy))
12559 return MP_NO_ACTION;
12561 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12562 if (can_move != MP_MOVING)
12565 // check if DigField() has caused relocation of the player
12566 if (player->jx != jx || player->jy != jy)
12567 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12569 StorePlayer[jx][jy] = 0;
12570 player->last_jx = jx;
12571 player->last_jy = jy;
12572 player->jx = new_jx;
12573 player->jy = new_jy;
12574 StorePlayer[new_jx][new_jy] = player->element_nr;
12576 if (player->move_delay_value_next != -1)
12578 player->move_delay_value = player->move_delay_value_next;
12579 player->move_delay_value_next = -1;
12583 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12585 player->step_counter++;
12587 PlayerVisit[jx][jy] = FrameCounter;
12589 player->is_moving = TRUE;
12592 // should better be called in MovePlayer(), but this breaks some tapes
12593 ScrollPlayer(player, SCROLL_INIT);
12599 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12601 int jx = player->jx, jy = player->jy;
12602 int old_jx = jx, old_jy = jy;
12603 int moved = MP_NO_ACTION;
12605 if (!player->active)
12610 if (player->MovPos == 0)
12612 player->is_moving = FALSE;
12613 player->is_digging = FALSE;
12614 player->is_collecting = FALSE;
12615 player->is_snapping = FALSE;
12616 player->is_pushing = FALSE;
12622 if (player->move_delay > 0)
12625 player->move_delay = -1; // set to "uninitialized" value
12627 // store if player is automatically moved to next field
12628 player->is_auto_moving = (player->programmed_action != MV_NONE);
12630 // remove the last programmed player action
12631 player->programmed_action = 0;
12633 if (player->MovPos)
12635 // should only happen if pre-1.2 tape recordings are played
12636 // this is only for backward compatibility
12638 int original_move_delay_value = player->move_delay_value;
12641 Debug("game:playing:MovePlayer",
12642 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12646 // scroll remaining steps with finest movement resolution
12647 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12649 while (player->MovPos)
12651 ScrollPlayer(player, SCROLL_GO_ON);
12652 ScrollScreen(NULL, SCROLL_GO_ON);
12654 AdvanceFrameAndPlayerCounters(player->index_nr);
12657 BackToFront_WithFrameDelay(0);
12660 player->move_delay_value = original_move_delay_value;
12663 player->is_active = FALSE;
12665 if (player->last_move_dir & MV_HORIZONTAL)
12667 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12668 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12672 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12673 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12676 if (!moved && !player->is_active)
12678 player->is_moving = FALSE;
12679 player->is_digging = FALSE;
12680 player->is_collecting = FALSE;
12681 player->is_snapping = FALSE;
12682 player->is_pushing = FALSE;
12688 if (moved & MP_MOVING && !ScreenMovPos &&
12689 (player->index_nr == game.centered_player_nr ||
12690 game.centered_player_nr == -1))
12692 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12694 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12696 // actual player has left the screen -- scroll in that direction
12697 if (jx != old_jx) // player has moved horizontally
12698 scroll_x += (jx - old_jx);
12699 else // player has moved vertically
12700 scroll_y += (jy - old_jy);
12704 int offset_raw = game.scroll_delay_value;
12706 if (jx != old_jx) // player has moved horizontally
12708 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12709 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12710 int new_scroll_x = jx - MIDPOSX + offset_x;
12712 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12713 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12714 scroll_x = new_scroll_x;
12716 // don't scroll over playfield boundaries
12717 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12719 // don't scroll more than one field at a time
12720 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12722 // don't scroll against the player's moving direction
12723 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12724 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12725 scroll_x = old_scroll_x;
12727 else // player has moved vertically
12729 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12730 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12731 int new_scroll_y = jy - MIDPOSY + offset_y;
12733 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12734 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12735 scroll_y = new_scroll_y;
12737 // don't scroll over playfield boundaries
12738 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12740 // don't scroll more than one field at a time
12741 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12743 // don't scroll against the player's moving direction
12744 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12745 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12746 scroll_y = old_scroll_y;
12750 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12752 if (!network.enabled && game.centered_player_nr == -1 &&
12753 !AllPlayersInVisibleScreen())
12755 scroll_x = old_scroll_x;
12756 scroll_y = old_scroll_y;
12760 ScrollScreen(player, SCROLL_INIT);
12761 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12766 player->StepFrame = 0;
12768 if (moved & MP_MOVING)
12770 if (old_jx != jx && old_jy == jy)
12771 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12772 else if (old_jx == jx && old_jy != jy)
12773 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12775 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12777 player->last_move_dir = player->MovDir;
12778 player->is_moving = TRUE;
12779 player->is_snapping = FALSE;
12780 player->is_switching = FALSE;
12781 player->is_dropping = FALSE;
12782 player->is_dropping_pressed = FALSE;
12783 player->drop_pressed_delay = 0;
12786 // should better be called here than above, but this breaks some tapes
12787 ScrollPlayer(player, SCROLL_INIT);
12792 CheckGravityMovementWhenNotMoving(player);
12794 player->is_moving = FALSE;
12796 /* at this point, the player is allowed to move, but cannot move right now
12797 (e.g. because of something blocking the way) -- ensure that the player
12798 is also allowed to move in the next frame (in old versions before 3.1.1,
12799 the player was forced to wait again for eight frames before next try) */
12801 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12802 player->move_delay = 0; // allow direct movement in the next frame
12805 if (player->move_delay == -1) // not yet initialized by DigField()
12806 player->move_delay = player->move_delay_value;
12808 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12810 TestIfPlayerTouchesBadThing(jx, jy);
12811 TestIfPlayerTouchesCustomElement(jx, jy);
12814 if (!player->active)
12815 RemovePlayer(player);
12820 void ScrollPlayer(struct PlayerInfo *player, int mode)
12822 int jx = player->jx, jy = player->jy;
12823 int last_jx = player->last_jx, last_jy = player->last_jy;
12824 int move_stepsize = TILEX / player->move_delay_value;
12826 if (!player->active)
12829 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12832 if (mode == SCROLL_INIT)
12834 player->actual_frame_counter = FrameCounter;
12835 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12837 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12838 Tile[last_jx][last_jy] == EL_EMPTY)
12840 int last_field_block_delay = 0; // start with no blocking at all
12841 int block_delay_adjustment = player->block_delay_adjustment;
12843 // if player blocks last field, add delay for exactly one move
12844 if (player->block_last_field)
12846 last_field_block_delay += player->move_delay_value;
12848 // when blocking enabled, prevent moving up despite gravity
12849 if (player->gravity && player->MovDir == MV_UP)
12850 block_delay_adjustment = -1;
12853 // add block delay adjustment (also possible when not blocking)
12854 last_field_block_delay += block_delay_adjustment;
12856 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12857 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12860 if (player->MovPos != 0) // player has not yet reached destination
12863 else if (!FrameReached(&player->actual_frame_counter, 1))
12866 if (player->MovPos != 0)
12868 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12869 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12871 // before DrawPlayer() to draw correct player graphic for this case
12872 if (player->MovPos == 0)
12873 CheckGravityMovement(player);
12876 if (player->MovPos == 0) // player reached destination field
12878 if (player->move_delay_reset_counter > 0)
12880 player->move_delay_reset_counter--;
12882 if (player->move_delay_reset_counter == 0)
12884 // continue with normal speed after quickly moving through gate
12885 HALVE_PLAYER_SPEED(player);
12887 // be able to make the next move without delay
12888 player->move_delay = 0;
12892 player->last_jx = jx;
12893 player->last_jy = jy;
12895 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12896 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12897 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12898 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12899 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12900 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12901 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12902 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12904 ExitPlayer(player);
12906 if (game.players_still_needed == 0 &&
12907 (game.friends_still_needed == 0 ||
12908 IS_SP_ELEMENT(Tile[jx][jy])))
12912 // this breaks one level: "machine", level 000
12914 int move_direction = player->MovDir;
12915 int enter_side = MV_DIR_OPPOSITE(move_direction);
12916 int leave_side = move_direction;
12917 int old_jx = last_jx;
12918 int old_jy = last_jy;
12919 int old_element = Tile[old_jx][old_jy];
12920 int new_element = Tile[jx][jy];
12922 if (IS_CUSTOM_ELEMENT(old_element))
12923 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12925 player->index_bit, leave_side);
12927 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12928 CE_PLAYER_LEAVES_X,
12929 player->index_bit, leave_side);
12931 if (IS_CUSTOM_ELEMENT(new_element))
12932 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12933 player->index_bit, enter_side);
12935 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12936 CE_PLAYER_ENTERS_X,
12937 player->index_bit, enter_side);
12939 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12940 CE_MOVE_OF_X, move_direction);
12943 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12945 TestIfPlayerTouchesBadThing(jx, jy);
12946 TestIfPlayerTouchesCustomElement(jx, jy);
12948 /* needed because pushed element has not yet reached its destination,
12949 so it would trigger a change event at its previous field location */
12950 if (!player->is_pushing)
12951 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12953 if (!player->active)
12954 RemovePlayer(player);
12957 if (!game.LevelSolved && level.use_step_counter)
12967 if (TimeLeft <= 10 && setup.time_limit)
12968 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12970 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12972 DisplayGameControlValues();
12974 if (!TimeLeft && setup.time_limit)
12975 for (i = 0; i < MAX_PLAYERS; i++)
12976 KillPlayer(&stored_player[i]);
12978 else if (game.no_time_limit && !game.all_players_gone)
12980 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12982 DisplayGameControlValues();
12986 if (tape.single_step && tape.recording && !tape.pausing &&
12987 !player->programmed_action)
12988 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12990 if (!player->programmed_action)
12991 CheckSaveEngineSnapshot(player);
12995 void ScrollScreen(struct PlayerInfo *player, int mode)
12997 static unsigned int screen_frame_counter = 0;
12999 if (mode == SCROLL_INIT)
13001 // set scrolling step size according to actual player's moving speed
13002 ScrollStepSize = TILEX / player->move_delay_value;
13004 screen_frame_counter = FrameCounter;
13005 ScreenMovDir = player->MovDir;
13006 ScreenMovPos = player->MovPos;
13007 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13010 else if (!FrameReached(&screen_frame_counter, 1))
13015 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13016 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13017 redraw_mask |= REDRAW_FIELD;
13020 ScreenMovDir = MV_NONE;
13023 void TestIfPlayerTouchesCustomElement(int x, int y)
13025 static int xy[4][2] =
13032 static int trigger_sides[4][2] =
13034 // center side border side
13035 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13036 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13037 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13038 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13040 static int touch_dir[4] =
13042 MV_LEFT | MV_RIGHT,
13047 int center_element = Tile[x][y]; // should always be non-moving!
13050 for (i = 0; i < NUM_DIRECTIONS; i++)
13052 int xx = x + xy[i][0];
13053 int yy = y + xy[i][1];
13054 int center_side = trigger_sides[i][0];
13055 int border_side = trigger_sides[i][1];
13056 int border_element;
13058 if (!IN_LEV_FIELD(xx, yy))
13061 if (IS_PLAYER(x, y)) // player found at center element
13063 struct PlayerInfo *player = PLAYERINFO(x, y);
13065 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13066 border_element = Tile[xx][yy]; // may be moving!
13067 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13068 border_element = Tile[xx][yy];
13069 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13070 border_element = MovingOrBlocked2Element(xx, yy);
13072 continue; // center and border element do not touch
13074 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13075 player->index_bit, border_side);
13076 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13077 CE_PLAYER_TOUCHES_X,
13078 player->index_bit, border_side);
13081 /* use player element that is initially defined in the level playfield,
13082 not the player element that corresponds to the runtime player number
13083 (example: a level that contains EL_PLAYER_3 as the only player would
13084 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13085 int player_element = PLAYERINFO(x, y)->initial_element;
13087 CheckElementChangeBySide(xx, yy, border_element, player_element,
13088 CE_TOUCHING_X, border_side);
13091 else if (IS_PLAYER(xx, yy)) // player found at border element
13093 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13095 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13097 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13098 continue; // center and border element do not touch
13101 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13102 player->index_bit, center_side);
13103 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13104 CE_PLAYER_TOUCHES_X,
13105 player->index_bit, center_side);
13108 /* use player element that is initially defined in the level playfield,
13109 not the player element that corresponds to the runtime player number
13110 (example: a level that contains EL_PLAYER_3 as the only player would
13111 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13112 int player_element = PLAYERINFO(xx, yy)->initial_element;
13114 CheckElementChangeBySide(x, y, center_element, player_element,
13115 CE_TOUCHING_X, center_side);
13123 void TestIfElementTouchesCustomElement(int x, int y)
13125 static int xy[4][2] =
13132 static int trigger_sides[4][2] =
13134 // center side border side
13135 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13136 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13137 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13138 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13140 static int touch_dir[4] =
13142 MV_LEFT | MV_RIGHT,
13147 boolean change_center_element = FALSE;
13148 int center_element = Tile[x][y]; // should always be non-moving!
13149 int border_element_old[NUM_DIRECTIONS];
13152 for (i = 0; i < NUM_DIRECTIONS; i++)
13154 int xx = x + xy[i][0];
13155 int yy = y + xy[i][1];
13156 int border_element;
13158 border_element_old[i] = -1;
13160 if (!IN_LEV_FIELD(xx, yy))
13163 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13164 border_element = Tile[xx][yy]; // may be moving!
13165 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13166 border_element = Tile[xx][yy];
13167 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13168 border_element = MovingOrBlocked2Element(xx, yy);
13170 continue; // center and border element do not touch
13172 border_element_old[i] = border_element;
13175 for (i = 0; i < NUM_DIRECTIONS; i++)
13177 int xx = x + xy[i][0];
13178 int yy = y + xy[i][1];
13179 int center_side = trigger_sides[i][0];
13180 int border_element = border_element_old[i];
13182 if (border_element == -1)
13185 // check for change of border element
13186 CheckElementChangeBySide(xx, yy, border_element, center_element,
13187 CE_TOUCHING_X, center_side);
13189 // (center element cannot be player, so we dont have to check this here)
13192 for (i = 0; i < NUM_DIRECTIONS; i++)
13194 int xx = x + xy[i][0];
13195 int yy = y + xy[i][1];
13196 int border_side = trigger_sides[i][1];
13197 int border_element = border_element_old[i];
13199 if (border_element == -1)
13202 // check for change of center element (but change it only once)
13203 if (!change_center_element)
13204 change_center_element =
13205 CheckElementChangeBySide(x, y, center_element, border_element,
13206 CE_TOUCHING_X, border_side);
13208 if (IS_PLAYER(xx, yy))
13210 /* use player element that is initially defined in the level playfield,
13211 not the player element that corresponds to the runtime player number
13212 (example: a level that contains EL_PLAYER_3 as the only player would
13213 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13214 int player_element = PLAYERINFO(xx, yy)->initial_element;
13216 CheckElementChangeBySide(x, y, center_element, player_element,
13217 CE_TOUCHING_X, border_side);
13222 void TestIfElementHitsCustomElement(int x, int y, int direction)
13224 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13225 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13226 int hitx = x + dx, hity = y + dy;
13227 int hitting_element = Tile[x][y];
13228 int touched_element;
13230 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13233 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13234 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13236 if (IN_LEV_FIELD(hitx, hity))
13238 int opposite_direction = MV_DIR_OPPOSITE(direction);
13239 int hitting_side = direction;
13240 int touched_side = opposite_direction;
13241 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13242 MovDir[hitx][hity] != direction ||
13243 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13249 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13250 CE_HITTING_X, touched_side);
13252 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13253 CE_HIT_BY_X, hitting_side);
13255 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13256 CE_HIT_BY_SOMETHING, opposite_direction);
13258 if (IS_PLAYER(hitx, hity))
13260 /* use player element that is initially defined in the level playfield,
13261 not the player element that corresponds to the runtime player number
13262 (example: a level that contains EL_PLAYER_3 as the only player would
13263 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13264 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13266 CheckElementChangeBySide(x, y, hitting_element, player_element,
13267 CE_HITTING_X, touched_side);
13272 // "hitting something" is also true when hitting the playfield border
13273 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13274 CE_HITTING_SOMETHING, direction);
13277 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13279 int i, kill_x = -1, kill_y = -1;
13281 int bad_element = -1;
13282 static int test_xy[4][2] =
13289 static int test_dir[4] =
13297 for (i = 0; i < NUM_DIRECTIONS; i++)
13299 int test_x, test_y, test_move_dir, test_element;
13301 test_x = good_x + test_xy[i][0];
13302 test_y = good_y + test_xy[i][1];
13304 if (!IN_LEV_FIELD(test_x, test_y))
13308 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13310 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13312 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13313 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13315 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13316 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13320 bad_element = test_element;
13326 if (kill_x != -1 || kill_y != -1)
13328 if (IS_PLAYER(good_x, good_y))
13330 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13332 if (player->shield_deadly_time_left > 0 &&
13333 !IS_INDESTRUCTIBLE(bad_element))
13334 Bang(kill_x, kill_y);
13335 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13336 KillPlayer(player);
13339 Bang(good_x, good_y);
13343 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13345 int i, kill_x = -1, kill_y = -1;
13346 int bad_element = Tile[bad_x][bad_y];
13347 static int test_xy[4][2] =
13354 static int touch_dir[4] =
13356 MV_LEFT | MV_RIGHT,
13361 static int test_dir[4] =
13369 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13372 for (i = 0; i < NUM_DIRECTIONS; i++)
13374 int test_x, test_y, test_move_dir, test_element;
13376 test_x = bad_x + test_xy[i][0];
13377 test_y = bad_y + test_xy[i][1];
13379 if (!IN_LEV_FIELD(test_x, test_y))
13383 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13385 test_element = Tile[test_x][test_y];
13387 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13388 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13390 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13391 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13393 // good thing is player or penguin that does not move away
13394 if (IS_PLAYER(test_x, test_y))
13396 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13398 if (bad_element == EL_ROBOT && player->is_moving)
13399 continue; // robot does not kill player if he is moving
13401 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13403 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13404 continue; // center and border element do not touch
13412 else if (test_element == EL_PENGUIN)
13422 if (kill_x != -1 || kill_y != -1)
13424 if (IS_PLAYER(kill_x, kill_y))
13426 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13428 if (player->shield_deadly_time_left > 0 &&
13429 !IS_INDESTRUCTIBLE(bad_element))
13430 Bang(bad_x, bad_y);
13431 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13432 KillPlayer(player);
13435 Bang(kill_x, kill_y);
13439 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13441 int bad_element = Tile[bad_x][bad_y];
13442 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13443 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13444 int test_x = bad_x + dx, test_y = bad_y + dy;
13445 int test_move_dir, test_element;
13446 int kill_x = -1, kill_y = -1;
13448 if (!IN_LEV_FIELD(test_x, test_y))
13452 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13454 test_element = Tile[test_x][test_y];
13456 if (test_move_dir != bad_move_dir)
13458 // good thing can be player or penguin that does not move away
13459 if (IS_PLAYER(test_x, test_y))
13461 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13463 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13464 player as being hit when he is moving towards the bad thing, because
13465 the "get hit by" condition would be lost after the player stops) */
13466 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13467 return; // player moves away from bad thing
13472 else if (test_element == EL_PENGUIN)
13479 if (kill_x != -1 || kill_y != -1)
13481 if (IS_PLAYER(kill_x, kill_y))
13483 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13485 if (player->shield_deadly_time_left > 0 &&
13486 !IS_INDESTRUCTIBLE(bad_element))
13487 Bang(bad_x, bad_y);
13488 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13489 KillPlayer(player);
13492 Bang(kill_x, kill_y);
13496 void TestIfPlayerTouchesBadThing(int x, int y)
13498 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13501 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13503 TestIfGoodThingHitsBadThing(x, y, move_dir);
13506 void TestIfBadThingTouchesPlayer(int x, int y)
13508 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13511 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13513 TestIfBadThingHitsGoodThing(x, y, move_dir);
13516 void TestIfFriendTouchesBadThing(int x, int y)
13518 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13521 void TestIfBadThingTouchesFriend(int x, int y)
13523 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13526 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13528 int i, kill_x = bad_x, kill_y = bad_y;
13529 static int xy[4][2] =
13537 for (i = 0; i < NUM_DIRECTIONS; i++)
13541 x = bad_x + xy[i][0];
13542 y = bad_y + xy[i][1];
13543 if (!IN_LEV_FIELD(x, y))
13546 element = Tile[x][y];
13547 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13548 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13556 if (kill_x != bad_x || kill_y != bad_y)
13557 Bang(bad_x, bad_y);
13560 void KillPlayer(struct PlayerInfo *player)
13562 int jx = player->jx, jy = player->jy;
13564 if (!player->active)
13568 Debug("game:playing:KillPlayer",
13569 "0: killed == %d, active == %d, reanimated == %d",
13570 player->killed, player->active, player->reanimated);
13573 /* the following code was introduced to prevent an infinite loop when calling
13575 -> CheckTriggeredElementChangeExt()
13576 -> ExecuteCustomElementAction()
13578 -> (infinitely repeating the above sequence of function calls)
13579 which occurs when killing the player while having a CE with the setting
13580 "kill player X when explosion of <player X>"; the solution using a new
13581 field "player->killed" was chosen for backwards compatibility, although
13582 clever use of the fields "player->active" etc. would probably also work */
13584 if (player->killed)
13588 player->killed = TRUE;
13590 // remove accessible field at the player's position
13591 Tile[jx][jy] = EL_EMPTY;
13593 // deactivate shield (else Bang()/Explode() would not work right)
13594 player->shield_normal_time_left = 0;
13595 player->shield_deadly_time_left = 0;
13598 Debug("game:playing:KillPlayer",
13599 "1: killed == %d, active == %d, reanimated == %d",
13600 player->killed, player->active, player->reanimated);
13606 Debug("game:playing:KillPlayer",
13607 "2: killed == %d, active == %d, reanimated == %d",
13608 player->killed, player->active, player->reanimated);
13611 if (player->reanimated) // killed player may have been reanimated
13612 player->killed = player->reanimated = FALSE;
13614 BuryPlayer(player);
13617 static void KillPlayerUnlessEnemyProtected(int x, int y)
13619 if (!PLAYER_ENEMY_PROTECTED(x, y))
13620 KillPlayer(PLAYERINFO(x, y));
13623 static void KillPlayerUnlessExplosionProtected(int x, int y)
13625 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13626 KillPlayer(PLAYERINFO(x, y));
13629 void BuryPlayer(struct PlayerInfo *player)
13631 int jx = player->jx, jy = player->jy;
13633 if (!player->active)
13636 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13637 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13639 RemovePlayer(player);
13641 player->buried = TRUE;
13643 if (game.all_players_gone)
13644 game.GameOver = TRUE;
13647 void RemovePlayer(struct PlayerInfo *player)
13649 int jx = player->jx, jy = player->jy;
13650 int i, found = FALSE;
13652 player->present = FALSE;
13653 player->active = FALSE;
13655 // required for some CE actions (even if the player is not active anymore)
13656 player->MovPos = 0;
13658 if (!ExplodeField[jx][jy])
13659 StorePlayer[jx][jy] = 0;
13661 if (player->is_moving)
13662 TEST_DrawLevelField(player->last_jx, player->last_jy);
13664 for (i = 0; i < MAX_PLAYERS; i++)
13665 if (stored_player[i].active)
13670 game.all_players_gone = TRUE;
13671 game.GameOver = TRUE;
13674 game.exit_x = game.robot_wheel_x = jx;
13675 game.exit_y = game.robot_wheel_y = jy;
13678 void ExitPlayer(struct PlayerInfo *player)
13680 DrawPlayer(player); // needed here only to cleanup last field
13681 RemovePlayer(player);
13683 if (game.players_still_needed > 0)
13684 game.players_still_needed--;
13687 static void setFieldForSnapping(int x, int y, int element, int direction)
13689 struct ElementInfo *ei = &element_info[element];
13690 int direction_bit = MV_DIR_TO_BIT(direction);
13691 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13692 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13693 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13695 Tile[x][y] = EL_ELEMENT_SNAPPING;
13696 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13698 ResetGfxAnimation(x, y);
13700 GfxElement[x][y] = element;
13701 GfxAction[x][y] = action;
13702 GfxDir[x][y] = direction;
13703 GfxFrame[x][y] = -1;
13707 =============================================================================
13708 checkDiagonalPushing()
13709 -----------------------------------------------------------------------------
13710 check if diagonal input device direction results in pushing of object
13711 (by checking if the alternative direction is walkable, diggable, ...)
13712 =============================================================================
13715 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13716 int x, int y, int real_dx, int real_dy)
13718 int jx, jy, dx, dy, xx, yy;
13720 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13723 // diagonal direction: check alternative direction
13728 xx = jx + (dx == 0 ? real_dx : 0);
13729 yy = jy + (dy == 0 ? real_dy : 0);
13731 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13735 =============================================================================
13737 -----------------------------------------------------------------------------
13738 x, y: field next to player (non-diagonal) to try to dig to
13739 real_dx, real_dy: direction as read from input device (can be diagonal)
13740 =============================================================================
13743 static int DigField(struct PlayerInfo *player,
13744 int oldx, int oldy, int x, int y,
13745 int real_dx, int real_dy, int mode)
13747 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13748 boolean player_was_pushing = player->is_pushing;
13749 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13750 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13751 int jx = oldx, jy = oldy;
13752 int dx = x - jx, dy = y - jy;
13753 int nextx = x + dx, nexty = y + dy;
13754 int move_direction = (dx == -1 ? MV_LEFT :
13755 dx == +1 ? MV_RIGHT :
13757 dy == +1 ? MV_DOWN : MV_NONE);
13758 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13759 int dig_side = MV_DIR_OPPOSITE(move_direction);
13760 int old_element = Tile[jx][jy];
13761 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13764 if (is_player) // function can also be called by EL_PENGUIN
13766 if (player->MovPos == 0)
13768 player->is_digging = FALSE;
13769 player->is_collecting = FALSE;
13772 if (player->MovPos == 0) // last pushing move finished
13773 player->is_pushing = FALSE;
13775 if (mode == DF_NO_PUSH) // player just stopped pushing
13777 player->is_switching = FALSE;
13778 player->push_delay = -1;
13780 return MP_NO_ACTION;
13784 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13785 old_element = Back[jx][jy];
13787 // in case of element dropped at player position, check background
13788 else if (Back[jx][jy] != EL_EMPTY &&
13789 game.engine_version >= VERSION_IDENT(2,2,0,0))
13790 old_element = Back[jx][jy];
13792 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13793 return MP_NO_ACTION; // field has no opening in this direction
13795 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13796 return MP_NO_ACTION; // field has no opening in this direction
13798 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13802 Tile[jx][jy] = player->artwork_element;
13803 InitMovingField(jx, jy, MV_DOWN);
13804 Store[jx][jy] = EL_ACID;
13805 ContinueMoving(jx, jy);
13806 BuryPlayer(player);
13808 return MP_DONT_RUN_INTO;
13811 if (player_can_move && DONT_RUN_INTO(element))
13813 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13815 return MP_DONT_RUN_INTO;
13818 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13819 return MP_NO_ACTION;
13821 collect_count = element_info[element].collect_count_initial;
13823 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13824 return MP_NO_ACTION;
13826 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13827 player_can_move = player_can_move_or_snap;
13829 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13830 game.engine_version >= VERSION_IDENT(2,2,0,0))
13832 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13833 player->index_bit, dig_side);
13834 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13835 player->index_bit, dig_side);
13837 if (element == EL_DC_LANDMINE)
13840 if (Tile[x][y] != element) // field changed by snapping
13843 return MP_NO_ACTION;
13846 if (player->gravity && is_player && !player->is_auto_moving &&
13847 canFallDown(player) && move_direction != MV_DOWN &&
13848 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13849 return MP_NO_ACTION; // player cannot walk here due to gravity
13851 if (player_can_move &&
13852 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13854 int sound_element = SND_ELEMENT(element);
13855 int sound_action = ACTION_WALKING;
13857 if (IS_RND_GATE(element))
13859 if (!player->key[RND_GATE_NR(element)])
13860 return MP_NO_ACTION;
13862 else if (IS_RND_GATE_GRAY(element))
13864 if (!player->key[RND_GATE_GRAY_NR(element)])
13865 return MP_NO_ACTION;
13867 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13869 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13870 return MP_NO_ACTION;
13872 else if (element == EL_EXIT_OPEN ||
13873 element == EL_EM_EXIT_OPEN ||
13874 element == EL_EM_EXIT_OPENING ||
13875 element == EL_STEEL_EXIT_OPEN ||
13876 element == EL_EM_STEEL_EXIT_OPEN ||
13877 element == EL_EM_STEEL_EXIT_OPENING ||
13878 element == EL_SP_EXIT_OPEN ||
13879 element == EL_SP_EXIT_OPENING)
13881 sound_action = ACTION_PASSING; // player is passing exit
13883 else if (element == EL_EMPTY)
13885 sound_action = ACTION_MOVING; // nothing to walk on
13888 // play sound from background or player, whatever is available
13889 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13890 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13892 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13894 else if (player_can_move &&
13895 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13897 if (!ACCESS_FROM(element, opposite_direction))
13898 return MP_NO_ACTION; // field not accessible from this direction
13900 if (CAN_MOVE(element)) // only fixed elements can be passed!
13901 return MP_NO_ACTION;
13903 if (IS_EM_GATE(element))
13905 if (!player->key[EM_GATE_NR(element)])
13906 return MP_NO_ACTION;
13908 else if (IS_EM_GATE_GRAY(element))
13910 if (!player->key[EM_GATE_GRAY_NR(element)])
13911 return MP_NO_ACTION;
13913 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13915 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13916 return MP_NO_ACTION;
13918 else if (IS_EMC_GATE(element))
13920 if (!player->key[EMC_GATE_NR(element)])
13921 return MP_NO_ACTION;
13923 else if (IS_EMC_GATE_GRAY(element))
13925 if (!player->key[EMC_GATE_GRAY_NR(element)])
13926 return MP_NO_ACTION;
13928 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13930 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13931 return MP_NO_ACTION;
13933 else if (element == EL_DC_GATE_WHITE ||
13934 element == EL_DC_GATE_WHITE_GRAY ||
13935 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13937 if (player->num_white_keys == 0)
13938 return MP_NO_ACTION;
13940 player->num_white_keys--;
13942 else if (IS_SP_PORT(element))
13944 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13945 element == EL_SP_GRAVITY_PORT_RIGHT ||
13946 element == EL_SP_GRAVITY_PORT_UP ||
13947 element == EL_SP_GRAVITY_PORT_DOWN)
13948 player->gravity = !player->gravity;
13949 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13950 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13951 element == EL_SP_GRAVITY_ON_PORT_UP ||
13952 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13953 player->gravity = TRUE;
13954 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13955 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13956 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13957 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13958 player->gravity = FALSE;
13961 // automatically move to the next field with double speed
13962 player->programmed_action = move_direction;
13964 if (player->move_delay_reset_counter == 0)
13966 player->move_delay_reset_counter = 2; // two double speed steps
13968 DOUBLE_PLAYER_SPEED(player);
13971 PlayLevelSoundAction(x, y, ACTION_PASSING);
13973 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13977 if (mode != DF_SNAP)
13979 GfxElement[x][y] = GFX_ELEMENT(element);
13980 player->is_digging = TRUE;
13983 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13985 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13986 player->index_bit, dig_side);
13988 if (mode == DF_SNAP)
13990 if (level.block_snap_field)
13991 setFieldForSnapping(x, y, element, move_direction);
13993 TestIfElementTouchesCustomElement(x, y); // for empty space
13995 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13996 player->index_bit, dig_side);
13999 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14003 if (is_player && mode != DF_SNAP)
14005 GfxElement[x][y] = element;
14006 player->is_collecting = TRUE;
14009 if (element == EL_SPEED_PILL)
14011 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14013 else if (element == EL_EXTRA_TIME && level.time > 0)
14015 TimeLeft += level.extra_time;
14017 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14019 DisplayGameControlValues();
14021 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14023 player->shield_normal_time_left += level.shield_normal_time;
14024 if (element == EL_SHIELD_DEADLY)
14025 player->shield_deadly_time_left += level.shield_deadly_time;
14027 else if (element == EL_DYNAMITE ||
14028 element == EL_EM_DYNAMITE ||
14029 element == EL_SP_DISK_RED)
14031 if (player->inventory_size < MAX_INVENTORY_SIZE)
14032 player->inventory_element[player->inventory_size++] = element;
14034 DrawGameDoorValues();
14036 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14038 player->dynabomb_count++;
14039 player->dynabombs_left++;
14041 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14043 player->dynabomb_size++;
14045 else if (element == EL_DYNABOMB_INCREASE_POWER)
14047 player->dynabomb_xl = TRUE;
14049 else if (IS_KEY(element))
14051 player->key[KEY_NR(element)] = TRUE;
14053 DrawGameDoorValues();
14055 else if (element == EL_DC_KEY_WHITE)
14057 player->num_white_keys++;
14059 // display white keys?
14060 // DrawGameDoorValues();
14062 else if (IS_ENVELOPE(element))
14064 player->show_envelope = element;
14066 else if (element == EL_EMC_LENSES)
14068 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14070 RedrawAllInvisibleElementsForLenses();
14072 else if (element == EL_EMC_MAGNIFIER)
14074 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14076 RedrawAllInvisibleElementsForMagnifier();
14078 else if (IS_DROPPABLE(element) ||
14079 IS_THROWABLE(element)) // can be collected and dropped
14083 if (collect_count == 0)
14084 player->inventory_infinite_element = element;
14086 for (i = 0; i < collect_count; i++)
14087 if (player->inventory_size < MAX_INVENTORY_SIZE)
14088 player->inventory_element[player->inventory_size++] = element;
14090 DrawGameDoorValues();
14092 else if (collect_count > 0)
14094 game.gems_still_needed -= collect_count;
14095 if (game.gems_still_needed < 0)
14096 game.gems_still_needed = 0;
14098 game.snapshot.collected_item = TRUE;
14100 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14102 DisplayGameControlValues();
14105 RaiseScoreElement(element);
14106 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14109 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14110 player->index_bit, dig_side);
14112 if (mode == DF_SNAP)
14114 if (level.block_snap_field)
14115 setFieldForSnapping(x, y, element, move_direction);
14117 TestIfElementTouchesCustomElement(x, y); // for empty space
14119 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14120 player->index_bit, dig_side);
14123 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14125 if (mode == DF_SNAP && element != EL_BD_ROCK)
14126 return MP_NO_ACTION;
14128 if (CAN_FALL(element) && dy)
14129 return MP_NO_ACTION;
14131 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14132 !(element == EL_SPRING && level.use_spring_bug))
14133 return MP_NO_ACTION;
14135 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14136 ((move_direction & MV_VERTICAL &&
14137 ((element_info[element].move_pattern & MV_LEFT &&
14138 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14139 (element_info[element].move_pattern & MV_RIGHT &&
14140 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14141 (move_direction & MV_HORIZONTAL &&
14142 ((element_info[element].move_pattern & MV_UP &&
14143 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14144 (element_info[element].move_pattern & MV_DOWN &&
14145 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14146 return MP_NO_ACTION;
14148 // do not push elements already moving away faster than player
14149 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14150 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14151 return MP_NO_ACTION;
14153 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14155 if (player->push_delay_value == -1 || !player_was_pushing)
14156 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14158 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14160 if (player->push_delay_value == -1)
14161 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14163 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14165 if (!player->is_pushing)
14166 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14169 player->is_pushing = TRUE;
14170 player->is_active = TRUE;
14172 if (!(IN_LEV_FIELD(nextx, nexty) &&
14173 (IS_FREE(nextx, nexty) ||
14174 (IS_SB_ELEMENT(element) &&
14175 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14176 (IS_CUSTOM_ELEMENT(element) &&
14177 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14178 return MP_NO_ACTION;
14180 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14181 return MP_NO_ACTION;
14183 if (player->push_delay == -1) // new pushing; restart delay
14184 player->push_delay = 0;
14186 if (player->push_delay < player->push_delay_value &&
14187 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14188 element != EL_SPRING && element != EL_BALLOON)
14190 // make sure that there is no move delay before next try to push
14191 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14192 player->move_delay = 0;
14194 return MP_NO_ACTION;
14197 if (IS_CUSTOM_ELEMENT(element) &&
14198 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14200 if (!DigFieldByCE(nextx, nexty, element))
14201 return MP_NO_ACTION;
14204 if (IS_SB_ELEMENT(element))
14206 boolean sokoban_task_solved = FALSE;
14208 if (element == EL_SOKOBAN_FIELD_FULL)
14210 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14212 IncrementSokobanFieldsNeeded();
14213 IncrementSokobanObjectsNeeded();
14216 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14218 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14220 DecrementSokobanFieldsNeeded();
14221 DecrementSokobanObjectsNeeded();
14223 // sokoban object was pushed from empty field to sokoban field
14224 if (Back[x][y] == EL_EMPTY)
14225 sokoban_task_solved = TRUE;
14228 Tile[x][y] = EL_SOKOBAN_OBJECT;
14230 if (Back[x][y] == Back[nextx][nexty])
14231 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14232 else if (Back[x][y] != 0)
14233 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14236 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14239 if (sokoban_task_solved &&
14240 game.sokoban_fields_still_needed == 0 &&
14241 game.sokoban_objects_still_needed == 0 &&
14242 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14244 game.players_still_needed = 0;
14248 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14252 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14254 InitMovingField(x, y, move_direction);
14255 GfxAction[x][y] = ACTION_PUSHING;
14257 if (mode == DF_SNAP)
14258 ContinueMoving(x, y);
14260 MovPos[x][y] = (dx != 0 ? dx : dy);
14262 Pushed[x][y] = TRUE;
14263 Pushed[nextx][nexty] = TRUE;
14265 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14266 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14268 player->push_delay_value = -1; // get new value later
14270 // check for element change _after_ element has been pushed
14271 if (game.use_change_when_pushing_bug)
14273 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14274 player->index_bit, dig_side);
14275 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14276 player->index_bit, dig_side);
14279 else if (IS_SWITCHABLE(element))
14281 if (PLAYER_SWITCHING(player, x, y))
14283 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14284 player->index_bit, dig_side);
14289 player->is_switching = TRUE;
14290 player->switch_x = x;
14291 player->switch_y = y;
14293 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14295 if (element == EL_ROBOT_WHEEL)
14297 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14299 game.robot_wheel_x = x;
14300 game.robot_wheel_y = y;
14301 game.robot_wheel_active = TRUE;
14303 TEST_DrawLevelField(x, y);
14305 else if (element == EL_SP_TERMINAL)
14309 SCAN_PLAYFIELD(xx, yy)
14311 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14315 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14317 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14319 ResetGfxAnimation(xx, yy);
14320 TEST_DrawLevelField(xx, yy);
14324 else if (IS_BELT_SWITCH(element))
14326 ToggleBeltSwitch(x, y);
14328 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14329 element == EL_SWITCHGATE_SWITCH_DOWN ||
14330 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14331 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14333 ToggleSwitchgateSwitch(x, y);
14335 else if (element == EL_LIGHT_SWITCH ||
14336 element == EL_LIGHT_SWITCH_ACTIVE)
14338 ToggleLightSwitch(x, y);
14340 else if (element == EL_TIMEGATE_SWITCH ||
14341 element == EL_DC_TIMEGATE_SWITCH)
14343 ActivateTimegateSwitch(x, y);
14345 else if (element == EL_BALLOON_SWITCH_LEFT ||
14346 element == EL_BALLOON_SWITCH_RIGHT ||
14347 element == EL_BALLOON_SWITCH_UP ||
14348 element == EL_BALLOON_SWITCH_DOWN ||
14349 element == EL_BALLOON_SWITCH_NONE ||
14350 element == EL_BALLOON_SWITCH_ANY)
14352 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14353 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14354 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14355 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14356 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14359 else if (element == EL_LAMP)
14361 Tile[x][y] = EL_LAMP_ACTIVE;
14362 game.lights_still_needed--;
14364 ResetGfxAnimation(x, y);
14365 TEST_DrawLevelField(x, y);
14367 else if (element == EL_TIME_ORB_FULL)
14369 Tile[x][y] = EL_TIME_ORB_EMPTY;
14371 if (level.time > 0 || level.use_time_orb_bug)
14373 TimeLeft += level.time_orb_time;
14374 game.no_time_limit = FALSE;
14376 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14378 DisplayGameControlValues();
14381 ResetGfxAnimation(x, y);
14382 TEST_DrawLevelField(x, y);
14384 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14385 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14389 game.ball_active = !game.ball_active;
14391 SCAN_PLAYFIELD(xx, yy)
14393 int e = Tile[xx][yy];
14395 if (game.ball_active)
14397 if (e == EL_EMC_MAGIC_BALL)
14398 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14399 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14400 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14404 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14405 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14406 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14407 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14412 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14413 player->index_bit, dig_side);
14415 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14416 player->index_bit, dig_side);
14418 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14419 player->index_bit, dig_side);
14425 if (!PLAYER_SWITCHING(player, x, y))
14427 player->is_switching = TRUE;
14428 player->switch_x = x;
14429 player->switch_y = y;
14431 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14432 player->index_bit, dig_side);
14433 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14434 player->index_bit, dig_side);
14436 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14437 player->index_bit, dig_side);
14438 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14439 player->index_bit, dig_side);
14442 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14443 player->index_bit, dig_side);
14444 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14445 player->index_bit, dig_side);
14447 return MP_NO_ACTION;
14450 player->push_delay = -1;
14452 if (is_player) // function can also be called by EL_PENGUIN
14454 if (Tile[x][y] != element) // really digged/collected something
14456 player->is_collecting = !player->is_digging;
14457 player->is_active = TRUE;
14464 static boolean DigFieldByCE(int x, int y, int digging_element)
14466 int element = Tile[x][y];
14468 if (!IS_FREE(x, y))
14470 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14471 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14474 // no element can dig solid indestructible elements
14475 if (IS_INDESTRUCTIBLE(element) &&
14476 !IS_DIGGABLE(element) &&
14477 !IS_COLLECTIBLE(element))
14480 if (AmoebaNr[x][y] &&
14481 (element == EL_AMOEBA_FULL ||
14482 element == EL_BD_AMOEBA ||
14483 element == EL_AMOEBA_GROWING))
14485 AmoebaCnt[AmoebaNr[x][y]]--;
14486 AmoebaCnt2[AmoebaNr[x][y]]--;
14489 if (IS_MOVING(x, y))
14490 RemoveMovingField(x, y);
14494 TEST_DrawLevelField(x, y);
14497 // if digged element was about to explode, prevent the explosion
14498 ExplodeField[x][y] = EX_TYPE_NONE;
14500 PlayLevelSoundAction(x, y, action);
14503 Store[x][y] = EL_EMPTY;
14505 // this makes it possible to leave the removed element again
14506 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14507 Store[x][y] = element;
14512 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14514 int jx = player->jx, jy = player->jy;
14515 int x = jx + dx, y = jy + dy;
14516 int snap_direction = (dx == -1 ? MV_LEFT :
14517 dx == +1 ? MV_RIGHT :
14519 dy == +1 ? MV_DOWN : MV_NONE);
14520 boolean can_continue_snapping = (level.continuous_snapping &&
14521 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14523 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14526 if (!player->active || !IN_LEV_FIELD(x, y))
14534 if (player->MovPos == 0)
14535 player->is_pushing = FALSE;
14537 player->is_snapping = FALSE;
14539 if (player->MovPos == 0)
14541 player->is_moving = FALSE;
14542 player->is_digging = FALSE;
14543 player->is_collecting = FALSE;
14549 // prevent snapping with already pressed snap key when not allowed
14550 if (player->is_snapping && !can_continue_snapping)
14553 player->MovDir = snap_direction;
14555 if (player->MovPos == 0)
14557 player->is_moving = FALSE;
14558 player->is_digging = FALSE;
14559 player->is_collecting = FALSE;
14562 player->is_dropping = FALSE;
14563 player->is_dropping_pressed = FALSE;
14564 player->drop_pressed_delay = 0;
14566 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14569 player->is_snapping = TRUE;
14570 player->is_active = TRUE;
14572 if (player->MovPos == 0)
14574 player->is_moving = FALSE;
14575 player->is_digging = FALSE;
14576 player->is_collecting = FALSE;
14579 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14580 TEST_DrawLevelField(player->last_jx, player->last_jy);
14582 TEST_DrawLevelField(x, y);
14587 static boolean DropElement(struct PlayerInfo *player)
14589 int old_element, new_element;
14590 int dropx = player->jx, dropy = player->jy;
14591 int drop_direction = player->MovDir;
14592 int drop_side = drop_direction;
14593 int drop_element = get_next_dropped_element(player);
14595 /* do not drop an element on top of another element; when holding drop key
14596 pressed without moving, dropped element must move away before the next
14597 element can be dropped (this is especially important if the next element
14598 is dynamite, which can be placed on background for historical reasons) */
14599 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14602 if (IS_THROWABLE(drop_element))
14604 dropx += GET_DX_FROM_DIR(drop_direction);
14605 dropy += GET_DY_FROM_DIR(drop_direction);
14607 if (!IN_LEV_FIELD(dropx, dropy))
14611 old_element = Tile[dropx][dropy]; // old element at dropping position
14612 new_element = drop_element; // default: no change when dropping
14614 // check if player is active, not moving and ready to drop
14615 if (!player->active || player->MovPos || player->drop_delay > 0)
14618 // check if player has anything that can be dropped
14619 if (new_element == EL_UNDEFINED)
14622 // only set if player has anything that can be dropped
14623 player->is_dropping_pressed = TRUE;
14625 // check if drop key was pressed long enough for EM style dynamite
14626 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14629 // check if anything can be dropped at the current position
14630 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14633 // collected custom elements can only be dropped on empty fields
14634 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14637 if (old_element != EL_EMPTY)
14638 Back[dropx][dropy] = old_element; // store old element on this field
14640 ResetGfxAnimation(dropx, dropy);
14641 ResetRandomAnimationValue(dropx, dropy);
14643 if (player->inventory_size > 0 ||
14644 player->inventory_infinite_element != EL_UNDEFINED)
14646 if (player->inventory_size > 0)
14648 player->inventory_size--;
14650 DrawGameDoorValues();
14652 if (new_element == EL_DYNAMITE)
14653 new_element = EL_DYNAMITE_ACTIVE;
14654 else if (new_element == EL_EM_DYNAMITE)
14655 new_element = EL_EM_DYNAMITE_ACTIVE;
14656 else if (new_element == EL_SP_DISK_RED)
14657 new_element = EL_SP_DISK_RED_ACTIVE;
14660 Tile[dropx][dropy] = new_element;
14662 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14663 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14664 el2img(Tile[dropx][dropy]), 0);
14666 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14668 // needed if previous element just changed to "empty" in the last frame
14669 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14671 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14672 player->index_bit, drop_side);
14673 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14675 player->index_bit, drop_side);
14677 TestIfElementTouchesCustomElement(dropx, dropy);
14679 else // player is dropping a dyna bomb
14681 player->dynabombs_left--;
14683 Tile[dropx][dropy] = new_element;
14685 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14686 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14687 el2img(Tile[dropx][dropy]), 0);
14689 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14692 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14693 InitField_WithBug1(dropx, dropy, FALSE);
14695 new_element = Tile[dropx][dropy]; // element might have changed
14697 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14698 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14700 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14701 MovDir[dropx][dropy] = drop_direction;
14703 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14705 // do not cause impact style collision by dropping elements that can fall
14706 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14709 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14710 player->is_dropping = TRUE;
14712 player->drop_pressed_delay = 0;
14713 player->is_dropping_pressed = FALSE;
14715 player->drop_x = dropx;
14716 player->drop_y = dropy;
14721 // ----------------------------------------------------------------------------
14722 // game sound playing functions
14723 // ----------------------------------------------------------------------------
14725 static int *loop_sound_frame = NULL;
14726 static int *loop_sound_volume = NULL;
14728 void InitPlayLevelSound(void)
14730 int num_sounds = getSoundListSize();
14732 checked_free(loop_sound_frame);
14733 checked_free(loop_sound_volume);
14735 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14736 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14739 static void PlayLevelSound(int x, int y, int nr)
14741 int sx = SCREENX(x), sy = SCREENY(y);
14742 int volume, stereo_position;
14743 int max_distance = 8;
14744 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14746 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14747 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14750 if (!IN_LEV_FIELD(x, y) ||
14751 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14752 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14755 volume = SOUND_MAX_VOLUME;
14757 if (!IN_SCR_FIELD(sx, sy))
14759 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14760 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14762 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14765 stereo_position = (SOUND_MAX_LEFT +
14766 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14767 (SCR_FIELDX + 2 * max_distance));
14769 if (IS_LOOP_SOUND(nr))
14771 /* This assures that quieter loop sounds do not overwrite louder ones,
14772 while restarting sound volume comparison with each new game frame. */
14774 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14777 loop_sound_volume[nr] = volume;
14778 loop_sound_frame[nr] = FrameCounter;
14781 PlaySoundExt(nr, volume, stereo_position, type);
14784 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14786 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14787 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14788 y < LEVELY(BY1) ? LEVELY(BY1) :
14789 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14793 static void PlayLevelSoundAction(int x, int y, int action)
14795 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14798 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14800 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14802 if (sound_effect != SND_UNDEFINED)
14803 PlayLevelSound(x, y, sound_effect);
14806 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14809 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14811 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14812 PlayLevelSound(x, y, sound_effect);
14815 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14817 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14819 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14820 PlayLevelSound(x, y, sound_effect);
14823 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14825 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14827 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14828 StopSound(sound_effect);
14831 static int getLevelMusicNr(void)
14833 if (levelset.music[level_nr] != MUS_UNDEFINED)
14834 return levelset.music[level_nr]; // from config file
14836 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14839 static void FadeLevelSounds(void)
14844 static void FadeLevelMusic(void)
14846 int music_nr = getLevelMusicNr();
14847 char *curr_music = getCurrentlyPlayingMusicFilename();
14848 char *next_music = getMusicInfoEntryFilename(music_nr);
14850 if (!strEqual(curr_music, next_music))
14854 void FadeLevelSoundsAndMusic(void)
14860 static void PlayLevelMusic(void)
14862 int music_nr = getLevelMusicNr();
14863 char *curr_music = getCurrentlyPlayingMusicFilename();
14864 char *next_music = getMusicInfoEntryFilename(music_nr);
14866 if (!strEqual(curr_music, next_music))
14867 PlayMusicLoop(music_nr);
14870 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14872 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14874 int x = xx - offset;
14875 int y = yy - offset;
14880 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14884 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14888 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14892 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14896 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14900 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14904 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14907 case SOUND_android_clone:
14908 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14911 case SOUND_android_move:
14912 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14916 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14920 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14924 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14927 case SOUND_eater_eat:
14928 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14932 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14935 case SOUND_collect:
14936 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14939 case SOUND_diamond:
14940 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14944 // !!! CHECK THIS !!!
14946 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14948 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14952 case SOUND_wonderfall:
14953 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14957 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14961 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14965 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14969 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14973 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14977 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14981 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14985 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14988 case SOUND_exit_open:
14989 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14992 case SOUND_exit_leave:
14993 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14996 case SOUND_dynamite:
14997 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15001 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15005 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15009 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15013 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15017 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15021 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15025 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15030 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15032 int element = map_element_SP_to_RND(element_sp);
15033 int action = map_action_SP_to_RND(action_sp);
15034 int offset = (setup.sp_show_border_elements ? 0 : 1);
15035 int x = xx - offset;
15036 int y = yy - offset;
15038 PlayLevelSoundElementAction(x, y, element, action);
15041 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15043 int element = map_element_MM_to_RND(element_mm);
15044 int action = map_action_MM_to_RND(action_mm);
15046 int x = xx - offset;
15047 int y = yy - offset;
15049 if (!IS_MM_ELEMENT(element))
15050 element = EL_MM_DEFAULT;
15052 PlayLevelSoundElementAction(x, y, element, action);
15055 void PlaySound_MM(int sound_mm)
15057 int sound = map_sound_MM_to_RND(sound_mm);
15059 if (sound == SND_UNDEFINED)
15065 void PlaySoundLoop_MM(int sound_mm)
15067 int sound = map_sound_MM_to_RND(sound_mm);
15069 if (sound == SND_UNDEFINED)
15072 PlaySoundLoop(sound);
15075 void StopSound_MM(int sound_mm)
15077 int sound = map_sound_MM_to_RND(sound_mm);
15079 if (sound == SND_UNDEFINED)
15085 void RaiseScore(int value)
15087 game.score += value;
15089 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15091 DisplayGameControlValues();
15094 void RaiseScoreElement(int element)
15099 case EL_BD_DIAMOND:
15100 case EL_EMERALD_YELLOW:
15101 case EL_EMERALD_RED:
15102 case EL_EMERALD_PURPLE:
15103 case EL_SP_INFOTRON:
15104 RaiseScore(level.score[SC_EMERALD]);
15107 RaiseScore(level.score[SC_DIAMOND]);
15110 RaiseScore(level.score[SC_CRYSTAL]);
15113 RaiseScore(level.score[SC_PEARL]);
15116 case EL_BD_BUTTERFLY:
15117 case EL_SP_ELECTRON:
15118 RaiseScore(level.score[SC_BUG]);
15121 case EL_BD_FIREFLY:
15122 case EL_SP_SNIKSNAK:
15123 RaiseScore(level.score[SC_SPACESHIP]);
15126 case EL_DARK_YAMYAM:
15127 RaiseScore(level.score[SC_YAMYAM]);
15130 RaiseScore(level.score[SC_ROBOT]);
15133 RaiseScore(level.score[SC_PACMAN]);
15136 RaiseScore(level.score[SC_NUT]);
15139 case EL_EM_DYNAMITE:
15140 case EL_SP_DISK_RED:
15141 case EL_DYNABOMB_INCREASE_NUMBER:
15142 case EL_DYNABOMB_INCREASE_SIZE:
15143 case EL_DYNABOMB_INCREASE_POWER:
15144 RaiseScore(level.score[SC_DYNAMITE]);
15146 case EL_SHIELD_NORMAL:
15147 case EL_SHIELD_DEADLY:
15148 RaiseScore(level.score[SC_SHIELD]);
15150 case EL_EXTRA_TIME:
15151 RaiseScore(level.extra_time_score);
15165 case EL_DC_KEY_WHITE:
15166 RaiseScore(level.score[SC_KEY]);
15169 RaiseScore(element_info[element].collect_score);
15174 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15176 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15178 // closing door required in case of envelope style request dialogs
15181 // prevent short reactivation of overlay buttons while closing door
15182 SetOverlayActive(FALSE);
15184 CloseDoor(DOOR_CLOSE_1);
15187 if (network.enabled)
15188 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15192 FadeSkipNextFadeIn();
15194 SetGameStatus(GAME_MODE_MAIN);
15199 else // continue playing the game
15201 if (tape.playing && tape.deactivate_display)
15202 TapeDeactivateDisplayOff(TRUE);
15204 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15206 if (tape.playing && tape.deactivate_display)
15207 TapeDeactivateDisplayOn();
15211 void RequestQuitGame(boolean ask_if_really_quit)
15213 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15214 boolean skip_request = game.all_players_gone || quick_quit;
15216 RequestQuitGameExt(skip_request, quick_quit,
15217 "Do you really want to quit the game?");
15220 void RequestRestartGame(char *message)
15222 game.restart_game_message = NULL;
15224 boolean has_started_game = hasStartedNetworkGame();
15225 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15227 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15229 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15233 SetGameStatus(GAME_MODE_MAIN);
15239 void CheckGameOver(void)
15241 static boolean last_game_over = FALSE;
15242 static int game_over_delay = 0;
15243 int game_over_delay_value = 50;
15244 boolean game_over = checkGameFailed();
15246 // do not handle game over if request dialog is already active
15247 if (game.request_active)
15250 // do not ask to play again if game was never actually played
15251 if (!game.GamePlayed)
15256 last_game_over = FALSE;
15257 game_over_delay = game_over_delay_value;
15262 if (game_over_delay > 0)
15269 if (last_game_over != game_over)
15270 game.restart_game_message = (hasStartedNetworkGame() ?
15271 "Game over! Play it again?" :
15274 last_game_over = game_over;
15277 boolean checkGameSolved(void)
15279 // set for all game engines if level was solved
15280 return game.LevelSolved_GameEnd;
15283 boolean checkGameFailed(void)
15285 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15286 return (game_em.game_over && !game_em.level_solved);
15287 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15288 return (game_sp.game_over && !game_sp.level_solved);
15289 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15290 return (game_mm.game_over && !game_mm.level_solved);
15291 else // GAME_ENGINE_TYPE_RND
15292 return (game.GameOver && !game.LevelSolved);
15295 boolean checkGameEnded(void)
15297 return (checkGameSolved() || checkGameFailed());
15301 // ----------------------------------------------------------------------------
15302 // random generator functions
15303 // ----------------------------------------------------------------------------
15305 unsigned int InitEngineRandom_RND(int seed)
15307 game.num_random_calls = 0;
15309 return InitEngineRandom(seed);
15312 unsigned int RND(int max)
15316 game.num_random_calls++;
15318 return GetEngineRandom(max);
15325 // ----------------------------------------------------------------------------
15326 // game engine snapshot handling functions
15327 // ----------------------------------------------------------------------------
15329 struct EngineSnapshotInfo
15331 // runtime values for custom element collect score
15332 int collect_score[NUM_CUSTOM_ELEMENTS];
15334 // runtime values for group element choice position
15335 int choice_pos[NUM_GROUP_ELEMENTS];
15337 // runtime values for belt position animations
15338 int belt_graphic[4][NUM_BELT_PARTS];
15339 int belt_anim_mode[4][NUM_BELT_PARTS];
15342 static struct EngineSnapshotInfo engine_snapshot_rnd;
15343 static char *snapshot_level_identifier = NULL;
15344 static int snapshot_level_nr = -1;
15346 static void SaveEngineSnapshotValues_RND(void)
15348 static int belt_base_active_element[4] =
15350 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15351 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15352 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15353 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15357 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15359 int element = EL_CUSTOM_START + i;
15361 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15364 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15366 int element = EL_GROUP_START + i;
15368 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15371 for (i = 0; i < 4; i++)
15373 for (j = 0; j < NUM_BELT_PARTS; j++)
15375 int element = belt_base_active_element[i] + j;
15376 int graphic = el2img(element);
15377 int anim_mode = graphic_info[graphic].anim_mode;
15379 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15380 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15385 static void LoadEngineSnapshotValues_RND(void)
15387 unsigned int num_random_calls = game.num_random_calls;
15390 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15392 int element = EL_CUSTOM_START + i;
15394 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15397 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15399 int element = EL_GROUP_START + i;
15401 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15404 for (i = 0; i < 4; i++)
15406 for (j = 0; j < NUM_BELT_PARTS; j++)
15408 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15409 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15411 graphic_info[graphic].anim_mode = anim_mode;
15415 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15417 InitRND(tape.random_seed);
15418 for (i = 0; i < num_random_calls; i++)
15422 if (game.num_random_calls != num_random_calls)
15424 Error(ERR_INFO, "number of random calls out of sync");
15425 Error(ERR_INFO, "number of random calls should be %d", num_random_calls);
15426 Error(ERR_INFO, "number of random calls is %d", game.num_random_calls);
15427 Error(ERR_EXIT, "this should not happen -- please debug");
15431 void FreeEngineSnapshotSingle(void)
15433 FreeSnapshotSingle();
15435 setString(&snapshot_level_identifier, NULL);
15436 snapshot_level_nr = -1;
15439 void FreeEngineSnapshotList(void)
15441 FreeSnapshotList();
15444 static ListNode *SaveEngineSnapshotBuffers(void)
15446 ListNode *buffers = NULL;
15448 // copy some special values to a structure better suited for the snapshot
15450 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15451 SaveEngineSnapshotValues_RND();
15452 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15453 SaveEngineSnapshotValues_EM();
15454 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15455 SaveEngineSnapshotValues_SP(&buffers);
15456 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15457 SaveEngineSnapshotValues_MM(&buffers);
15459 // save values stored in special snapshot structure
15461 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15462 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15463 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15464 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15465 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15466 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15467 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15470 // save further RND engine values
15472 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15473 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15474 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15479 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15489 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15491 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15494 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15510 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15513 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15520 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15521 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15523 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15524 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15527 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15530 ListNode *node = engine_snapshot_list_rnd;
15533 while (node != NULL)
15535 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15540 Debug("game:playing:SaveEngineSnapshotBuffers",
15541 "size of engine snapshot: %d bytes", num_bytes);
15547 void SaveEngineSnapshotSingle(void)
15549 ListNode *buffers = SaveEngineSnapshotBuffers();
15551 // finally save all snapshot buffers to single snapshot
15552 SaveSnapshotSingle(buffers);
15554 // save level identification information
15555 setString(&snapshot_level_identifier, leveldir_current->identifier);
15556 snapshot_level_nr = level_nr;
15559 boolean CheckSaveEngineSnapshotToList(void)
15561 boolean save_snapshot =
15562 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15563 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15564 game.snapshot.changed_action) ||
15565 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15566 game.snapshot.collected_item));
15568 game.snapshot.changed_action = FALSE;
15569 game.snapshot.collected_item = FALSE;
15570 game.snapshot.save_snapshot = save_snapshot;
15572 return save_snapshot;
15575 void SaveEngineSnapshotToList(void)
15577 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15581 ListNode *buffers = SaveEngineSnapshotBuffers();
15583 // finally save all snapshot buffers to snapshot list
15584 SaveSnapshotToList(buffers);
15587 void SaveEngineSnapshotToListInitial(void)
15589 FreeEngineSnapshotList();
15591 SaveEngineSnapshotToList();
15594 static void LoadEngineSnapshotValues(void)
15596 // restore special values from snapshot structure
15598 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15599 LoadEngineSnapshotValues_RND();
15600 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15601 LoadEngineSnapshotValues_EM();
15602 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15603 LoadEngineSnapshotValues_SP();
15604 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15605 LoadEngineSnapshotValues_MM();
15608 void LoadEngineSnapshotSingle(void)
15610 LoadSnapshotSingle();
15612 LoadEngineSnapshotValues();
15615 static void LoadEngineSnapshot_Undo(int steps)
15617 LoadSnapshotFromList_Older(steps);
15619 LoadEngineSnapshotValues();
15622 static void LoadEngineSnapshot_Redo(int steps)
15624 LoadSnapshotFromList_Newer(steps);
15626 LoadEngineSnapshotValues();
15629 boolean CheckEngineSnapshotSingle(void)
15631 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15632 snapshot_level_nr == level_nr);
15635 boolean CheckEngineSnapshotList(void)
15637 return CheckSnapshotList();
15641 // ---------- new game button stuff -------------------------------------------
15648 boolean *setup_value;
15649 boolean allowed_on_tape;
15650 boolean is_touch_button;
15652 } gamebutton_info[NUM_GAME_BUTTONS] =
15655 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15656 GAME_CTRL_ID_STOP, NULL,
15657 TRUE, FALSE, "stop game"
15660 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15661 GAME_CTRL_ID_PAUSE, NULL,
15662 TRUE, FALSE, "pause game"
15665 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15666 GAME_CTRL_ID_PLAY, NULL,
15667 TRUE, FALSE, "play game"
15670 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15671 GAME_CTRL_ID_UNDO, NULL,
15672 TRUE, FALSE, "undo step"
15675 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15676 GAME_CTRL_ID_REDO, NULL,
15677 TRUE, FALSE, "redo step"
15680 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15681 GAME_CTRL_ID_SAVE, NULL,
15682 TRUE, FALSE, "save game"
15685 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15686 GAME_CTRL_ID_PAUSE2, NULL,
15687 TRUE, FALSE, "pause game"
15690 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15691 GAME_CTRL_ID_LOAD, NULL,
15692 TRUE, FALSE, "load game"
15695 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15696 GAME_CTRL_ID_PANEL_STOP, NULL,
15697 FALSE, FALSE, "stop game"
15700 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15701 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15702 FALSE, FALSE, "pause game"
15705 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15706 GAME_CTRL_ID_PANEL_PLAY, NULL,
15707 FALSE, FALSE, "play game"
15710 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15711 GAME_CTRL_ID_TOUCH_STOP, NULL,
15712 FALSE, TRUE, "stop game"
15715 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15716 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15717 FALSE, TRUE, "pause game"
15720 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15721 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15722 TRUE, FALSE, "background music on/off"
15725 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15726 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15727 TRUE, FALSE, "sound loops on/off"
15730 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15731 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15732 TRUE, FALSE, "normal sounds on/off"
15735 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15736 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15737 FALSE, FALSE, "background music on/off"
15740 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15741 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15742 FALSE, FALSE, "sound loops on/off"
15745 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15746 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15747 FALSE, FALSE, "normal sounds on/off"
15751 void CreateGameButtons(void)
15755 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15757 int graphic = gamebutton_info[i].graphic;
15758 struct GraphicInfo *gfx = &graphic_info[graphic];
15759 struct XY *pos = gamebutton_info[i].pos;
15760 struct GadgetInfo *gi;
15763 unsigned int event_mask;
15764 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15765 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15766 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15767 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15768 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15769 int gd_x = gfx->src_x;
15770 int gd_y = gfx->src_y;
15771 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15772 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15773 int gd_xa = gfx->src_x + gfx->active_xoffset;
15774 int gd_ya = gfx->src_y + gfx->active_yoffset;
15775 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15776 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15777 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15778 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15781 if (gfx->bitmap == NULL)
15783 game_gadget[id] = NULL;
15788 if (id == GAME_CTRL_ID_STOP ||
15789 id == GAME_CTRL_ID_PANEL_STOP ||
15790 id == GAME_CTRL_ID_TOUCH_STOP ||
15791 id == GAME_CTRL_ID_PLAY ||
15792 id == GAME_CTRL_ID_PANEL_PLAY ||
15793 id == GAME_CTRL_ID_SAVE ||
15794 id == GAME_CTRL_ID_LOAD)
15796 button_type = GD_TYPE_NORMAL_BUTTON;
15798 event_mask = GD_EVENT_RELEASED;
15800 else if (id == GAME_CTRL_ID_UNDO ||
15801 id == GAME_CTRL_ID_REDO)
15803 button_type = GD_TYPE_NORMAL_BUTTON;
15805 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15809 button_type = GD_TYPE_CHECK_BUTTON;
15810 checked = (gamebutton_info[i].setup_value != NULL ?
15811 *gamebutton_info[i].setup_value : FALSE);
15812 event_mask = GD_EVENT_PRESSED;
15815 gi = CreateGadget(GDI_CUSTOM_ID, id,
15816 GDI_IMAGE_ID, graphic,
15817 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15820 GDI_WIDTH, gfx->width,
15821 GDI_HEIGHT, gfx->height,
15822 GDI_TYPE, button_type,
15823 GDI_STATE, GD_BUTTON_UNPRESSED,
15824 GDI_CHECKED, checked,
15825 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15826 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15827 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15828 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15829 GDI_DIRECT_DRAW, FALSE,
15830 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15831 GDI_EVENT_MASK, event_mask,
15832 GDI_CALLBACK_ACTION, HandleGameButtons,
15836 Error(ERR_EXIT, "cannot create gadget");
15838 game_gadget[id] = gi;
15842 void FreeGameButtons(void)
15846 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15847 FreeGadget(game_gadget[i]);
15850 static void UnmapGameButtonsAtSamePosition(int id)
15854 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15856 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15857 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15858 UnmapGadget(game_gadget[i]);
15861 static void UnmapGameButtonsAtSamePosition_All(void)
15863 if (setup.show_snapshot_buttons)
15865 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15866 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15867 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15871 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15872 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15873 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15875 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15876 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15877 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15881 static void MapGameButtonsAtSamePosition(int id)
15885 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15887 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15888 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15889 MapGadget(game_gadget[i]);
15891 UnmapGameButtonsAtSamePosition_All();
15894 void MapUndoRedoButtons(void)
15896 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15897 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15899 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15900 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15903 void UnmapUndoRedoButtons(void)
15905 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15906 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15908 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15909 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15912 void ModifyPauseButtons(void)
15916 GAME_CTRL_ID_PAUSE,
15917 GAME_CTRL_ID_PAUSE2,
15918 GAME_CTRL_ID_PANEL_PAUSE,
15919 GAME_CTRL_ID_TOUCH_PAUSE,
15924 for (i = 0; ids[i] > -1; i++)
15925 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15928 static void MapGameButtonsExt(boolean on_tape)
15932 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15933 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15934 i != GAME_CTRL_ID_UNDO &&
15935 i != GAME_CTRL_ID_REDO)
15936 MapGadget(game_gadget[i]);
15938 UnmapGameButtonsAtSamePosition_All();
15940 RedrawGameButtons();
15943 static void UnmapGameButtonsExt(boolean on_tape)
15947 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15948 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15949 UnmapGadget(game_gadget[i]);
15952 static void RedrawGameButtonsExt(boolean on_tape)
15956 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15957 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15958 RedrawGadget(game_gadget[i]);
15961 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15966 gi->checked = state;
15969 static void RedrawSoundButtonGadget(int id)
15971 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15972 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15973 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15974 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15975 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15976 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15979 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15980 RedrawGadget(game_gadget[id2]);
15983 void MapGameButtons(void)
15985 MapGameButtonsExt(FALSE);
15988 void UnmapGameButtons(void)
15990 UnmapGameButtonsExt(FALSE);
15993 void RedrawGameButtons(void)
15995 RedrawGameButtonsExt(FALSE);
15998 void MapGameButtonsOnTape(void)
16000 MapGameButtonsExt(TRUE);
16003 void UnmapGameButtonsOnTape(void)
16005 UnmapGameButtonsExt(TRUE);
16008 void RedrawGameButtonsOnTape(void)
16010 RedrawGameButtonsExt(TRUE);
16013 static void GameUndoRedoExt(void)
16015 ClearPlayerAction();
16017 tape.pausing = TRUE;
16020 UpdateAndDisplayGameControlValues();
16022 DrawCompleteVideoDisplay();
16023 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16024 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16025 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16030 static void GameUndo(int steps)
16032 if (!CheckEngineSnapshotList())
16035 LoadEngineSnapshot_Undo(steps);
16040 static void GameRedo(int steps)
16042 if (!CheckEngineSnapshotList())
16045 LoadEngineSnapshot_Redo(steps);
16050 static void HandleGameButtonsExt(int id, int button)
16052 static boolean game_undo_executed = FALSE;
16053 int steps = BUTTON_STEPSIZE(button);
16054 boolean handle_game_buttons =
16055 (game_status == GAME_MODE_PLAYING ||
16056 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16058 if (!handle_game_buttons)
16063 case GAME_CTRL_ID_STOP:
16064 case GAME_CTRL_ID_PANEL_STOP:
16065 case GAME_CTRL_ID_TOUCH_STOP:
16066 if (game_status == GAME_MODE_MAIN)
16072 RequestQuitGame(TRUE);
16076 case GAME_CTRL_ID_PAUSE:
16077 case GAME_CTRL_ID_PAUSE2:
16078 case GAME_CTRL_ID_PANEL_PAUSE:
16079 case GAME_CTRL_ID_TOUCH_PAUSE:
16080 if (network.enabled && game_status == GAME_MODE_PLAYING)
16083 SendToServer_ContinuePlaying();
16085 SendToServer_PausePlaying();
16088 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16090 game_undo_executed = FALSE;
16094 case GAME_CTRL_ID_PLAY:
16095 case GAME_CTRL_ID_PANEL_PLAY:
16096 if (game_status == GAME_MODE_MAIN)
16098 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16100 else if (tape.pausing)
16102 if (network.enabled)
16103 SendToServer_ContinuePlaying();
16105 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16109 case GAME_CTRL_ID_UNDO:
16110 // Important: When using "save snapshot when collecting an item" mode,
16111 // load last (current) snapshot for first "undo" after pressing "pause"
16112 // (else the last-but-one snapshot would be loaded, because the snapshot
16113 // pointer already points to the last snapshot when pressing "pause",
16114 // which is fine for "every step/move" mode, but not for "every collect")
16115 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16116 !game_undo_executed)
16119 game_undo_executed = TRUE;
16124 case GAME_CTRL_ID_REDO:
16128 case GAME_CTRL_ID_SAVE:
16132 case GAME_CTRL_ID_LOAD:
16136 case SOUND_CTRL_ID_MUSIC:
16137 case SOUND_CTRL_ID_PANEL_MUSIC:
16138 if (setup.sound_music)
16140 setup.sound_music = FALSE;
16144 else if (audio.music_available)
16146 setup.sound = setup.sound_music = TRUE;
16148 SetAudioMode(setup.sound);
16150 if (game_status == GAME_MODE_PLAYING)
16154 RedrawSoundButtonGadget(id);
16158 case SOUND_CTRL_ID_LOOPS:
16159 case SOUND_CTRL_ID_PANEL_LOOPS:
16160 if (setup.sound_loops)
16161 setup.sound_loops = FALSE;
16162 else if (audio.loops_available)
16164 setup.sound = setup.sound_loops = TRUE;
16166 SetAudioMode(setup.sound);
16169 RedrawSoundButtonGadget(id);
16173 case SOUND_CTRL_ID_SIMPLE:
16174 case SOUND_CTRL_ID_PANEL_SIMPLE:
16175 if (setup.sound_simple)
16176 setup.sound_simple = FALSE;
16177 else if (audio.sound_available)
16179 setup.sound = setup.sound_simple = TRUE;
16181 SetAudioMode(setup.sound);
16184 RedrawSoundButtonGadget(id);
16193 static void HandleGameButtons(struct GadgetInfo *gi)
16195 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16198 void HandleSoundButtonKeys(Key key)
16200 if (key == setup.shortcut.sound_simple)
16201 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16202 else if (key == setup.shortcut.sound_loops)
16203 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16204 else if (key == setup.shortcut.sound_music)
16205 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);