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_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2036 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2039 static void InitField_WithBug1(int x, int y, boolean init_game)
2041 InitField(x, y, init_game);
2043 // not needed to call InitMovDir() -- already done by InitField()!
2044 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2045 CAN_MOVE(Tile[x][y]))
2049 static void InitField_WithBug2(int x, int y, boolean init_game)
2051 int old_element = Tile[x][y];
2053 InitField(x, y, init_game);
2055 // not needed to call InitMovDir() -- already done by InitField()!
2056 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2057 CAN_MOVE(old_element) &&
2058 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2061 /* this case is in fact a combination of not less than three bugs:
2062 first, it calls InitMovDir() for elements that can move, although this is
2063 already done by InitField(); then, it checks the element that was at this
2064 field _before_ the call to InitField() (which can change it); lastly, it
2065 was not called for "mole with direction" elements, which were treated as
2066 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2070 static int get_key_element_from_nr(int key_nr)
2072 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2073 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2074 EL_EM_KEY_1 : EL_KEY_1);
2076 return key_base_element + key_nr;
2079 static int get_next_dropped_element(struct PlayerInfo *player)
2081 return (player->inventory_size > 0 ?
2082 player->inventory_element[player->inventory_size - 1] :
2083 player->inventory_infinite_element != EL_UNDEFINED ?
2084 player->inventory_infinite_element :
2085 player->dynabombs_left > 0 ?
2086 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2090 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2092 // pos >= 0: get element from bottom of the stack;
2093 // pos < 0: get element from top of the stack
2097 int min_inventory_size = -pos;
2098 int inventory_pos = player->inventory_size - min_inventory_size;
2099 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2101 return (player->inventory_size >= min_inventory_size ?
2102 player->inventory_element[inventory_pos] :
2103 player->inventory_infinite_element != EL_UNDEFINED ?
2104 player->inventory_infinite_element :
2105 player->dynabombs_left >= min_dynabombs_left ?
2106 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2111 int min_dynabombs_left = pos + 1;
2112 int min_inventory_size = pos + 1 - player->dynabombs_left;
2113 int inventory_pos = pos - player->dynabombs_left;
2115 return (player->inventory_infinite_element != EL_UNDEFINED ?
2116 player->inventory_infinite_element :
2117 player->dynabombs_left >= min_dynabombs_left ?
2118 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 player->inventory_size >= min_inventory_size ?
2120 player->inventory_element[inventory_pos] :
2125 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2127 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2128 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2131 if (gpo1->sort_priority != gpo2->sort_priority)
2132 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2134 compare_result = gpo1->nr - gpo2->nr;
2136 return compare_result;
2139 int getPlayerInventorySize(int player_nr)
2141 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2142 return game_em.ply[player_nr]->dynamite;
2143 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2144 return game_sp.red_disk_count;
2146 return stored_player[player_nr].inventory_size;
2149 static void InitGameControlValues(void)
2153 for (i = 0; game_panel_controls[i].nr != -1; i++)
2155 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2156 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2157 struct TextPosInfo *pos = gpc->pos;
2159 int type = gpc->type;
2163 Error("'game_panel_controls' structure corrupted at %d", i);
2165 Fail("this should not happen -- please debug");
2168 // force update of game controls after initialization
2169 gpc->value = gpc->last_value = -1;
2170 gpc->frame = gpc->last_frame = -1;
2171 gpc->gfx_frame = -1;
2173 // determine panel value width for later calculation of alignment
2174 if (type == TYPE_INTEGER || type == TYPE_STRING)
2176 pos->width = pos->size * getFontWidth(pos->font);
2177 pos->height = getFontHeight(pos->font);
2179 else if (type == TYPE_ELEMENT)
2181 pos->width = pos->size;
2182 pos->height = pos->size;
2185 // fill structure for game panel draw order
2187 gpo->sort_priority = pos->sort_priority;
2190 // sort game panel controls according to sort_priority and control number
2191 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2192 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2195 static void UpdatePlayfieldElementCount(void)
2197 boolean use_element_count = FALSE;
2200 // first check if it is needed at all to calculate playfield element count
2201 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2202 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2203 use_element_count = TRUE;
2205 if (!use_element_count)
2208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2209 element_info[i].element_count = 0;
2211 SCAN_PLAYFIELD(x, y)
2213 element_info[Tile[x][y]].element_count++;
2216 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2217 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2218 if (IS_IN_GROUP(j, i))
2219 element_info[EL_GROUP_START + i].element_count +=
2220 element_info[j].element_count;
2223 static void UpdateGameControlValues(void)
2226 int time = (game.LevelSolved ?
2227 game.LevelSolved_CountingTime :
2228 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2230 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2231 game_sp.time_played :
2232 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 game_mm.energy_left :
2234 game.no_time_limit ? TimePlayed : TimeLeft);
2235 int score = (game.LevelSolved ?
2236 game.LevelSolved_CountingScore :
2237 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->score :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2244 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2245 game_em.lev->gems_needed :
2246 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2247 game_sp.infotrons_still_needed :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 game_mm.kettles_still_needed :
2250 game.gems_still_needed);
2251 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2252 game_em.lev->gems_needed > 0 :
2253 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2254 game_sp.infotrons_still_needed > 0 :
2255 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2256 game_mm.kettles_still_needed > 0 ||
2257 game_mm.lights_still_needed > 0 :
2258 game.gems_still_needed > 0 ||
2259 game.sokoban_fields_still_needed > 0 ||
2260 game.sokoban_objects_still_needed > 0 ||
2261 game.lights_still_needed > 0);
2262 int health = (game.LevelSolved ?
2263 game.LevelSolved_CountingHealth :
2264 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2265 MM_HEALTH(game_mm.laser_overload_value) :
2267 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2269 UpdatePlayfieldElementCount();
2271 // update game panel control values
2273 // used instead of "level_nr" (for network games)
2274 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2275 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2277 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2278 for (i = 0; i < MAX_NUM_KEYS; i++)
2279 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2280 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2281 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2283 if (game.centered_player_nr == -1)
2285 for (i = 0; i < MAX_PLAYERS; i++)
2287 // only one player in Supaplex game engine
2288 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2291 for (k = 0; k < MAX_NUM_KEYS; k++)
2293 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2295 if (game_em.ply[i]->keys & (1 << k))
2296 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2297 get_key_element_from_nr(k);
2299 else if (stored_player[i].key[k])
2300 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2301 get_key_element_from_nr(k);
2304 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2305 getPlayerInventorySize(i);
2307 if (stored_player[i].num_white_keys > 0)
2308 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2311 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2312 stored_player[i].num_white_keys;
2317 int player_nr = game.centered_player_nr;
2319 for (k = 0; k < MAX_NUM_KEYS; k++)
2321 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2323 if (game_em.ply[player_nr]->keys & (1 << k))
2324 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2325 get_key_element_from_nr(k);
2327 else if (stored_player[player_nr].key[k])
2328 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2329 get_key_element_from_nr(k);
2332 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2333 getPlayerInventorySize(player_nr);
2335 if (stored_player[player_nr].num_white_keys > 0)
2336 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2338 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2339 stored_player[player_nr].num_white_keys;
2342 // re-arrange keys on game panel, if needed or if defined by style settings
2343 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2345 int nr = GAME_PANEL_KEY_1 + i;
2346 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2347 struct TextPosInfo *pos = gpc->pos;
2349 // skip check if key is not in the player's inventory
2350 if (gpc->value == EL_EMPTY)
2353 // check if keys should be arranged on panel from left to right
2354 if (pos->style == STYLE_LEFTMOST_POSITION)
2356 // check previous key positions (left from current key)
2357 for (k = 0; k < i; k++)
2359 int nr_new = GAME_PANEL_KEY_1 + k;
2361 if (game_panel_controls[nr_new].value == EL_EMPTY)
2363 game_panel_controls[nr_new].value = gpc->value;
2364 gpc->value = EL_EMPTY;
2371 // check if "undefined" keys can be placed at some other position
2372 if (pos->x == -1 && pos->y == -1)
2374 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2376 // 1st try: display key at the same position as normal or EM keys
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2383 // 2nd try: display key at the next free position in the key panel
2384 for (k = 0; k < STD_NUM_KEYS; k++)
2386 nr_new = GAME_PANEL_KEY_1 + k;
2388 if (game_panel_controls[nr_new].value == EL_EMPTY)
2390 game_panel_controls[nr_new].value = gpc->value;
2399 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2401 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2402 get_inventory_element_from_pos(local_player, i);
2403 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2404 get_inventory_element_from_pos(local_player, -i - 1);
2407 game_panel_controls[GAME_PANEL_SCORE].value = score;
2408 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2410 game_panel_controls[GAME_PANEL_TIME].value = time;
2412 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2413 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2414 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2416 if (level.time == 0)
2417 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2419 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2421 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2422 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2424 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2427 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2429 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2430 local_player->shield_normal_time_left;
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2432 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2434 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2435 local_player->shield_deadly_time_left;
2437 game_panel_controls[GAME_PANEL_EXIT].value =
2438 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2440 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2441 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2442 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2443 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2444 EL_EMC_MAGIC_BALL_SWITCH);
2446 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2447 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2448 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2449 game.light_time_left;
2451 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2452 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2453 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2454 game.timegate_time_left;
2456 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2457 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2459 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2460 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2461 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2462 game.lenses_time_left;
2464 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2465 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2466 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2467 game.magnify_time_left;
2469 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2470 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2471 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2472 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2473 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2474 EL_BALLOON_SWITCH_NONE);
2476 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2477 local_player->dynabomb_count;
2478 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2479 local_player->dynabomb_size;
2480 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2481 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2483 game_panel_controls[GAME_PANEL_PENGUINS].value =
2484 game.friends_still_needed;
2486 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2487 game.sokoban_objects_still_needed;
2488 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2489 game.sokoban_fields_still_needed;
2491 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2492 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2494 for (i = 0; i < NUM_BELTS; i++)
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2497 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2498 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2499 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2500 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2503 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2504 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2505 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2506 game.magic_wall_time_left;
2508 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2509 local_player->gravity;
2511 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2512 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2514 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2515 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2516 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2517 game.panel.element[i].id : EL_UNDEFINED);
2519 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2520 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2521 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2522 element_info[game.panel.element_count[i].id].element_count : 0);
2524 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2525 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2526 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2527 element_info[game.panel.ce_score[i].id].collect_score : 0);
2529 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2530 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2531 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2532 element_info[game.panel.ce_score_element[i].id].collect_score :
2535 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2536 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2537 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2539 // update game panel control frames
2541 for (i = 0; game_panel_controls[i].nr != -1; i++)
2543 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2545 if (gpc->type == TYPE_ELEMENT)
2547 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2549 int last_anim_random_frame = gfx.anim_random_frame;
2550 int element = gpc->value;
2551 int graphic = el2panelimg(element);
2552 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2553 sync_random_frame : INIT_GFX_RANDOM());
2555 if (gpc->value != gpc->last_value)
2558 gpc->gfx_random = init_gfx_random;
2564 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2565 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2566 gpc->gfx_random = init_gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2570 gfx.anim_random_frame = gpc->gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2573 gpc->gfx_frame = element_info[element].collect_score;
2575 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = last_anim_random_frame;
2581 else if (gpc->type == TYPE_GRAPHIC)
2583 if (gpc->graphic != IMG_UNDEFINED)
2585 int last_anim_random_frame = gfx.anim_random_frame;
2586 int graphic = gpc->graphic;
2587 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2588 sync_random_frame : INIT_GFX_RANDOM());
2590 if (gpc->value != gpc->last_value)
2593 gpc->gfx_random = init_gfx_random;
2599 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2600 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2601 gpc->gfx_random = init_gfx_random;
2604 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2605 gfx.anim_random_frame = gpc->gfx_random;
2607 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2609 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2610 gfx.anim_random_frame = last_anim_random_frame;
2616 static void DisplayGameControlValues(void)
2618 boolean redraw_panel = FALSE;
2621 for (i = 0; game_panel_controls[i].nr != -1; i++)
2623 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2625 if (PANEL_DEACTIVATED(gpc->pos))
2628 if (gpc->value == gpc->last_value &&
2629 gpc->frame == gpc->last_frame)
2632 redraw_panel = TRUE;
2638 // copy default game door content to main double buffer
2640 // !!! CHECK AGAIN !!!
2641 SetPanelBackground();
2642 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2643 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2645 // redraw game control buttons
2646 RedrawGameButtons();
2648 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2650 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2652 int nr = game_panel_order[i].nr;
2653 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2654 struct TextPosInfo *pos = gpc->pos;
2655 int type = gpc->type;
2656 int value = gpc->value;
2657 int frame = gpc->frame;
2658 int size = pos->size;
2659 int font = pos->font;
2660 boolean draw_masked = pos->draw_masked;
2661 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2663 if (PANEL_DEACTIVATED(pos))
2666 if (pos->class == get_hash_from_key("extra_panel_items") &&
2667 !setup.prefer_extra_panel_items)
2670 gpc->last_value = value;
2671 gpc->last_frame = frame;
2673 if (type == TYPE_INTEGER)
2675 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2676 nr == GAME_PANEL_TIME)
2678 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2680 if (use_dynamic_size) // use dynamic number of digits
2682 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2683 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2684 int size2 = size1 + 1;
2685 int font1 = pos->font;
2686 int font2 = pos->font_alt;
2688 size = (value < value_change ? size1 : size2);
2689 font = (value < value_change ? font1 : font2);
2693 // correct text size if "digits" is zero or less
2695 size = strlen(int2str(value, size));
2697 // dynamically correct text alignment
2698 pos->width = size * getFontWidth(font);
2700 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2701 int2str(value, size), font, mask_mode);
2703 else if (type == TYPE_ELEMENT)
2705 int element, graphic;
2709 int dst_x = PANEL_XPOS(pos);
2710 int dst_y = PANEL_YPOS(pos);
2712 if (value != EL_UNDEFINED && value != EL_EMPTY)
2715 graphic = el2panelimg(value);
2718 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2719 element, EL_NAME(element), size);
2722 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2725 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2728 width = graphic_info[graphic].width * size / TILESIZE;
2729 height = graphic_info[graphic].height * size / TILESIZE;
2732 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2735 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2739 else if (type == TYPE_GRAPHIC)
2741 int graphic = gpc->graphic;
2742 int graphic_active = gpc->graphic_active;
2746 int dst_x = PANEL_XPOS(pos);
2747 int dst_y = PANEL_YPOS(pos);
2748 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2749 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2751 if (graphic != IMG_UNDEFINED && !skip)
2753 if (pos->style == STYLE_REVERSE)
2754 value = 100 - value;
2756 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2758 if (pos->direction & MV_HORIZONTAL)
2760 width = graphic_info[graphic_active].width * value / 100;
2761 height = graphic_info[graphic_active].height;
2763 if (pos->direction == MV_LEFT)
2765 src_x += graphic_info[graphic_active].width - width;
2766 dst_x += graphic_info[graphic_active].width - width;
2771 width = graphic_info[graphic_active].width;
2772 height = graphic_info[graphic_active].height * value / 100;
2774 if (pos->direction == MV_UP)
2776 src_y += graphic_info[graphic_active].height - height;
2777 dst_y += graphic_info[graphic_active].height - height;
2782 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2785 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2788 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2790 if (pos->direction & MV_HORIZONTAL)
2792 if (pos->direction == MV_RIGHT)
2799 dst_x = PANEL_XPOS(pos);
2802 width = graphic_info[graphic].width - width;
2806 if (pos->direction == MV_DOWN)
2813 dst_y = PANEL_YPOS(pos);
2816 height = graphic_info[graphic].height - height;
2820 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2823 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2827 else if (type == TYPE_STRING)
2829 boolean active = (value != 0);
2830 char *state_normal = "off";
2831 char *state_active = "on";
2832 char *state = (active ? state_active : state_normal);
2833 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2834 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2835 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2836 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2838 if (nr == GAME_PANEL_GRAVITY_STATE)
2840 int font1 = pos->font; // (used for normal state)
2841 int font2 = pos->font_alt; // (used for active state)
2843 font = (active ? font2 : font1);
2852 // don't truncate output if "chars" is zero or less
2855 // dynamically correct text alignment
2856 pos->width = size * getFontWidth(font);
2859 s_cut = getStringCopyN(s, size);
2861 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2862 s_cut, font, mask_mode);
2868 redraw_mask |= REDRAW_DOOR_1;
2871 SetGameStatus(GAME_MODE_PLAYING);
2874 void UpdateAndDisplayGameControlValues(void)
2876 if (tape.deactivate_display)
2879 UpdateGameControlValues();
2880 DisplayGameControlValues();
2883 void UpdateGameDoorValues(void)
2885 UpdateGameControlValues();
2888 void DrawGameDoorValues(void)
2890 DisplayGameControlValues();
2894 // ============================================================================
2896 // ----------------------------------------------------------------------------
2897 // initialize game engine due to level / tape version number
2898 // ============================================================================
2900 static void InitGameEngine(void)
2902 int i, j, k, l, x, y;
2904 // set game engine from tape file when re-playing, else from level file
2905 game.engine_version = (tape.playing ? tape.engine_version :
2906 level.game_version);
2908 // set single or multi-player game mode (needed for re-playing tapes)
2909 game.team_mode = setup.team_mode;
2913 int num_players = 0;
2915 for (i = 0; i < MAX_PLAYERS; i++)
2916 if (tape.player_participates[i])
2919 // multi-player tapes contain input data for more than one player
2920 game.team_mode = (num_players > 1);
2924 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2925 level.game_version);
2926 Debug("game:init:level", " tape.file_version == %06d",
2928 Debug("game:init:level", " tape.game_version == %06d",
2930 Debug("game:init:level", " tape.engine_version == %06d",
2931 tape.engine_version);
2932 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2933 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2936 // --------------------------------------------------------------------------
2937 // set flags for bugs and changes according to active game engine version
2938 // --------------------------------------------------------------------------
2942 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2944 Bug was introduced in version:
2947 Bug was fixed in version:
2951 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2952 but the property "can fall" was missing, which caused some levels to be
2953 unsolvable. This was fixed in version 4.2.0.0.
2955 Affected levels/tapes:
2956 An example for a tape that was fixed by this bugfix is tape 029 from the
2957 level set "rnd_sam_bateman".
2958 The wrong behaviour will still be used for all levels or tapes that were
2959 created/recorded with it. An example for this is tape 023 from the level
2960 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2963 boolean use_amoeba_dropping_cannot_fall_bug =
2964 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2965 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2967 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2968 tape.game_version < VERSION_IDENT(4,2,0,0)));
2971 Summary of bugfix/change:
2972 Fixed move speed of elements entering or leaving magic wall.
2974 Fixed/changed in version:
2978 Before 2.0.1, move speed of elements entering or leaving magic wall was
2979 twice as fast as it is now.
2980 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2982 Affected levels/tapes:
2983 The first condition is generally needed for all levels/tapes before version
2984 2.0.1, which might use the old behaviour before it was changed; known tapes
2985 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2986 The second condition is an exception from the above case and is needed for
2987 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2988 above, but before it was known that this change would break tapes like the
2989 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2990 although the engine version while recording maybe was before 2.0.1. There
2991 are a lot of tapes that are affected by this exception, like tape 006 from
2992 the level set "rnd_conor_mancone".
2995 boolean use_old_move_stepsize_for_magic_wall =
2996 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2998 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2999 tape.game_version < VERSION_IDENT(4,2,0,0)));
3002 Summary of bugfix/change:
3003 Fixed handling for custom elements that change when pushed by the player.
3005 Fixed/changed in version:
3009 Before 3.1.0, custom elements that "change when pushing" changed directly
3010 after the player started pushing them (until then handled in "DigField()").
3011 Since 3.1.0, these custom elements are not changed until the "pushing"
3012 move of the element is finished (now handled in "ContinueMoving()").
3014 Affected levels/tapes:
3015 The first condition is generally needed for all levels/tapes before version
3016 3.1.0, which might use the old behaviour before it was changed; known tapes
3017 that are affected are some tapes from the level set "Walpurgis Gardens" by
3019 The second condition is an exception from the above case and is needed for
3020 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3021 above (including some development versions of 3.1.0), but before it was
3022 known that this change would break tapes like the above and was fixed in
3023 3.1.1, so that the changed behaviour was active although the engine version
3024 while recording maybe was before 3.1.0. There is at least one tape that is
3025 affected by this exception, which is the tape for the one-level set "Bug
3026 Machine" by Juergen Bonhagen.
3029 game.use_change_when_pushing_bug =
3030 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3032 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3033 tape.game_version < VERSION_IDENT(3,1,1,0)));
3036 Summary of bugfix/change:
3037 Fixed handling for blocking the field the player leaves when moving.
3039 Fixed/changed in version:
3043 Before 3.1.1, when "block last field when moving" was enabled, the field
3044 the player is leaving when moving was blocked for the time of the move,
3045 and was directly unblocked afterwards. This resulted in the last field
3046 being blocked for exactly one less than the number of frames of one player
3047 move. Additionally, even when blocking was disabled, the last field was
3048 blocked for exactly one frame.
3049 Since 3.1.1, due to changes in player movement handling, the last field
3050 is not blocked at all when blocking is disabled. When blocking is enabled,
3051 the last field is blocked for exactly the number of frames of one player
3052 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3053 last field is blocked for exactly one more than the number of frames of
3056 Affected levels/tapes:
3057 (!!! yet to be determined -- probably many !!!)
3060 game.use_block_last_field_bug =
3061 (game.engine_version < VERSION_IDENT(3,1,1,0));
3063 /* various special flags and settings for native Emerald Mine game engine */
3065 game_em.use_single_button =
3066 (game.engine_version > VERSION_IDENT(4,0,0,2));
3068 game_em.use_snap_key_bug =
3069 (game.engine_version < VERSION_IDENT(4,0,1,0));
3071 game_em.use_random_bug =
3072 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3074 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3076 game_em.use_old_explosions = use_old_em_engine;
3077 game_em.use_old_android = use_old_em_engine;
3078 game_em.use_old_push_elements = use_old_em_engine;
3079 game_em.use_old_push_into_acid = use_old_em_engine;
3081 game_em.use_wrap_around = !use_old_em_engine;
3083 // --------------------------------------------------------------------------
3085 // set maximal allowed number of custom element changes per game frame
3086 game.max_num_changes_per_frame = 1;
3088 // default scan direction: scan playfield from top/left to bottom/right
3089 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3091 // dynamically adjust element properties according to game engine version
3092 InitElementPropertiesEngine(game.engine_version);
3094 // ---------- initialize special element properties -------------------------
3096 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3097 if (use_amoeba_dropping_cannot_fall_bug)
3098 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3100 // ---------- initialize player's initial move delay ------------------------
3102 // dynamically adjust player properties according to level information
3103 for (i = 0; i < MAX_PLAYERS; i++)
3104 game.initial_move_delay_value[i] =
3105 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3107 // dynamically adjust player properties according to game engine version
3108 for (i = 0; i < MAX_PLAYERS; i++)
3109 game.initial_move_delay[i] =
3110 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3111 game.initial_move_delay_value[i] : 0);
3113 // ---------- initialize player's initial push delay ------------------------
3115 // dynamically adjust player properties according to game engine version
3116 game.initial_push_delay_value =
3117 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3119 // ---------- initialize changing elements ----------------------------------
3121 // initialize changing elements information
3122 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3124 struct ElementInfo *ei = &element_info[i];
3126 // this pointer might have been changed in the level editor
3127 ei->change = &ei->change_page[0];
3129 if (!IS_CUSTOM_ELEMENT(i))
3131 ei->change->target_element = EL_EMPTY_SPACE;
3132 ei->change->delay_fixed = 0;
3133 ei->change->delay_random = 0;
3134 ei->change->delay_frames = 1;
3137 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3139 ei->has_change_event[j] = FALSE;
3141 ei->event_page_nr[j] = 0;
3142 ei->event_page[j] = &ei->change_page[0];
3146 // add changing elements from pre-defined list
3147 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3149 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3150 struct ElementInfo *ei = &element_info[ch_delay->element];
3152 ei->change->target_element = ch_delay->target_element;
3153 ei->change->delay_fixed = ch_delay->change_delay;
3155 ei->change->pre_change_function = ch_delay->pre_change_function;
3156 ei->change->change_function = ch_delay->change_function;
3157 ei->change->post_change_function = ch_delay->post_change_function;
3159 ei->change->can_change = TRUE;
3160 ei->change->can_change_or_has_action = TRUE;
3162 ei->has_change_event[CE_DELAY] = TRUE;
3164 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3165 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3168 // ---------- initialize internal run-time variables ------------------------
3170 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3172 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3174 for (j = 0; j < ei->num_change_pages; j++)
3176 ei->change_page[j].can_change_or_has_action =
3177 (ei->change_page[j].can_change |
3178 ei->change_page[j].has_action);
3182 // add change events from custom element configuration
3183 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3185 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3187 for (j = 0; j < ei->num_change_pages; j++)
3189 if (!ei->change_page[j].can_change_or_has_action)
3192 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3194 // only add event page for the first page found with this event
3195 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3197 ei->has_change_event[k] = TRUE;
3199 ei->event_page_nr[k] = j;
3200 ei->event_page[k] = &ei->change_page[j];
3206 // ---------- initialize reference elements in change conditions ------------
3208 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3210 int element = EL_CUSTOM_START + i;
3211 struct ElementInfo *ei = &element_info[element];
3213 for (j = 0; j < ei->num_change_pages; j++)
3215 int trigger_element = ei->change_page[j].initial_trigger_element;
3217 if (trigger_element >= EL_PREV_CE_8 &&
3218 trigger_element <= EL_NEXT_CE_8)
3219 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3221 ei->change_page[j].trigger_element = trigger_element;
3225 // ---------- initialize run-time trigger player and element ----------------
3227 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3229 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3231 for (j = 0; j < ei->num_change_pages; j++)
3233 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3234 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3235 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3236 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3237 ei->change_page[j].actual_trigger_ce_value = 0;
3238 ei->change_page[j].actual_trigger_ce_score = 0;
3242 // ---------- initialize trigger events -------------------------------------
3244 // initialize trigger events information
3245 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3246 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3247 trigger_events[i][j] = FALSE;
3249 // add trigger events from element change event properties
3250 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3252 struct ElementInfo *ei = &element_info[i];
3254 for (j = 0; j < ei->num_change_pages; j++)
3256 if (!ei->change_page[j].can_change_or_has_action)
3259 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3261 int trigger_element = ei->change_page[j].trigger_element;
3263 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3265 if (ei->change_page[j].has_event[k])
3267 if (IS_GROUP_ELEMENT(trigger_element))
3269 struct ElementGroupInfo *group =
3270 element_info[trigger_element].group;
3272 for (l = 0; l < group->num_elements_resolved; l++)
3273 trigger_events[group->element_resolved[l]][k] = TRUE;
3275 else if (trigger_element == EL_ANY_ELEMENT)
3276 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3277 trigger_events[l][k] = TRUE;
3279 trigger_events[trigger_element][k] = TRUE;
3286 // ---------- initialize push delay -----------------------------------------
3288 // initialize push delay values to default
3289 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3291 if (!IS_CUSTOM_ELEMENT(i))
3293 // set default push delay values (corrected since version 3.0.7-1)
3294 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3296 element_info[i].push_delay_fixed = 2;
3297 element_info[i].push_delay_random = 8;
3301 element_info[i].push_delay_fixed = 8;
3302 element_info[i].push_delay_random = 8;
3307 // set push delay value for certain elements from pre-defined list
3308 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3310 int e = push_delay_list[i].element;
3312 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3313 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3316 // set push delay value for Supaplex elements for newer engine versions
3317 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3319 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3321 if (IS_SP_ELEMENT(i))
3323 // set SP push delay to just enough to push under a falling zonk
3324 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3326 element_info[i].push_delay_fixed = delay;
3327 element_info[i].push_delay_random = 0;
3332 // ---------- initialize move stepsize --------------------------------------
3334 // initialize move stepsize values to default
3335 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3336 if (!IS_CUSTOM_ELEMENT(i))
3337 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3339 // set move stepsize value for certain elements from pre-defined list
3340 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3342 int e = move_stepsize_list[i].element;
3344 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3346 // set move stepsize value for certain elements for older engine versions
3347 if (use_old_move_stepsize_for_magic_wall)
3349 if (e == EL_MAGIC_WALL_FILLING ||
3350 e == EL_MAGIC_WALL_EMPTYING ||
3351 e == EL_BD_MAGIC_WALL_FILLING ||
3352 e == EL_BD_MAGIC_WALL_EMPTYING)
3353 element_info[e].move_stepsize *= 2;
3357 // ---------- initialize collect score --------------------------------------
3359 // initialize collect score values for custom elements from initial value
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3361 if (IS_CUSTOM_ELEMENT(i))
3362 element_info[i].collect_score = element_info[i].collect_score_initial;
3364 // ---------- initialize collect count --------------------------------------
3366 // initialize collect count values for non-custom elements
3367 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3368 if (!IS_CUSTOM_ELEMENT(i))
3369 element_info[i].collect_count_initial = 0;
3371 // add collect count values for all elements from pre-defined list
3372 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3373 element_info[collect_count_list[i].element].collect_count_initial =
3374 collect_count_list[i].count;
3376 // ---------- initialize access direction -----------------------------------
3378 // initialize access direction values to default (access from every side)
3379 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (!IS_CUSTOM_ELEMENT(i))
3381 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3383 // set access direction value for certain elements from pre-defined list
3384 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3385 element_info[access_direction_list[i].element].access_direction =
3386 access_direction_list[i].direction;
3388 // ---------- initialize explosion content ----------------------------------
3389 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3391 if (IS_CUSTOM_ELEMENT(i))
3394 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3396 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3398 element_info[i].content.e[x][y] =
3399 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3400 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3401 i == EL_PLAYER_3 ? EL_EMERALD :
3402 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3403 i == EL_MOLE ? EL_EMERALD_RED :
3404 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3405 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3406 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3407 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3408 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3409 i == EL_WALL_EMERALD ? EL_EMERALD :
3410 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3411 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3412 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3413 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3414 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3415 i == EL_WALL_PEARL ? EL_PEARL :
3416 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3421 // ---------- initialize recursion detection --------------------------------
3422 recursion_loop_depth = 0;
3423 recursion_loop_detected = FALSE;
3424 recursion_loop_element = EL_UNDEFINED;
3426 // ---------- initialize graphics engine ------------------------------------
3427 game.scroll_delay_value =
3428 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3429 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3430 !setup.forced_scroll_delay ? 0 :
3431 setup.scroll_delay ? setup.scroll_delay_value : 0);
3432 game.scroll_delay_value =
3433 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3435 // ---------- initialize game engine snapshots ------------------------------
3436 for (i = 0; i < MAX_PLAYERS; i++)
3437 game.snapshot.last_action[i] = 0;
3438 game.snapshot.changed_action = FALSE;
3439 game.snapshot.collected_item = FALSE;
3440 game.snapshot.mode =
3441 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3442 SNAPSHOT_MODE_EVERY_STEP :
3443 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3444 SNAPSHOT_MODE_EVERY_MOVE :
3445 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3446 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3447 game.snapshot.save_snapshot = FALSE;
3449 // ---------- initialize level time for Supaplex engine ---------------------
3450 // Supaplex levels with time limit currently unsupported -- should be added
3451 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3454 // ---------- initialize flags for handling game actions --------------------
3456 // set flags for game actions to default values
3457 game.use_key_actions = TRUE;
3458 game.use_mouse_actions = FALSE;
3460 // when using Mirror Magic game engine, handle mouse events only
3461 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3463 game.use_key_actions = FALSE;
3464 game.use_mouse_actions = TRUE;
3467 // check for custom elements with mouse click events
3468 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3470 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3472 int element = EL_CUSTOM_START + i;
3474 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3475 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3476 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3477 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3478 game.use_mouse_actions = TRUE;
3483 static int get_num_special_action(int element, int action_first,
3486 int num_special_action = 0;
3489 for (i = action_first; i <= action_last; i++)
3491 boolean found = FALSE;
3493 for (j = 0; j < NUM_DIRECTIONS; j++)
3494 if (el_act_dir2img(element, i, j) !=
3495 el_act_dir2img(element, ACTION_DEFAULT, j))
3499 num_special_action++;
3504 return num_special_action;
3508 // ============================================================================
3510 // ----------------------------------------------------------------------------
3511 // initialize and start new game
3512 // ============================================================================
3514 #if DEBUG_INIT_PLAYER
3515 static void DebugPrintPlayerStatus(char *message)
3522 Debug("game:init:player", "%s:", message);
3524 for (i = 0; i < MAX_PLAYERS; i++)
3526 struct PlayerInfo *player = &stored_player[i];
3528 Debug("game:init:player",
3529 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3533 player->connected_locally,
3534 player->connected_network,
3536 (local_player == player ? " (local player)" : ""));
3543 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3544 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3545 int fade_mask = REDRAW_FIELD;
3547 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3548 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3549 int initial_move_dir = MV_DOWN;
3552 // required here to update video display before fading (FIX THIS)
3553 DrawMaskedBorder(REDRAW_DOOR_2);
3555 if (!game.restart_level)
3556 CloseDoor(DOOR_CLOSE_1);
3558 SetGameStatus(GAME_MODE_PLAYING);
3560 if (level_editor_test_game)
3561 FadeSkipNextFadeOut();
3563 FadeSetEnterScreen();
3566 fade_mask = REDRAW_ALL;
3568 FadeLevelSoundsAndMusic();
3570 ExpireSoundLoops(TRUE);
3574 if (level_editor_test_game)
3575 FadeSkipNextFadeIn();
3577 // needed if different viewport properties defined for playing
3578 ChangeViewportPropertiesIfNeeded();
3582 DrawCompleteVideoDisplay();
3584 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3587 InitGameControlValues();
3591 // initialize tape actions from game when recording tape
3592 tape.use_key_actions = game.use_key_actions;
3593 tape.use_mouse_actions = game.use_mouse_actions;
3595 // initialize visible playfield size when recording tape (for team mode)
3596 tape.scr_fieldx = SCR_FIELDX;
3597 tape.scr_fieldy = SCR_FIELDY;
3600 // don't play tapes over network
3601 network_playing = (network.enabled && !tape.playing);
3603 for (i = 0; i < MAX_PLAYERS; i++)
3605 struct PlayerInfo *player = &stored_player[i];
3607 player->index_nr = i;
3608 player->index_bit = (1 << i);
3609 player->element_nr = EL_PLAYER_1 + i;
3611 player->present = FALSE;
3612 player->active = FALSE;
3613 player->mapped = FALSE;
3615 player->killed = FALSE;
3616 player->reanimated = FALSE;
3617 player->buried = FALSE;
3620 player->effective_action = 0;
3621 player->programmed_action = 0;
3622 player->snap_action = 0;
3624 player->mouse_action.lx = 0;
3625 player->mouse_action.ly = 0;
3626 player->mouse_action.button = 0;
3627 player->mouse_action.button_hint = 0;
3629 player->effective_mouse_action.lx = 0;
3630 player->effective_mouse_action.ly = 0;
3631 player->effective_mouse_action.button = 0;
3632 player->effective_mouse_action.button_hint = 0;
3634 for (j = 0; j < MAX_NUM_KEYS; j++)
3635 player->key[j] = FALSE;
3637 player->num_white_keys = 0;
3639 player->dynabomb_count = 0;
3640 player->dynabomb_size = 1;
3641 player->dynabombs_left = 0;
3642 player->dynabomb_xl = FALSE;
3644 player->MovDir = initial_move_dir;
3647 player->GfxDir = initial_move_dir;
3648 player->GfxAction = ACTION_DEFAULT;
3650 player->StepFrame = 0;
3652 player->initial_element = player->element_nr;
3653 player->artwork_element =
3654 (level.use_artwork_element[i] ? level.artwork_element[i] :
3655 player->element_nr);
3656 player->use_murphy = FALSE;
3658 player->block_last_field = FALSE; // initialized in InitPlayerField()
3659 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3661 player->gravity = level.initial_player_gravity[i];
3663 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3665 player->actual_frame_counter = 0;
3667 player->step_counter = 0;
3669 player->last_move_dir = initial_move_dir;
3671 player->is_active = FALSE;
3673 player->is_waiting = FALSE;
3674 player->is_moving = FALSE;
3675 player->is_auto_moving = FALSE;
3676 player->is_digging = FALSE;
3677 player->is_snapping = FALSE;
3678 player->is_collecting = FALSE;
3679 player->is_pushing = FALSE;
3680 player->is_switching = FALSE;
3681 player->is_dropping = FALSE;
3682 player->is_dropping_pressed = FALSE;
3684 player->is_bored = FALSE;
3685 player->is_sleeping = FALSE;
3687 player->was_waiting = TRUE;
3688 player->was_moving = FALSE;
3689 player->was_snapping = FALSE;
3690 player->was_dropping = FALSE;
3692 player->force_dropping = FALSE;
3694 player->frame_counter_bored = -1;
3695 player->frame_counter_sleeping = -1;
3697 player->anim_delay_counter = 0;
3698 player->post_delay_counter = 0;
3700 player->dir_waiting = initial_move_dir;
3701 player->action_waiting = ACTION_DEFAULT;
3702 player->last_action_waiting = ACTION_DEFAULT;
3703 player->special_action_bored = ACTION_DEFAULT;
3704 player->special_action_sleeping = ACTION_DEFAULT;
3706 player->switch_x = -1;
3707 player->switch_y = -1;
3709 player->drop_x = -1;
3710 player->drop_y = -1;
3712 player->show_envelope = 0;
3714 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3716 player->push_delay = -1; // initialized when pushing starts
3717 player->push_delay_value = game.initial_push_delay_value;
3719 player->drop_delay = 0;
3720 player->drop_pressed_delay = 0;
3722 player->last_jx = -1;
3723 player->last_jy = -1;
3727 player->shield_normal_time_left = 0;
3728 player->shield_deadly_time_left = 0;
3730 player->last_removed_element = EL_UNDEFINED;
3732 player->inventory_infinite_element = EL_UNDEFINED;
3733 player->inventory_size = 0;
3735 if (level.use_initial_inventory[i])
3737 for (j = 0; j < level.initial_inventory_size[i]; j++)
3739 int element = level.initial_inventory_content[i][j];
3740 int collect_count = element_info[element].collect_count_initial;
3743 if (!IS_CUSTOM_ELEMENT(element))
3746 if (collect_count == 0)
3747 player->inventory_infinite_element = element;
3749 for (k = 0; k < collect_count; k++)
3750 if (player->inventory_size < MAX_INVENTORY_SIZE)
3751 player->inventory_element[player->inventory_size++] = element;
3755 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3756 SnapField(player, 0, 0);
3758 map_player_action[i] = i;
3761 network_player_action_received = FALSE;
3763 // initial null action
3764 if (network_playing)
3765 SendToServer_MovePlayer(MV_NONE);
3770 TimeLeft = level.time;
3773 ScreenMovDir = MV_NONE;
3777 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3779 game.robot_wheel_x = -1;
3780 game.robot_wheel_y = -1;
3785 game.all_players_gone = FALSE;
3787 game.LevelSolved = FALSE;
3788 game.GameOver = FALSE;
3790 game.GamePlayed = !tape.playing;
3792 game.LevelSolved_GameWon = FALSE;
3793 game.LevelSolved_GameEnd = FALSE;
3794 game.LevelSolved_SaveTape = FALSE;
3795 game.LevelSolved_SaveScore = FALSE;
3797 game.LevelSolved_CountingTime = 0;
3798 game.LevelSolved_CountingScore = 0;
3799 game.LevelSolved_CountingHealth = 0;
3801 game.panel.active = TRUE;
3803 game.no_time_limit = (level.time == 0);
3805 game.yamyam_content_nr = 0;
3806 game.robot_wheel_active = FALSE;
3807 game.magic_wall_active = FALSE;
3808 game.magic_wall_time_left = 0;
3809 game.light_time_left = 0;
3810 game.timegate_time_left = 0;
3811 game.switchgate_pos = 0;
3812 game.wind_direction = level.wind_direction_initial;
3814 game.time_final = 0;
3815 game.score_time_final = 0;
3818 game.score_final = 0;
3820 game.health = MAX_HEALTH;
3821 game.health_final = MAX_HEALTH;
3823 game.gems_still_needed = level.gems_needed;
3824 game.sokoban_fields_still_needed = 0;
3825 game.sokoban_objects_still_needed = 0;
3826 game.lights_still_needed = 0;
3827 game.players_still_needed = 0;
3828 game.friends_still_needed = 0;
3830 game.lenses_time_left = 0;
3831 game.magnify_time_left = 0;
3833 game.ball_active = level.ball_active_initial;
3834 game.ball_content_nr = 0;
3836 game.explosions_delayed = TRUE;
3838 game.envelope_active = FALSE;
3840 for (i = 0; i < NUM_BELTS; i++)
3842 game.belt_dir[i] = MV_NONE;
3843 game.belt_dir_nr[i] = 3; // not moving, next moving left
3846 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3847 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3849 #if DEBUG_INIT_PLAYER
3850 DebugPrintPlayerStatus("Player status at level initialization");
3853 SCAN_PLAYFIELD(x, y)
3855 Tile[x][y] = Last[x][y] = level.field[x][y];
3856 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3857 ChangeDelay[x][y] = 0;
3858 ChangePage[x][y] = -1;
3859 CustomValue[x][y] = 0; // initialized in InitField()
3860 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3862 WasJustMoving[x][y] = 0;
3863 WasJustFalling[x][y] = 0;
3864 CheckCollision[x][y] = 0;
3865 CheckImpact[x][y] = 0;
3867 Pushed[x][y] = FALSE;
3869 ChangeCount[x][y] = 0;
3870 ChangeEvent[x][y] = -1;
3872 ExplodePhase[x][y] = 0;
3873 ExplodeDelay[x][y] = 0;
3874 ExplodeField[x][y] = EX_TYPE_NONE;
3876 RunnerVisit[x][y] = 0;
3877 PlayerVisit[x][y] = 0;
3880 GfxRandom[x][y] = INIT_GFX_RANDOM();
3881 GfxElement[x][y] = EL_UNDEFINED;
3882 GfxAction[x][y] = ACTION_DEFAULT;
3883 GfxDir[x][y] = MV_NONE;
3884 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3887 SCAN_PLAYFIELD(x, y)
3889 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3891 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3894 InitField(x, y, TRUE);
3896 ResetGfxAnimation(x, y);
3901 for (i = 0; i < MAX_PLAYERS; i++)
3903 struct PlayerInfo *player = &stored_player[i];
3905 // set number of special actions for bored and sleeping animation
3906 player->num_special_action_bored =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_BORING_1, ACTION_BORING_LAST);
3909 player->num_special_action_sleeping =
3910 get_num_special_action(player->artwork_element,
3911 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3914 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3915 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3917 // initialize type of slippery elements
3918 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3920 if (!IS_CUSTOM_ELEMENT(i))
3922 // default: elements slip down either to the left or right randomly
3923 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3925 // SP style elements prefer to slip down on the left side
3926 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3927 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3929 // BD style elements prefer to slip down on the left side
3930 if (game.emulation == EMU_BOULDERDASH)
3931 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3935 // initialize explosion and ignition delay
3936 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3938 if (!IS_CUSTOM_ELEMENT(i))
3941 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3942 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3943 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3944 int last_phase = (num_phase + 1) * delay;
3945 int half_phase = (num_phase / 2) * delay;
3947 element_info[i].explosion_delay = last_phase - 1;
3948 element_info[i].ignition_delay = half_phase;
3950 if (i == EL_BLACK_ORB)
3951 element_info[i].ignition_delay = 1;
3955 // correct non-moving belts to start moving left
3956 for (i = 0; i < NUM_BELTS; i++)
3957 if (game.belt_dir[i] == MV_NONE)
3958 game.belt_dir_nr[i] = 3; // not moving, next moving left
3960 #if USE_NEW_PLAYER_ASSIGNMENTS
3961 // use preferred player also in local single-player mode
3962 if (!network.enabled && !game.team_mode)
3964 int new_index_nr = setup.network_player_nr;
3966 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3968 for (i = 0; i < MAX_PLAYERS; i++)
3969 stored_player[i].connected_locally = FALSE;
3971 stored_player[new_index_nr].connected_locally = TRUE;
3975 for (i = 0; i < MAX_PLAYERS; i++)
3977 stored_player[i].connected = FALSE;
3979 // in network game mode, the local player might not be the first player
3980 if (stored_player[i].connected_locally)
3981 local_player = &stored_player[i];
3984 if (!network.enabled)
3985 local_player->connected = TRUE;
3989 for (i = 0; i < MAX_PLAYERS; i++)
3990 stored_player[i].connected = tape.player_participates[i];
3992 else if (network.enabled)
3994 // add team mode players connected over the network (needed for correct
3995 // assignment of player figures from level to locally playing players)
3997 for (i = 0; i < MAX_PLAYERS; i++)
3998 if (stored_player[i].connected_network)
3999 stored_player[i].connected = TRUE;
4001 else if (game.team_mode)
4003 // try to guess locally connected team mode players (needed for correct
4004 // assignment of player figures from level to locally playing players)
4006 for (i = 0; i < MAX_PLAYERS; i++)
4007 if (setup.input[i].use_joystick ||
4008 setup.input[i].key.left != KSYM_UNDEFINED)
4009 stored_player[i].connected = TRUE;
4012 #if DEBUG_INIT_PLAYER
4013 DebugPrintPlayerStatus("Player status after level initialization");
4016 #if DEBUG_INIT_PLAYER
4017 Debug("game:init:player", "Reassigning players ...");
4020 // check if any connected player was not found in playfield
4021 for (i = 0; i < MAX_PLAYERS; i++)
4023 struct PlayerInfo *player = &stored_player[i];
4025 if (player->connected && !player->present)
4027 struct PlayerInfo *field_player = NULL;
4029 #if DEBUG_INIT_PLAYER
4030 Debug("game:init:player",
4031 "- looking for field player for player %d ...", i + 1);
4034 // assign first free player found that is present in the playfield
4036 // first try: look for unmapped playfield player that is not connected
4037 for (j = 0; j < MAX_PLAYERS; j++)
4038 if (field_player == NULL &&
4039 stored_player[j].present &&
4040 !stored_player[j].mapped &&
4041 !stored_player[j].connected)
4042 field_player = &stored_player[j];
4044 // second try: look for *any* unmapped playfield player
4045 for (j = 0; j < MAX_PLAYERS; j++)
4046 if (field_player == NULL &&
4047 stored_player[j].present &&
4048 !stored_player[j].mapped)
4049 field_player = &stored_player[j];
4051 if (field_player != NULL)
4053 int jx = field_player->jx, jy = field_player->jy;
4055 #if DEBUG_INIT_PLAYER
4056 Debug("game:init:player", "- found player %d",
4057 field_player->index_nr + 1);
4060 player->present = FALSE;
4061 player->active = FALSE;
4063 field_player->present = TRUE;
4064 field_player->active = TRUE;
4067 player->initial_element = field_player->initial_element;
4068 player->artwork_element = field_player->artwork_element;
4070 player->block_last_field = field_player->block_last_field;
4071 player->block_delay_adjustment = field_player->block_delay_adjustment;
4074 StorePlayer[jx][jy] = field_player->element_nr;
4076 field_player->jx = field_player->last_jx = jx;
4077 field_player->jy = field_player->last_jy = jy;
4079 if (local_player == player)
4080 local_player = field_player;
4082 map_player_action[field_player->index_nr] = i;
4084 field_player->mapped = TRUE;
4086 #if DEBUG_INIT_PLAYER
4087 Debug("game:init:player", "- map_player_action[%d] == %d",
4088 field_player->index_nr + 1, i + 1);
4093 if (player->connected && player->present)
4094 player->mapped = TRUE;
4097 #if DEBUG_INIT_PLAYER
4098 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4103 // check if any connected player was not found in playfield
4104 for (i = 0; i < MAX_PLAYERS; i++)
4106 struct PlayerInfo *player = &stored_player[i];
4108 if (player->connected && !player->present)
4110 for (j = 0; j < MAX_PLAYERS; j++)
4112 struct PlayerInfo *field_player = &stored_player[j];
4113 int jx = field_player->jx, jy = field_player->jy;
4115 // assign first free player found that is present in the playfield
4116 if (field_player->present && !field_player->connected)
4118 player->present = TRUE;
4119 player->active = TRUE;
4121 field_player->present = FALSE;
4122 field_player->active = FALSE;
4124 player->initial_element = field_player->initial_element;
4125 player->artwork_element = field_player->artwork_element;
4127 player->block_last_field = field_player->block_last_field;
4128 player->block_delay_adjustment = field_player->block_delay_adjustment;
4130 StorePlayer[jx][jy] = player->element_nr;
4132 player->jx = player->last_jx = jx;
4133 player->jy = player->last_jy = jy;
4143 Debug("game:init:player", "local_player->present == %d",
4144 local_player->present);
4147 // set focus to local player for network games, else to all players
4148 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4149 game.centered_player_nr_next = game.centered_player_nr;
4150 game.set_centered_player = FALSE;
4151 game.set_centered_player_wrap = FALSE;
4153 if (network_playing && tape.recording)
4155 // store client dependent player focus when recording network games
4156 tape.centered_player_nr_next = game.centered_player_nr_next;
4157 tape.set_centered_player = TRUE;
4162 // when playing a tape, eliminate all players who do not participate
4164 #if USE_NEW_PLAYER_ASSIGNMENTS
4166 if (!game.team_mode)
4168 for (i = 0; i < MAX_PLAYERS; i++)
4170 if (stored_player[i].active &&
4171 !tape.player_participates[map_player_action[i]])
4173 struct PlayerInfo *player = &stored_player[i];
4174 int jx = player->jx, jy = player->jy;
4176 #if DEBUG_INIT_PLAYER
4177 Debug("game:init:player", "Removing player %d at (%d, %d)",
4181 player->active = FALSE;
4182 StorePlayer[jx][jy] = 0;
4183 Tile[jx][jy] = EL_EMPTY;
4190 for (i = 0; i < MAX_PLAYERS; i++)
4192 if (stored_player[i].active &&
4193 !tape.player_participates[i])
4195 struct PlayerInfo *player = &stored_player[i];
4196 int jx = player->jx, jy = player->jy;
4198 player->active = FALSE;
4199 StorePlayer[jx][jy] = 0;
4200 Tile[jx][jy] = EL_EMPTY;
4205 else if (!network.enabled && !game.team_mode) // && !tape.playing
4207 // when in single player mode, eliminate all but the local player
4209 for (i = 0; i < MAX_PLAYERS; i++)
4211 struct PlayerInfo *player = &stored_player[i];
4213 if (player->active && player != local_player)
4215 int jx = player->jx, jy = player->jy;
4217 player->active = FALSE;
4218 player->present = FALSE;
4220 StorePlayer[jx][jy] = 0;
4221 Tile[jx][jy] = EL_EMPTY;
4226 for (i = 0; i < MAX_PLAYERS; i++)
4227 if (stored_player[i].active)
4228 game.players_still_needed++;
4230 if (level.solved_by_one_player)
4231 game.players_still_needed = 1;
4233 // when recording the game, store which players take part in the game
4236 #if USE_NEW_PLAYER_ASSIGNMENTS
4237 for (i = 0; i < MAX_PLAYERS; i++)
4238 if (stored_player[i].connected)
4239 tape.player_participates[i] = TRUE;
4241 for (i = 0; i < MAX_PLAYERS; i++)
4242 if (stored_player[i].active)
4243 tape.player_participates[i] = TRUE;
4247 #if DEBUG_INIT_PLAYER
4248 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4251 if (BorderElement == EL_EMPTY)
4254 SBX_Right = lev_fieldx - SCR_FIELDX;
4256 SBY_Lower = lev_fieldy - SCR_FIELDY;
4261 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4263 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4266 if (full_lev_fieldx <= SCR_FIELDX)
4267 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4268 if (full_lev_fieldy <= SCR_FIELDY)
4269 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4271 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4273 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4276 // if local player not found, look for custom element that might create
4277 // the player (make some assumptions about the right custom element)
4278 if (!local_player->present)
4280 int start_x = 0, start_y = 0;
4281 int found_rating = 0;
4282 int found_element = EL_UNDEFINED;
4283 int player_nr = local_player->index_nr;
4285 SCAN_PLAYFIELD(x, y)
4287 int element = Tile[x][y];
4292 if (level.use_start_element[player_nr] &&
4293 level.start_element[player_nr] == element &&
4300 found_element = element;
4303 if (!IS_CUSTOM_ELEMENT(element))
4306 if (CAN_CHANGE(element))
4308 for (i = 0; i < element_info[element].num_change_pages; i++)
4310 // check for player created from custom element as single target
4311 content = element_info[element].change_page[i].target_element;
4312 is_player = IS_PLAYER_ELEMENT(content);
4314 if (is_player && (found_rating < 3 ||
4315 (found_rating == 3 && element < found_element)))
4321 found_element = element;
4326 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4328 // check for player created from custom element as explosion content
4329 content = element_info[element].content.e[xx][yy];
4330 is_player = IS_PLAYER_ELEMENT(content);
4332 if (is_player && (found_rating < 2 ||
4333 (found_rating == 2 && element < found_element)))
4335 start_x = x + xx - 1;
4336 start_y = y + yy - 1;
4339 found_element = element;
4342 if (!CAN_CHANGE(element))
4345 for (i = 0; i < element_info[element].num_change_pages; i++)
4347 // check for player created from custom element as extended target
4349 element_info[element].change_page[i].target_content.e[xx][yy];
4351 is_player = IS_PLAYER_ELEMENT(content);
4353 if (is_player && (found_rating < 1 ||
4354 (found_rating == 1 && element < found_element)))
4356 start_x = x + xx - 1;
4357 start_y = y + yy - 1;
4360 found_element = element;
4366 scroll_x = SCROLL_POSITION_X(start_x);
4367 scroll_y = SCROLL_POSITION_Y(start_y);
4371 scroll_x = SCROLL_POSITION_X(local_player->jx);
4372 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4375 // !!! FIX THIS (START) !!!
4376 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4378 InitGameEngine_EM();
4380 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4382 InitGameEngine_SP();
4384 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4386 InitGameEngine_MM();
4390 DrawLevel(REDRAW_FIELD);
4393 // after drawing the level, correct some elements
4394 if (game.timegate_time_left == 0)
4395 CloseAllOpenTimegates();
4398 // blit playfield from scroll buffer to normal back buffer for fading in
4399 BlitScreenToBitmap(backbuffer);
4400 // !!! FIX THIS (END) !!!
4402 DrawMaskedBorder(fade_mask);
4407 // full screen redraw is required at this point in the following cases:
4408 // - special editor door undrawn when game was started from level editor
4409 // - drawing area (playfield) was changed and has to be removed completely
4410 redraw_mask = REDRAW_ALL;
4414 if (!game.restart_level)
4416 // copy default game door content to main double buffer
4418 // !!! CHECK AGAIN !!!
4419 SetPanelBackground();
4420 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4421 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4424 SetPanelBackground();
4425 SetDrawBackgroundMask(REDRAW_DOOR_1);
4427 UpdateAndDisplayGameControlValues();
4429 if (!game.restart_level)
4435 CreateGameButtons();
4440 // copy actual game door content to door double buffer for OpenDoor()
4441 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4443 OpenDoor(DOOR_OPEN_ALL);
4445 KeyboardAutoRepeatOffUnlessAutoplay();
4447 #if DEBUG_INIT_PLAYER
4448 DebugPrintPlayerStatus("Player status (final)");
4457 if (!game.restart_level && !tape.playing)
4459 LevelStats_incPlayed(level_nr);
4461 SaveLevelSetup_SeriesInfo();
4464 game.restart_level = FALSE;
4465 game.restart_game_message = NULL;
4467 game.request_active = FALSE;
4468 game.request_active_or_moving = FALSE;
4470 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4471 InitGameActions_MM();
4473 SaveEngineSnapshotToListInitial();
4475 if (!game.restart_level)
4477 PlaySound(SND_GAME_STARTING);
4479 if (setup.sound_music)
4484 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4485 int actual_player_x, int actual_player_y)
4487 // this is used for non-R'n'D game engines to update certain engine values
4489 // needed to determine if sounds are played within the visible screen area
4490 scroll_x = actual_scroll_x;
4491 scroll_y = actual_scroll_y;
4493 // needed to get player position for "follow finger" playing input method
4494 local_player->jx = actual_player_x;
4495 local_player->jy = actual_player_y;
4498 void InitMovDir(int x, int y)
4500 int i, element = Tile[x][y];
4501 static int xy[4][2] =
4508 static int direction[3][4] =
4510 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4511 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4512 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4521 Tile[x][y] = EL_BUG;
4522 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4525 case EL_SPACESHIP_RIGHT:
4526 case EL_SPACESHIP_UP:
4527 case EL_SPACESHIP_LEFT:
4528 case EL_SPACESHIP_DOWN:
4529 Tile[x][y] = EL_SPACESHIP;
4530 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4533 case EL_BD_BUTTERFLY_RIGHT:
4534 case EL_BD_BUTTERFLY_UP:
4535 case EL_BD_BUTTERFLY_LEFT:
4536 case EL_BD_BUTTERFLY_DOWN:
4537 Tile[x][y] = EL_BD_BUTTERFLY;
4538 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4541 case EL_BD_FIREFLY_RIGHT:
4542 case EL_BD_FIREFLY_UP:
4543 case EL_BD_FIREFLY_LEFT:
4544 case EL_BD_FIREFLY_DOWN:
4545 Tile[x][y] = EL_BD_FIREFLY;
4546 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4549 case EL_PACMAN_RIGHT:
4551 case EL_PACMAN_LEFT:
4552 case EL_PACMAN_DOWN:
4553 Tile[x][y] = EL_PACMAN;
4554 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4557 case EL_YAMYAM_LEFT:
4558 case EL_YAMYAM_RIGHT:
4560 case EL_YAMYAM_DOWN:
4561 Tile[x][y] = EL_YAMYAM;
4562 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4565 case EL_SP_SNIKSNAK:
4566 MovDir[x][y] = MV_UP;
4569 case EL_SP_ELECTRON:
4570 MovDir[x][y] = MV_LEFT;
4577 Tile[x][y] = EL_MOLE;
4578 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4581 case EL_SPRING_LEFT:
4582 case EL_SPRING_RIGHT:
4583 Tile[x][y] = EL_SPRING;
4584 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4588 if (IS_CUSTOM_ELEMENT(element))
4590 struct ElementInfo *ei = &element_info[element];
4591 int move_direction_initial = ei->move_direction_initial;
4592 int move_pattern = ei->move_pattern;
4594 if (move_direction_initial == MV_START_PREVIOUS)
4596 if (MovDir[x][y] != MV_NONE)
4599 move_direction_initial = MV_START_AUTOMATIC;
4602 if (move_direction_initial == MV_START_RANDOM)
4603 MovDir[x][y] = 1 << RND(4);
4604 else if (move_direction_initial & MV_ANY_DIRECTION)
4605 MovDir[x][y] = move_direction_initial;
4606 else if (move_pattern == MV_ALL_DIRECTIONS ||
4607 move_pattern == MV_TURNING_LEFT ||
4608 move_pattern == MV_TURNING_RIGHT ||
4609 move_pattern == MV_TURNING_LEFT_RIGHT ||
4610 move_pattern == MV_TURNING_RIGHT_LEFT ||
4611 move_pattern == MV_TURNING_RANDOM)
4612 MovDir[x][y] = 1 << RND(4);
4613 else if (move_pattern == MV_HORIZONTAL)
4614 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4615 else if (move_pattern == MV_VERTICAL)
4616 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4617 else if (move_pattern & MV_ANY_DIRECTION)
4618 MovDir[x][y] = element_info[element].move_pattern;
4619 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4620 move_pattern == MV_ALONG_RIGHT_SIDE)
4622 // use random direction as default start direction
4623 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4624 MovDir[x][y] = 1 << RND(4);
4626 for (i = 0; i < NUM_DIRECTIONS; i++)
4628 int x1 = x + xy[i][0];
4629 int y1 = y + xy[i][1];
4631 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4633 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4634 MovDir[x][y] = direction[0][i];
4636 MovDir[x][y] = direction[1][i];
4645 MovDir[x][y] = 1 << RND(4);
4647 if (element != EL_BUG &&
4648 element != EL_SPACESHIP &&
4649 element != EL_BD_BUTTERFLY &&
4650 element != EL_BD_FIREFLY)
4653 for (i = 0; i < NUM_DIRECTIONS; i++)
4655 int x1 = x + xy[i][0];
4656 int y1 = y + xy[i][1];
4658 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4660 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4662 MovDir[x][y] = direction[0][i];
4665 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4666 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4668 MovDir[x][y] = direction[1][i];
4677 GfxDir[x][y] = MovDir[x][y];
4680 void InitAmoebaNr(int x, int y)
4683 int group_nr = AmoebaNeighbourNr(x, y);
4687 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4689 if (AmoebaCnt[i] == 0)
4697 AmoebaNr[x][y] = group_nr;
4698 AmoebaCnt[group_nr]++;
4699 AmoebaCnt2[group_nr]++;
4702 static void LevelSolved_SetFinalGameValues(void)
4704 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4705 game.score_time_final = (level.use_step_counter ? TimePlayed :
4706 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4708 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4709 game_em.lev->score :
4710 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4714 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4715 MM_HEALTH(game_mm.laser_overload_value) :
4718 game.LevelSolved_CountingTime = game.time_final;
4719 game.LevelSolved_CountingScore = game.score_final;
4720 game.LevelSolved_CountingHealth = game.health_final;
4723 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4725 game.LevelSolved_CountingTime = time;
4726 game.LevelSolved_CountingScore = score;
4727 game.LevelSolved_CountingHealth = health;
4729 game_panel_controls[GAME_PANEL_TIME].value = time;
4730 game_panel_controls[GAME_PANEL_SCORE].value = score;
4731 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4733 DisplayGameControlValues();
4736 static void LevelSolved(void)
4738 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4739 game.players_still_needed > 0)
4742 game.LevelSolved = TRUE;
4743 game.GameOver = TRUE;
4745 // needed here to display correct panel values while player walks into exit
4746 LevelSolved_SetFinalGameValues();
4751 static int time_count_steps;
4752 static int time, time_final;
4753 static float score, score_final; // needed for time score < 10 for 10 seconds
4754 static int health, health_final;
4755 static int game_over_delay_1 = 0;
4756 static int game_over_delay_2 = 0;
4757 static int game_over_delay_3 = 0;
4758 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4759 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4761 if (!game.LevelSolved_GameWon)
4765 // do not start end game actions before the player stops moving (to exit)
4766 if (local_player->active && local_player->MovPos)
4769 // calculate final game values after player finished walking into exit
4770 LevelSolved_SetFinalGameValues();
4772 game.LevelSolved_GameWon = TRUE;
4773 game.LevelSolved_SaveTape = tape.recording;
4774 game.LevelSolved_SaveScore = !tape.playing;
4778 LevelStats_incSolved(level_nr);
4780 SaveLevelSetup_SeriesInfo();
4783 if (tape.auto_play) // tape might already be stopped here
4784 tape.auto_play_level_solved = TRUE;
4788 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4789 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4790 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4792 time = time_final = game.time_final;
4793 score = score_final = game.score_final;
4794 health = health_final = game.health_final;
4796 // update game panel values before (delayed) counting of score (if any)
4797 LevelSolved_DisplayFinalGameValues(time, score, health);
4799 // if level has time score defined, calculate new final game values
4802 int time_final_max = 999;
4803 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4804 int time_frames = 0;
4805 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4806 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4811 time_frames = time_frames_left;
4813 else if (game.no_time_limit && TimePlayed < time_final_max)
4815 time_final = time_final_max;
4816 time_frames = time_frames_final_max - time_frames_played;
4819 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4821 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4823 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4826 score_final += health * time_score;
4829 game.score_final = score_final;
4830 game.health_final = health_final;
4833 // if not counting score after game, immediately update game panel values
4834 if (level_editor_test_game || !setup.count_score_after_game)
4837 score = score_final;
4839 LevelSolved_DisplayFinalGameValues(time, score, health);
4842 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4844 // check if last player has left the level
4845 if (game.exit_x >= 0 &&
4848 int x = game.exit_x;
4849 int y = game.exit_y;
4850 int element = Tile[x][y];
4852 // close exit door after last player
4853 if ((game.all_players_gone &&
4854 (element == EL_EXIT_OPEN ||
4855 element == EL_SP_EXIT_OPEN ||
4856 element == EL_STEEL_EXIT_OPEN)) ||
4857 element == EL_EM_EXIT_OPEN ||
4858 element == EL_EM_STEEL_EXIT_OPEN)
4862 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4863 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4864 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4865 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4866 EL_EM_STEEL_EXIT_CLOSING);
4868 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4871 // player disappears
4872 DrawLevelField(x, y);
4875 for (i = 0; i < MAX_PLAYERS; i++)
4877 struct PlayerInfo *player = &stored_player[i];
4879 if (player->present)
4881 RemovePlayer(player);
4883 // player disappears
4884 DrawLevelField(player->jx, player->jy);
4889 PlaySound(SND_GAME_WINNING);
4892 if (setup.count_score_after_game)
4894 if (time != time_final)
4896 if (game_over_delay_1 > 0)
4898 game_over_delay_1--;
4903 int time_to_go = ABS(time_final - time);
4904 int time_count_dir = (time < time_final ? +1 : -1);
4906 if (time_to_go < time_count_steps)
4907 time_count_steps = 1;
4909 time += time_count_steps * time_count_dir;
4910 score += time_count_steps * time_score;
4912 // set final score to correct rounding differences after counting score
4913 if (time == time_final)
4914 score = score_final;
4916 LevelSolved_DisplayFinalGameValues(time, score, health);
4918 if (time == time_final)
4919 StopSound(SND_GAME_LEVELTIME_BONUS);
4920 else if (setup.sound_loops)
4921 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4923 PlaySound(SND_GAME_LEVELTIME_BONUS);
4928 if (health != health_final)
4930 if (game_over_delay_2 > 0)
4932 game_over_delay_2--;
4937 int health_count_dir = (health < health_final ? +1 : -1);
4939 health += health_count_dir;
4940 score += time_score;
4942 LevelSolved_DisplayFinalGameValues(time, score, health);
4944 if (health == health_final)
4945 StopSound(SND_GAME_LEVELTIME_BONUS);
4946 else if (setup.sound_loops)
4947 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4949 PlaySound(SND_GAME_LEVELTIME_BONUS);
4955 game.panel.active = FALSE;
4957 if (game_over_delay_3 > 0)
4959 game_over_delay_3--;
4969 // used instead of "level_nr" (needed for network games)
4970 int last_level_nr = levelset.level_nr;
4971 boolean tape_saved = FALSE;
4973 game.LevelSolved_GameEnd = TRUE;
4975 if (game.LevelSolved_SaveTape)
4977 // make sure that request dialog to save tape does not open door again
4978 if (!global.use_envelope_request)
4979 CloseDoor(DOOR_CLOSE_1);
4982 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4984 // set unique basename for score tape (also saved in high score table)
4985 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4988 // if no tape is to be saved, close both doors simultaneously
4989 CloseDoor(DOOR_CLOSE_ALL);
4991 if (level_editor_test_game)
4993 SetGameStatus(GAME_MODE_MAIN);
5000 if (!game.LevelSolved_SaveScore)
5002 SetGameStatus(GAME_MODE_MAIN);
5009 if (level_nr == leveldir_current->handicap_level)
5011 leveldir_current->handicap_level++;
5013 SaveLevelSetup_SeriesInfo();
5016 // save score and score tape before potentially erasing tape below
5017 NewHighScore(last_level_nr, tape_saved);
5019 if (setup.increment_levels &&
5020 level_nr < leveldir_current->last_level &&
5023 level_nr++; // advance to next level
5024 TapeErase(); // start with empty tape
5026 if (setup.auto_play_next_level)
5028 LoadLevel(level_nr);
5030 SaveLevelSetup_SeriesInfo();
5034 if (scores.last_added >= 0 && setup.show_scores_after_game)
5036 SetGameStatus(GAME_MODE_SCORES);
5038 DrawHallOfFame(last_level_nr);
5040 else if (setup.auto_play_next_level && setup.increment_levels &&
5041 last_level_nr < leveldir_current->last_level &&
5044 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5048 SetGameStatus(GAME_MODE_MAIN);
5054 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5055 boolean one_score_entry_per_name)
5059 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5062 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5064 struct ScoreEntry *entry = &list->entry[i];
5065 boolean score_is_better = (new_entry->score > entry->score);
5066 boolean score_is_equal = (new_entry->score == entry->score);
5067 boolean time_is_better = (new_entry->time < entry->time);
5068 boolean time_is_equal = (new_entry->time == entry->time);
5069 boolean better_by_score = (score_is_better ||
5070 (score_is_equal && time_is_better));
5071 boolean better_by_time = (time_is_better ||
5072 (time_is_equal && score_is_better));
5073 boolean is_better = (level.rate_time_over_score ? better_by_time :
5075 boolean entry_is_empty = (entry->score == 0 &&
5078 // prevent adding server score entries if also existing in local score file
5079 // (special case: historic score entries have an empty tape basename entry)
5080 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5081 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5084 if (is_better || entry_is_empty)
5086 // player has made it to the hall of fame
5088 if (i < MAX_SCORE_ENTRIES - 1)
5090 int m = MAX_SCORE_ENTRIES - 1;
5093 if (one_score_entry_per_name)
5095 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5096 if (strEqual(list->entry[l].name, new_entry->name))
5099 if (m == i) // player's new highscore overwrites his old one
5103 for (l = m; l > i; l--)
5104 list->entry[l] = list->entry[l - 1];
5109 *entry = *new_entry;
5113 else if (one_score_entry_per_name &&
5114 strEqual(entry->name, new_entry->name))
5116 // player already in high score list with better score or time
5125 void NewHighScore(int level_nr, boolean tape_saved)
5127 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5128 boolean one_per_name = FALSE;
5130 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5131 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5133 new_entry.score = game.score_final;
5134 new_entry.time = game.score_time_final;
5136 LoadScore(level_nr);
5138 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5140 if (scores.last_added < 0)
5143 SaveScore(level_nr);
5145 // store last added local score entry (before merging server scores)
5146 scores.last_added_local = scores.last_added;
5148 if (!game.LevelSolved_SaveTape)
5151 SaveScoreTape(level_nr);
5153 if (setup.ask_for_using_api_server)
5155 setup.use_api_server =
5156 Request("Upload your score and tape to the high score server?", REQ_ASK);
5158 if (!setup.use_api_server)
5159 Request("Not using high score server! Use setup menu to enable again!",
5162 runtime.use_api_server = setup.use_api_server;
5164 // after asking for using API server once, do not ask again
5165 setup.ask_for_using_api_server = FALSE;
5167 SaveSetup_ServerSetup();
5170 SaveServerScore(level_nr, tape_saved);
5173 void MergeServerScore(void)
5175 struct ScoreEntry last_added_entry;
5176 boolean one_per_name = FALSE;
5179 if (scores.last_added >= 0)
5180 last_added_entry = scores.entry[scores.last_added];
5182 for (i = 0; i < server_scores.num_entries; i++)
5184 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5186 if (pos >= 0 && pos <= scores.last_added)
5187 scores.last_added++;
5190 if (scores.last_added >= MAX_SCORE_ENTRIES)
5192 scores.last_added = MAX_SCORE_ENTRIES - 1;
5193 scores.force_last_added = TRUE;
5195 scores.entry[scores.last_added] = last_added_entry;
5199 static int getElementMoveStepsizeExt(int x, int y, int direction)
5201 int element = Tile[x][y];
5202 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5203 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5204 int horiz_move = (dx != 0);
5205 int sign = (horiz_move ? dx : dy);
5206 int step = sign * element_info[element].move_stepsize;
5208 // special values for move stepsize for spring and things on conveyor belt
5211 if (CAN_FALL(element) &&
5212 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5213 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5214 else if (element == EL_SPRING)
5215 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5221 static int getElementMoveStepsize(int x, int y)
5223 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5226 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5228 if (player->GfxAction != action || player->GfxDir != dir)
5230 player->GfxAction = action;
5231 player->GfxDir = dir;
5233 player->StepFrame = 0;
5237 static void ResetGfxFrame(int x, int y)
5239 // profiling showed that "autotest" spends 10~20% of its time in this function
5240 if (DrawingDeactivatedField())
5243 int element = Tile[x][y];
5244 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5246 if (graphic_info[graphic].anim_global_sync)
5247 GfxFrame[x][y] = FrameCounter;
5248 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5249 GfxFrame[x][y] = CustomValue[x][y];
5250 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5251 GfxFrame[x][y] = element_info[element].collect_score;
5252 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5253 GfxFrame[x][y] = ChangeDelay[x][y];
5256 static void ResetGfxAnimation(int x, int y)
5258 GfxAction[x][y] = ACTION_DEFAULT;
5259 GfxDir[x][y] = MovDir[x][y];
5262 ResetGfxFrame(x, y);
5265 static void ResetRandomAnimationValue(int x, int y)
5267 GfxRandom[x][y] = INIT_GFX_RANDOM();
5270 static void InitMovingField(int x, int y, int direction)
5272 int element = Tile[x][y];
5273 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5274 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5277 boolean is_moving_before, is_moving_after;
5279 // check if element was/is moving or being moved before/after mode change
5280 is_moving_before = (WasJustMoving[x][y] != 0);
5281 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5283 // reset animation only for moving elements which change direction of moving
5284 // or which just started or stopped moving
5285 // (else CEs with property "can move" / "not moving" are reset each frame)
5286 if (is_moving_before != is_moving_after ||
5287 direction != MovDir[x][y])
5288 ResetGfxAnimation(x, y);
5290 MovDir[x][y] = direction;
5291 GfxDir[x][y] = direction;
5293 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5294 direction == MV_DOWN && CAN_FALL(element) ?
5295 ACTION_FALLING : ACTION_MOVING);
5297 // this is needed for CEs with property "can move" / "not moving"
5299 if (is_moving_after)
5301 if (Tile[newx][newy] == EL_EMPTY)
5302 Tile[newx][newy] = EL_BLOCKED;
5304 MovDir[newx][newy] = MovDir[x][y];
5306 CustomValue[newx][newy] = CustomValue[x][y];
5308 GfxFrame[newx][newy] = GfxFrame[x][y];
5309 GfxRandom[newx][newy] = GfxRandom[x][y];
5310 GfxAction[newx][newy] = GfxAction[x][y];
5311 GfxDir[newx][newy] = GfxDir[x][y];
5315 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5317 int direction = MovDir[x][y];
5318 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5319 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5325 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5327 int oldx = x, oldy = y;
5328 int direction = MovDir[x][y];
5330 if (direction == MV_LEFT)
5332 else if (direction == MV_RIGHT)
5334 else if (direction == MV_UP)
5336 else if (direction == MV_DOWN)
5339 *comes_from_x = oldx;
5340 *comes_from_y = oldy;
5343 static int MovingOrBlocked2Element(int x, int y)
5345 int element = Tile[x][y];
5347 if (element == EL_BLOCKED)
5351 Blocked2Moving(x, y, &oldx, &oldy);
5352 return Tile[oldx][oldy];
5358 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5360 // like MovingOrBlocked2Element(), but if element is moving
5361 // and (x,y) is the field the moving element is just leaving,
5362 // return EL_BLOCKED instead of the element value
5363 int element = Tile[x][y];
5365 if (IS_MOVING(x, y))
5367 if (element == EL_BLOCKED)
5371 Blocked2Moving(x, y, &oldx, &oldy);
5372 return Tile[oldx][oldy];
5381 static void RemoveField(int x, int y)
5383 Tile[x][y] = EL_EMPTY;
5389 CustomValue[x][y] = 0;
5392 ChangeDelay[x][y] = 0;
5393 ChangePage[x][y] = -1;
5394 Pushed[x][y] = FALSE;
5396 GfxElement[x][y] = EL_UNDEFINED;
5397 GfxAction[x][y] = ACTION_DEFAULT;
5398 GfxDir[x][y] = MV_NONE;
5401 static void RemoveMovingField(int x, int y)
5403 int oldx = x, oldy = y, newx = x, newy = y;
5404 int element = Tile[x][y];
5405 int next_element = EL_UNDEFINED;
5407 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5410 if (IS_MOVING(x, y))
5412 Moving2Blocked(x, y, &newx, &newy);
5414 if (Tile[newx][newy] != EL_BLOCKED)
5416 // element is moving, but target field is not free (blocked), but
5417 // already occupied by something different (example: acid pool);
5418 // in this case, only remove the moving field, but not the target
5420 RemoveField(oldx, oldy);
5422 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5424 TEST_DrawLevelField(oldx, oldy);
5429 else if (element == EL_BLOCKED)
5431 Blocked2Moving(x, y, &oldx, &oldy);
5432 if (!IS_MOVING(oldx, oldy))
5436 if (element == EL_BLOCKED &&
5437 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5438 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5439 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5440 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5441 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5442 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5443 next_element = get_next_element(Tile[oldx][oldy]);
5445 RemoveField(oldx, oldy);
5446 RemoveField(newx, newy);
5448 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5450 if (next_element != EL_UNDEFINED)
5451 Tile[oldx][oldy] = next_element;
5453 TEST_DrawLevelField(oldx, oldy);
5454 TEST_DrawLevelField(newx, newy);
5457 void DrawDynamite(int x, int y)
5459 int sx = SCREENX(x), sy = SCREENY(y);
5460 int graphic = el2img(Tile[x][y]);
5463 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5466 if (IS_WALKABLE_INSIDE(Back[x][y]))
5470 DrawLevelElement(x, y, Back[x][y]);
5471 else if (Store[x][y])
5472 DrawLevelElement(x, y, Store[x][y]);
5473 else if (game.use_masked_elements)
5474 DrawLevelElement(x, y, EL_EMPTY);
5476 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5478 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5479 DrawGraphicThruMask(sx, sy, graphic, frame);
5481 DrawGraphic(sx, sy, graphic, frame);
5484 static void CheckDynamite(int x, int y)
5486 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5490 if (MovDelay[x][y] != 0)
5493 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5499 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5504 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5506 boolean num_checked_players = 0;
5509 for (i = 0; i < MAX_PLAYERS; i++)
5511 if (stored_player[i].active)
5513 int sx = stored_player[i].jx;
5514 int sy = stored_player[i].jy;
5516 if (num_checked_players == 0)
5523 *sx1 = MIN(*sx1, sx);
5524 *sy1 = MIN(*sy1, sy);
5525 *sx2 = MAX(*sx2, sx);
5526 *sy2 = MAX(*sy2, sy);
5529 num_checked_players++;
5534 static boolean checkIfAllPlayersFitToScreen_RND(void)
5536 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5538 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5540 return (sx2 - sx1 < SCR_FIELDX &&
5541 sy2 - sy1 < SCR_FIELDY);
5544 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5546 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5548 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5550 *sx = (sx1 + sx2) / 2;
5551 *sy = (sy1 + sy2) / 2;
5554 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5555 boolean center_screen, boolean quick_relocation)
5557 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5558 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5559 boolean no_delay = (tape.warp_forward);
5560 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5561 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5562 int new_scroll_x, new_scroll_y;
5564 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5566 // case 1: quick relocation inside visible screen (without scrolling)
5573 if (!level.shifted_relocation || center_screen)
5575 // relocation _with_ centering of screen
5577 new_scroll_x = SCROLL_POSITION_X(x);
5578 new_scroll_y = SCROLL_POSITION_Y(y);
5582 // relocation _without_ centering of screen
5584 int center_scroll_x = SCROLL_POSITION_X(old_x);
5585 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5586 int offset_x = x + (scroll_x - center_scroll_x);
5587 int offset_y = y + (scroll_y - center_scroll_y);
5589 // for new screen position, apply previous offset to center position
5590 new_scroll_x = SCROLL_POSITION_X(offset_x);
5591 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5594 if (quick_relocation)
5596 // case 2: quick relocation (redraw without visible scrolling)
5598 scroll_x = new_scroll_x;
5599 scroll_y = new_scroll_y;
5606 // case 3: visible relocation (with scrolling to new position)
5608 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5610 SetVideoFrameDelay(wait_delay_value);
5612 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5614 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5615 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5617 if (dx == 0 && dy == 0) // no scrolling needed at all
5623 // set values for horizontal/vertical screen scrolling (half tile size)
5624 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5625 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5626 int pos_x = dx * TILEX / 2;
5627 int pos_y = dy * TILEY / 2;
5628 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5629 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5631 ScrollLevel(dx, dy);
5634 // scroll in two steps of half tile size to make things smoother
5635 BlitScreenToBitmapExt_RND(window, fx, fy);
5637 // scroll second step to align at full tile size
5638 BlitScreenToBitmap(window);
5644 SetVideoFrameDelay(frame_delay_value_old);
5647 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5649 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5650 int player_nr = GET_PLAYER_NR(el_player);
5651 struct PlayerInfo *player = &stored_player[player_nr];
5652 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5653 boolean no_delay = (tape.warp_forward);
5654 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5655 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5656 int old_jx = player->jx;
5657 int old_jy = player->jy;
5658 int old_element = Tile[old_jx][old_jy];
5659 int element = Tile[jx][jy];
5660 boolean player_relocated = (old_jx != jx || old_jy != jy);
5662 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5663 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5664 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5665 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5666 int leave_side_horiz = move_dir_horiz;
5667 int leave_side_vert = move_dir_vert;
5668 int enter_side = enter_side_horiz | enter_side_vert;
5669 int leave_side = leave_side_horiz | leave_side_vert;
5671 if (player->buried) // do not reanimate dead player
5674 if (!player_relocated) // no need to relocate the player
5677 if (IS_PLAYER(jx, jy)) // player already placed at new position
5679 RemoveField(jx, jy); // temporarily remove newly placed player
5680 DrawLevelField(jx, jy);
5683 if (player->present)
5685 while (player->MovPos)
5687 ScrollPlayer(player, SCROLL_GO_ON);
5688 ScrollScreen(NULL, SCROLL_GO_ON);
5690 AdvanceFrameAndPlayerCounters(player->index_nr);
5694 BackToFront_WithFrameDelay(wait_delay_value);
5697 DrawPlayer(player); // needed here only to cleanup last field
5698 DrawLevelField(player->jx, player->jy); // remove player graphic
5700 player->is_moving = FALSE;
5703 if (IS_CUSTOM_ELEMENT(old_element))
5704 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5706 player->index_bit, leave_side);
5708 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5710 player->index_bit, leave_side);
5712 Tile[jx][jy] = el_player;
5713 InitPlayerField(jx, jy, el_player, TRUE);
5715 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5716 possible that the relocation target field did not contain a player element,
5717 but a walkable element, to which the new player was relocated -- in this
5718 case, restore that (already initialized!) element on the player field */
5719 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5721 Tile[jx][jy] = element; // restore previously existing element
5724 // only visually relocate centered player
5725 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5726 FALSE, level.instant_relocation);
5728 TestIfPlayerTouchesBadThing(jx, jy);
5729 TestIfPlayerTouchesCustomElement(jx, jy);
5731 if (IS_CUSTOM_ELEMENT(element))
5732 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5733 player->index_bit, enter_side);
5735 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5736 player->index_bit, enter_side);
5738 if (player->is_switching)
5740 /* ensure that relocation while still switching an element does not cause
5741 a new element to be treated as also switched directly after relocation
5742 (this is important for teleporter switches that teleport the player to
5743 a place where another teleporter switch is in the same direction, which
5744 would then incorrectly be treated as immediately switched before the
5745 direction key that caused the switch was released) */
5747 player->switch_x += jx - old_jx;
5748 player->switch_y += jy - old_jy;
5752 static void Explode(int ex, int ey, int phase, int mode)
5758 // !!! eliminate this variable !!!
5759 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5761 if (game.explosions_delayed)
5763 ExplodeField[ex][ey] = mode;
5767 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5769 int center_element = Tile[ex][ey];
5770 int artwork_element, explosion_element; // set these values later
5772 // remove things displayed in background while burning dynamite
5773 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5776 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5778 // put moving element to center field (and let it explode there)
5779 center_element = MovingOrBlocked2Element(ex, ey);
5780 RemoveMovingField(ex, ey);
5781 Tile[ex][ey] = center_element;
5784 // now "center_element" is finally determined -- set related values now
5785 artwork_element = center_element; // for custom player artwork
5786 explosion_element = center_element; // for custom player artwork
5788 if (IS_PLAYER(ex, ey))
5790 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5792 artwork_element = stored_player[player_nr].artwork_element;
5794 if (level.use_explosion_element[player_nr])
5796 explosion_element = level.explosion_element[player_nr];
5797 artwork_element = explosion_element;
5801 if (mode == EX_TYPE_NORMAL ||
5802 mode == EX_TYPE_CENTER ||
5803 mode == EX_TYPE_CROSS)
5804 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5806 last_phase = element_info[explosion_element].explosion_delay + 1;
5808 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5810 int xx = x - ex + 1;
5811 int yy = y - ey + 1;
5814 if (!IN_LEV_FIELD(x, y) ||
5815 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5816 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5819 element = Tile[x][y];
5821 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5823 element = MovingOrBlocked2Element(x, y);
5825 if (!IS_EXPLOSION_PROOF(element))
5826 RemoveMovingField(x, y);
5829 // indestructible elements can only explode in center (but not flames)
5830 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5831 mode == EX_TYPE_BORDER)) ||
5832 element == EL_FLAMES)
5835 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5836 behaviour, for example when touching a yamyam that explodes to rocks
5837 with active deadly shield, a rock is created under the player !!! */
5838 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5840 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5841 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5842 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5844 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5847 if (IS_ACTIVE_BOMB(element))
5849 // re-activate things under the bomb like gate or penguin
5850 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5857 // save walkable background elements while explosion on same tile
5858 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5859 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5860 Back[x][y] = element;
5862 // ignite explodable elements reached by other explosion
5863 if (element == EL_EXPLOSION)
5864 element = Store2[x][y];
5866 if (AmoebaNr[x][y] &&
5867 (element == EL_AMOEBA_FULL ||
5868 element == EL_BD_AMOEBA ||
5869 element == EL_AMOEBA_GROWING))
5871 AmoebaCnt[AmoebaNr[x][y]]--;
5872 AmoebaCnt2[AmoebaNr[x][y]]--;
5877 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5879 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5881 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5883 if (PLAYERINFO(ex, ey)->use_murphy)
5884 Store[x][y] = EL_EMPTY;
5887 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5888 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5889 else if (IS_PLAYER_ELEMENT(center_element))
5890 Store[x][y] = EL_EMPTY;
5891 else if (center_element == EL_YAMYAM)
5892 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5893 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5894 Store[x][y] = element_info[center_element].content.e[xx][yy];
5896 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5897 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5898 // otherwise) -- FIX THIS !!!
5899 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5900 Store[x][y] = element_info[element].content.e[1][1];
5902 else if (!CAN_EXPLODE(element))
5903 Store[x][y] = element_info[element].content.e[1][1];
5906 Store[x][y] = EL_EMPTY;
5908 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5909 center_element == EL_AMOEBA_TO_DIAMOND)
5910 Store2[x][y] = element;
5912 Tile[x][y] = EL_EXPLOSION;
5913 GfxElement[x][y] = artwork_element;
5915 ExplodePhase[x][y] = 1;
5916 ExplodeDelay[x][y] = last_phase;
5921 if (center_element == EL_YAMYAM)
5922 game.yamyam_content_nr =
5923 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5935 GfxFrame[x][y] = 0; // restart explosion animation
5937 last_phase = ExplodeDelay[x][y];
5939 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5941 // this can happen if the player leaves an explosion just in time
5942 if (GfxElement[x][y] == EL_UNDEFINED)
5943 GfxElement[x][y] = EL_EMPTY;
5945 border_element = Store2[x][y];
5946 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5947 border_element = StorePlayer[x][y];
5949 if (phase == element_info[border_element].ignition_delay ||
5950 phase == last_phase)
5952 boolean border_explosion = FALSE;
5954 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5955 !PLAYER_EXPLOSION_PROTECTED(x, y))
5957 KillPlayerUnlessExplosionProtected(x, y);
5958 border_explosion = TRUE;
5960 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5962 Tile[x][y] = Store2[x][y];
5965 border_explosion = TRUE;
5967 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5969 AmoebaToDiamond(x, y);
5971 border_explosion = TRUE;
5974 // if an element just explodes due to another explosion (chain-reaction),
5975 // do not immediately end the new explosion when it was the last frame of
5976 // the explosion (as it would be done in the following "if"-statement!)
5977 if (border_explosion && phase == last_phase)
5981 if (phase == last_phase)
5985 element = Tile[x][y] = Store[x][y];
5986 Store[x][y] = Store2[x][y] = 0;
5987 GfxElement[x][y] = EL_UNDEFINED;
5989 // player can escape from explosions and might therefore be still alive
5990 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5991 element <= EL_PLAYER_IS_EXPLODING_4)
5993 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5994 int explosion_element = EL_PLAYER_1 + player_nr;
5995 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5996 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5998 if (level.use_explosion_element[player_nr])
5999 explosion_element = level.explosion_element[player_nr];
6001 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6002 element_info[explosion_element].content.e[xx][yy]);
6005 // restore probably existing indestructible background element
6006 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6007 element = Tile[x][y] = Back[x][y];
6010 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6011 GfxDir[x][y] = MV_NONE;
6012 ChangeDelay[x][y] = 0;
6013 ChangePage[x][y] = -1;
6015 CustomValue[x][y] = 0;
6017 InitField_WithBug2(x, y, FALSE);
6019 TEST_DrawLevelField(x, y);
6021 TestIfElementTouchesCustomElement(x, y);
6023 if (GFX_CRUMBLED(element))
6024 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6026 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6027 StorePlayer[x][y] = 0;
6029 if (IS_PLAYER_ELEMENT(element))
6030 RelocatePlayer(x, y, element);
6032 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6034 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6035 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
6038 TEST_DrawLevelFieldCrumbled(x, y);
6040 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6042 DrawLevelElement(x, y, Back[x][y]);
6043 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6045 else if (IS_WALKABLE_UNDER(Back[x][y]))
6047 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6048 DrawLevelElementThruMask(x, y, Back[x][y]);
6050 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6051 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6055 static void DynaExplode(int ex, int ey)
6058 int dynabomb_element = Tile[ex][ey];
6059 int dynabomb_size = 1;
6060 boolean dynabomb_xl = FALSE;
6061 struct PlayerInfo *player;
6062 static int xy[4][2] =
6070 if (IS_ACTIVE_BOMB(dynabomb_element))
6072 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6073 dynabomb_size = player->dynabomb_size;
6074 dynabomb_xl = player->dynabomb_xl;
6075 player->dynabombs_left++;
6078 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6080 for (i = 0; i < NUM_DIRECTIONS; i++)
6082 for (j = 1; j <= dynabomb_size; j++)
6084 int x = ex + j * xy[i][0];
6085 int y = ey + j * xy[i][1];
6088 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6091 element = Tile[x][y];
6093 // do not restart explosions of fields with active bombs
6094 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6097 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6099 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6100 !IS_DIGGABLE(element) && !dynabomb_xl)
6106 void Bang(int x, int y)
6108 int element = MovingOrBlocked2Element(x, y);
6109 int explosion_type = EX_TYPE_NORMAL;
6111 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6113 struct PlayerInfo *player = PLAYERINFO(x, y);
6115 element = Tile[x][y] = player->initial_element;
6117 if (level.use_explosion_element[player->index_nr])
6119 int explosion_element = level.explosion_element[player->index_nr];
6121 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6122 explosion_type = EX_TYPE_CROSS;
6123 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6124 explosion_type = EX_TYPE_CENTER;
6132 case EL_BD_BUTTERFLY:
6135 case EL_DARK_YAMYAM:
6139 RaiseScoreElement(element);
6142 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6143 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6144 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6145 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6146 case EL_DYNABOMB_INCREASE_NUMBER:
6147 case EL_DYNABOMB_INCREASE_SIZE:
6148 case EL_DYNABOMB_INCREASE_POWER:
6149 explosion_type = EX_TYPE_DYNA;
6152 case EL_DC_LANDMINE:
6153 explosion_type = EX_TYPE_CENTER;
6158 case EL_LAMP_ACTIVE:
6159 case EL_AMOEBA_TO_DIAMOND:
6160 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6161 explosion_type = EX_TYPE_CENTER;
6165 if (element_info[element].explosion_type == EXPLODES_CROSS)
6166 explosion_type = EX_TYPE_CROSS;
6167 else if (element_info[element].explosion_type == EXPLODES_1X1)
6168 explosion_type = EX_TYPE_CENTER;
6172 if (explosion_type == EX_TYPE_DYNA)
6175 Explode(x, y, EX_PHASE_START, explosion_type);
6177 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6180 static void SplashAcid(int x, int y)
6182 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6183 (!IN_LEV_FIELD(x - 1, y - 2) ||
6184 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6185 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6187 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6188 (!IN_LEV_FIELD(x + 1, y - 2) ||
6189 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6190 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6192 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6195 static void InitBeltMovement(void)
6197 static int belt_base_element[4] =
6199 EL_CONVEYOR_BELT_1_LEFT,
6200 EL_CONVEYOR_BELT_2_LEFT,
6201 EL_CONVEYOR_BELT_3_LEFT,
6202 EL_CONVEYOR_BELT_4_LEFT
6204 static int belt_base_active_element[4] =
6206 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6207 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6208 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6209 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6214 // set frame order for belt animation graphic according to belt direction
6215 for (i = 0; i < NUM_BELTS; i++)
6219 for (j = 0; j < NUM_BELT_PARTS; j++)
6221 int element = belt_base_active_element[belt_nr] + j;
6222 int graphic_1 = el2img(element);
6223 int graphic_2 = el2panelimg(element);
6225 if (game.belt_dir[i] == MV_LEFT)
6227 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6228 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6232 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6233 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6238 SCAN_PLAYFIELD(x, y)
6240 int element = Tile[x][y];
6242 for (i = 0; i < NUM_BELTS; i++)
6244 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6246 int e_belt_nr = getBeltNrFromBeltElement(element);
6249 if (e_belt_nr == belt_nr)
6251 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6253 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6260 static void ToggleBeltSwitch(int x, int y)
6262 static int belt_base_element[4] =
6264 EL_CONVEYOR_BELT_1_LEFT,
6265 EL_CONVEYOR_BELT_2_LEFT,
6266 EL_CONVEYOR_BELT_3_LEFT,
6267 EL_CONVEYOR_BELT_4_LEFT
6269 static int belt_base_active_element[4] =
6271 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6272 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6273 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6274 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6276 static int belt_base_switch_element[4] =
6278 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6279 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6280 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6281 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6283 static int belt_move_dir[4] =
6291 int element = Tile[x][y];
6292 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6293 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6294 int belt_dir = belt_move_dir[belt_dir_nr];
6297 if (!IS_BELT_SWITCH(element))
6300 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6301 game.belt_dir[belt_nr] = belt_dir;
6303 if (belt_dir_nr == 3)
6306 // set frame order for belt animation graphic according to belt direction
6307 for (i = 0; i < NUM_BELT_PARTS; i++)
6309 int element = belt_base_active_element[belt_nr] + i;
6310 int graphic_1 = el2img(element);
6311 int graphic_2 = el2panelimg(element);
6313 if (belt_dir == MV_LEFT)
6315 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6316 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6320 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6321 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6325 SCAN_PLAYFIELD(xx, yy)
6327 int element = Tile[xx][yy];
6329 if (IS_BELT_SWITCH(element))
6331 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6333 if (e_belt_nr == belt_nr)
6335 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6336 TEST_DrawLevelField(xx, yy);
6339 else if (IS_BELT(element) && belt_dir != MV_NONE)
6341 int e_belt_nr = getBeltNrFromBeltElement(element);
6343 if (e_belt_nr == belt_nr)
6345 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6347 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6348 TEST_DrawLevelField(xx, yy);
6351 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6353 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6355 if (e_belt_nr == belt_nr)
6357 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6359 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6360 TEST_DrawLevelField(xx, yy);
6366 static void ToggleSwitchgateSwitch(int x, int y)
6370 game.switchgate_pos = !game.switchgate_pos;
6372 SCAN_PLAYFIELD(xx, yy)
6374 int element = Tile[xx][yy];
6376 if (element == EL_SWITCHGATE_SWITCH_UP)
6378 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6379 TEST_DrawLevelField(xx, yy);
6381 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6383 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6384 TEST_DrawLevelField(xx, yy);
6386 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6388 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6389 TEST_DrawLevelField(xx, yy);
6391 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6393 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6394 TEST_DrawLevelField(xx, yy);
6396 else if (element == EL_SWITCHGATE_OPEN ||
6397 element == EL_SWITCHGATE_OPENING)
6399 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6401 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6403 else if (element == EL_SWITCHGATE_CLOSED ||
6404 element == EL_SWITCHGATE_CLOSING)
6406 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6408 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6413 static int getInvisibleActiveFromInvisibleElement(int element)
6415 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6416 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6417 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6421 static int getInvisibleFromInvisibleActiveElement(int element)
6423 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6424 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6425 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6429 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6433 SCAN_PLAYFIELD(x, y)
6435 int element = Tile[x][y];
6437 if (element == EL_LIGHT_SWITCH &&
6438 game.light_time_left > 0)
6440 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6441 TEST_DrawLevelField(x, y);
6443 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6444 game.light_time_left == 0)
6446 Tile[x][y] = EL_LIGHT_SWITCH;
6447 TEST_DrawLevelField(x, y);
6449 else if (element == EL_EMC_DRIPPER &&
6450 game.light_time_left > 0)
6452 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6453 TEST_DrawLevelField(x, y);
6455 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6456 game.light_time_left == 0)
6458 Tile[x][y] = EL_EMC_DRIPPER;
6459 TEST_DrawLevelField(x, y);
6461 else if (element == EL_INVISIBLE_STEELWALL ||
6462 element == EL_INVISIBLE_WALL ||
6463 element == EL_INVISIBLE_SAND)
6465 if (game.light_time_left > 0)
6466 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6468 TEST_DrawLevelField(x, y);
6470 // uncrumble neighbour fields, if needed
6471 if (element == EL_INVISIBLE_SAND)
6472 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6474 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6475 element == EL_INVISIBLE_WALL_ACTIVE ||
6476 element == EL_INVISIBLE_SAND_ACTIVE)
6478 if (game.light_time_left == 0)
6479 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6481 TEST_DrawLevelField(x, y);
6483 // re-crumble neighbour fields, if needed
6484 if (element == EL_INVISIBLE_SAND)
6485 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6490 static void RedrawAllInvisibleElementsForLenses(void)
6494 SCAN_PLAYFIELD(x, y)
6496 int element = Tile[x][y];
6498 if (element == EL_EMC_DRIPPER &&
6499 game.lenses_time_left > 0)
6501 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6502 TEST_DrawLevelField(x, y);
6504 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6505 game.lenses_time_left == 0)
6507 Tile[x][y] = EL_EMC_DRIPPER;
6508 TEST_DrawLevelField(x, y);
6510 else if (element == EL_INVISIBLE_STEELWALL ||
6511 element == EL_INVISIBLE_WALL ||
6512 element == EL_INVISIBLE_SAND)
6514 if (game.lenses_time_left > 0)
6515 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6517 TEST_DrawLevelField(x, y);
6519 // uncrumble neighbour fields, if needed
6520 if (element == EL_INVISIBLE_SAND)
6521 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6523 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6524 element == EL_INVISIBLE_WALL_ACTIVE ||
6525 element == EL_INVISIBLE_SAND_ACTIVE)
6527 if (game.lenses_time_left == 0)
6528 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6530 TEST_DrawLevelField(x, y);
6532 // re-crumble neighbour fields, if needed
6533 if (element == EL_INVISIBLE_SAND)
6534 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6539 static void RedrawAllInvisibleElementsForMagnifier(void)
6543 SCAN_PLAYFIELD(x, y)
6545 int element = Tile[x][y];
6547 if (element == EL_EMC_FAKE_GRASS &&
6548 game.magnify_time_left > 0)
6550 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6551 TEST_DrawLevelField(x, y);
6553 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6554 game.magnify_time_left == 0)
6556 Tile[x][y] = EL_EMC_FAKE_GRASS;
6557 TEST_DrawLevelField(x, y);
6559 else if (IS_GATE_GRAY(element) &&
6560 game.magnify_time_left > 0)
6562 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6563 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6564 IS_EM_GATE_GRAY(element) ?
6565 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6566 IS_EMC_GATE_GRAY(element) ?
6567 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6568 IS_DC_GATE_GRAY(element) ?
6569 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6571 TEST_DrawLevelField(x, y);
6573 else if (IS_GATE_GRAY_ACTIVE(element) &&
6574 game.magnify_time_left == 0)
6576 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6577 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6578 IS_EM_GATE_GRAY_ACTIVE(element) ?
6579 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6580 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6581 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6582 IS_DC_GATE_GRAY_ACTIVE(element) ?
6583 EL_DC_GATE_WHITE_GRAY :
6585 TEST_DrawLevelField(x, y);
6590 static void ToggleLightSwitch(int x, int y)
6592 int element = Tile[x][y];
6594 game.light_time_left =
6595 (element == EL_LIGHT_SWITCH ?
6596 level.time_light * FRAMES_PER_SECOND : 0);
6598 RedrawAllLightSwitchesAndInvisibleElements();
6601 static void ActivateTimegateSwitch(int x, int y)
6605 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6607 SCAN_PLAYFIELD(xx, yy)
6609 int element = Tile[xx][yy];
6611 if (element == EL_TIMEGATE_CLOSED ||
6612 element == EL_TIMEGATE_CLOSING)
6614 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6615 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6619 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6621 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6622 TEST_DrawLevelField(xx, yy);
6628 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6629 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6632 static void Impact(int x, int y)
6634 boolean last_line = (y == lev_fieldy - 1);
6635 boolean object_hit = FALSE;
6636 boolean impact = (last_line || object_hit);
6637 int element = Tile[x][y];
6638 int smashed = EL_STEELWALL;
6640 if (!last_line) // check if element below was hit
6642 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6645 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6646 MovDir[x][y + 1] != MV_DOWN ||
6647 MovPos[x][y + 1] <= TILEY / 2));
6649 // do not smash moving elements that left the smashed field in time
6650 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6651 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6654 #if USE_QUICKSAND_IMPACT_BUGFIX
6655 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6657 RemoveMovingField(x, y + 1);
6658 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6659 Tile[x][y + 2] = EL_ROCK;
6660 TEST_DrawLevelField(x, y + 2);
6665 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6667 RemoveMovingField(x, y + 1);
6668 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6669 Tile[x][y + 2] = EL_ROCK;
6670 TEST_DrawLevelField(x, y + 2);
6677 smashed = MovingOrBlocked2Element(x, y + 1);
6679 impact = (last_line || object_hit);
6682 if (!last_line && smashed == EL_ACID) // element falls into acid
6684 SplashAcid(x, y + 1);
6688 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6689 // only reset graphic animation if graphic really changes after impact
6691 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6693 ResetGfxAnimation(x, y);
6694 TEST_DrawLevelField(x, y);
6697 if (impact && CAN_EXPLODE_IMPACT(element))
6702 else if (impact && element == EL_PEARL &&
6703 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6705 ResetGfxAnimation(x, y);
6707 Tile[x][y] = EL_PEARL_BREAKING;
6708 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6711 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6713 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6718 if (impact && element == EL_AMOEBA_DROP)
6720 if (object_hit && IS_PLAYER(x, y + 1))
6721 KillPlayerUnlessEnemyProtected(x, y + 1);
6722 else if (object_hit && smashed == EL_PENGUIN)
6726 Tile[x][y] = EL_AMOEBA_GROWING;
6727 Store[x][y] = EL_AMOEBA_WET;
6729 ResetRandomAnimationValue(x, y);
6734 if (object_hit) // check which object was hit
6736 if ((CAN_PASS_MAGIC_WALL(element) &&
6737 (smashed == EL_MAGIC_WALL ||
6738 smashed == EL_BD_MAGIC_WALL)) ||
6739 (CAN_PASS_DC_MAGIC_WALL(element) &&
6740 smashed == EL_DC_MAGIC_WALL))
6743 int activated_magic_wall =
6744 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6745 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6746 EL_DC_MAGIC_WALL_ACTIVE);
6748 // activate magic wall / mill
6749 SCAN_PLAYFIELD(xx, yy)
6751 if (Tile[xx][yy] == smashed)
6752 Tile[xx][yy] = activated_magic_wall;
6755 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6756 game.magic_wall_active = TRUE;
6758 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6759 SND_MAGIC_WALL_ACTIVATING :
6760 smashed == EL_BD_MAGIC_WALL ?
6761 SND_BD_MAGIC_WALL_ACTIVATING :
6762 SND_DC_MAGIC_WALL_ACTIVATING));
6765 if (IS_PLAYER(x, y + 1))
6767 if (CAN_SMASH_PLAYER(element))
6769 KillPlayerUnlessEnemyProtected(x, y + 1);
6773 else if (smashed == EL_PENGUIN)
6775 if (CAN_SMASH_PLAYER(element))
6781 else if (element == EL_BD_DIAMOND)
6783 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6789 else if (((element == EL_SP_INFOTRON ||
6790 element == EL_SP_ZONK) &&
6791 (smashed == EL_SP_SNIKSNAK ||
6792 smashed == EL_SP_ELECTRON ||
6793 smashed == EL_SP_DISK_ORANGE)) ||
6794 (element == EL_SP_INFOTRON &&
6795 smashed == EL_SP_DISK_YELLOW))
6800 else if (CAN_SMASH_EVERYTHING(element))
6802 if (IS_CLASSIC_ENEMY(smashed) ||
6803 CAN_EXPLODE_SMASHED(smashed))
6808 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6810 if (smashed == EL_LAMP ||
6811 smashed == EL_LAMP_ACTIVE)
6816 else if (smashed == EL_NUT)
6818 Tile[x][y + 1] = EL_NUT_BREAKING;
6819 PlayLevelSound(x, y, SND_NUT_BREAKING);
6820 RaiseScoreElement(EL_NUT);
6823 else if (smashed == EL_PEARL)
6825 ResetGfxAnimation(x, y);
6827 Tile[x][y + 1] = EL_PEARL_BREAKING;
6828 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6831 else if (smashed == EL_DIAMOND)
6833 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6834 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6837 else if (IS_BELT_SWITCH(smashed))
6839 ToggleBeltSwitch(x, y + 1);
6841 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6842 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6843 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6844 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6846 ToggleSwitchgateSwitch(x, y + 1);
6848 else if (smashed == EL_LIGHT_SWITCH ||
6849 smashed == EL_LIGHT_SWITCH_ACTIVE)
6851 ToggleLightSwitch(x, y + 1);
6855 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6857 CheckElementChangeBySide(x, y + 1, smashed, element,
6858 CE_SWITCHED, CH_SIDE_TOP);
6859 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6865 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6870 // play sound of magic wall / mill
6872 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6873 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6874 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6876 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6877 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6878 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6879 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6880 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6881 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6886 // play sound of object that hits the ground
6887 if (last_line || object_hit)
6888 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6891 static void TurnRoundExt(int x, int y)
6903 { 0, 0 }, { 0, 0 }, { 0, 0 },
6908 int left, right, back;
6912 { MV_DOWN, MV_UP, MV_RIGHT },
6913 { MV_UP, MV_DOWN, MV_LEFT },
6915 { MV_LEFT, MV_RIGHT, MV_DOWN },
6919 { MV_RIGHT, MV_LEFT, MV_UP }
6922 int element = Tile[x][y];
6923 int move_pattern = element_info[element].move_pattern;
6925 int old_move_dir = MovDir[x][y];
6926 int left_dir = turn[old_move_dir].left;
6927 int right_dir = turn[old_move_dir].right;
6928 int back_dir = turn[old_move_dir].back;
6930 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6931 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6932 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6933 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6935 int left_x = x + left_dx, left_y = y + left_dy;
6936 int right_x = x + right_dx, right_y = y + right_dy;
6937 int move_x = x + move_dx, move_y = y + move_dy;
6941 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6943 TestIfBadThingTouchesOtherBadThing(x, y);
6945 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6946 MovDir[x][y] = right_dir;
6947 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6948 MovDir[x][y] = left_dir;
6950 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6952 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6955 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6957 TestIfBadThingTouchesOtherBadThing(x, y);
6959 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6960 MovDir[x][y] = left_dir;
6961 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6962 MovDir[x][y] = right_dir;
6964 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6966 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6969 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6971 TestIfBadThingTouchesOtherBadThing(x, y);
6973 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6974 MovDir[x][y] = left_dir;
6975 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6976 MovDir[x][y] = right_dir;
6978 if (MovDir[x][y] != old_move_dir)
6981 else if (element == EL_YAMYAM)
6983 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6984 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6986 if (can_turn_left && can_turn_right)
6987 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6988 else if (can_turn_left)
6989 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6990 else if (can_turn_right)
6991 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6993 MovDir[x][y] = back_dir;
6995 MovDelay[x][y] = 16 + 16 * RND(3);
6997 else if (element == EL_DARK_YAMYAM)
6999 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7001 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7004 if (can_turn_left && can_turn_right)
7005 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7006 else if (can_turn_left)
7007 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7008 else if (can_turn_right)
7009 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7011 MovDir[x][y] = back_dir;
7013 MovDelay[x][y] = 16 + 16 * RND(3);
7015 else if (element == EL_PACMAN)
7017 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7018 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7020 if (can_turn_left && can_turn_right)
7021 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7022 else if (can_turn_left)
7023 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7024 else if (can_turn_right)
7025 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7027 MovDir[x][y] = back_dir;
7029 MovDelay[x][y] = 6 + RND(40);
7031 else if (element == EL_PIG)
7033 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7034 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7035 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7036 boolean should_turn_left, should_turn_right, should_move_on;
7038 int rnd = RND(rnd_value);
7040 should_turn_left = (can_turn_left &&
7042 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7043 y + back_dy + left_dy)));
7044 should_turn_right = (can_turn_right &&
7046 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7047 y + back_dy + right_dy)));
7048 should_move_on = (can_move_on &&
7051 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7052 y + move_dy + left_dy) ||
7053 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7054 y + move_dy + right_dy)));
7056 if (should_turn_left || should_turn_right || should_move_on)
7058 if (should_turn_left && should_turn_right && should_move_on)
7059 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7060 rnd < 2 * rnd_value / 3 ? right_dir :
7062 else if (should_turn_left && should_turn_right)
7063 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7064 else if (should_turn_left && should_move_on)
7065 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7066 else if (should_turn_right && should_move_on)
7067 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7068 else if (should_turn_left)
7069 MovDir[x][y] = left_dir;
7070 else if (should_turn_right)
7071 MovDir[x][y] = right_dir;
7072 else if (should_move_on)
7073 MovDir[x][y] = old_move_dir;
7075 else if (can_move_on && rnd > rnd_value / 8)
7076 MovDir[x][y] = old_move_dir;
7077 else if (can_turn_left && can_turn_right)
7078 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7079 else if (can_turn_left && rnd > rnd_value / 8)
7080 MovDir[x][y] = left_dir;
7081 else if (can_turn_right && rnd > rnd_value/8)
7082 MovDir[x][y] = right_dir;
7084 MovDir[x][y] = back_dir;
7086 xx = x + move_xy[MovDir[x][y]].dx;
7087 yy = y + move_xy[MovDir[x][y]].dy;
7089 if (!IN_LEV_FIELD(xx, yy) ||
7090 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7091 MovDir[x][y] = old_move_dir;
7095 else if (element == EL_DRAGON)
7097 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7098 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7099 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7101 int rnd = RND(rnd_value);
7103 if (can_move_on && rnd > rnd_value / 8)
7104 MovDir[x][y] = old_move_dir;
7105 else if (can_turn_left && can_turn_right)
7106 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7107 else if (can_turn_left && rnd > rnd_value / 8)
7108 MovDir[x][y] = left_dir;
7109 else if (can_turn_right && rnd > rnd_value / 8)
7110 MovDir[x][y] = right_dir;
7112 MovDir[x][y] = back_dir;
7114 xx = x + move_xy[MovDir[x][y]].dx;
7115 yy = y + move_xy[MovDir[x][y]].dy;
7117 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7118 MovDir[x][y] = old_move_dir;
7122 else if (element == EL_MOLE)
7124 boolean can_move_on =
7125 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7126 IS_AMOEBOID(Tile[move_x][move_y]) ||
7127 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7130 boolean can_turn_left =
7131 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7132 IS_AMOEBOID(Tile[left_x][left_y])));
7134 boolean can_turn_right =
7135 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7136 IS_AMOEBOID(Tile[right_x][right_y])));
7138 if (can_turn_left && can_turn_right)
7139 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7140 else if (can_turn_left)
7141 MovDir[x][y] = left_dir;
7143 MovDir[x][y] = right_dir;
7146 if (MovDir[x][y] != old_move_dir)
7149 else if (element == EL_BALLOON)
7151 MovDir[x][y] = game.wind_direction;
7154 else if (element == EL_SPRING)
7156 if (MovDir[x][y] & MV_HORIZONTAL)
7158 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7159 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7161 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7162 ResetGfxAnimation(move_x, move_y);
7163 TEST_DrawLevelField(move_x, move_y);
7165 MovDir[x][y] = back_dir;
7167 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7168 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7169 MovDir[x][y] = MV_NONE;
7174 else if (element == EL_ROBOT ||
7175 element == EL_SATELLITE ||
7176 element == EL_PENGUIN ||
7177 element == EL_EMC_ANDROID)
7179 int attr_x = -1, attr_y = -1;
7181 if (game.all_players_gone)
7183 attr_x = game.exit_x;
7184 attr_y = game.exit_y;
7190 for (i = 0; i < MAX_PLAYERS; i++)
7192 struct PlayerInfo *player = &stored_player[i];
7193 int jx = player->jx, jy = player->jy;
7195 if (!player->active)
7199 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7207 if (element == EL_ROBOT &&
7208 game.robot_wheel_x >= 0 &&
7209 game.robot_wheel_y >= 0 &&
7210 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7211 game.engine_version < VERSION_IDENT(3,1,0,0)))
7213 attr_x = game.robot_wheel_x;
7214 attr_y = game.robot_wheel_y;
7217 if (element == EL_PENGUIN)
7220 static int xy[4][2] =
7228 for (i = 0; i < NUM_DIRECTIONS; i++)
7230 int ex = x + xy[i][0];
7231 int ey = y + xy[i][1];
7233 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7234 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7235 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7236 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7245 MovDir[x][y] = MV_NONE;
7247 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7248 else if (attr_x > x)
7249 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7251 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7252 else if (attr_y > y)
7253 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7255 if (element == EL_ROBOT)
7259 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7260 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7261 Moving2Blocked(x, y, &newx, &newy);
7263 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7264 MovDelay[x][y] = 8 + 8 * !RND(3);
7266 MovDelay[x][y] = 16;
7268 else if (element == EL_PENGUIN)
7274 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7276 boolean first_horiz = RND(2);
7277 int new_move_dir = MovDir[x][y];
7280 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7281 Moving2Blocked(x, y, &newx, &newy);
7283 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7287 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7288 Moving2Blocked(x, y, &newx, &newy);
7290 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7293 MovDir[x][y] = old_move_dir;
7297 else if (element == EL_SATELLITE)
7303 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7305 boolean first_horiz = RND(2);
7306 int new_move_dir = MovDir[x][y];
7309 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7310 Moving2Blocked(x, y, &newx, &newy);
7312 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7316 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7317 Moving2Blocked(x, y, &newx, &newy);
7319 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7322 MovDir[x][y] = old_move_dir;
7326 else if (element == EL_EMC_ANDROID)
7328 static int check_pos[16] =
7330 -1, // 0 => (invalid)
7333 -1, // 3 => (invalid)
7335 0, // 5 => MV_LEFT | MV_UP
7336 2, // 6 => MV_RIGHT | MV_UP
7337 -1, // 7 => (invalid)
7339 6, // 9 => MV_LEFT | MV_DOWN
7340 4, // 10 => MV_RIGHT | MV_DOWN
7341 -1, // 11 => (invalid)
7342 -1, // 12 => (invalid)
7343 -1, // 13 => (invalid)
7344 -1, // 14 => (invalid)
7345 -1, // 15 => (invalid)
7353 { -1, -1, MV_LEFT | MV_UP },
7355 { +1, -1, MV_RIGHT | MV_UP },
7356 { +1, 0, MV_RIGHT },
7357 { +1, +1, MV_RIGHT | MV_DOWN },
7359 { -1, +1, MV_LEFT | MV_DOWN },
7362 int start_pos, check_order;
7363 boolean can_clone = FALSE;
7366 // check if there is any free field around current position
7367 for (i = 0; i < 8; i++)
7369 int newx = x + check_xy[i].dx;
7370 int newy = y + check_xy[i].dy;
7372 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7380 if (can_clone) // randomly find an element to clone
7384 start_pos = check_pos[RND(8)];
7385 check_order = (RND(2) ? -1 : +1);
7387 for (i = 0; i < 8; i++)
7389 int pos_raw = start_pos + i * check_order;
7390 int pos = (pos_raw + 8) % 8;
7391 int newx = x + check_xy[pos].dx;
7392 int newy = y + check_xy[pos].dy;
7394 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7396 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7397 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7399 Store[x][y] = Tile[newx][newy];
7408 if (can_clone) // randomly find a direction to move
7412 start_pos = check_pos[RND(8)];
7413 check_order = (RND(2) ? -1 : +1);
7415 for (i = 0; i < 8; i++)
7417 int pos_raw = start_pos + i * check_order;
7418 int pos = (pos_raw + 8) % 8;
7419 int newx = x + check_xy[pos].dx;
7420 int newy = y + check_xy[pos].dy;
7421 int new_move_dir = check_xy[pos].dir;
7423 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7425 MovDir[x][y] = new_move_dir;
7426 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7435 if (can_clone) // cloning and moving successful
7438 // cannot clone -- try to move towards player
7440 start_pos = check_pos[MovDir[x][y] & 0x0f];
7441 check_order = (RND(2) ? -1 : +1);
7443 for (i = 0; i < 3; i++)
7445 // first check start_pos, then previous/next or (next/previous) pos
7446 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7447 int pos = (pos_raw + 8) % 8;
7448 int newx = x + check_xy[pos].dx;
7449 int newy = y + check_xy[pos].dy;
7450 int new_move_dir = check_xy[pos].dir;
7452 if (IS_PLAYER(newx, newy))
7455 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7457 MovDir[x][y] = new_move_dir;
7458 MovDelay[x][y] = level.android_move_time * 8 + 1;
7465 else if (move_pattern == MV_TURNING_LEFT ||
7466 move_pattern == MV_TURNING_RIGHT ||
7467 move_pattern == MV_TURNING_LEFT_RIGHT ||
7468 move_pattern == MV_TURNING_RIGHT_LEFT ||
7469 move_pattern == MV_TURNING_RANDOM ||
7470 move_pattern == MV_ALL_DIRECTIONS)
7472 boolean can_turn_left =
7473 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7474 boolean can_turn_right =
7475 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7477 if (element_info[element].move_stepsize == 0) // "not moving"
7480 if (move_pattern == MV_TURNING_LEFT)
7481 MovDir[x][y] = left_dir;
7482 else if (move_pattern == MV_TURNING_RIGHT)
7483 MovDir[x][y] = right_dir;
7484 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7485 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7486 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7487 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7488 else if (move_pattern == MV_TURNING_RANDOM)
7489 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7490 can_turn_right && !can_turn_left ? right_dir :
7491 RND(2) ? left_dir : right_dir);
7492 else if (can_turn_left && can_turn_right)
7493 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7494 else if (can_turn_left)
7495 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7496 else if (can_turn_right)
7497 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7499 MovDir[x][y] = back_dir;
7501 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7503 else if (move_pattern == MV_HORIZONTAL ||
7504 move_pattern == MV_VERTICAL)
7506 if (move_pattern & old_move_dir)
7507 MovDir[x][y] = back_dir;
7508 else if (move_pattern == MV_HORIZONTAL)
7509 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7510 else if (move_pattern == MV_VERTICAL)
7511 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7513 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7515 else if (move_pattern & MV_ANY_DIRECTION)
7517 MovDir[x][y] = move_pattern;
7518 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7520 else if (move_pattern & MV_WIND_DIRECTION)
7522 MovDir[x][y] = game.wind_direction;
7523 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7525 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7527 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7528 MovDir[x][y] = left_dir;
7529 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7530 MovDir[x][y] = right_dir;
7532 if (MovDir[x][y] != old_move_dir)
7533 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7535 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7537 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7538 MovDir[x][y] = right_dir;
7539 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7540 MovDir[x][y] = left_dir;
7542 if (MovDir[x][y] != old_move_dir)
7543 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7545 else if (move_pattern == MV_TOWARDS_PLAYER ||
7546 move_pattern == MV_AWAY_FROM_PLAYER)
7548 int attr_x = -1, attr_y = -1;
7550 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7552 if (game.all_players_gone)
7554 attr_x = game.exit_x;
7555 attr_y = game.exit_y;
7561 for (i = 0; i < MAX_PLAYERS; i++)
7563 struct PlayerInfo *player = &stored_player[i];
7564 int jx = player->jx, jy = player->jy;
7566 if (!player->active)
7570 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7578 MovDir[x][y] = MV_NONE;
7580 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7581 else if (attr_x > x)
7582 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7584 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7585 else if (attr_y > y)
7586 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7588 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7590 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7592 boolean first_horiz = RND(2);
7593 int new_move_dir = MovDir[x][y];
7595 if (element_info[element].move_stepsize == 0) // "not moving"
7597 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7598 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7604 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7605 Moving2Blocked(x, y, &newx, &newy);
7607 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7611 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7612 Moving2Blocked(x, y, &newx, &newy);
7614 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7617 MovDir[x][y] = old_move_dir;
7620 else if (move_pattern == MV_WHEN_PUSHED ||
7621 move_pattern == MV_WHEN_DROPPED)
7623 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7624 MovDir[x][y] = MV_NONE;
7628 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7630 static int test_xy[7][2] =
7640 static int test_dir[7] =
7650 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7651 int move_preference = -1000000; // start with very low preference
7652 int new_move_dir = MV_NONE;
7653 int start_test = RND(4);
7656 for (i = 0; i < NUM_DIRECTIONS; i++)
7658 int move_dir = test_dir[start_test + i];
7659 int move_dir_preference;
7661 xx = x + test_xy[start_test + i][0];
7662 yy = y + test_xy[start_test + i][1];
7664 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7665 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7667 new_move_dir = move_dir;
7672 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7675 move_dir_preference = -1 * RunnerVisit[xx][yy];
7676 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7677 move_dir_preference = PlayerVisit[xx][yy];
7679 if (move_dir_preference > move_preference)
7681 // prefer field that has not been visited for the longest time
7682 move_preference = move_dir_preference;
7683 new_move_dir = move_dir;
7685 else if (move_dir_preference == move_preference &&
7686 move_dir == old_move_dir)
7688 // prefer last direction when all directions are preferred equally
7689 move_preference = move_dir_preference;
7690 new_move_dir = move_dir;
7694 MovDir[x][y] = new_move_dir;
7695 if (old_move_dir != new_move_dir)
7696 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7700 static void TurnRound(int x, int y)
7702 int direction = MovDir[x][y];
7706 GfxDir[x][y] = MovDir[x][y];
7708 if (direction != MovDir[x][y])
7712 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7714 ResetGfxFrame(x, y);
7717 static boolean JustBeingPushed(int x, int y)
7721 for (i = 0; i < MAX_PLAYERS; i++)
7723 struct PlayerInfo *player = &stored_player[i];
7725 if (player->active && player->is_pushing && player->MovPos)
7727 int next_jx = player->jx + (player->jx - player->last_jx);
7728 int next_jy = player->jy + (player->jy - player->last_jy);
7730 if (x == next_jx && y == next_jy)
7738 static void StartMoving(int x, int y)
7740 boolean started_moving = FALSE; // some elements can fall _and_ move
7741 int element = Tile[x][y];
7746 if (MovDelay[x][y] == 0)
7747 GfxAction[x][y] = ACTION_DEFAULT;
7749 if (CAN_FALL(element) && y < lev_fieldy - 1)
7751 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7752 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7753 if (JustBeingPushed(x, y))
7756 if (element == EL_QUICKSAND_FULL)
7758 if (IS_FREE(x, y + 1))
7760 InitMovingField(x, y, MV_DOWN);
7761 started_moving = TRUE;
7763 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7764 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7765 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7766 Store[x][y] = EL_ROCK;
7768 Store[x][y] = EL_ROCK;
7771 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7773 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7775 if (!MovDelay[x][y])
7777 MovDelay[x][y] = TILEY + 1;
7779 ResetGfxAnimation(x, y);
7780 ResetGfxAnimation(x, y + 1);
7785 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7786 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7793 Tile[x][y] = EL_QUICKSAND_EMPTY;
7794 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7795 Store[x][y + 1] = Store[x][y];
7798 PlayLevelSoundAction(x, y, ACTION_FILLING);
7800 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7802 if (!MovDelay[x][y])
7804 MovDelay[x][y] = TILEY + 1;
7806 ResetGfxAnimation(x, y);
7807 ResetGfxAnimation(x, y + 1);
7812 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7813 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7820 Tile[x][y] = EL_QUICKSAND_EMPTY;
7821 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7822 Store[x][y + 1] = Store[x][y];
7825 PlayLevelSoundAction(x, y, ACTION_FILLING);
7828 else if (element == EL_QUICKSAND_FAST_FULL)
7830 if (IS_FREE(x, y + 1))
7832 InitMovingField(x, y, MV_DOWN);
7833 started_moving = TRUE;
7835 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7836 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7837 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7838 Store[x][y] = EL_ROCK;
7840 Store[x][y] = EL_ROCK;
7843 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7845 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7847 if (!MovDelay[x][y])
7849 MovDelay[x][y] = TILEY + 1;
7851 ResetGfxAnimation(x, y);
7852 ResetGfxAnimation(x, y + 1);
7857 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7858 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7865 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7866 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7867 Store[x][y + 1] = Store[x][y];
7870 PlayLevelSoundAction(x, y, ACTION_FILLING);
7872 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7874 if (!MovDelay[x][y])
7876 MovDelay[x][y] = TILEY + 1;
7878 ResetGfxAnimation(x, y);
7879 ResetGfxAnimation(x, y + 1);
7884 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7885 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7892 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7893 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7894 Store[x][y + 1] = Store[x][y];
7897 PlayLevelSoundAction(x, y, ACTION_FILLING);
7900 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7901 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7903 InitMovingField(x, y, MV_DOWN);
7904 started_moving = TRUE;
7906 Tile[x][y] = EL_QUICKSAND_FILLING;
7907 Store[x][y] = element;
7909 PlayLevelSoundAction(x, y, ACTION_FILLING);
7911 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7912 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7914 InitMovingField(x, y, MV_DOWN);
7915 started_moving = TRUE;
7917 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7918 Store[x][y] = element;
7920 PlayLevelSoundAction(x, y, ACTION_FILLING);
7922 else if (element == EL_MAGIC_WALL_FULL)
7924 if (IS_FREE(x, y + 1))
7926 InitMovingField(x, y, MV_DOWN);
7927 started_moving = TRUE;
7929 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7930 Store[x][y] = EL_CHANGED(Store[x][y]);
7932 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7934 if (!MovDelay[x][y])
7935 MovDelay[x][y] = TILEY / 4 + 1;
7944 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7945 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7946 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7950 else if (element == EL_BD_MAGIC_WALL_FULL)
7952 if (IS_FREE(x, y + 1))
7954 InitMovingField(x, y, MV_DOWN);
7955 started_moving = TRUE;
7957 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7958 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7960 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7962 if (!MovDelay[x][y])
7963 MovDelay[x][y] = TILEY / 4 + 1;
7972 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7973 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7974 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7978 else if (element == EL_DC_MAGIC_WALL_FULL)
7980 if (IS_FREE(x, y + 1))
7982 InitMovingField(x, y, MV_DOWN);
7983 started_moving = TRUE;
7985 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7986 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7988 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7990 if (!MovDelay[x][y])
7991 MovDelay[x][y] = TILEY / 4 + 1;
8000 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8001 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8002 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8006 else if ((CAN_PASS_MAGIC_WALL(element) &&
8007 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8008 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8009 (CAN_PASS_DC_MAGIC_WALL(element) &&
8010 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8013 InitMovingField(x, y, MV_DOWN);
8014 started_moving = TRUE;
8017 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8018 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8019 EL_DC_MAGIC_WALL_FILLING);
8020 Store[x][y] = element;
8022 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8024 SplashAcid(x, y + 1);
8026 InitMovingField(x, y, MV_DOWN);
8027 started_moving = TRUE;
8029 Store[x][y] = EL_ACID;
8032 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8033 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8034 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8035 CAN_FALL(element) && WasJustFalling[x][y] &&
8036 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8038 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8039 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8040 (Tile[x][y + 1] == EL_BLOCKED)))
8042 /* this is needed for a special case not covered by calling "Impact()"
8043 from "ContinueMoving()": if an element moves to a tile directly below
8044 another element which was just falling on that tile (which was empty
8045 in the previous frame), the falling element above would just stop
8046 instead of smashing the element below (in previous version, the above
8047 element was just checked for "moving" instead of "falling", resulting
8048 in incorrect smashes caused by horizontal movement of the above
8049 element; also, the case of the player being the element to smash was
8050 simply not covered here... :-/ ) */
8052 CheckCollision[x][y] = 0;
8053 CheckImpact[x][y] = 0;
8057 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8059 if (MovDir[x][y] == MV_NONE)
8061 InitMovingField(x, y, MV_DOWN);
8062 started_moving = TRUE;
8065 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8067 if (WasJustFalling[x][y]) // prevent animation from being restarted
8068 MovDir[x][y] = MV_DOWN;
8070 InitMovingField(x, y, MV_DOWN);
8071 started_moving = TRUE;
8073 else if (element == EL_AMOEBA_DROP)
8075 Tile[x][y] = EL_AMOEBA_GROWING;
8076 Store[x][y] = EL_AMOEBA_WET;
8078 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8079 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8080 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8081 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8083 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8084 (IS_FREE(x - 1, y + 1) ||
8085 Tile[x - 1][y + 1] == EL_ACID));
8086 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8087 (IS_FREE(x + 1, y + 1) ||
8088 Tile[x + 1][y + 1] == EL_ACID));
8089 boolean can_fall_any = (can_fall_left || can_fall_right);
8090 boolean can_fall_both = (can_fall_left && can_fall_right);
8091 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8093 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8095 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8096 can_fall_right = FALSE;
8097 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8098 can_fall_left = FALSE;
8099 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8100 can_fall_right = FALSE;
8101 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8102 can_fall_left = FALSE;
8104 can_fall_any = (can_fall_left || can_fall_right);
8105 can_fall_both = FALSE;
8110 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8111 can_fall_right = FALSE; // slip down on left side
8113 can_fall_left = !(can_fall_right = RND(2));
8115 can_fall_both = FALSE;
8120 // if not determined otherwise, prefer left side for slipping down
8121 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8122 started_moving = TRUE;
8125 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8127 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8128 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8129 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8130 int belt_dir = game.belt_dir[belt_nr];
8132 if ((belt_dir == MV_LEFT && left_is_free) ||
8133 (belt_dir == MV_RIGHT && right_is_free))
8135 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8137 InitMovingField(x, y, belt_dir);
8138 started_moving = TRUE;
8140 Pushed[x][y] = TRUE;
8141 Pushed[nextx][y] = TRUE;
8143 GfxAction[x][y] = ACTION_DEFAULT;
8147 MovDir[x][y] = 0; // if element was moving, stop it
8152 // not "else if" because of elements that can fall and move (EL_SPRING)
8153 if (CAN_MOVE(element) && !started_moving)
8155 int move_pattern = element_info[element].move_pattern;
8158 Moving2Blocked(x, y, &newx, &newy);
8160 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8163 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8164 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8166 WasJustMoving[x][y] = 0;
8167 CheckCollision[x][y] = 0;
8169 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8171 if (Tile[x][y] != element) // element has changed
8175 if (!MovDelay[x][y]) // start new movement phase
8177 // all objects that can change their move direction after each step
8178 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8180 if (element != EL_YAMYAM &&
8181 element != EL_DARK_YAMYAM &&
8182 element != EL_PACMAN &&
8183 !(move_pattern & MV_ANY_DIRECTION) &&
8184 move_pattern != MV_TURNING_LEFT &&
8185 move_pattern != MV_TURNING_RIGHT &&
8186 move_pattern != MV_TURNING_LEFT_RIGHT &&
8187 move_pattern != MV_TURNING_RIGHT_LEFT &&
8188 move_pattern != MV_TURNING_RANDOM)
8192 if (MovDelay[x][y] && (element == EL_BUG ||
8193 element == EL_SPACESHIP ||
8194 element == EL_SP_SNIKSNAK ||
8195 element == EL_SP_ELECTRON ||
8196 element == EL_MOLE))
8197 TEST_DrawLevelField(x, y);
8201 if (MovDelay[x][y]) // wait some time before next movement
8205 if (element == EL_ROBOT ||
8206 element == EL_YAMYAM ||
8207 element == EL_DARK_YAMYAM)
8209 DrawLevelElementAnimationIfNeeded(x, y, element);
8210 PlayLevelSoundAction(x, y, ACTION_WAITING);
8212 else if (element == EL_SP_ELECTRON)
8213 DrawLevelElementAnimationIfNeeded(x, y, element);
8214 else if (element == EL_DRAGON)
8217 int dir = MovDir[x][y];
8218 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8219 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8220 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8221 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8222 dir == MV_UP ? IMG_FLAMES_1_UP :
8223 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8224 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8226 GfxAction[x][y] = ACTION_ATTACKING;
8228 if (IS_PLAYER(x, y))
8229 DrawPlayerField(x, y);
8231 TEST_DrawLevelField(x, y);
8233 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8235 for (i = 1; i <= 3; i++)
8237 int xx = x + i * dx;
8238 int yy = y + i * dy;
8239 int sx = SCREENX(xx);
8240 int sy = SCREENY(yy);
8241 int flame_graphic = graphic + (i - 1);
8243 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8248 int flamed = MovingOrBlocked2Element(xx, yy);
8250 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8253 RemoveMovingField(xx, yy);
8255 ChangeDelay[xx][yy] = 0;
8257 Tile[xx][yy] = EL_FLAMES;
8259 if (IN_SCR_FIELD(sx, sy))
8261 TEST_DrawLevelFieldCrumbled(xx, yy);
8262 DrawGraphic(sx, sy, flame_graphic, frame);
8267 if (Tile[xx][yy] == EL_FLAMES)
8268 Tile[xx][yy] = EL_EMPTY;
8269 TEST_DrawLevelField(xx, yy);
8274 if (MovDelay[x][y]) // element still has to wait some time
8276 PlayLevelSoundAction(x, y, ACTION_WAITING);
8282 // now make next step
8284 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8286 if (DONT_COLLIDE_WITH(element) &&
8287 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8288 !PLAYER_ENEMY_PROTECTED(newx, newy))
8290 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8295 else if (CAN_MOVE_INTO_ACID(element) &&
8296 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8297 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8298 (MovDir[x][y] == MV_DOWN ||
8299 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8301 SplashAcid(newx, newy);
8302 Store[x][y] = EL_ACID;
8304 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8306 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8307 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8308 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8309 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8312 TEST_DrawLevelField(x, y);
8314 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8315 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8316 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8318 game.friends_still_needed--;
8319 if (!game.friends_still_needed &&
8321 game.all_players_gone)
8326 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8328 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8329 TEST_DrawLevelField(newx, newy);
8331 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8333 else if (!IS_FREE(newx, newy))
8335 GfxAction[x][y] = ACTION_WAITING;
8337 if (IS_PLAYER(x, y))
8338 DrawPlayerField(x, y);
8340 TEST_DrawLevelField(x, y);
8345 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8347 if (IS_FOOD_PIG(Tile[newx][newy]))
8349 if (IS_MOVING(newx, newy))
8350 RemoveMovingField(newx, newy);
8353 Tile[newx][newy] = EL_EMPTY;
8354 TEST_DrawLevelField(newx, newy);
8357 PlayLevelSound(x, y, SND_PIG_DIGGING);
8359 else if (!IS_FREE(newx, newy))
8361 if (IS_PLAYER(x, y))
8362 DrawPlayerField(x, y);
8364 TEST_DrawLevelField(x, y);
8369 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8371 if (Store[x][y] != EL_EMPTY)
8373 boolean can_clone = FALSE;
8376 // check if element to clone is still there
8377 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8379 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8387 // cannot clone or target field not free anymore -- do not clone
8388 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8389 Store[x][y] = EL_EMPTY;
8392 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8394 if (IS_MV_DIAGONAL(MovDir[x][y]))
8396 int diagonal_move_dir = MovDir[x][y];
8397 int stored = Store[x][y];
8398 int change_delay = 8;
8401 // android is moving diagonally
8403 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8405 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8406 GfxElement[x][y] = EL_EMC_ANDROID;
8407 GfxAction[x][y] = ACTION_SHRINKING;
8408 GfxDir[x][y] = diagonal_move_dir;
8409 ChangeDelay[x][y] = change_delay;
8411 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8414 DrawLevelGraphicAnimation(x, y, graphic);
8415 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8417 if (Tile[newx][newy] == EL_ACID)
8419 SplashAcid(newx, newy);
8424 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8426 Store[newx][newy] = EL_EMC_ANDROID;
8427 GfxElement[newx][newy] = EL_EMC_ANDROID;
8428 GfxAction[newx][newy] = ACTION_GROWING;
8429 GfxDir[newx][newy] = diagonal_move_dir;
8430 ChangeDelay[newx][newy] = change_delay;
8432 graphic = el_act_dir2img(GfxElement[newx][newy],
8433 GfxAction[newx][newy], GfxDir[newx][newy]);
8435 DrawLevelGraphicAnimation(newx, newy, graphic);
8436 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8442 Tile[newx][newy] = EL_EMPTY;
8443 TEST_DrawLevelField(newx, newy);
8445 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8448 else if (!IS_FREE(newx, newy))
8453 else if (IS_CUSTOM_ELEMENT(element) &&
8454 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8456 if (!DigFieldByCE(newx, newy, element))
8459 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8461 RunnerVisit[x][y] = FrameCounter;
8462 PlayerVisit[x][y] /= 8; // expire player visit path
8465 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8467 if (!IS_FREE(newx, newy))
8469 if (IS_PLAYER(x, y))
8470 DrawPlayerField(x, y);
8472 TEST_DrawLevelField(x, y);
8478 boolean wanna_flame = !RND(10);
8479 int dx = newx - x, dy = newy - y;
8480 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8481 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8482 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8483 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8484 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8485 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8488 IS_CLASSIC_ENEMY(element1) ||
8489 IS_CLASSIC_ENEMY(element2)) &&
8490 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8491 element1 != EL_FLAMES && element2 != EL_FLAMES)
8493 ResetGfxAnimation(x, y);
8494 GfxAction[x][y] = ACTION_ATTACKING;
8496 if (IS_PLAYER(x, y))
8497 DrawPlayerField(x, y);
8499 TEST_DrawLevelField(x, y);
8501 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8503 MovDelay[x][y] = 50;
8505 Tile[newx][newy] = EL_FLAMES;
8506 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8507 Tile[newx1][newy1] = EL_FLAMES;
8508 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8509 Tile[newx2][newy2] = EL_FLAMES;
8515 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8516 Tile[newx][newy] == EL_DIAMOND)
8518 if (IS_MOVING(newx, newy))
8519 RemoveMovingField(newx, newy);
8522 Tile[newx][newy] = EL_EMPTY;
8523 TEST_DrawLevelField(newx, newy);
8526 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8528 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8529 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8531 if (AmoebaNr[newx][newy])
8533 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8534 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8535 Tile[newx][newy] == EL_BD_AMOEBA)
8536 AmoebaCnt[AmoebaNr[newx][newy]]--;
8539 if (IS_MOVING(newx, newy))
8541 RemoveMovingField(newx, newy);
8545 Tile[newx][newy] = EL_EMPTY;
8546 TEST_DrawLevelField(newx, newy);
8549 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8551 else if ((element == EL_PACMAN || element == EL_MOLE)
8552 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8554 if (AmoebaNr[newx][newy])
8556 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8557 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8558 Tile[newx][newy] == EL_BD_AMOEBA)
8559 AmoebaCnt[AmoebaNr[newx][newy]]--;
8562 if (element == EL_MOLE)
8564 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8565 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8567 ResetGfxAnimation(x, y);
8568 GfxAction[x][y] = ACTION_DIGGING;
8569 TEST_DrawLevelField(x, y);
8571 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8573 return; // wait for shrinking amoeba
8575 else // element == EL_PACMAN
8577 Tile[newx][newy] = EL_EMPTY;
8578 TEST_DrawLevelField(newx, newy);
8579 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8582 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8583 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8584 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8586 // wait for shrinking amoeba to completely disappear
8589 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8591 // object was running against a wall
8595 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8596 DrawLevelElementAnimation(x, y, element);
8598 if (DONT_TOUCH(element))
8599 TestIfBadThingTouchesPlayer(x, y);
8604 InitMovingField(x, y, MovDir[x][y]);
8606 PlayLevelSoundAction(x, y, ACTION_MOVING);
8610 ContinueMoving(x, y);
8613 void ContinueMoving(int x, int y)
8615 int element = Tile[x][y];
8616 struct ElementInfo *ei = &element_info[element];
8617 int direction = MovDir[x][y];
8618 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8619 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8620 int newx = x + dx, newy = y + dy;
8621 int stored = Store[x][y];
8622 int stored_new = Store[newx][newy];
8623 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8624 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8625 boolean last_line = (newy == lev_fieldy - 1);
8626 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8628 if (pushed_by_player) // special case: moving object pushed by player
8630 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8632 else if (use_step_delay) // special case: moving object has step delay
8634 if (!MovDelay[x][y])
8635 MovPos[x][y] += getElementMoveStepsize(x, y);
8640 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8644 TEST_DrawLevelField(x, y);
8646 return; // element is still waiting
8649 else // normal case: generically moving object
8651 MovPos[x][y] += getElementMoveStepsize(x, y);
8654 if (ABS(MovPos[x][y]) < TILEX)
8656 TEST_DrawLevelField(x, y);
8658 return; // element is still moving
8661 // element reached destination field
8663 Tile[x][y] = EL_EMPTY;
8664 Tile[newx][newy] = element;
8665 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8667 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8669 element = Tile[newx][newy] = EL_ACID;
8671 else if (element == EL_MOLE)
8673 Tile[x][y] = EL_SAND;
8675 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8677 else if (element == EL_QUICKSAND_FILLING)
8679 element = Tile[newx][newy] = get_next_element(element);
8680 Store[newx][newy] = Store[x][y];
8682 else if (element == EL_QUICKSAND_EMPTYING)
8684 Tile[x][y] = get_next_element(element);
8685 element = Tile[newx][newy] = Store[x][y];
8687 else if (element == EL_QUICKSAND_FAST_FILLING)
8689 element = Tile[newx][newy] = get_next_element(element);
8690 Store[newx][newy] = Store[x][y];
8692 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8694 Tile[x][y] = get_next_element(element);
8695 element = Tile[newx][newy] = Store[x][y];
8697 else if (element == EL_MAGIC_WALL_FILLING)
8699 element = Tile[newx][newy] = get_next_element(element);
8700 if (!game.magic_wall_active)
8701 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8702 Store[newx][newy] = Store[x][y];
8704 else if (element == EL_MAGIC_WALL_EMPTYING)
8706 Tile[x][y] = get_next_element(element);
8707 if (!game.magic_wall_active)
8708 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8709 element = Tile[newx][newy] = Store[x][y];
8711 InitField(newx, newy, FALSE);
8713 else if (element == EL_BD_MAGIC_WALL_FILLING)
8715 element = Tile[newx][newy] = get_next_element(element);
8716 if (!game.magic_wall_active)
8717 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8718 Store[newx][newy] = Store[x][y];
8720 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8722 Tile[x][y] = get_next_element(element);
8723 if (!game.magic_wall_active)
8724 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8725 element = Tile[newx][newy] = Store[x][y];
8727 InitField(newx, newy, FALSE);
8729 else if (element == EL_DC_MAGIC_WALL_FILLING)
8731 element = Tile[newx][newy] = get_next_element(element);
8732 if (!game.magic_wall_active)
8733 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8734 Store[newx][newy] = Store[x][y];
8736 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8738 Tile[x][y] = get_next_element(element);
8739 if (!game.magic_wall_active)
8740 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8741 element = Tile[newx][newy] = Store[x][y];
8743 InitField(newx, newy, FALSE);
8745 else if (element == EL_AMOEBA_DROPPING)
8747 Tile[x][y] = get_next_element(element);
8748 element = Tile[newx][newy] = Store[x][y];
8750 else if (element == EL_SOKOBAN_OBJECT)
8753 Tile[x][y] = Back[x][y];
8755 if (Back[newx][newy])
8756 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8758 Back[x][y] = Back[newx][newy] = 0;
8761 Store[x][y] = EL_EMPTY;
8766 MovDelay[newx][newy] = 0;
8768 if (CAN_CHANGE_OR_HAS_ACTION(element))
8770 // copy element change control values to new field
8771 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8772 ChangePage[newx][newy] = ChangePage[x][y];
8773 ChangeCount[newx][newy] = ChangeCount[x][y];
8774 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8777 CustomValue[newx][newy] = CustomValue[x][y];
8779 ChangeDelay[x][y] = 0;
8780 ChangePage[x][y] = -1;
8781 ChangeCount[x][y] = 0;
8782 ChangeEvent[x][y] = -1;
8784 CustomValue[x][y] = 0;
8786 // copy animation control values to new field
8787 GfxFrame[newx][newy] = GfxFrame[x][y];
8788 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8789 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8790 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8792 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8794 // some elements can leave other elements behind after moving
8795 if (ei->move_leave_element != EL_EMPTY &&
8796 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8797 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8799 int move_leave_element = ei->move_leave_element;
8801 // this makes it possible to leave the removed element again
8802 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8803 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8805 Tile[x][y] = move_leave_element;
8807 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8808 MovDir[x][y] = direction;
8810 InitField(x, y, FALSE);
8812 if (GFX_CRUMBLED(Tile[x][y]))
8813 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8815 if (IS_PLAYER_ELEMENT(move_leave_element))
8816 RelocatePlayer(x, y, move_leave_element);
8819 // do this after checking for left-behind element
8820 ResetGfxAnimation(x, y); // reset animation values for old field
8822 if (!CAN_MOVE(element) ||
8823 (CAN_FALL(element) && direction == MV_DOWN &&
8824 (element == EL_SPRING ||
8825 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8826 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8827 GfxDir[x][y] = MovDir[newx][newy] = 0;
8829 TEST_DrawLevelField(x, y);
8830 TEST_DrawLevelField(newx, newy);
8832 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8834 // prevent pushed element from moving on in pushed direction
8835 if (pushed_by_player && CAN_MOVE(element) &&
8836 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8837 !(element_info[element].move_pattern & direction))
8838 TurnRound(newx, newy);
8840 // prevent elements on conveyor belt from moving on in last direction
8841 if (pushed_by_conveyor && CAN_FALL(element) &&
8842 direction & MV_HORIZONTAL)
8843 MovDir[newx][newy] = 0;
8845 if (!pushed_by_player)
8847 int nextx = newx + dx, nexty = newy + dy;
8848 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8850 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8852 if (CAN_FALL(element) && direction == MV_DOWN)
8853 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8855 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8856 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8858 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8859 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8862 if (DONT_TOUCH(element)) // object may be nasty to player or others
8864 TestIfBadThingTouchesPlayer(newx, newy);
8865 TestIfBadThingTouchesFriend(newx, newy);
8867 if (!IS_CUSTOM_ELEMENT(element))
8868 TestIfBadThingTouchesOtherBadThing(newx, newy);
8870 else if (element == EL_PENGUIN)
8871 TestIfFriendTouchesBadThing(newx, newy);
8873 if (DONT_GET_HIT_BY(element))
8875 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8878 // give the player one last chance (one more frame) to move away
8879 if (CAN_FALL(element) && direction == MV_DOWN &&
8880 (last_line || (!IS_FREE(x, newy + 1) &&
8881 (!IS_PLAYER(x, newy + 1) ||
8882 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8885 if (pushed_by_player && !game.use_change_when_pushing_bug)
8887 int push_side = MV_DIR_OPPOSITE(direction);
8888 struct PlayerInfo *player = PLAYERINFO(x, y);
8890 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8891 player->index_bit, push_side);
8892 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8893 player->index_bit, push_side);
8896 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8897 MovDelay[newx][newy] = 1;
8899 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8901 TestIfElementTouchesCustomElement(x, y); // empty or new element
8902 TestIfElementHitsCustomElement(newx, newy, direction);
8903 TestIfPlayerTouchesCustomElement(newx, newy);
8904 TestIfElementTouchesCustomElement(newx, newy);
8906 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8907 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8908 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8909 MV_DIR_OPPOSITE(direction));
8912 int AmoebaNeighbourNr(int ax, int ay)
8915 int element = Tile[ax][ay];
8917 static int xy[4][2] =
8925 for (i = 0; i < NUM_DIRECTIONS; i++)
8927 int x = ax + xy[i][0];
8928 int y = ay + xy[i][1];
8930 if (!IN_LEV_FIELD(x, y))
8933 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8934 group_nr = AmoebaNr[x][y];
8940 static void AmoebaMerge(int ax, int ay)
8942 int i, x, y, xx, yy;
8943 int new_group_nr = AmoebaNr[ax][ay];
8944 static int xy[4][2] =
8952 if (new_group_nr == 0)
8955 for (i = 0; i < NUM_DIRECTIONS; i++)
8960 if (!IN_LEV_FIELD(x, y))
8963 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8964 Tile[x][y] == EL_BD_AMOEBA ||
8965 Tile[x][y] == EL_AMOEBA_DEAD) &&
8966 AmoebaNr[x][y] != new_group_nr)
8968 int old_group_nr = AmoebaNr[x][y];
8970 if (old_group_nr == 0)
8973 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8974 AmoebaCnt[old_group_nr] = 0;
8975 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8976 AmoebaCnt2[old_group_nr] = 0;
8978 SCAN_PLAYFIELD(xx, yy)
8980 if (AmoebaNr[xx][yy] == old_group_nr)
8981 AmoebaNr[xx][yy] = new_group_nr;
8987 void AmoebaToDiamond(int ax, int ay)
8991 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8993 int group_nr = AmoebaNr[ax][ay];
8998 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8999 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9005 SCAN_PLAYFIELD(x, y)
9007 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9010 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9014 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9015 SND_AMOEBA_TURNING_TO_GEM :
9016 SND_AMOEBA_TURNING_TO_ROCK));
9021 static int xy[4][2] =
9029 for (i = 0; i < NUM_DIRECTIONS; i++)
9034 if (!IN_LEV_FIELD(x, y))
9037 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9039 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9040 SND_AMOEBA_TURNING_TO_GEM :
9041 SND_AMOEBA_TURNING_TO_ROCK));
9048 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9051 int group_nr = AmoebaNr[ax][ay];
9052 boolean done = FALSE;
9057 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9058 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9064 SCAN_PLAYFIELD(x, y)
9066 if (AmoebaNr[x][y] == group_nr &&
9067 (Tile[x][y] == EL_AMOEBA_DEAD ||
9068 Tile[x][y] == EL_BD_AMOEBA ||
9069 Tile[x][y] == EL_AMOEBA_GROWING))
9072 Tile[x][y] = new_element;
9073 InitField(x, y, FALSE);
9074 TEST_DrawLevelField(x, y);
9080 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9081 SND_BD_AMOEBA_TURNING_TO_ROCK :
9082 SND_BD_AMOEBA_TURNING_TO_GEM));
9085 static void AmoebaGrowing(int x, int y)
9087 static unsigned int sound_delay = 0;
9088 static unsigned int sound_delay_value = 0;
9090 if (!MovDelay[x][y]) // start new growing cycle
9094 if (DelayReached(&sound_delay, sound_delay_value))
9096 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9097 sound_delay_value = 30;
9101 if (MovDelay[x][y]) // wait some time before growing bigger
9104 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9106 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9107 6 - MovDelay[x][y]);
9109 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9112 if (!MovDelay[x][y])
9114 Tile[x][y] = Store[x][y];
9116 TEST_DrawLevelField(x, y);
9121 static void AmoebaShrinking(int x, int y)
9123 static unsigned int sound_delay = 0;
9124 static unsigned int sound_delay_value = 0;
9126 if (!MovDelay[x][y]) // start new shrinking cycle
9130 if (DelayReached(&sound_delay, sound_delay_value))
9131 sound_delay_value = 30;
9134 if (MovDelay[x][y]) // wait some time before shrinking
9137 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9139 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9140 6 - MovDelay[x][y]);
9142 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9145 if (!MovDelay[x][y])
9147 Tile[x][y] = EL_EMPTY;
9148 TEST_DrawLevelField(x, y);
9150 // don't let mole enter this field in this cycle;
9151 // (give priority to objects falling to this field from above)
9157 static void AmoebaReproduce(int ax, int ay)
9160 int element = Tile[ax][ay];
9161 int graphic = el2img(element);
9162 int newax = ax, neway = ay;
9163 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9164 static int xy[4][2] =
9172 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9174 Tile[ax][ay] = EL_AMOEBA_DEAD;
9175 TEST_DrawLevelField(ax, ay);
9179 if (IS_ANIMATED(graphic))
9180 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9182 if (!MovDelay[ax][ay]) // start making new amoeba field
9183 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9185 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9188 if (MovDelay[ax][ay])
9192 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9195 int x = ax + xy[start][0];
9196 int y = ay + xy[start][1];
9198 if (!IN_LEV_FIELD(x, y))
9201 if (IS_FREE(x, y) ||
9202 CAN_GROW_INTO(Tile[x][y]) ||
9203 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9204 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9210 if (newax == ax && neway == ay)
9213 else // normal or "filled" (BD style) amoeba
9216 boolean waiting_for_player = FALSE;
9218 for (i = 0; i < NUM_DIRECTIONS; i++)
9220 int j = (start + i) % 4;
9221 int x = ax + xy[j][0];
9222 int y = ay + xy[j][1];
9224 if (!IN_LEV_FIELD(x, y))
9227 if (IS_FREE(x, y) ||
9228 CAN_GROW_INTO(Tile[x][y]) ||
9229 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9230 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9236 else if (IS_PLAYER(x, y))
9237 waiting_for_player = TRUE;
9240 if (newax == ax && neway == ay) // amoeba cannot grow
9242 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9244 Tile[ax][ay] = EL_AMOEBA_DEAD;
9245 TEST_DrawLevelField(ax, ay);
9246 AmoebaCnt[AmoebaNr[ax][ay]]--;
9248 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9250 if (element == EL_AMOEBA_FULL)
9251 AmoebaToDiamond(ax, ay);
9252 else if (element == EL_BD_AMOEBA)
9253 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9258 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9260 // amoeba gets larger by growing in some direction
9262 int new_group_nr = AmoebaNr[ax][ay];
9265 if (new_group_nr == 0)
9267 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9269 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9275 AmoebaNr[newax][neway] = new_group_nr;
9276 AmoebaCnt[new_group_nr]++;
9277 AmoebaCnt2[new_group_nr]++;
9279 // if amoeba touches other amoeba(s) after growing, unify them
9280 AmoebaMerge(newax, neway);
9282 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9284 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9290 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9291 (neway == lev_fieldy - 1 && newax != ax))
9293 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9294 Store[newax][neway] = element;
9296 else if (neway == ay || element == EL_EMC_DRIPPER)
9298 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9300 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9304 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9305 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9306 Store[ax][ay] = EL_AMOEBA_DROP;
9307 ContinueMoving(ax, ay);
9311 TEST_DrawLevelField(newax, neway);
9314 static void Life(int ax, int ay)
9318 int element = Tile[ax][ay];
9319 int graphic = el2img(element);
9320 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9322 boolean changed = FALSE;
9324 if (IS_ANIMATED(graphic))
9325 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9330 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9331 MovDelay[ax][ay] = life_time;
9333 if (MovDelay[ax][ay]) // wait some time before next cycle
9336 if (MovDelay[ax][ay])
9340 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9342 int xx = ax+x1, yy = ay+y1;
9343 int old_element = Tile[xx][yy];
9344 int num_neighbours = 0;
9346 if (!IN_LEV_FIELD(xx, yy))
9349 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9351 int x = xx+x2, y = yy+y2;
9353 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9356 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9357 boolean is_neighbour = FALSE;
9359 if (level.use_life_bugs)
9361 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9362 (IS_FREE(x, y) && Stop[x][y]));
9365 (Last[x][y] == element || is_player_cell);
9371 boolean is_free = FALSE;
9373 if (level.use_life_bugs)
9374 is_free = (IS_FREE(xx, yy));
9376 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9378 if (xx == ax && yy == ay) // field in the middle
9380 if (num_neighbours < life_parameter[0] ||
9381 num_neighbours > life_parameter[1])
9383 Tile[xx][yy] = EL_EMPTY;
9384 if (Tile[xx][yy] != old_element)
9385 TEST_DrawLevelField(xx, yy);
9386 Stop[xx][yy] = TRUE;
9390 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9391 { // free border field
9392 if (num_neighbours >= life_parameter[2] &&
9393 num_neighbours <= life_parameter[3])
9395 Tile[xx][yy] = element;
9396 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9397 if (Tile[xx][yy] != old_element)
9398 TEST_DrawLevelField(xx, yy);
9399 Stop[xx][yy] = TRUE;
9406 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9407 SND_GAME_OF_LIFE_GROWING);
9410 static void InitRobotWheel(int x, int y)
9412 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9415 static void RunRobotWheel(int x, int y)
9417 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9420 static void StopRobotWheel(int x, int y)
9422 if (game.robot_wheel_x == x &&
9423 game.robot_wheel_y == y)
9425 game.robot_wheel_x = -1;
9426 game.robot_wheel_y = -1;
9427 game.robot_wheel_active = FALSE;
9431 static void InitTimegateWheel(int x, int y)
9433 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9436 static void RunTimegateWheel(int x, int y)
9438 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9441 static void InitMagicBallDelay(int x, int y)
9443 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9446 static void ActivateMagicBall(int bx, int by)
9450 if (level.ball_random)
9452 int pos_border = RND(8); // select one of the eight border elements
9453 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9454 int xx = pos_content % 3;
9455 int yy = pos_content / 3;
9460 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9461 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9465 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9467 int xx = x - bx + 1;
9468 int yy = y - by + 1;
9470 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9471 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9475 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9478 static void CheckExit(int x, int y)
9480 if (game.gems_still_needed > 0 ||
9481 game.sokoban_fields_still_needed > 0 ||
9482 game.sokoban_objects_still_needed > 0 ||
9483 game.lights_still_needed > 0)
9485 int element = Tile[x][y];
9486 int graphic = el2img(element);
9488 if (IS_ANIMATED(graphic))
9489 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9494 // do not re-open exit door closed after last player
9495 if (game.all_players_gone)
9498 Tile[x][y] = EL_EXIT_OPENING;
9500 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9503 static void CheckExitEM(int x, int y)
9505 if (game.gems_still_needed > 0 ||
9506 game.sokoban_fields_still_needed > 0 ||
9507 game.sokoban_objects_still_needed > 0 ||
9508 game.lights_still_needed > 0)
9510 int element = Tile[x][y];
9511 int graphic = el2img(element);
9513 if (IS_ANIMATED(graphic))
9514 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9519 // do not re-open exit door closed after last player
9520 if (game.all_players_gone)
9523 Tile[x][y] = EL_EM_EXIT_OPENING;
9525 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9528 static void CheckExitSteel(int x, int y)
9530 if (game.gems_still_needed > 0 ||
9531 game.sokoban_fields_still_needed > 0 ||
9532 game.sokoban_objects_still_needed > 0 ||
9533 game.lights_still_needed > 0)
9535 int element = Tile[x][y];
9536 int graphic = el2img(element);
9538 if (IS_ANIMATED(graphic))
9539 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9544 // do not re-open exit door closed after last player
9545 if (game.all_players_gone)
9548 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9550 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9553 static void CheckExitSteelEM(int x, int y)
9555 if (game.gems_still_needed > 0 ||
9556 game.sokoban_fields_still_needed > 0 ||
9557 game.sokoban_objects_still_needed > 0 ||
9558 game.lights_still_needed > 0)
9560 int element = Tile[x][y];
9561 int graphic = el2img(element);
9563 if (IS_ANIMATED(graphic))
9564 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9569 // do not re-open exit door closed after last player
9570 if (game.all_players_gone)
9573 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9575 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9578 static void CheckExitSP(int x, int y)
9580 if (game.gems_still_needed > 0)
9582 int element = Tile[x][y];
9583 int graphic = el2img(element);
9585 if (IS_ANIMATED(graphic))
9586 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9591 // do not re-open exit door closed after last player
9592 if (game.all_players_gone)
9595 Tile[x][y] = EL_SP_EXIT_OPENING;
9597 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9600 static void CloseAllOpenTimegates(void)
9604 SCAN_PLAYFIELD(x, y)
9606 int element = Tile[x][y];
9608 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9610 Tile[x][y] = EL_TIMEGATE_CLOSING;
9612 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9617 static void DrawTwinkleOnField(int x, int y)
9619 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9622 if (Tile[x][y] == EL_BD_DIAMOND)
9625 if (MovDelay[x][y] == 0) // next animation frame
9626 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9628 if (MovDelay[x][y] != 0) // wait some time before next frame
9632 DrawLevelElementAnimation(x, y, Tile[x][y]);
9634 if (MovDelay[x][y] != 0)
9636 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9637 10 - MovDelay[x][y]);
9639 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9644 static void MauerWaechst(int x, int y)
9648 if (!MovDelay[x][y]) // next animation frame
9649 MovDelay[x][y] = 3 * delay;
9651 if (MovDelay[x][y]) // wait some time before next frame
9655 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9657 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9658 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9660 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9663 if (!MovDelay[x][y])
9665 if (MovDir[x][y] == MV_LEFT)
9667 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9668 TEST_DrawLevelField(x - 1, y);
9670 else if (MovDir[x][y] == MV_RIGHT)
9672 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9673 TEST_DrawLevelField(x + 1, y);
9675 else if (MovDir[x][y] == MV_UP)
9677 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9678 TEST_DrawLevelField(x, y - 1);
9682 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9683 TEST_DrawLevelField(x, y + 1);
9686 Tile[x][y] = Store[x][y];
9688 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9689 TEST_DrawLevelField(x, y);
9694 static void MauerAbleger(int ax, int ay)
9696 int element = Tile[ax][ay];
9697 int graphic = el2img(element);
9698 boolean oben_frei = FALSE, unten_frei = FALSE;
9699 boolean links_frei = FALSE, rechts_frei = FALSE;
9700 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9701 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9702 boolean new_wall = FALSE;
9704 if (IS_ANIMATED(graphic))
9705 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9707 if (!MovDelay[ax][ay]) // start building new wall
9708 MovDelay[ax][ay] = 6;
9710 if (MovDelay[ax][ay]) // wait some time before building new wall
9713 if (MovDelay[ax][ay])
9717 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9719 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9721 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9723 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9726 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9727 element == EL_EXPANDABLE_WALL_ANY)
9731 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9732 Store[ax][ay-1] = element;
9733 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9734 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9735 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9736 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9741 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9742 Store[ax][ay+1] = element;
9743 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9744 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9745 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9746 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9751 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9752 element == EL_EXPANDABLE_WALL_ANY ||
9753 element == EL_EXPANDABLE_WALL ||
9754 element == EL_BD_EXPANDABLE_WALL)
9758 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9759 Store[ax-1][ay] = element;
9760 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9761 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9762 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9763 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9769 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9770 Store[ax+1][ay] = element;
9771 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9772 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9773 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9774 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9779 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9780 TEST_DrawLevelField(ax, ay);
9782 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9784 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9785 unten_massiv = TRUE;
9786 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9787 links_massiv = TRUE;
9788 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9789 rechts_massiv = TRUE;
9791 if (((oben_massiv && unten_massiv) ||
9792 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9793 element == EL_EXPANDABLE_WALL) &&
9794 ((links_massiv && rechts_massiv) ||
9795 element == EL_EXPANDABLE_WALL_VERTICAL))
9796 Tile[ax][ay] = EL_WALL;
9799 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9802 static void MauerAblegerStahl(int ax, int ay)
9804 int element = Tile[ax][ay];
9805 int graphic = el2img(element);
9806 boolean oben_frei = FALSE, unten_frei = FALSE;
9807 boolean links_frei = FALSE, rechts_frei = FALSE;
9808 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9809 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9810 boolean new_wall = FALSE;
9812 if (IS_ANIMATED(graphic))
9813 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9815 if (!MovDelay[ax][ay]) // start building new wall
9816 MovDelay[ax][ay] = 6;
9818 if (MovDelay[ax][ay]) // wait some time before building new wall
9821 if (MovDelay[ax][ay])
9825 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9827 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9829 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9831 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9834 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9835 element == EL_EXPANDABLE_STEELWALL_ANY)
9839 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9840 Store[ax][ay-1] = element;
9841 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9842 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9843 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9844 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9849 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9850 Store[ax][ay+1] = element;
9851 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9852 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9853 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9854 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9859 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9860 element == EL_EXPANDABLE_STEELWALL_ANY)
9864 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9865 Store[ax-1][ay] = element;
9866 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9867 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9868 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9869 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9875 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9876 Store[ax+1][ay] = element;
9877 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9878 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9879 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9880 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9885 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9887 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9888 unten_massiv = TRUE;
9889 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9890 links_massiv = TRUE;
9891 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9892 rechts_massiv = TRUE;
9894 if (((oben_massiv && unten_massiv) ||
9895 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9896 ((links_massiv && rechts_massiv) ||
9897 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9898 Tile[ax][ay] = EL_STEELWALL;
9901 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9904 static void CheckForDragon(int x, int y)
9907 boolean dragon_found = FALSE;
9908 static int xy[4][2] =
9916 for (i = 0; i < NUM_DIRECTIONS; i++)
9918 for (j = 0; j < 4; j++)
9920 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9922 if (IN_LEV_FIELD(xx, yy) &&
9923 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9925 if (Tile[xx][yy] == EL_DRAGON)
9926 dragon_found = TRUE;
9935 for (i = 0; i < NUM_DIRECTIONS; i++)
9937 for (j = 0; j < 3; j++)
9939 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9941 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9943 Tile[xx][yy] = EL_EMPTY;
9944 TEST_DrawLevelField(xx, yy);
9953 static void InitBuggyBase(int x, int y)
9955 int element = Tile[x][y];
9956 int activating_delay = FRAMES_PER_SECOND / 4;
9959 (element == EL_SP_BUGGY_BASE ?
9960 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9961 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9963 element == EL_SP_BUGGY_BASE_ACTIVE ?
9964 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9967 static void WarnBuggyBase(int x, int y)
9970 static int xy[4][2] =
9978 for (i = 0; i < NUM_DIRECTIONS; i++)
9980 int xx = x + xy[i][0];
9981 int yy = y + xy[i][1];
9983 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9985 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9992 static void InitTrap(int x, int y)
9994 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9997 static void ActivateTrap(int x, int y)
9999 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10002 static void ChangeActiveTrap(int x, int y)
10004 int graphic = IMG_TRAP_ACTIVE;
10006 // if new animation frame was drawn, correct crumbled sand border
10007 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10008 TEST_DrawLevelFieldCrumbled(x, y);
10011 static int getSpecialActionElement(int element, int number, int base_element)
10013 return (element != EL_EMPTY ? element :
10014 number != -1 ? base_element + number - 1 :
10018 static int getModifiedActionNumber(int value_old, int operator, int operand,
10019 int value_min, int value_max)
10021 int value_new = (operator == CA_MODE_SET ? operand :
10022 operator == CA_MODE_ADD ? value_old + operand :
10023 operator == CA_MODE_SUBTRACT ? value_old - operand :
10024 operator == CA_MODE_MULTIPLY ? value_old * operand :
10025 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10026 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10029 return (value_new < value_min ? value_min :
10030 value_new > value_max ? value_max :
10034 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10036 struct ElementInfo *ei = &element_info[element];
10037 struct ElementChangeInfo *change = &ei->change_page[page];
10038 int target_element = change->target_element;
10039 int action_type = change->action_type;
10040 int action_mode = change->action_mode;
10041 int action_arg = change->action_arg;
10042 int action_element = change->action_element;
10045 if (!change->has_action)
10048 // ---------- determine action paramater values -----------------------------
10050 int level_time_value =
10051 (level.time > 0 ? TimeLeft :
10054 int action_arg_element_raw =
10055 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10056 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10057 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10058 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10059 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10060 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10061 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10063 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10065 int action_arg_direction =
10066 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10067 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10068 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10069 change->actual_trigger_side :
10070 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10071 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10074 int action_arg_number_min =
10075 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10078 int action_arg_number_max =
10079 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10080 action_type == CA_SET_LEVEL_GEMS ? 999 :
10081 action_type == CA_SET_LEVEL_TIME ? 9999 :
10082 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10083 action_type == CA_SET_CE_VALUE ? 9999 :
10084 action_type == CA_SET_CE_SCORE ? 9999 :
10087 int action_arg_number_reset =
10088 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10089 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10090 action_type == CA_SET_LEVEL_TIME ? level.time :
10091 action_type == CA_SET_LEVEL_SCORE ? 0 :
10092 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10093 action_type == CA_SET_CE_SCORE ? 0 :
10096 int action_arg_number =
10097 (action_arg <= CA_ARG_MAX ? action_arg :
10098 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10099 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10100 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10101 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10102 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10103 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10104 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10105 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10106 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10107 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10108 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10109 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10110 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10111 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10112 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10113 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10114 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10115 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10116 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10117 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10118 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10121 int action_arg_number_old =
10122 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10123 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10124 action_type == CA_SET_LEVEL_SCORE ? game.score :
10125 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10126 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10129 int action_arg_number_new =
10130 getModifiedActionNumber(action_arg_number_old,
10131 action_mode, action_arg_number,
10132 action_arg_number_min, action_arg_number_max);
10134 int trigger_player_bits =
10135 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10136 change->actual_trigger_player_bits : change->trigger_player);
10138 int action_arg_player_bits =
10139 (action_arg >= CA_ARG_PLAYER_1 &&
10140 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10141 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10142 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10145 // ---------- execute action -----------------------------------------------
10147 switch (action_type)
10154 // ---------- level actions ----------------------------------------------
10156 case CA_RESTART_LEVEL:
10158 game.restart_level = TRUE;
10163 case CA_SHOW_ENVELOPE:
10165 int element = getSpecialActionElement(action_arg_element,
10166 action_arg_number, EL_ENVELOPE_1);
10168 if (IS_ENVELOPE(element))
10169 local_player->show_envelope = element;
10174 case CA_SET_LEVEL_TIME:
10176 if (level.time > 0) // only modify limited time value
10178 TimeLeft = action_arg_number_new;
10180 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10182 DisplayGameControlValues();
10184 if (!TimeLeft && setup.time_limit)
10185 for (i = 0; i < MAX_PLAYERS; i++)
10186 KillPlayer(&stored_player[i]);
10192 case CA_SET_LEVEL_SCORE:
10194 game.score = action_arg_number_new;
10196 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10198 DisplayGameControlValues();
10203 case CA_SET_LEVEL_GEMS:
10205 game.gems_still_needed = action_arg_number_new;
10207 game.snapshot.collected_item = TRUE;
10209 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10211 DisplayGameControlValues();
10216 case CA_SET_LEVEL_WIND:
10218 game.wind_direction = action_arg_direction;
10223 case CA_SET_LEVEL_RANDOM_SEED:
10225 // ensure that setting a new random seed while playing is predictable
10226 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10231 // ---------- player actions ---------------------------------------------
10233 case CA_MOVE_PLAYER:
10234 case CA_MOVE_PLAYER_NEW:
10236 // automatically move to the next field in specified direction
10237 for (i = 0; i < MAX_PLAYERS; i++)
10238 if (trigger_player_bits & (1 << i))
10239 if (action_type == CA_MOVE_PLAYER ||
10240 stored_player[i].MovPos == 0)
10241 stored_player[i].programmed_action = action_arg_direction;
10246 case CA_EXIT_PLAYER:
10248 for (i = 0; i < MAX_PLAYERS; i++)
10249 if (action_arg_player_bits & (1 << i))
10250 ExitPlayer(&stored_player[i]);
10252 if (game.players_still_needed == 0)
10258 case CA_KILL_PLAYER:
10260 for (i = 0; i < MAX_PLAYERS; i++)
10261 if (action_arg_player_bits & (1 << i))
10262 KillPlayer(&stored_player[i]);
10267 case CA_SET_PLAYER_KEYS:
10269 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10270 int element = getSpecialActionElement(action_arg_element,
10271 action_arg_number, EL_KEY_1);
10273 if (IS_KEY(element))
10275 for (i = 0; i < MAX_PLAYERS; i++)
10277 if (trigger_player_bits & (1 << i))
10279 stored_player[i].key[KEY_NR(element)] = key_state;
10281 DrawGameDoorValues();
10289 case CA_SET_PLAYER_SPEED:
10291 for (i = 0; i < MAX_PLAYERS; i++)
10293 if (trigger_player_bits & (1 << i))
10295 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10297 if (action_arg == CA_ARG_SPEED_FASTER &&
10298 stored_player[i].cannot_move)
10300 action_arg_number = STEPSIZE_VERY_SLOW;
10302 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10303 action_arg == CA_ARG_SPEED_FASTER)
10305 action_arg_number = 2;
10306 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10309 else if (action_arg == CA_ARG_NUMBER_RESET)
10311 action_arg_number = level.initial_player_stepsize[i];
10315 getModifiedActionNumber(move_stepsize,
10318 action_arg_number_min,
10319 action_arg_number_max);
10321 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10328 case CA_SET_PLAYER_SHIELD:
10330 for (i = 0; i < MAX_PLAYERS; i++)
10332 if (trigger_player_bits & (1 << i))
10334 if (action_arg == CA_ARG_SHIELD_OFF)
10336 stored_player[i].shield_normal_time_left = 0;
10337 stored_player[i].shield_deadly_time_left = 0;
10339 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10341 stored_player[i].shield_normal_time_left = 999999;
10343 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10345 stored_player[i].shield_normal_time_left = 999999;
10346 stored_player[i].shield_deadly_time_left = 999999;
10354 case CA_SET_PLAYER_GRAVITY:
10356 for (i = 0; i < MAX_PLAYERS; i++)
10358 if (trigger_player_bits & (1 << i))
10360 stored_player[i].gravity =
10361 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10362 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10363 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10364 stored_player[i].gravity);
10371 case CA_SET_PLAYER_ARTWORK:
10373 for (i = 0; i < MAX_PLAYERS; i++)
10375 if (trigger_player_bits & (1 << i))
10377 int artwork_element = action_arg_element;
10379 if (action_arg == CA_ARG_ELEMENT_RESET)
10381 (level.use_artwork_element[i] ? level.artwork_element[i] :
10382 stored_player[i].element_nr);
10384 if (stored_player[i].artwork_element != artwork_element)
10385 stored_player[i].Frame = 0;
10387 stored_player[i].artwork_element = artwork_element;
10389 SetPlayerWaiting(&stored_player[i], FALSE);
10391 // set number of special actions for bored and sleeping animation
10392 stored_player[i].num_special_action_bored =
10393 get_num_special_action(artwork_element,
10394 ACTION_BORING_1, ACTION_BORING_LAST);
10395 stored_player[i].num_special_action_sleeping =
10396 get_num_special_action(artwork_element,
10397 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10404 case CA_SET_PLAYER_INVENTORY:
10406 for (i = 0; i < MAX_PLAYERS; i++)
10408 struct PlayerInfo *player = &stored_player[i];
10411 if (trigger_player_bits & (1 << i))
10413 int inventory_element = action_arg_element;
10415 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10416 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10417 action_arg == CA_ARG_ELEMENT_ACTION)
10419 int element = inventory_element;
10420 int collect_count = element_info[element].collect_count_initial;
10422 if (!IS_CUSTOM_ELEMENT(element))
10425 if (collect_count == 0)
10426 player->inventory_infinite_element = element;
10428 for (k = 0; k < collect_count; k++)
10429 if (player->inventory_size < MAX_INVENTORY_SIZE)
10430 player->inventory_element[player->inventory_size++] =
10433 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10434 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10435 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10437 if (player->inventory_infinite_element != EL_UNDEFINED &&
10438 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10439 action_arg_element_raw))
10440 player->inventory_infinite_element = EL_UNDEFINED;
10442 for (k = 0, j = 0; j < player->inventory_size; j++)
10444 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10445 action_arg_element_raw))
10446 player->inventory_element[k++] = player->inventory_element[j];
10449 player->inventory_size = k;
10451 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10453 if (player->inventory_size > 0)
10455 for (j = 0; j < player->inventory_size - 1; j++)
10456 player->inventory_element[j] = player->inventory_element[j + 1];
10458 player->inventory_size--;
10461 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10463 if (player->inventory_size > 0)
10464 player->inventory_size--;
10466 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10468 player->inventory_infinite_element = EL_UNDEFINED;
10469 player->inventory_size = 0;
10471 else if (action_arg == CA_ARG_INVENTORY_RESET)
10473 player->inventory_infinite_element = EL_UNDEFINED;
10474 player->inventory_size = 0;
10476 if (level.use_initial_inventory[i])
10478 for (j = 0; j < level.initial_inventory_size[i]; j++)
10480 int element = level.initial_inventory_content[i][j];
10481 int collect_count = element_info[element].collect_count_initial;
10483 if (!IS_CUSTOM_ELEMENT(element))
10486 if (collect_count == 0)
10487 player->inventory_infinite_element = element;
10489 for (k = 0; k < collect_count; k++)
10490 if (player->inventory_size < MAX_INVENTORY_SIZE)
10491 player->inventory_element[player->inventory_size++] =
10502 // ---------- CE actions -------------------------------------------------
10504 case CA_SET_CE_VALUE:
10506 int last_ce_value = CustomValue[x][y];
10508 CustomValue[x][y] = action_arg_number_new;
10510 if (CustomValue[x][y] != last_ce_value)
10512 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10513 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10515 if (CustomValue[x][y] == 0)
10517 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10518 ChangeCount[x][y] = 0; // allow at least one more change
10520 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10521 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10528 case CA_SET_CE_SCORE:
10530 int last_ce_score = ei->collect_score;
10532 ei->collect_score = action_arg_number_new;
10534 if (ei->collect_score != last_ce_score)
10536 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10537 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10539 if (ei->collect_score == 0)
10543 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10544 ChangeCount[x][y] = 0; // allow at least one more change
10546 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10547 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10550 This is a very special case that seems to be a mixture between
10551 CheckElementChange() and CheckTriggeredElementChange(): while
10552 the first one only affects single elements that are triggered
10553 directly, the second one affects multiple elements in the playfield
10554 that are triggered indirectly by another element. This is a third
10555 case: Changing the CE score always affects multiple identical CEs,
10556 so every affected CE must be checked, not only the single CE for
10557 which the CE score was changed in the first place (as every instance
10558 of that CE shares the same CE score, and therefore also can change)!
10560 SCAN_PLAYFIELD(xx, yy)
10562 if (Tile[xx][yy] == element)
10563 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10564 CE_SCORE_GETS_ZERO);
10572 case CA_SET_CE_ARTWORK:
10574 int artwork_element = action_arg_element;
10575 boolean reset_frame = FALSE;
10578 if (action_arg == CA_ARG_ELEMENT_RESET)
10579 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10582 if (ei->gfx_element != artwork_element)
10583 reset_frame = TRUE;
10585 ei->gfx_element = artwork_element;
10587 SCAN_PLAYFIELD(xx, yy)
10589 if (Tile[xx][yy] == element)
10593 ResetGfxAnimation(xx, yy);
10594 ResetRandomAnimationValue(xx, yy);
10597 TEST_DrawLevelField(xx, yy);
10604 // ---------- engine actions ---------------------------------------------
10606 case CA_SET_ENGINE_SCAN_MODE:
10608 InitPlayfieldScanMode(action_arg);
10618 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10620 int old_element = Tile[x][y];
10621 int new_element = GetElementFromGroupElement(element);
10622 int previous_move_direction = MovDir[x][y];
10623 int last_ce_value = CustomValue[x][y];
10624 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10625 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10626 boolean add_player_onto_element = (new_element_is_player &&
10627 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10628 IS_WALKABLE(old_element));
10630 if (!add_player_onto_element)
10632 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10633 RemoveMovingField(x, y);
10637 Tile[x][y] = new_element;
10639 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10640 MovDir[x][y] = previous_move_direction;
10642 if (element_info[new_element].use_last_ce_value)
10643 CustomValue[x][y] = last_ce_value;
10645 InitField_WithBug1(x, y, FALSE);
10647 new_element = Tile[x][y]; // element may have changed
10649 ResetGfxAnimation(x, y);
10650 ResetRandomAnimationValue(x, y);
10652 TEST_DrawLevelField(x, y);
10654 if (GFX_CRUMBLED(new_element))
10655 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10658 // check if element under the player changes from accessible to unaccessible
10659 // (needed for special case of dropping element which then changes)
10660 // (must be checked after creating new element for walkable group elements)
10661 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10662 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10669 // "ChangeCount" not set yet to allow "entered by player" change one time
10670 if (new_element_is_player)
10671 RelocatePlayer(x, y, new_element);
10674 ChangeCount[x][y]++; // count number of changes in the same frame
10676 TestIfBadThingTouchesPlayer(x, y);
10677 TestIfPlayerTouchesCustomElement(x, y);
10678 TestIfElementTouchesCustomElement(x, y);
10681 static void CreateField(int x, int y, int element)
10683 CreateFieldExt(x, y, element, FALSE);
10686 static void CreateElementFromChange(int x, int y, int element)
10688 element = GET_VALID_RUNTIME_ELEMENT(element);
10690 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10692 int old_element = Tile[x][y];
10694 // prevent changed element from moving in same engine frame
10695 // unless both old and new element can either fall or move
10696 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10697 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10701 CreateFieldExt(x, y, element, TRUE);
10704 static boolean ChangeElement(int x, int y, int element, int page)
10706 struct ElementInfo *ei = &element_info[element];
10707 struct ElementChangeInfo *change = &ei->change_page[page];
10708 int ce_value = CustomValue[x][y];
10709 int ce_score = ei->collect_score;
10710 int target_element;
10711 int old_element = Tile[x][y];
10713 // always use default change event to prevent running into a loop
10714 if (ChangeEvent[x][y] == -1)
10715 ChangeEvent[x][y] = CE_DELAY;
10717 if (ChangeEvent[x][y] == CE_DELAY)
10719 // reset actual trigger element, trigger player and action element
10720 change->actual_trigger_element = EL_EMPTY;
10721 change->actual_trigger_player = EL_EMPTY;
10722 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10723 change->actual_trigger_side = CH_SIDE_NONE;
10724 change->actual_trigger_ce_value = 0;
10725 change->actual_trigger_ce_score = 0;
10728 // do not change elements more than a specified maximum number of changes
10729 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10732 ChangeCount[x][y]++; // count number of changes in the same frame
10734 if (change->explode)
10741 if (change->use_target_content)
10743 boolean complete_replace = TRUE;
10744 boolean can_replace[3][3];
10747 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10750 boolean is_walkable;
10751 boolean is_diggable;
10752 boolean is_collectible;
10753 boolean is_removable;
10754 boolean is_destructible;
10755 int ex = x + xx - 1;
10756 int ey = y + yy - 1;
10757 int content_element = change->target_content.e[xx][yy];
10760 can_replace[xx][yy] = TRUE;
10762 if (ex == x && ey == y) // do not check changing element itself
10765 if (content_element == EL_EMPTY_SPACE)
10767 can_replace[xx][yy] = FALSE; // do not replace border with space
10772 if (!IN_LEV_FIELD(ex, ey))
10774 can_replace[xx][yy] = FALSE;
10775 complete_replace = FALSE;
10782 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10783 e = MovingOrBlocked2Element(ex, ey);
10785 is_empty = (IS_FREE(ex, ey) ||
10786 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10788 is_walkable = (is_empty || IS_WALKABLE(e));
10789 is_diggable = (is_empty || IS_DIGGABLE(e));
10790 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10791 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10792 is_removable = (is_diggable || is_collectible);
10794 can_replace[xx][yy] =
10795 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10796 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10797 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10798 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10799 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10800 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10801 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10803 if (!can_replace[xx][yy])
10804 complete_replace = FALSE;
10807 if (!change->only_if_complete || complete_replace)
10809 boolean something_has_changed = FALSE;
10811 if (change->only_if_complete && change->use_random_replace &&
10812 RND(100) < change->random_percentage)
10815 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10817 int ex = x + xx - 1;
10818 int ey = y + yy - 1;
10819 int content_element;
10821 if (can_replace[xx][yy] && (!change->use_random_replace ||
10822 RND(100) < change->random_percentage))
10824 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10825 RemoveMovingField(ex, ey);
10827 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10829 content_element = change->target_content.e[xx][yy];
10830 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10831 ce_value, ce_score);
10833 CreateElementFromChange(ex, ey, target_element);
10835 something_has_changed = TRUE;
10837 // for symmetry reasons, freeze newly created border elements
10838 if (ex != x || ey != y)
10839 Stop[ex][ey] = TRUE; // no more moving in this frame
10843 if (something_has_changed)
10845 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10846 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10852 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10853 ce_value, ce_score);
10855 if (element == EL_DIAGONAL_GROWING ||
10856 element == EL_DIAGONAL_SHRINKING)
10858 target_element = Store[x][y];
10860 Store[x][y] = EL_EMPTY;
10863 // special case: element changes to player (and may be kept if walkable)
10864 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10865 CreateElementFromChange(x, y, EL_EMPTY);
10867 CreateElementFromChange(x, y, target_element);
10869 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10870 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10873 // this uses direct change before indirect change
10874 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10879 static void HandleElementChange(int x, int y, int page)
10881 int element = MovingOrBlocked2Element(x, y);
10882 struct ElementInfo *ei = &element_info[element];
10883 struct ElementChangeInfo *change = &ei->change_page[page];
10884 boolean handle_action_before_change = FALSE;
10887 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10888 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10890 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10891 x, y, element, element_info[element].token_name);
10892 Debug("game:playing:HandleElementChange", "This should never happen!");
10896 // this can happen with classic bombs on walkable, changing elements
10897 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10902 if (ChangeDelay[x][y] == 0) // initialize element change
10904 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10906 if (change->can_change)
10908 // !!! not clear why graphic animation should be reset at all here !!!
10909 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10910 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10913 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10915 When using an animation frame delay of 1 (this only happens with
10916 "sp_zonk.moving.left/right" in the classic graphics), the default
10917 (non-moving) animation shows wrong animation frames (while the
10918 moving animation, like "sp_zonk.moving.left/right", is correct,
10919 so this graphical bug never shows up with the classic graphics).
10920 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10921 be drawn instead of the correct frames 0,1,2,3. This is caused by
10922 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10923 an element change: First when the change delay ("ChangeDelay[][]")
10924 counter has reached zero after decrementing, then a second time in
10925 the next frame (after "GfxFrame[][]" was already incremented) when
10926 "ChangeDelay[][]" is reset to the initial delay value again.
10928 This causes frame 0 to be drawn twice, while the last frame won't
10929 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10931 As some animations may already be cleverly designed around this bug
10932 (at least the "Snake Bite" snake tail animation does this), it cannot
10933 simply be fixed here without breaking such existing animations.
10934 Unfortunately, it cannot easily be detected if a graphics set was
10935 designed "before" or "after" the bug was fixed. As a workaround,
10936 a new graphics set option "game.graphics_engine_version" was added
10937 to be able to specify the game's major release version for which the
10938 graphics set was designed, which can then be used to decide if the
10939 bugfix should be used (version 4 and above) or not (version 3 or
10940 below, or if no version was specified at all, as with old sets).
10942 (The wrong/fixed animation frames can be tested with the test level set
10943 "test_gfxframe" and level "000", which contains a specially prepared
10944 custom element at level position (x/y) == (11/9) which uses the zonk
10945 animation mentioned above. Using "game.graphics_engine_version: 4"
10946 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10947 This can also be seen from the debug output for this test element.)
10950 // when a custom element is about to change (for example by change delay),
10951 // do not reset graphic animation when the custom element is moving
10952 if (game.graphics_engine_version < 4 &&
10955 ResetGfxAnimation(x, y);
10956 ResetRandomAnimationValue(x, y);
10959 if (change->pre_change_function)
10960 change->pre_change_function(x, y);
10964 ChangeDelay[x][y]--;
10966 if (ChangeDelay[x][y] != 0) // continue element change
10968 if (change->can_change)
10970 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10972 if (IS_ANIMATED(graphic))
10973 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10975 if (change->change_function)
10976 change->change_function(x, y);
10979 else // finish element change
10981 if (ChangePage[x][y] != -1) // remember page from delayed change
10983 page = ChangePage[x][y];
10984 ChangePage[x][y] = -1;
10986 change = &ei->change_page[page];
10989 if (IS_MOVING(x, y)) // never change a running system ;-)
10991 ChangeDelay[x][y] = 1; // try change after next move step
10992 ChangePage[x][y] = page; // remember page to use for change
10997 // special case: set new level random seed before changing element
10998 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10999 handle_action_before_change = TRUE;
11001 if (change->has_action && handle_action_before_change)
11002 ExecuteCustomElementAction(x, y, element, page);
11004 if (change->can_change)
11006 if (ChangeElement(x, y, element, page))
11008 if (change->post_change_function)
11009 change->post_change_function(x, y);
11013 if (change->has_action && !handle_action_before_change)
11014 ExecuteCustomElementAction(x, y, element, page);
11018 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11019 int trigger_element,
11021 int trigger_player,
11025 boolean change_done_any = FALSE;
11026 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11029 if (!(trigger_events[trigger_element][trigger_event]))
11032 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11034 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11036 int element = EL_CUSTOM_START + i;
11037 boolean change_done = FALSE;
11040 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11041 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11044 for (p = 0; p < element_info[element].num_change_pages; p++)
11046 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11048 if (change->can_change_or_has_action &&
11049 change->has_event[trigger_event] &&
11050 change->trigger_side & trigger_side &&
11051 change->trigger_player & trigger_player &&
11052 change->trigger_page & trigger_page_bits &&
11053 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11055 change->actual_trigger_element = trigger_element;
11056 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11057 change->actual_trigger_player_bits = trigger_player;
11058 change->actual_trigger_side = trigger_side;
11059 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11060 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11062 if ((change->can_change && !change_done) || change->has_action)
11066 SCAN_PLAYFIELD(x, y)
11068 if (Tile[x][y] == element)
11070 if (change->can_change && !change_done)
11072 // if element already changed in this frame, not only prevent
11073 // another element change (checked in ChangeElement()), but
11074 // also prevent additional element actions for this element
11076 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11077 !level.use_action_after_change_bug)
11080 ChangeDelay[x][y] = 1;
11081 ChangeEvent[x][y] = trigger_event;
11083 HandleElementChange(x, y, p);
11085 else if (change->has_action)
11087 // if element already changed in this frame, not only prevent
11088 // another element change (checked in ChangeElement()), but
11089 // also prevent additional element actions for this element
11091 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11092 !level.use_action_after_change_bug)
11095 ExecuteCustomElementAction(x, y, element, p);
11096 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11101 if (change->can_change)
11103 change_done = TRUE;
11104 change_done_any = TRUE;
11111 RECURSION_LOOP_DETECTION_END();
11113 return change_done_any;
11116 static boolean CheckElementChangeExt(int x, int y,
11118 int trigger_element,
11120 int trigger_player,
11123 boolean change_done = FALSE;
11126 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11127 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11130 if (Tile[x][y] == EL_BLOCKED)
11132 Blocked2Moving(x, y, &x, &y);
11133 element = Tile[x][y];
11136 // check if element has already changed or is about to change after moving
11137 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11138 Tile[x][y] != element) ||
11140 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11141 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11142 ChangePage[x][y] != -1)))
11145 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11147 for (p = 0; p < element_info[element].num_change_pages; p++)
11149 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11151 /* check trigger element for all events where the element that is checked
11152 for changing interacts with a directly adjacent element -- this is
11153 different to element changes that affect other elements to change on the
11154 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11155 boolean check_trigger_element =
11156 (trigger_event == CE_NEXT_TO_X ||
11157 trigger_event == CE_TOUCHING_X ||
11158 trigger_event == CE_HITTING_X ||
11159 trigger_event == CE_HIT_BY_X ||
11160 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11162 if (change->can_change_or_has_action &&
11163 change->has_event[trigger_event] &&
11164 change->trigger_side & trigger_side &&
11165 change->trigger_player & trigger_player &&
11166 (!check_trigger_element ||
11167 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11169 change->actual_trigger_element = trigger_element;
11170 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11171 change->actual_trigger_player_bits = trigger_player;
11172 change->actual_trigger_side = trigger_side;
11173 change->actual_trigger_ce_value = CustomValue[x][y];
11174 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11176 // special case: trigger element not at (x,y) position for some events
11177 if (check_trigger_element)
11189 { 0, 0 }, { 0, 0 }, { 0, 0 },
11193 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11194 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11196 change->actual_trigger_ce_value = CustomValue[xx][yy];
11197 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11200 if (change->can_change && !change_done)
11202 ChangeDelay[x][y] = 1;
11203 ChangeEvent[x][y] = trigger_event;
11205 HandleElementChange(x, y, p);
11207 change_done = TRUE;
11209 else if (change->has_action)
11211 ExecuteCustomElementAction(x, y, element, p);
11212 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11217 RECURSION_LOOP_DETECTION_END();
11219 return change_done;
11222 static void PlayPlayerSound(struct PlayerInfo *player)
11224 int jx = player->jx, jy = player->jy;
11225 int sound_element = player->artwork_element;
11226 int last_action = player->last_action_waiting;
11227 int action = player->action_waiting;
11229 if (player->is_waiting)
11231 if (action != last_action)
11232 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11234 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11238 if (action != last_action)
11239 StopSound(element_info[sound_element].sound[last_action]);
11241 if (last_action == ACTION_SLEEPING)
11242 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11246 static void PlayAllPlayersSound(void)
11250 for (i = 0; i < MAX_PLAYERS; i++)
11251 if (stored_player[i].active)
11252 PlayPlayerSound(&stored_player[i]);
11255 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11257 boolean last_waiting = player->is_waiting;
11258 int move_dir = player->MovDir;
11260 player->dir_waiting = move_dir;
11261 player->last_action_waiting = player->action_waiting;
11265 if (!last_waiting) // not waiting -> waiting
11267 player->is_waiting = TRUE;
11269 player->frame_counter_bored =
11271 game.player_boring_delay_fixed +
11272 GetSimpleRandom(game.player_boring_delay_random);
11273 player->frame_counter_sleeping =
11275 game.player_sleeping_delay_fixed +
11276 GetSimpleRandom(game.player_sleeping_delay_random);
11278 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11281 if (game.player_sleeping_delay_fixed +
11282 game.player_sleeping_delay_random > 0 &&
11283 player->anim_delay_counter == 0 &&
11284 player->post_delay_counter == 0 &&
11285 FrameCounter >= player->frame_counter_sleeping)
11286 player->is_sleeping = TRUE;
11287 else if (game.player_boring_delay_fixed +
11288 game.player_boring_delay_random > 0 &&
11289 FrameCounter >= player->frame_counter_bored)
11290 player->is_bored = TRUE;
11292 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11293 player->is_bored ? ACTION_BORING :
11296 if (player->is_sleeping && player->use_murphy)
11298 // special case for sleeping Murphy when leaning against non-free tile
11300 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11301 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11302 !IS_MOVING(player->jx - 1, player->jy)))
11303 move_dir = MV_LEFT;
11304 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11305 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11306 !IS_MOVING(player->jx + 1, player->jy)))
11307 move_dir = MV_RIGHT;
11309 player->is_sleeping = FALSE;
11311 player->dir_waiting = move_dir;
11314 if (player->is_sleeping)
11316 if (player->num_special_action_sleeping > 0)
11318 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11320 int last_special_action = player->special_action_sleeping;
11321 int num_special_action = player->num_special_action_sleeping;
11322 int special_action =
11323 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11324 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11325 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11326 last_special_action + 1 : ACTION_SLEEPING);
11327 int special_graphic =
11328 el_act_dir2img(player->artwork_element, special_action, move_dir);
11330 player->anim_delay_counter =
11331 graphic_info[special_graphic].anim_delay_fixed +
11332 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11333 player->post_delay_counter =
11334 graphic_info[special_graphic].post_delay_fixed +
11335 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11337 player->special_action_sleeping = special_action;
11340 if (player->anim_delay_counter > 0)
11342 player->action_waiting = player->special_action_sleeping;
11343 player->anim_delay_counter--;
11345 else if (player->post_delay_counter > 0)
11347 player->post_delay_counter--;
11351 else if (player->is_bored)
11353 if (player->num_special_action_bored > 0)
11355 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11357 int special_action =
11358 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11359 int special_graphic =
11360 el_act_dir2img(player->artwork_element, special_action, move_dir);
11362 player->anim_delay_counter =
11363 graphic_info[special_graphic].anim_delay_fixed +
11364 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11365 player->post_delay_counter =
11366 graphic_info[special_graphic].post_delay_fixed +
11367 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11369 player->special_action_bored = special_action;
11372 if (player->anim_delay_counter > 0)
11374 player->action_waiting = player->special_action_bored;
11375 player->anim_delay_counter--;
11377 else if (player->post_delay_counter > 0)
11379 player->post_delay_counter--;
11384 else if (last_waiting) // waiting -> not waiting
11386 player->is_waiting = FALSE;
11387 player->is_bored = FALSE;
11388 player->is_sleeping = FALSE;
11390 player->frame_counter_bored = -1;
11391 player->frame_counter_sleeping = -1;
11393 player->anim_delay_counter = 0;
11394 player->post_delay_counter = 0;
11396 player->dir_waiting = player->MovDir;
11397 player->action_waiting = ACTION_DEFAULT;
11399 player->special_action_bored = ACTION_DEFAULT;
11400 player->special_action_sleeping = ACTION_DEFAULT;
11404 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11406 if ((!player->is_moving && player->was_moving) ||
11407 (player->MovPos == 0 && player->was_moving) ||
11408 (player->is_snapping && !player->was_snapping) ||
11409 (player->is_dropping && !player->was_dropping))
11411 if (!CheckSaveEngineSnapshotToList())
11414 player->was_moving = FALSE;
11415 player->was_snapping = TRUE;
11416 player->was_dropping = TRUE;
11420 if (player->is_moving)
11421 player->was_moving = TRUE;
11423 if (!player->is_snapping)
11424 player->was_snapping = FALSE;
11426 if (!player->is_dropping)
11427 player->was_dropping = FALSE;
11430 static struct MouseActionInfo mouse_action_last = { 0 };
11431 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11432 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11435 CheckSaveEngineSnapshotToList();
11437 mouse_action_last = mouse_action;
11440 static void CheckSingleStepMode(struct PlayerInfo *player)
11442 if (tape.single_step && tape.recording && !tape.pausing)
11444 // as it is called "single step mode", just return to pause mode when the
11445 // player stopped moving after one tile (or never starts moving at all)
11446 // (reverse logic needed here in case single step mode used in team mode)
11447 if (player->is_moving ||
11448 player->is_pushing ||
11449 player->is_dropping_pressed ||
11450 player->effective_mouse_action.button)
11451 game.enter_single_step_mode = FALSE;
11454 CheckSaveEngineSnapshot(player);
11457 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11459 int left = player_action & JOY_LEFT;
11460 int right = player_action & JOY_RIGHT;
11461 int up = player_action & JOY_UP;
11462 int down = player_action & JOY_DOWN;
11463 int button1 = player_action & JOY_BUTTON_1;
11464 int button2 = player_action & JOY_BUTTON_2;
11465 int dx = (left ? -1 : right ? 1 : 0);
11466 int dy = (up ? -1 : down ? 1 : 0);
11468 if (!player->active || tape.pausing)
11474 SnapField(player, dx, dy);
11478 DropElement(player);
11480 MovePlayer(player, dx, dy);
11483 CheckSingleStepMode(player);
11485 SetPlayerWaiting(player, FALSE);
11487 return player_action;
11491 // no actions for this player (no input at player's configured device)
11493 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11494 SnapField(player, 0, 0);
11495 CheckGravityMovementWhenNotMoving(player);
11497 if (player->MovPos == 0)
11498 SetPlayerWaiting(player, TRUE);
11500 if (player->MovPos == 0) // needed for tape.playing
11501 player->is_moving = FALSE;
11503 player->is_dropping = FALSE;
11504 player->is_dropping_pressed = FALSE;
11505 player->drop_pressed_delay = 0;
11507 CheckSingleStepMode(player);
11513 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11516 if (!tape.use_mouse_actions)
11519 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11520 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11521 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11524 static void SetTapeActionFromMouseAction(byte *tape_action,
11525 struct MouseActionInfo *mouse_action)
11527 if (!tape.use_mouse_actions)
11530 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11531 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11532 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11535 static void CheckLevelSolved(void)
11537 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11539 if (game_em.level_solved &&
11540 !game_em.game_over) // game won
11544 game_em.game_over = TRUE;
11546 game.all_players_gone = TRUE;
11549 if (game_em.game_over) // game lost
11550 game.all_players_gone = TRUE;
11552 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11554 if (game_sp.level_solved &&
11555 !game_sp.game_over) // game won
11559 game_sp.game_over = TRUE;
11561 game.all_players_gone = TRUE;
11564 if (game_sp.game_over) // game lost
11565 game.all_players_gone = TRUE;
11567 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11569 if (game_mm.level_solved &&
11570 !game_mm.game_over) // game won
11574 game_mm.game_over = TRUE;
11576 game.all_players_gone = TRUE;
11579 if (game_mm.game_over) // game lost
11580 game.all_players_gone = TRUE;
11584 static void CheckLevelTime(void)
11588 if (TimeFrames >= FRAMES_PER_SECOND)
11593 for (i = 0; i < MAX_PLAYERS; i++)
11595 struct PlayerInfo *player = &stored_player[i];
11597 if (SHIELD_ON(player))
11599 player->shield_normal_time_left--;
11601 if (player->shield_deadly_time_left > 0)
11602 player->shield_deadly_time_left--;
11606 if (!game.LevelSolved && !level.use_step_counter)
11614 if (TimeLeft <= 10 && setup.time_limit)
11615 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11617 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11618 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11620 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11622 if (!TimeLeft && setup.time_limit)
11624 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11625 game_em.lev->killed_out_of_time = TRUE;
11627 for (i = 0; i < MAX_PLAYERS; i++)
11628 KillPlayer(&stored_player[i]);
11631 else if (game.no_time_limit && !game.all_players_gone)
11633 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11636 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11639 if (tape.recording || tape.playing)
11640 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11643 if (tape.recording || tape.playing)
11644 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11646 UpdateAndDisplayGameControlValues();
11649 void AdvanceFrameAndPlayerCounters(int player_nr)
11653 // advance frame counters (global frame counter and time frame counter)
11657 // advance player counters (counters for move delay, move animation etc.)
11658 for (i = 0; i < MAX_PLAYERS; i++)
11660 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11661 int move_delay_value = stored_player[i].move_delay_value;
11662 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11664 if (!advance_player_counters) // not all players may be affected
11667 if (move_frames == 0) // less than one move per game frame
11669 int stepsize = TILEX / move_delay_value;
11670 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11671 int count = (stored_player[i].is_moving ?
11672 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11674 if (count % delay == 0)
11678 stored_player[i].Frame += move_frames;
11680 if (stored_player[i].MovPos != 0)
11681 stored_player[i].StepFrame += move_frames;
11683 if (stored_player[i].move_delay > 0)
11684 stored_player[i].move_delay--;
11686 // due to bugs in previous versions, counter must count up, not down
11687 if (stored_player[i].push_delay != -1)
11688 stored_player[i].push_delay++;
11690 if (stored_player[i].drop_delay > 0)
11691 stored_player[i].drop_delay--;
11693 if (stored_player[i].is_dropping_pressed)
11694 stored_player[i].drop_pressed_delay++;
11698 void StartGameActions(boolean init_network_game, boolean record_tape,
11701 unsigned int new_random_seed = InitRND(random_seed);
11704 TapeStartRecording(new_random_seed);
11706 if (init_network_game)
11708 SendToServer_LevelFile();
11709 SendToServer_StartPlaying();
11717 static void GameActionsExt(void)
11720 static unsigned int game_frame_delay = 0;
11722 unsigned int game_frame_delay_value;
11723 byte *recorded_player_action;
11724 byte summarized_player_action = 0;
11725 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11728 // detect endless loops, caused by custom element programming
11729 if (recursion_loop_detected && recursion_loop_depth == 0)
11731 char *message = getStringCat3("Internal Error! Element ",
11732 EL_NAME(recursion_loop_element),
11733 " caused endless loop! Quit the game?");
11735 Warn("element '%s' caused endless loop in game engine",
11736 EL_NAME(recursion_loop_element));
11738 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11740 recursion_loop_detected = FALSE; // if game should be continued
11747 if (game.restart_level)
11748 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11750 CheckLevelSolved();
11752 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11755 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11758 if (game_status != GAME_MODE_PLAYING) // status might have changed
11761 game_frame_delay_value =
11762 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11764 if (tape.playing && tape.warp_forward && !tape.pausing)
11765 game_frame_delay_value = 0;
11767 SetVideoFrameDelay(game_frame_delay_value);
11769 // (de)activate virtual buttons depending on current game status
11770 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11772 if (game.all_players_gone) // if no players there to be controlled anymore
11773 SetOverlayActive(FALSE);
11774 else if (!tape.playing) // if game continues after tape stopped playing
11775 SetOverlayActive(TRUE);
11780 // ---------- main game synchronization point ----------
11782 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11784 Debug("game:playing:skip", "skip == %d", skip);
11787 // ---------- main game synchronization point ----------
11789 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11793 if (network_playing && !network_player_action_received)
11795 // try to get network player actions in time
11797 // last chance to get network player actions without main loop delay
11798 HandleNetworking();
11800 // game was quit by network peer
11801 if (game_status != GAME_MODE_PLAYING)
11804 // check if network player actions still missing and game still running
11805 if (!network_player_action_received && !checkGameEnded())
11806 return; // failed to get network player actions in time
11808 // do not yet reset "network_player_action_received" (for tape.pausing)
11814 // at this point we know that we really continue executing the game
11816 network_player_action_received = FALSE;
11818 // when playing tape, read previously recorded player input from tape data
11819 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11821 local_player->effective_mouse_action = local_player->mouse_action;
11823 if (recorded_player_action != NULL)
11824 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11825 recorded_player_action);
11827 // TapePlayAction() may return NULL when toggling to "pause before death"
11831 if (tape.set_centered_player)
11833 game.centered_player_nr_next = tape.centered_player_nr_next;
11834 game.set_centered_player = TRUE;
11837 for (i = 0; i < MAX_PLAYERS; i++)
11839 summarized_player_action |= stored_player[i].action;
11841 if (!network_playing && (game.team_mode || tape.playing))
11842 stored_player[i].effective_action = stored_player[i].action;
11845 if (network_playing && !checkGameEnded())
11846 SendToServer_MovePlayer(summarized_player_action);
11848 // summarize all actions at local players mapped input device position
11849 // (this allows using different input devices in single player mode)
11850 if (!network.enabled && !game.team_mode)
11851 stored_player[map_player_action[local_player->index_nr]].effective_action =
11852 summarized_player_action;
11854 // summarize all actions at centered player in local team mode
11855 if (tape.recording &&
11856 setup.team_mode && !network.enabled &&
11857 setup.input_on_focus &&
11858 game.centered_player_nr != -1)
11860 for (i = 0; i < MAX_PLAYERS; i++)
11861 stored_player[map_player_action[i]].effective_action =
11862 (i == game.centered_player_nr ? summarized_player_action : 0);
11865 if (recorded_player_action != NULL)
11866 for (i = 0; i < MAX_PLAYERS; i++)
11867 stored_player[i].effective_action = recorded_player_action[i];
11869 for (i = 0; i < MAX_PLAYERS; i++)
11871 tape_action[i] = stored_player[i].effective_action;
11873 /* (this may happen in the RND game engine if a player was not present on
11874 the playfield on level start, but appeared later from a custom element */
11875 if (setup.team_mode &&
11878 !tape.player_participates[i])
11879 tape.player_participates[i] = TRUE;
11882 SetTapeActionFromMouseAction(tape_action,
11883 &local_player->effective_mouse_action);
11885 // only record actions from input devices, but not programmed actions
11886 if (tape.recording)
11887 TapeRecordAction(tape_action);
11889 // remember if game was played (especially after tape stopped playing)
11890 if (!tape.playing && summarized_player_action)
11891 game.GamePlayed = TRUE;
11893 #if USE_NEW_PLAYER_ASSIGNMENTS
11894 // !!! also map player actions in single player mode !!!
11895 // if (game.team_mode)
11898 byte mapped_action[MAX_PLAYERS];
11900 #if DEBUG_PLAYER_ACTIONS
11901 for (i = 0; i < MAX_PLAYERS; i++)
11902 DebugContinued("", "%d, ", stored_player[i].effective_action);
11905 for (i = 0; i < MAX_PLAYERS; i++)
11906 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11908 for (i = 0; i < MAX_PLAYERS; i++)
11909 stored_player[i].effective_action = mapped_action[i];
11911 #if DEBUG_PLAYER_ACTIONS
11912 DebugContinued("", "=> ");
11913 for (i = 0; i < MAX_PLAYERS; i++)
11914 DebugContinued("", "%d, ", stored_player[i].effective_action);
11915 DebugContinued("game:playing:player", "\n");
11918 #if DEBUG_PLAYER_ACTIONS
11921 for (i = 0; i < MAX_PLAYERS; i++)
11922 DebugContinued("", "%d, ", stored_player[i].effective_action);
11923 DebugContinued("game:playing:player", "\n");
11928 for (i = 0; i < MAX_PLAYERS; i++)
11930 // allow engine snapshot in case of changed movement attempt
11931 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11932 (stored_player[i].effective_action & KEY_MOTION))
11933 game.snapshot.changed_action = TRUE;
11935 // allow engine snapshot in case of snapping/dropping attempt
11936 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11937 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11938 game.snapshot.changed_action = TRUE;
11940 game.snapshot.last_action[i] = stored_player[i].effective_action;
11943 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11945 GameActions_EM_Main();
11947 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11949 GameActions_SP_Main();
11951 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11953 GameActions_MM_Main();
11957 GameActions_RND_Main();
11960 BlitScreenToBitmap(backbuffer);
11962 CheckLevelSolved();
11965 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11967 if (global.show_frames_per_second)
11969 static unsigned int fps_counter = 0;
11970 static int fps_frames = 0;
11971 unsigned int fps_delay_ms = Counter() - fps_counter;
11975 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11977 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11980 fps_counter = Counter();
11982 // always draw FPS to screen after FPS value was updated
11983 redraw_mask |= REDRAW_FPS;
11986 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11987 if (GetDrawDeactivationMask() == REDRAW_NONE)
11988 redraw_mask |= REDRAW_FPS;
11992 static void GameActions_CheckSaveEngineSnapshot(void)
11994 if (!game.snapshot.save_snapshot)
11997 // clear flag for saving snapshot _before_ saving snapshot
11998 game.snapshot.save_snapshot = FALSE;
12000 SaveEngineSnapshotToList();
12003 void GameActions(void)
12007 GameActions_CheckSaveEngineSnapshot();
12010 void GameActions_EM_Main(void)
12012 byte effective_action[MAX_PLAYERS];
12013 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12016 for (i = 0; i < MAX_PLAYERS; i++)
12017 effective_action[i] = stored_player[i].effective_action;
12019 GameActions_EM(effective_action, warp_mode);
12022 void GameActions_SP_Main(void)
12024 byte effective_action[MAX_PLAYERS];
12025 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12028 for (i = 0; i < MAX_PLAYERS; i++)
12029 effective_action[i] = stored_player[i].effective_action;
12031 GameActions_SP(effective_action, warp_mode);
12033 for (i = 0; i < MAX_PLAYERS; i++)
12035 if (stored_player[i].force_dropping)
12036 stored_player[i].action |= KEY_BUTTON_DROP;
12038 stored_player[i].force_dropping = FALSE;
12042 void GameActions_MM_Main(void)
12044 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12046 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12049 void GameActions_RND_Main(void)
12054 void GameActions_RND(void)
12056 static struct MouseActionInfo mouse_action_last = { 0 };
12057 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12058 int magic_wall_x = 0, magic_wall_y = 0;
12059 int i, x, y, element, graphic, last_gfx_frame;
12061 InitPlayfieldScanModeVars();
12063 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12065 SCAN_PLAYFIELD(x, y)
12067 ChangeCount[x][y] = 0;
12068 ChangeEvent[x][y] = -1;
12072 if (game.set_centered_player)
12074 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12076 // switching to "all players" only possible if all players fit to screen
12077 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12079 game.centered_player_nr_next = game.centered_player_nr;
12080 game.set_centered_player = FALSE;
12083 // do not switch focus to non-existing (or non-active) player
12084 if (game.centered_player_nr_next >= 0 &&
12085 !stored_player[game.centered_player_nr_next].active)
12087 game.centered_player_nr_next = game.centered_player_nr;
12088 game.set_centered_player = FALSE;
12092 if (game.set_centered_player &&
12093 ScreenMovPos == 0) // screen currently aligned at tile position
12097 if (game.centered_player_nr_next == -1)
12099 setScreenCenteredToAllPlayers(&sx, &sy);
12103 sx = stored_player[game.centered_player_nr_next].jx;
12104 sy = stored_player[game.centered_player_nr_next].jy;
12107 game.centered_player_nr = game.centered_player_nr_next;
12108 game.set_centered_player = FALSE;
12110 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12111 DrawGameDoorValues();
12114 // check single step mode (set flag and clear again if any player is active)
12115 game.enter_single_step_mode =
12116 (tape.single_step && tape.recording && !tape.pausing);
12118 for (i = 0; i < MAX_PLAYERS; i++)
12120 int actual_player_action = stored_player[i].effective_action;
12123 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12124 - rnd_equinox_tetrachloride 048
12125 - rnd_equinox_tetrachloride_ii 096
12126 - rnd_emanuel_schmieg 002
12127 - doctor_sloan_ww 001, 020
12129 if (stored_player[i].MovPos == 0)
12130 CheckGravityMovement(&stored_player[i]);
12133 // overwrite programmed action with tape action
12134 if (stored_player[i].programmed_action)
12135 actual_player_action = stored_player[i].programmed_action;
12137 PlayerActions(&stored_player[i], actual_player_action);
12139 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12142 // single step pause mode may already have been toggled by "ScrollPlayer()"
12143 if (game.enter_single_step_mode && !tape.pausing)
12144 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12146 ScrollScreen(NULL, SCROLL_GO_ON);
12148 /* for backwards compatibility, the following code emulates a fixed bug that
12149 occured when pushing elements (causing elements that just made their last
12150 pushing step to already (if possible) make their first falling step in the
12151 same game frame, which is bad); this code is also needed to use the famous
12152 "spring push bug" which is used in older levels and might be wanted to be
12153 used also in newer levels, but in this case the buggy pushing code is only
12154 affecting the "spring" element and no other elements */
12156 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12158 for (i = 0; i < MAX_PLAYERS; i++)
12160 struct PlayerInfo *player = &stored_player[i];
12161 int x = player->jx;
12162 int y = player->jy;
12164 if (player->active && player->is_pushing && player->is_moving &&
12166 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12167 Tile[x][y] == EL_SPRING))
12169 ContinueMoving(x, y);
12171 // continue moving after pushing (this is actually a bug)
12172 if (!IS_MOVING(x, y))
12173 Stop[x][y] = FALSE;
12178 SCAN_PLAYFIELD(x, y)
12180 Last[x][y] = Tile[x][y];
12182 ChangeCount[x][y] = 0;
12183 ChangeEvent[x][y] = -1;
12185 // this must be handled before main playfield loop
12186 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12189 if (MovDelay[x][y] <= 0)
12193 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12196 if (MovDelay[x][y] <= 0)
12198 int element = Store[x][y];
12199 int move_direction = MovDir[x][y];
12200 int player_index_bit = Store2[x][y];
12206 TEST_DrawLevelField(x, y);
12208 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12210 if (IS_ENVELOPE(element))
12211 local_player->show_envelope = element;
12216 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12218 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12220 Debug("game:playing:GameActions_RND", "This should never happen!");
12222 ChangePage[x][y] = -1;
12226 Stop[x][y] = FALSE;
12227 if (WasJustMoving[x][y] > 0)
12228 WasJustMoving[x][y]--;
12229 if (WasJustFalling[x][y] > 0)
12230 WasJustFalling[x][y]--;
12231 if (CheckCollision[x][y] > 0)
12232 CheckCollision[x][y]--;
12233 if (CheckImpact[x][y] > 0)
12234 CheckImpact[x][y]--;
12238 /* reset finished pushing action (not done in ContinueMoving() to allow
12239 continuous pushing animation for elements with zero push delay) */
12240 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12242 ResetGfxAnimation(x, y);
12243 TEST_DrawLevelField(x, y);
12247 if (IS_BLOCKED(x, y))
12251 Blocked2Moving(x, y, &oldx, &oldy);
12252 if (!IS_MOVING(oldx, oldy))
12254 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12255 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12256 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12257 Debug("game:playing:GameActions_RND", "This should never happen!");
12263 if (mouse_action.button)
12265 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12266 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12268 x = mouse_action.lx;
12269 y = mouse_action.ly;
12270 element = Tile[x][y];
12274 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12275 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12279 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12280 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12284 SCAN_PLAYFIELD(x, y)
12286 element = Tile[x][y];
12287 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12288 last_gfx_frame = GfxFrame[x][y];
12290 ResetGfxFrame(x, y);
12292 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12293 DrawLevelGraphicAnimation(x, y, graphic);
12295 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12296 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12297 ResetRandomAnimationValue(x, y);
12299 SetRandomAnimationValue(x, y);
12301 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12303 if (IS_INACTIVE(element))
12305 if (IS_ANIMATED(graphic))
12306 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12311 // this may take place after moving, so 'element' may have changed
12312 if (IS_CHANGING(x, y) &&
12313 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12315 int page = element_info[element].event_page_nr[CE_DELAY];
12317 HandleElementChange(x, y, page);
12319 element = Tile[x][y];
12320 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12323 CheckNextToConditions(x, y);
12325 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12329 element = Tile[x][y];
12330 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12332 if (IS_ANIMATED(graphic) &&
12333 !IS_MOVING(x, y) &&
12335 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12337 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12338 TEST_DrawTwinkleOnField(x, y);
12340 else if (element == EL_ACID)
12343 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12345 else if ((element == EL_EXIT_OPEN ||
12346 element == EL_EM_EXIT_OPEN ||
12347 element == EL_SP_EXIT_OPEN ||
12348 element == EL_STEEL_EXIT_OPEN ||
12349 element == EL_EM_STEEL_EXIT_OPEN ||
12350 element == EL_SP_TERMINAL ||
12351 element == EL_SP_TERMINAL_ACTIVE ||
12352 element == EL_EXTRA_TIME ||
12353 element == EL_SHIELD_NORMAL ||
12354 element == EL_SHIELD_DEADLY) &&
12355 IS_ANIMATED(graphic))
12356 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12357 else if (IS_MOVING(x, y))
12358 ContinueMoving(x, y);
12359 else if (IS_ACTIVE_BOMB(element))
12360 CheckDynamite(x, y);
12361 else if (element == EL_AMOEBA_GROWING)
12362 AmoebaGrowing(x, y);
12363 else if (element == EL_AMOEBA_SHRINKING)
12364 AmoebaShrinking(x, y);
12366 #if !USE_NEW_AMOEBA_CODE
12367 else if (IS_AMOEBALIVE(element))
12368 AmoebaReproduce(x, y);
12371 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12373 else if (element == EL_EXIT_CLOSED)
12375 else if (element == EL_EM_EXIT_CLOSED)
12377 else if (element == EL_STEEL_EXIT_CLOSED)
12378 CheckExitSteel(x, y);
12379 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12380 CheckExitSteelEM(x, y);
12381 else if (element == EL_SP_EXIT_CLOSED)
12383 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12384 element == EL_EXPANDABLE_STEELWALL_GROWING)
12385 MauerWaechst(x, y);
12386 else if (element == EL_EXPANDABLE_WALL ||
12387 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12388 element == EL_EXPANDABLE_WALL_VERTICAL ||
12389 element == EL_EXPANDABLE_WALL_ANY ||
12390 element == EL_BD_EXPANDABLE_WALL)
12391 MauerAbleger(x, y);
12392 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12393 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12394 element == EL_EXPANDABLE_STEELWALL_ANY)
12395 MauerAblegerStahl(x, y);
12396 else if (element == EL_FLAMES)
12397 CheckForDragon(x, y);
12398 else if (element == EL_EXPLOSION)
12399 ; // drawing of correct explosion animation is handled separately
12400 else if (element == EL_ELEMENT_SNAPPING ||
12401 element == EL_DIAGONAL_SHRINKING ||
12402 element == EL_DIAGONAL_GROWING)
12404 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12406 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12408 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12409 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12411 if (IS_BELT_ACTIVE(element))
12412 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12414 if (game.magic_wall_active)
12416 int jx = local_player->jx, jy = local_player->jy;
12418 // play the element sound at the position nearest to the player
12419 if ((element == EL_MAGIC_WALL_FULL ||
12420 element == EL_MAGIC_WALL_ACTIVE ||
12421 element == EL_MAGIC_WALL_EMPTYING ||
12422 element == EL_BD_MAGIC_WALL_FULL ||
12423 element == EL_BD_MAGIC_WALL_ACTIVE ||
12424 element == EL_BD_MAGIC_WALL_EMPTYING ||
12425 element == EL_DC_MAGIC_WALL_FULL ||
12426 element == EL_DC_MAGIC_WALL_ACTIVE ||
12427 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12428 ABS(x - jx) + ABS(y - jy) <
12429 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12437 #if USE_NEW_AMOEBA_CODE
12438 // new experimental amoeba growth stuff
12439 if (!(FrameCounter % 8))
12441 static unsigned int random = 1684108901;
12443 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12445 x = RND(lev_fieldx);
12446 y = RND(lev_fieldy);
12447 element = Tile[x][y];
12449 if (!IS_PLAYER(x,y) &&
12450 (element == EL_EMPTY ||
12451 CAN_GROW_INTO(element) ||
12452 element == EL_QUICKSAND_EMPTY ||
12453 element == EL_QUICKSAND_FAST_EMPTY ||
12454 element == EL_ACID_SPLASH_LEFT ||
12455 element == EL_ACID_SPLASH_RIGHT))
12457 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12458 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12459 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12460 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12461 Tile[x][y] = EL_AMOEBA_DROP;
12464 random = random * 129 + 1;
12469 game.explosions_delayed = FALSE;
12471 SCAN_PLAYFIELD(x, y)
12473 element = Tile[x][y];
12475 if (ExplodeField[x][y])
12476 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12477 else if (element == EL_EXPLOSION)
12478 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12480 ExplodeField[x][y] = EX_TYPE_NONE;
12483 game.explosions_delayed = TRUE;
12485 if (game.magic_wall_active)
12487 if (!(game.magic_wall_time_left % 4))
12489 int element = Tile[magic_wall_x][magic_wall_y];
12491 if (element == EL_BD_MAGIC_WALL_FULL ||
12492 element == EL_BD_MAGIC_WALL_ACTIVE ||
12493 element == EL_BD_MAGIC_WALL_EMPTYING)
12494 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12495 else if (element == EL_DC_MAGIC_WALL_FULL ||
12496 element == EL_DC_MAGIC_WALL_ACTIVE ||
12497 element == EL_DC_MAGIC_WALL_EMPTYING)
12498 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12500 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12503 if (game.magic_wall_time_left > 0)
12505 game.magic_wall_time_left--;
12507 if (!game.magic_wall_time_left)
12509 SCAN_PLAYFIELD(x, y)
12511 element = Tile[x][y];
12513 if (element == EL_MAGIC_WALL_ACTIVE ||
12514 element == EL_MAGIC_WALL_FULL)
12516 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12517 TEST_DrawLevelField(x, y);
12519 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12520 element == EL_BD_MAGIC_WALL_FULL)
12522 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12523 TEST_DrawLevelField(x, y);
12525 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12526 element == EL_DC_MAGIC_WALL_FULL)
12528 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12529 TEST_DrawLevelField(x, y);
12533 game.magic_wall_active = FALSE;
12538 if (game.light_time_left > 0)
12540 game.light_time_left--;
12542 if (game.light_time_left == 0)
12543 RedrawAllLightSwitchesAndInvisibleElements();
12546 if (game.timegate_time_left > 0)
12548 game.timegate_time_left--;
12550 if (game.timegate_time_left == 0)
12551 CloseAllOpenTimegates();
12554 if (game.lenses_time_left > 0)
12556 game.lenses_time_left--;
12558 if (game.lenses_time_left == 0)
12559 RedrawAllInvisibleElementsForLenses();
12562 if (game.magnify_time_left > 0)
12564 game.magnify_time_left--;
12566 if (game.magnify_time_left == 0)
12567 RedrawAllInvisibleElementsForMagnifier();
12570 for (i = 0; i < MAX_PLAYERS; i++)
12572 struct PlayerInfo *player = &stored_player[i];
12574 if (SHIELD_ON(player))
12576 if (player->shield_deadly_time_left)
12577 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12578 else if (player->shield_normal_time_left)
12579 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12583 #if USE_DELAYED_GFX_REDRAW
12584 SCAN_PLAYFIELD(x, y)
12586 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12588 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12589 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12591 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12592 DrawLevelField(x, y);
12594 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12595 DrawLevelFieldCrumbled(x, y);
12597 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12598 DrawLevelFieldCrumbledNeighbours(x, y);
12600 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12601 DrawTwinkleOnField(x, y);
12604 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12609 PlayAllPlayersSound();
12611 for (i = 0; i < MAX_PLAYERS; i++)
12613 struct PlayerInfo *player = &stored_player[i];
12615 if (player->show_envelope != 0 && (!player->active ||
12616 player->MovPos == 0))
12618 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12620 player->show_envelope = 0;
12624 // use random number generator in every frame to make it less predictable
12625 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12628 mouse_action_last = mouse_action;
12631 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12633 int min_x = x, min_y = y, max_x = x, max_y = y;
12634 int scr_fieldx = getScreenFieldSizeX();
12635 int scr_fieldy = getScreenFieldSizeY();
12638 for (i = 0; i < MAX_PLAYERS; i++)
12640 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12642 if (!stored_player[i].active || &stored_player[i] == player)
12645 min_x = MIN(min_x, jx);
12646 min_y = MIN(min_y, jy);
12647 max_x = MAX(max_x, jx);
12648 max_y = MAX(max_y, jy);
12651 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12654 static boolean AllPlayersInVisibleScreen(void)
12658 for (i = 0; i < MAX_PLAYERS; i++)
12660 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12662 if (!stored_player[i].active)
12665 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12672 void ScrollLevel(int dx, int dy)
12674 int scroll_offset = 2 * TILEX_VAR;
12677 BlitBitmap(drawto_field, drawto_field,
12678 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12679 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12680 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12681 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12682 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12683 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12687 x = (dx == 1 ? BX1 : BX2);
12688 for (y = BY1; y <= BY2; y++)
12689 DrawScreenField(x, y);
12694 y = (dy == 1 ? BY1 : BY2);
12695 for (x = BX1; x <= BX2; x++)
12696 DrawScreenField(x, y);
12699 redraw_mask |= REDRAW_FIELD;
12702 static boolean canFallDown(struct PlayerInfo *player)
12704 int jx = player->jx, jy = player->jy;
12706 return (IN_LEV_FIELD(jx, jy + 1) &&
12707 (IS_FREE(jx, jy + 1) ||
12708 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12709 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12710 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12713 static boolean canPassField(int x, int y, int move_dir)
12715 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12716 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12717 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12718 int nextx = x + dx;
12719 int nexty = y + dy;
12720 int element = Tile[x][y];
12722 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12723 !CAN_MOVE(element) &&
12724 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12725 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12726 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12729 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12731 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12732 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12733 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12737 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12738 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12739 (IS_DIGGABLE(Tile[newx][newy]) ||
12740 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12741 canPassField(newx, newy, move_dir)));
12744 static void CheckGravityMovement(struct PlayerInfo *player)
12746 if (player->gravity && !player->programmed_action)
12748 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12749 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12750 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12751 int jx = player->jx, jy = player->jy;
12752 boolean player_is_moving_to_valid_field =
12753 (!player_is_snapping &&
12754 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12755 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12756 boolean player_can_fall_down = canFallDown(player);
12758 if (player_can_fall_down &&
12759 !player_is_moving_to_valid_field)
12760 player->programmed_action = MV_DOWN;
12764 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12766 return CheckGravityMovement(player);
12768 if (player->gravity && !player->programmed_action)
12770 int jx = player->jx, jy = player->jy;
12771 boolean field_under_player_is_free =
12772 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12773 boolean player_is_standing_on_valid_field =
12774 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12775 (IS_WALKABLE(Tile[jx][jy]) &&
12776 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12778 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12779 player->programmed_action = MV_DOWN;
12784 MovePlayerOneStep()
12785 -----------------------------------------------------------------------------
12786 dx, dy: direction (non-diagonal) to try to move the player to
12787 real_dx, real_dy: direction as read from input device (can be diagonal)
12790 boolean MovePlayerOneStep(struct PlayerInfo *player,
12791 int dx, int dy, int real_dx, int real_dy)
12793 int jx = player->jx, jy = player->jy;
12794 int new_jx = jx + dx, new_jy = jy + dy;
12796 boolean player_can_move = !player->cannot_move;
12798 if (!player->active || (!dx && !dy))
12799 return MP_NO_ACTION;
12801 player->MovDir = (dx < 0 ? MV_LEFT :
12802 dx > 0 ? MV_RIGHT :
12804 dy > 0 ? MV_DOWN : MV_NONE);
12806 if (!IN_LEV_FIELD(new_jx, new_jy))
12807 return MP_NO_ACTION;
12809 if (!player_can_move)
12811 if (player->MovPos == 0)
12813 player->is_moving = FALSE;
12814 player->is_digging = FALSE;
12815 player->is_collecting = FALSE;
12816 player->is_snapping = FALSE;
12817 player->is_pushing = FALSE;
12821 if (!network.enabled && game.centered_player_nr == -1 &&
12822 !AllPlayersInSight(player, new_jx, new_jy))
12823 return MP_NO_ACTION;
12825 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12826 if (can_move != MP_MOVING)
12829 // check if DigField() has caused relocation of the player
12830 if (player->jx != jx || player->jy != jy)
12831 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12833 StorePlayer[jx][jy] = 0;
12834 player->last_jx = jx;
12835 player->last_jy = jy;
12836 player->jx = new_jx;
12837 player->jy = new_jy;
12838 StorePlayer[new_jx][new_jy] = player->element_nr;
12840 if (player->move_delay_value_next != -1)
12842 player->move_delay_value = player->move_delay_value_next;
12843 player->move_delay_value_next = -1;
12847 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12849 player->step_counter++;
12851 PlayerVisit[jx][jy] = FrameCounter;
12853 player->is_moving = TRUE;
12856 // should better be called in MovePlayer(), but this breaks some tapes
12857 ScrollPlayer(player, SCROLL_INIT);
12863 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12865 int jx = player->jx, jy = player->jy;
12866 int old_jx = jx, old_jy = jy;
12867 int moved = MP_NO_ACTION;
12869 if (!player->active)
12874 if (player->MovPos == 0)
12876 player->is_moving = FALSE;
12877 player->is_digging = FALSE;
12878 player->is_collecting = FALSE;
12879 player->is_snapping = FALSE;
12880 player->is_pushing = FALSE;
12886 if (player->move_delay > 0)
12889 player->move_delay = -1; // set to "uninitialized" value
12891 // store if player is automatically moved to next field
12892 player->is_auto_moving = (player->programmed_action != MV_NONE);
12894 // remove the last programmed player action
12895 player->programmed_action = 0;
12897 if (player->MovPos)
12899 // should only happen if pre-1.2 tape recordings are played
12900 // this is only for backward compatibility
12902 int original_move_delay_value = player->move_delay_value;
12905 Debug("game:playing:MovePlayer",
12906 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12910 // scroll remaining steps with finest movement resolution
12911 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12913 while (player->MovPos)
12915 ScrollPlayer(player, SCROLL_GO_ON);
12916 ScrollScreen(NULL, SCROLL_GO_ON);
12918 AdvanceFrameAndPlayerCounters(player->index_nr);
12921 BackToFront_WithFrameDelay(0);
12924 player->move_delay_value = original_move_delay_value;
12927 player->is_active = FALSE;
12929 if (player->last_move_dir & MV_HORIZONTAL)
12931 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12932 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12936 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12937 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12940 if (!moved && !player->is_active)
12942 player->is_moving = FALSE;
12943 player->is_digging = FALSE;
12944 player->is_collecting = FALSE;
12945 player->is_snapping = FALSE;
12946 player->is_pushing = FALSE;
12952 if (moved & MP_MOVING && !ScreenMovPos &&
12953 (player->index_nr == game.centered_player_nr ||
12954 game.centered_player_nr == -1))
12956 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12958 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12960 // actual player has left the screen -- scroll in that direction
12961 if (jx != old_jx) // player has moved horizontally
12962 scroll_x += (jx - old_jx);
12963 else // player has moved vertically
12964 scroll_y += (jy - old_jy);
12968 int offset_raw = game.scroll_delay_value;
12970 if (jx != old_jx) // player has moved horizontally
12972 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12973 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12974 int new_scroll_x = jx - MIDPOSX + offset_x;
12976 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12977 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12978 scroll_x = new_scroll_x;
12980 // don't scroll over playfield boundaries
12981 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12983 // don't scroll more than one field at a time
12984 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12986 // don't scroll against the player's moving direction
12987 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12988 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12989 scroll_x = old_scroll_x;
12991 else // player has moved vertically
12993 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12994 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12995 int new_scroll_y = jy - MIDPOSY + offset_y;
12997 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12998 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12999 scroll_y = new_scroll_y;
13001 // don't scroll over playfield boundaries
13002 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13004 // don't scroll more than one field at a time
13005 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13007 // don't scroll against the player's moving direction
13008 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13009 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13010 scroll_y = old_scroll_y;
13014 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13016 if (!network.enabled && game.centered_player_nr == -1 &&
13017 !AllPlayersInVisibleScreen())
13019 scroll_x = old_scroll_x;
13020 scroll_y = old_scroll_y;
13024 ScrollScreen(player, SCROLL_INIT);
13025 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13030 player->StepFrame = 0;
13032 if (moved & MP_MOVING)
13034 if (old_jx != jx && old_jy == jy)
13035 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13036 else if (old_jx == jx && old_jy != jy)
13037 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13039 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13041 player->last_move_dir = player->MovDir;
13042 player->is_moving = TRUE;
13043 player->is_snapping = FALSE;
13044 player->is_switching = FALSE;
13045 player->is_dropping = FALSE;
13046 player->is_dropping_pressed = FALSE;
13047 player->drop_pressed_delay = 0;
13050 // should better be called here than above, but this breaks some tapes
13051 ScrollPlayer(player, SCROLL_INIT);
13056 CheckGravityMovementWhenNotMoving(player);
13058 player->is_moving = FALSE;
13060 /* at this point, the player is allowed to move, but cannot move right now
13061 (e.g. because of something blocking the way) -- ensure that the player
13062 is also allowed to move in the next frame (in old versions before 3.1.1,
13063 the player was forced to wait again for eight frames before next try) */
13065 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13066 player->move_delay = 0; // allow direct movement in the next frame
13069 if (player->move_delay == -1) // not yet initialized by DigField()
13070 player->move_delay = player->move_delay_value;
13072 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13074 TestIfPlayerTouchesBadThing(jx, jy);
13075 TestIfPlayerTouchesCustomElement(jx, jy);
13078 if (!player->active)
13079 RemovePlayer(player);
13084 void ScrollPlayer(struct PlayerInfo *player, int mode)
13086 int jx = player->jx, jy = player->jy;
13087 int last_jx = player->last_jx, last_jy = player->last_jy;
13088 int move_stepsize = TILEX / player->move_delay_value;
13090 if (!player->active)
13093 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13096 if (mode == SCROLL_INIT)
13098 player->actual_frame_counter = FrameCounter;
13099 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13101 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13102 Tile[last_jx][last_jy] == EL_EMPTY)
13104 int last_field_block_delay = 0; // start with no blocking at all
13105 int block_delay_adjustment = player->block_delay_adjustment;
13107 // if player blocks last field, add delay for exactly one move
13108 if (player->block_last_field)
13110 last_field_block_delay += player->move_delay_value;
13112 // when blocking enabled, prevent moving up despite gravity
13113 if (player->gravity && player->MovDir == MV_UP)
13114 block_delay_adjustment = -1;
13117 // add block delay adjustment (also possible when not blocking)
13118 last_field_block_delay += block_delay_adjustment;
13120 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13121 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13124 if (player->MovPos != 0) // player has not yet reached destination
13127 else if (!FrameReached(&player->actual_frame_counter, 1))
13130 if (player->MovPos != 0)
13132 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13133 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13135 // before DrawPlayer() to draw correct player graphic for this case
13136 if (player->MovPos == 0)
13137 CheckGravityMovement(player);
13140 if (player->MovPos == 0) // player reached destination field
13142 if (player->move_delay_reset_counter > 0)
13144 player->move_delay_reset_counter--;
13146 if (player->move_delay_reset_counter == 0)
13148 // continue with normal speed after quickly moving through gate
13149 HALVE_PLAYER_SPEED(player);
13151 // be able to make the next move without delay
13152 player->move_delay = 0;
13156 player->last_jx = jx;
13157 player->last_jy = jy;
13159 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13160 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13161 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13162 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13163 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13164 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13165 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13166 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13168 ExitPlayer(player);
13170 if (game.players_still_needed == 0 &&
13171 (game.friends_still_needed == 0 ||
13172 IS_SP_ELEMENT(Tile[jx][jy])))
13176 // this breaks one level: "machine", level 000
13178 int move_direction = player->MovDir;
13179 int enter_side = MV_DIR_OPPOSITE(move_direction);
13180 int leave_side = move_direction;
13181 int old_jx = last_jx;
13182 int old_jy = last_jy;
13183 int old_element = Tile[old_jx][old_jy];
13184 int new_element = Tile[jx][jy];
13186 if (IS_CUSTOM_ELEMENT(old_element))
13187 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13189 player->index_bit, leave_side);
13191 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13192 CE_PLAYER_LEAVES_X,
13193 player->index_bit, leave_side);
13195 if (IS_CUSTOM_ELEMENT(new_element))
13196 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13197 player->index_bit, enter_side);
13199 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13200 CE_PLAYER_ENTERS_X,
13201 player->index_bit, enter_side);
13203 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13204 CE_MOVE_OF_X, move_direction);
13207 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13209 TestIfPlayerTouchesBadThing(jx, jy);
13210 TestIfPlayerTouchesCustomElement(jx, jy);
13212 /* needed because pushed element has not yet reached its destination,
13213 so it would trigger a change event at its previous field location */
13214 if (!player->is_pushing)
13215 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13217 if (level.finish_dig_collect &&
13218 (player->is_digging || player->is_collecting))
13220 int last_element = player->last_removed_element;
13221 int move_direction = player->MovDir;
13222 int enter_side = MV_DIR_OPPOSITE(move_direction);
13223 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13224 CE_PLAYER_COLLECTS_X);
13226 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13227 player->index_bit, enter_side);
13229 player->last_removed_element = EL_UNDEFINED;
13232 if (!player->active)
13233 RemovePlayer(player);
13236 if (level.use_step_counter)
13246 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13247 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13249 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13251 DisplayGameControlValues();
13253 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13254 for (i = 0; i < MAX_PLAYERS; i++)
13255 KillPlayer(&stored_player[i]);
13257 else if (game.no_time_limit && !game.all_players_gone)
13259 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13261 DisplayGameControlValues();
13265 if (tape.single_step && tape.recording && !tape.pausing &&
13266 !player->programmed_action)
13267 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13269 if (!player->programmed_action)
13270 CheckSaveEngineSnapshot(player);
13274 void ScrollScreen(struct PlayerInfo *player, int mode)
13276 static unsigned int screen_frame_counter = 0;
13278 if (mode == SCROLL_INIT)
13280 // set scrolling step size according to actual player's moving speed
13281 ScrollStepSize = TILEX / player->move_delay_value;
13283 screen_frame_counter = FrameCounter;
13284 ScreenMovDir = player->MovDir;
13285 ScreenMovPos = player->MovPos;
13286 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13289 else if (!FrameReached(&screen_frame_counter, 1))
13294 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13295 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13296 redraw_mask |= REDRAW_FIELD;
13299 ScreenMovDir = MV_NONE;
13302 void CheckNextToConditions(int x, int y)
13304 int element = Tile[x][y];
13306 if (IS_PLAYER(x, y))
13307 TestIfPlayerNextToCustomElement(x, y);
13309 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13310 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13311 TestIfElementNextToCustomElement(x, y);
13314 void TestIfPlayerNextToCustomElement(int x, int y)
13316 static int xy[4][2] =
13323 static int trigger_sides[4][2] =
13325 // center side border side
13326 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13327 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13328 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13329 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13333 if (!IS_PLAYER(x, y))
13336 struct PlayerInfo *player = PLAYERINFO(x, y);
13338 if (player->is_moving)
13341 for (i = 0; i < NUM_DIRECTIONS; i++)
13343 int xx = x + xy[i][0];
13344 int yy = y + xy[i][1];
13345 int border_side = trigger_sides[i][1];
13346 int border_element;
13348 if (!IN_LEV_FIELD(xx, yy))
13351 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13352 continue; // center and border element not connected
13354 border_element = Tile[xx][yy];
13356 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13357 player->index_bit, border_side);
13358 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13359 CE_PLAYER_NEXT_TO_X,
13360 player->index_bit, border_side);
13362 /* use player element that is initially defined in the level playfield,
13363 not the player element that corresponds to the runtime player number
13364 (example: a level that contains EL_PLAYER_3 as the only player would
13365 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13367 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13368 CE_NEXT_TO_X, border_side);
13372 void TestIfPlayerTouchesCustomElement(int x, int y)
13374 static int xy[4][2] =
13381 static int trigger_sides[4][2] =
13383 // center side border side
13384 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13385 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13386 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13387 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13389 static int touch_dir[4] =
13391 MV_LEFT | MV_RIGHT,
13396 int center_element = Tile[x][y]; // should always be non-moving!
13399 for (i = 0; i < NUM_DIRECTIONS; i++)
13401 int xx = x + xy[i][0];
13402 int yy = y + xy[i][1];
13403 int center_side = trigger_sides[i][0];
13404 int border_side = trigger_sides[i][1];
13405 int border_element;
13407 if (!IN_LEV_FIELD(xx, yy))
13410 if (IS_PLAYER(x, y)) // player found at center element
13412 struct PlayerInfo *player = PLAYERINFO(x, y);
13414 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13415 border_element = Tile[xx][yy]; // may be moving!
13416 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13417 border_element = Tile[xx][yy];
13418 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13419 border_element = MovingOrBlocked2Element(xx, yy);
13421 continue; // center and border element do not touch
13423 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13424 player->index_bit, border_side);
13425 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13426 CE_PLAYER_TOUCHES_X,
13427 player->index_bit, border_side);
13430 /* use player element that is initially defined in the level playfield,
13431 not the player element that corresponds to the runtime player number
13432 (example: a level that contains EL_PLAYER_3 as the only player would
13433 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13434 int player_element = PLAYERINFO(x, y)->initial_element;
13436 CheckElementChangeBySide(xx, yy, border_element, player_element,
13437 CE_TOUCHING_X, border_side);
13440 else if (IS_PLAYER(xx, yy)) // player found at border element
13442 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13444 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13446 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13447 continue; // center and border element do not touch
13450 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13451 player->index_bit, center_side);
13452 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13453 CE_PLAYER_TOUCHES_X,
13454 player->index_bit, center_side);
13457 /* use player element that is initially defined in the level playfield,
13458 not the player element that corresponds to the runtime player number
13459 (example: a level that contains EL_PLAYER_3 as the only player would
13460 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13461 int player_element = PLAYERINFO(xx, yy)->initial_element;
13463 CheckElementChangeBySide(x, y, center_element, player_element,
13464 CE_TOUCHING_X, center_side);
13472 void TestIfElementNextToCustomElement(int x, int y)
13474 static int xy[4][2] =
13481 static int trigger_sides[4][2] =
13483 // center side border side
13484 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13485 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13486 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13487 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13489 int center_element = Tile[x][y]; // should always be non-moving!
13492 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13495 for (i = 0; i < NUM_DIRECTIONS; i++)
13497 int xx = x + xy[i][0];
13498 int yy = y + xy[i][1];
13499 int border_side = trigger_sides[i][1];
13500 int border_element;
13502 if (!IN_LEV_FIELD(xx, yy))
13505 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13506 continue; // center and border element not connected
13508 border_element = Tile[xx][yy];
13510 // check for change of center element (but change it only once)
13511 if (CheckElementChangeBySide(x, y, center_element, border_element,
13512 CE_NEXT_TO_X, border_side))
13517 void TestIfElementTouchesCustomElement(int x, int y)
13519 static int xy[4][2] =
13526 static int trigger_sides[4][2] =
13528 // center side border side
13529 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13530 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13531 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13532 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13534 static int touch_dir[4] =
13536 MV_LEFT | MV_RIGHT,
13541 boolean change_center_element = FALSE;
13542 int center_element = Tile[x][y]; // should always be non-moving!
13543 int border_element_old[NUM_DIRECTIONS];
13546 for (i = 0; i < NUM_DIRECTIONS; i++)
13548 int xx = x + xy[i][0];
13549 int yy = y + xy[i][1];
13550 int border_element;
13552 border_element_old[i] = -1;
13554 if (!IN_LEV_FIELD(xx, yy))
13557 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13558 border_element = Tile[xx][yy]; // may be moving!
13559 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13560 border_element = Tile[xx][yy];
13561 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13562 border_element = MovingOrBlocked2Element(xx, yy);
13564 continue; // center and border element do not touch
13566 border_element_old[i] = border_element;
13569 for (i = 0; i < NUM_DIRECTIONS; i++)
13571 int xx = x + xy[i][0];
13572 int yy = y + xy[i][1];
13573 int center_side = trigger_sides[i][0];
13574 int border_element = border_element_old[i];
13576 if (border_element == -1)
13579 // check for change of border element
13580 CheckElementChangeBySide(xx, yy, border_element, center_element,
13581 CE_TOUCHING_X, center_side);
13583 // (center element cannot be player, so we dont have to check this here)
13586 for (i = 0; i < NUM_DIRECTIONS; i++)
13588 int xx = x + xy[i][0];
13589 int yy = y + xy[i][1];
13590 int border_side = trigger_sides[i][1];
13591 int border_element = border_element_old[i];
13593 if (border_element == -1)
13596 // check for change of center element (but change it only once)
13597 if (!change_center_element)
13598 change_center_element =
13599 CheckElementChangeBySide(x, y, center_element, border_element,
13600 CE_TOUCHING_X, border_side);
13602 if (IS_PLAYER(xx, yy))
13604 /* use player element that is initially defined in the level playfield,
13605 not the player element that corresponds to the runtime player number
13606 (example: a level that contains EL_PLAYER_3 as the only player would
13607 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13608 int player_element = PLAYERINFO(xx, yy)->initial_element;
13610 CheckElementChangeBySide(x, y, center_element, player_element,
13611 CE_TOUCHING_X, border_side);
13616 void TestIfElementHitsCustomElement(int x, int y, int direction)
13618 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13619 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13620 int hitx = x + dx, hity = y + dy;
13621 int hitting_element = Tile[x][y];
13622 int touched_element;
13624 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13627 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13628 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13630 if (IN_LEV_FIELD(hitx, hity))
13632 int opposite_direction = MV_DIR_OPPOSITE(direction);
13633 int hitting_side = direction;
13634 int touched_side = opposite_direction;
13635 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13636 MovDir[hitx][hity] != direction ||
13637 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13643 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13644 CE_HITTING_X, touched_side);
13646 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13647 CE_HIT_BY_X, hitting_side);
13649 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13650 CE_HIT_BY_SOMETHING, opposite_direction);
13652 if (IS_PLAYER(hitx, hity))
13654 /* use player element that is initially defined in the level playfield,
13655 not the player element that corresponds to the runtime player number
13656 (example: a level that contains EL_PLAYER_3 as the only player would
13657 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13658 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13660 CheckElementChangeBySide(x, y, hitting_element, player_element,
13661 CE_HITTING_X, touched_side);
13666 // "hitting something" is also true when hitting the playfield border
13667 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13668 CE_HITTING_SOMETHING, direction);
13671 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13673 int i, kill_x = -1, kill_y = -1;
13675 int bad_element = -1;
13676 static int test_xy[4][2] =
13683 static int test_dir[4] =
13691 for (i = 0; i < NUM_DIRECTIONS; i++)
13693 int test_x, test_y, test_move_dir, test_element;
13695 test_x = good_x + test_xy[i][0];
13696 test_y = good_y + test_xy[i][1];
13698 if (!IN_LEV_FIELD(test_x, test_y))
13702 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13704 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13706 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13707 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13709 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13710 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13714 bad_element = test_element;
13720 if (kill_x != -1 || kill_y != -1)
13722 if (IS_PLAYER(good_x, good_y))
13724 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13726 if (player->shield_deadly_time_left > 0 &&
13727 !IS_INDESTRUCTIBLE(bad_element))
13728 Bang(kill_x, kill_y);
13729 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13730 KillPlayer(player);
13733 Bang(good_x, good_y);
13737 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13739 int i, kill_x = -1, kill_y = -1;
13740 int bad_element = Tile[bad_x][bad_y];
13741 static int test_xy[4][2] =
13748 static int touch_dir[4] =
13750 MV_LEFT | MV_RIGHT,
13755 static int test_dir[4] =
13763 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13766 for (i = 0; i < NUM_DIRECTIONS; i++)
13768 int test_x, test_y, test_move_dir, test_element;
13770 test_x = bad_x + test_xy[i][0];
13771 test_y = bad_y + test_xy[i][1];
13773 if (!IN_LEV_FIELD(test_x, test_y))
13777 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13779 test_element = Tile[test_x][test_y];
13781 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13782 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13784 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13785 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13787 // good thing is player or penguin that does not move away
13788 if (IS_PLAYER(test_x, test_y))
13790 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13792 if (bad_element == EL_ROBOT && player->is_moving)
13793 continue; // robot does not kill player if he is moving
13795 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13797 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13798 continue; // center and border element do not touch
13806 else if (test_element == EL_PENGUIN)
13816 if (kill_x != -1 || kill_y != -1)
13818 if (IS_PLAYER(kill_x, kill_y))
13820 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13822 if (player->shield_deadly_time_left > 0 &&
13823 !IS_INDESTRUCTIBLE(bad_element))
13824 Bang(bad_x, bad_y);
13825 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13826 KillPlayer(player);
13829 Bang(kill_x, kill_y);
13833 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13835 int bad_element = Tile[bad_x][bad_y];
13836 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13837 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13838 int test_x = bad_x + dx, test_y = bad_y + dy;
13839 int test_move_dir, test_element;
13840 int kill_x = -1, kill_y = -1;
13842 if (!IN_LEV_FIELD(test_x, test_y))
13846 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13848 test_element = Tile[test_x][test_y];
13850 if (test_move_dir != bad_move_dir)
13852 // good thing can be player or penguin that does not move away
13853 if (IS_PLAYER(test_x, test_y))
13855 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13857 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13858 player as being hit when he is moving towards the bad thing, because
13859 the "get hit by" condition would be lost after the player stops) */
13860 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13861 return; // player moves away from bad thing
13866 else if (test_element == EL_PENGUIN)
13873 if (kill_x != -1 || kill_y != -1)
13875 if (IS_PLAYER(kill_x, kill_y))
13877 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13879 if (player->shield_deadly_time_left > 0 &&
13880 !IS_INDESTRUCTIBLE(bad_element))
13881 Bang(bad_x, bad_y);
13882 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13883 KillPlayer(player);
13886 Bang(kill_x, kill_y);
13890 void TestIfPlayerTouchesBadThing(int x, int y)
13892 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13895 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13897 TestIfGoodThingHitsBadThing(x, y, move_dir);
13900 void TestIfBadThingTouchesPlayer(int x, int y)
13902 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13905 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13907 TestIfBadThingHitsGoodThing(x, y, move_dir);
13910 void TestIfFriendTouchesBadThing(int x, int y)
13912 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13915 void TestIfBadThingTouchesFriend(int x, int y)
13917 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13920 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13922 int i, kill_x = bad_x, kill_y = bad_y;
13923 static int xy[4][2] =
13931 for (i = 0; i < NUM_DIRECTIONS; i++)
13935 x = bad_x + xy[i][0];
13936 y = bad_y + xy[i][1];
13937 if (!IN_LEV_FIELD(x, y))
13940 element = Tile[x][y];
13941 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13942 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13950 if (kill_x != bad_x || kill_y != bad_y)
13951 Bang(bad_x, bad_y);
13954 void KillPlayer(struct PlayerInfo *player)
13956 int jx = player->jx, jy = player->jy;
13958 if (!player->active)
13962 Debug("game:playing:KillPlayer",
13963 "0: killed == %d, active == %d, reanimated == %d",
13964 player->killed, player->active, player->reanimated);
13967 /* the following code was introduced to prevent an infinite loop when calling
13969 -> CheckTriggeredElementChangeExt()
13970 -> ExecuteCustomElementAction()
13972 -> (infinitely repeating the above sequence of function calls)
13973 which occurs when killing the player while having a CE with the setting
13974 "kill player X when explosion of <player X>"; the solution using a new
13975 field "player->killed" was chosen for backwards compatibility, although
13976 clever use of the fields "player->active" etc. would probably also work */
13978 if (player->killed)
13982 player->killed = TRUE;
13984 // remove accessible field at the player's position
13985 Tile[jx][jy] = EL_EMPTY;
13987 // deactivate shield (else Bang()/Explode() would not work right)
13988 player->shield_normal_time_left = 0;
13989 player->shield_deadly_time_left = 0;
13992 Debug("game:playing:KillPlayer",
13993 "1: killed == %d, active == %d, reanimated == %d",
13994 player->killed, player->active, player->reanimated);
14000 Debug("game:playing:KillPlayer",
14001 "2: killed == %d, active == %d, reanimated == %d",
14002 player->killed, player->active, player->reanimated);
14005 if (player->reanimated) // killed player may have been reanimated
14006 player->killed = player->reanimated = FALSE;
14008 BuryPlayer(player);
14011 static void KillPlayerUnlessEnemyProtected(int x, int y)
14013 if (!PLAYER_ENEMY_PROTECTED(x, y))
14014 KillPlayer(PLAYERINFO(x, y));
14017 static void KillPlayerUnlessExplosionProtected(int x, int y)
14019 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14020 KillPlayer(PLAYERINFO(x, y));
14023 void BuryPlayer(struct PlayerInfo *player)
14025 int jx = player->jx, jy = player->jy;
14027 if (!player->active)
14030 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14031 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14033 RemovePlayer(player);
14035 player->buried = TRUE;
14037 if (game.all_players_gone)
14038 game.GameOver = TRUE;
14041 void RemovePlayer(struct PlayerInfo *player)
14043 int jx = player->jx, jy = player->jy;
14044 int i, found = FALSE;
14046 player->present = FALSE;
14047 player->active = FALSE;
14049 // required for some CE actions (even if the player is not active anymore)
14050 player->MovPos = 0;
14052 if (!ExplodeField[jx][jy])
14053 StorePlayer[jx][jy] = 0;
14055 if (player->is_moving)
14056 TEST_DrawLevelField(player->last_jx, player->last_jy);
14058 for (i = 0; i < MAX_PLAYERS; i++)
14059 if (stored_player[i].active)
14064 game.all_players_gone = TRUE;
14065 game.GameOver = TRUE;
14068 game.exit_x = game.robot_wheel_x = jx;
14069 game.exit_y = game.robot_wheel_y = jy;
14072 void ExitPlayer(struct PlayerInfo *player)
14074 DrawPlayer(player); // needed here only to cleanup last field
14075 RemovePlayer(player);
14077 if (game.players_still_needed > 0)
14078 game.players_still_needed--;
14081 static void SetFieldForSnapping(int x, int y, int element, int direction,
14082 int player_index_bit)
14084 struct ElementInfo *ei = &element_info[element];
14085 int direction_bit = MV_DIR_TO_BIT(direction);
14086 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14087 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14088 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14090 Tile[x][y] = EL_ELEMENT_SNAPPING;
14091 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14092 MovDir[x][y] = direction;
14093 Store[x][y] = element;
14094 Store2[x][y] = player_index_bit;
14096 ResetGfxAnimation(x, y);
14098 GfxElement[x][y] = element;
14099 GfxAction[x][y] = action;
14100 GfxDir[x][y] = direction;
14101 GfxFrame[x][y] = -1;
14104 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14105 int player_index_bit)
14107 TestIfElementTouchesCustomElement(x, y); // for empty space
14109 if (level.finish_dig_collect)
14111 int dig_side = MV_DIR_OPPOSITE(direction);
14112 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14113 CE_PLAYER_COLLECTS_X);
14115 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14116 player_index_bit, dig_side);
14117 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14118 player_index_bit, dig_side);
14123 =============================================================================
14124 checkDiagonalPushing()
14125 -----------------------------------------------------------------------------
14126 check if diagonal input device direction results in pushing of object
14127 (by checking if the alternative direction is walkable, diggable, ...)
14128 =============================================================================
14131 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14132 int x, int y, int real_dx, int real_dy)
14134 int jx, jy, dx, dy, xx, yy;
14136 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14139 // diagonal direction: check alternative direction
14144 xx = jx + (dx == 0 ? real_dx : 0);
14145 yy = jy + (dy == 0 ? real_dy : 0);
14147 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14151 =============================================================================
14153 -----------------------------------------------------------------------------
14154 x, y: field next to player (non-diagonal) to try to dig to
14155 real_dx, real_dy: direction as read from input device (can be diagonal)
14156 =============================================================================
14159 static int DigField(struct PlayerInfo *player,
14160 int oldx, int oldy, int x, int y,
14161 int real_dx, int real_dy, int mode)
14163 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14164 boolean player_was_pushing = player->is_pushing;
14165 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14166 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14167 int jx = oldx, jy = oldy;
14168 int dx = x - jx, dy = y - jy;
14169 int nextx = x + dx, nexty = y + dy;
14170 int move_direction = (dx == -1 ? MV_LEFT :
14171 dx == +1 ? MV_RIGHT :
14173 dy == +1 ? MV_DOWN : MV_NONE);
14174 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14175 int dig_side = MV_DIR_OPPOSITE(move_direction);
14176 int old_element = Tile[jx][jy];
14177 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14180 if (is_player) // function can also be called by EL_PENGUIN
14182 if (player->MovPos == 0)
14184 player->is_digging = FALSE;
14185 player->is_collecting = FALSE;
14188 if (player->MovPos == 0) // last pushing move finished
14189 player->is_pushing = FALSE;
14191 if (mode == DF_NO_PUSH) // player just stopped pushing
14193 player->is_switching = FALSE;
14194 player->push_delay = -1;
14196 return MP_NO_ACTION;
14200 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14201 old_element = Back[jx][jy];
14203 // in case of element dropped at player position, check background
14204 else if (Back[jx][jy] != EL_EMPTY &&
14205 game.engine_version >= VERSION_IDENT(2,2,0,0))
14206 old_element = Back[jx][jy];
14208 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14209 return MP_NO_ACTION; // field has no opening in this direction
14211 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14212 return MP_NO_ACTION; // field has no opening in this direction
14214 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14218 Tile[jx][jy] = player->artwork_element;
14219 InitMovingField(jx, jy, MV_DOWN);
14220 Store[jx][jy] = EL_ACID;
14221 ContinueMoving(jx, jy);
14222 BuryPlayer(player);
14224 return MP_DONT_RUN_INTO;
14227 if (player_can_move && DONT_RUN_INTO(element))
14229 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14231 return MP_DONT_RUN_INTO;
14234 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14235 return MP_NO_ACTION;
14237 collect_count = element_info[element].collect_count_initial;
14239 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14240 return MP_NO_ACTION;
14242 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14243 player_can_move = player_can_move_or_snap;
14245 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14246 game.engine_version >= VERSION_IDENT(2,2,0,0))
14248 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14249 player->index_bit, dig_side);
14250 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14251 player->index_bit, dig_side);
14253 if (element == EL_DC_LANDMINE)
14256 if (Tile[x][y] != element) // field changed by snapping
14259 return MP_NO_ACTION;
14262 if (player->gravity && is_player && !player->is_auto_moving &&
14263 canFallDown(player) && move_direction != MV_DOWN &&
14264 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14265 return MP_NO_ACTION; // player cannot walk here due to gravity
14267 if (player_can_move &&
14268 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14270 int sound_element = SND_ELEMENT(element);
14271 int sound_action = ACTION_WALKING;
14273 if (IS_RND_GATE(element))
14275 if (!player->key[RND_GATE_NR(element)])
14276 return MP_NO_ACTION;
14278 else if (IS_RND_GATE_GRAY(element))
14280 if (!player->key[RND_GATE_GRAY_NR(element)])
14281 return MP_NO_ACTION;
14283 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14285 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14286 return MP_NO_ACTION;
14288 else if (element == EL_EXIT_OPEN ||
14289 element == EL_EM_EXIT_OPEN ||
14290 element == EL_EM_EXIT_OPENING ||
14291 element == EL_STEEL_EXIT_OPEN ||
14292 element == EL_EM_STEEL_EXIT_OPEN ||
14293 element == EL_EM_STEEL_EXIT_OPENING ||
14294 element == EL_SP_EXIT_OPEN ||
14295 element == EL_SP_EXIT_OPENING)
14297 sound_action = ACTION_PASSING; // player is passing exit
14299 else if (element == EL_EMPTY)
14301 sound_action = ACTION_MOVING; // nothing to walk on
14304 // play sound from background or player, whatever is available
14305 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14306 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14308 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14310 else if (player_can_move &&
14311 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14313 if (!ACCESS_FROM(element, opposite_direction))
14314 return MP_NO_ACTION; // field not accessible from this direction
14316 if (CAN_MOVE(element)) // only fixed elements can be passed!
14317 return MP_NO_ACTION;
14319 if (IS_EM_GATE(element))
14321 if (!player->key[EM_GATE_NR(element)])
14322 return MP_NO_ACTION;
14324 else if (IS_EM_GATE_GRAY(element))
14326 if (!player->key[EM_GATE_GRAY_NR(element)])
14327 return MP_NO_ACTION;
14329 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14331 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14332 return MP_NO_ACTION;
14334 else if (IS_EMC_GATE(element))
14336 if (!player->key[EMC_GATE_NR(element)])
14337 return MP_NO_ACTION;
14339 else if (IS_EMC_GATE_GRAY(element))
14341 if (!player->key[EMC_GATE_GRAY_NR(element)])
14342 return MP_NO_ACTION;
14344 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14346 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14347 return MP_NO_ACTION;
14349 else if (element == EL_DC_GATE_WHITE ||
14350 element == EL_DC_GATE_WHITE_GRAY ||
14351 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14353 if (player->num_white_keys == 0)
14354 return MP_NO_ACTION;
14356 player->num_white_keys--;
14358 else if (IS_SP_PORT(element))
14360 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14361 element == EL_SP_GRAVITY_PORT_RIGHT ||
14362 element == EL_SP_GRAVITY_PORT_UP ||
14363 element == EL_SP_GRAVITY_PORT_DOWN)
14364 player->gravity = !player->gravity;
14365 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14366 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14367 element == EL_SP_GRAVITY_ON_PORT_UP ||
14368 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14369 player->gravity = TRUE;
14370 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14371 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14372 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14373 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14374 player->gravity = FALSE;
14377 // automatically move to the next field with double speed
14378 player->programmed_action = move_direction;
14380 if (player->move_delay_reset_counter == 0)
14382 player->move_delay_reset_counter = 2; // two double speed steps
14384 DOUBLE_PLAYER_SPEED(player);
14387 PlayLevelSoundAction(x, y, ACTION_PASSING);
14389 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14393 if (mode != DF_SNAP)
14395 GfxElement[x][y] = GFX_ELEMENT(element);
14396 player->is_digging = TRUE;
14399 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14401 // use old behaviour for old levels (digging)
14402 if (!level.finish_dig_collect)
14404 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14405 player->index_bit, dig_side);
14407 // if digging triggered player relocation, finish digging tile
14408 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14409 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14412 if (mode == DF_SNAP)
14414 if (level.block_snap_field)
14415 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14417 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14419 // use old behaviour for old levels (snapping)
14420 if (!level.finish_dig_collect)
14421 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14422 player->index_bit, dig_side);
14425 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14429 if (is_player && mode != DF_SNAP)
14431 GfxElement[x][y] = element;
14432 player->is_collecting = TRUE;
14435 if (element == EL_SPEED_PILL)
14437 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14439 else if (element == EL_EXTRA_TIME && level.time > 0)
14441 TimeLeft += level.extra_time;
14443 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14445 DisplayGameControlValues();
14447 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14449 player->shield_normal_time_left += level.shield_normal_time;
14450 if (element == EL_SHIELD_DEADLY)
14451 player->shield_deadly_time_left += level.shield_deadly_time;
14453 else if (element == EL_DYNAMITE ||
14454 element == EL_EM_DYNAMITE ||
14455 element == EL_SP_DISK_RED)
14457 if (player->inventory_size < MAX_INVENTORY_SIZE)
14458 player->inventory_element[player->inventory_size++] = element;
14460 DrawGameDoorValues();
14462 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14464 player->dynabomb_count++;
14465 player->dynabombs_left++;
14467 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14469 player->dynabomb_size++;
14471 else if (element == EL_DYNABOMB_INCREASE_POWER)
14473 player->dynabomb_xl = TRUE;
14475 else if (IS_KEY(element))
14477 player->key[KEY_NR(element)] = TRUE;
14479 DrawGameDoorValues();
14481 else if (element == EL_DC_KEY_WHITE)
14483 player->num_white_keys++;
14485 // display white keys?
14486 // DrawGameDoorValues();
14488 else if (IS_ENVELOPE(element))
14490 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14492 if (!wait_for_snapping)
14493 player->show_envelope = element;
14495 else if (element == EL_EMC_LENSES)
14497 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14499 RedrawAllInvisibleElementsForLenses();
14501 else if (element == EL_EMC_MAGNIFIER)
14503 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14505 RedrawAllInvisibleElementsForMagnifier();
14507 else if (IS_DROPPABLE(element) ||
14508 IS_THROWABLE(element)) // can be collected and dropped
14512 if (collect_count == 0)
14513 player->inventory_infinite_element = element;
14515 for (i = 0; i < collect_count; i++)
14516 if (player->inventory_size < MAX_INVENTORY_SIZE)
14517 player->inventory_element[player->inventory_size++] = element;
14519 DrawGameDoorValues();
14521 else if (collect_count > 0)
14523 game.gems_still_needed -= collect_count;
14524 if (game.gems_still_needed < 0)
14525 game.gems_still_needed = 0;
14527 game.snapshot.collected_item = TRUE;
14529 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14531 DisplayGameControlValues();
14534 RaiseScoreElement(element);
14535 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14537 // use old behaviour for old levels (collecting)
14538 if (!level.finish_dig_collect && is_player)
14540 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14541 player->index_bit, dig_side);
14543 // if collecting triggered player relocation, finish collecting tile
14544 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14545 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14548 if (mode == DF_SNAP)
14550 if (level.block_snap_field)
14551 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14553 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14555 // use old behaviour for old levels (snapping)
14556 if (!level.finish_dig_collect)
14557 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14558 player->index_bit, dig_side);
14561 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14563 if (mode == DF_SNAP && element != EL_BD_ROCK)
14564 return MP_NO_ACTION;
14566 if (CAN_FALL(element) && dy)
14567 return MP_NO_ACTION;
14569 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14570 !(element == EL_SPRING && level.use_spring_bug))
14571 return MP_NO_ACTION;
14573 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14574 ((move_direction & MV_VERTICAL &&
14575 ((element_info[element].move_pattern & MV_LEFT &&
14576 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14577 (element_info[element].move_pattern & MV_RIGHT &&
14578 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14579 (move_direction & MV_HORIZONTAL &&
14580 ((element_info[element].move_pattern & MV_UP &&
14581 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14582 (element_info[element].move_pattern & MV_DOWN &&
14583 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14584 return MP_NO_ACTION;
14586 // do not push elements already moving away faster than player
14587 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14588 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14589 return MP_NO_ACTION;
14591 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14593 if (player->push_delay_value == -1 || !player_was_pushing)
14594 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14596 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14598 if (player->push_delay_value == -1)
14599 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14601 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14603 if (!player->is_pushing)
14604 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14607 player->is_pushing = TRUE;
14608 player->is_active = TRUE;
14610 if (!(IN_LEV_FIELD(nextx, nexty) &&
14611 (IS_FREE(nextx, nexty) ||
14612 (IS_SB_ELEMENT(element) &&
14613 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14614 (IS_CUSTOM_ELEMENT(element) &&
14615 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14616 return MP_NO_ACTION;
14618 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14619 return MP_NO_ACTION;
14621 if (player->push_delay == -1) // new pushing; restart delay
14622 player->push_delay = 0;
14624 if (player->push_delay < player->push_delay_value &&
14625 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14626 element != EL_SPRING && element != EL_BALLOON)
14628 // make sure that there is no move delay before next try to push
14629 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14630 player->move_delay = 0;
14632 return MP_NO_ACTION;
14635 if (IS_CUSTOM_ELEMENT(element) &&
14636 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14638 if (!DigFieldByCE(nextx, nexty, element))
14639 return MP_NO_ACTION;
14642 if (IS_SB_ELEMENT(element))
14644 boolean sokoban_task_solved = FALSE;
14646 if (element == EL_SOKOBAN_FIELD_FULL)
14648 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14650 IncrementSokobanFieldsNeeded();
14651 IncrementSokobanObjectsNeeded();
14654 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14656 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14658 DecrementSokobanFieldsNeeded();
14659 DecrementSokobanObjectsNeeded();
14661 // sokoban object was pushed from empty field to sokoban field
14662 if (Back[x][y] == EL_EMPTY)
14663 sokoban_task_solved = TRUE;
14666 Tile[x][y] = EL_SOKOBAN_OBJECT;
14668 if (Back[x][y] == Back[nextx][nexty])
14669 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14670 else if (Back[x][y] != 0)
14671 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14674 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14677 if (sokoban_task_solved &&
14678 game.sokoban_fields_still_needed == 0 &&
14679 game.sokoban_objects_still_needed == 0 &&
14680 level.auto_exit_sokoban)
14682 game.players_still_needed = 0;
14686 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14690 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14692 InitMovingField(x, y, move_direction);
14693 GfxAction[x][y] = ACTION_PUSHING;
14695 if (mode == DF_SNAP)
14696 ContinueMoving(x, y);
14698 MovPos[x][y] = (dx != 0 ? dx : dy);
14700 Pushed[x][y] = TRUE;
14701 Pushed[nextx][nexty] = TRUE;
14703 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14704 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14706 player->push_delay_value = -1; // get new value later
14708 // check for element change _after_ element has been pushed
14709 if (game.use_change_when_pushing_bug)
14711 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14712 player->index_bit, dig_side);
14713 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14714 player->index_bit, dig_side);
14717 else if (IS_SWITCHABLE(element))
14719 if (PLAYER_SWITCHING(player, x, y))
14721 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14722 player->index_bit, dig_side);
14727 player->is_switching = TRUE;
14728 player->switch_x = x;
14729 player->switch_y = y;
14731 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14733 if (element == EL_ROBOT_WHEEL)
14735 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14737 game.robot_wheel_x = x;
14738 game.robot_wheel_y = y;
14739 game.robot_wheel_active = TRUE;
14741 TEST_DrawLevelField(x, y);
14743 else if (element == EL_SP_TERMINAL)
14747 SCAN_PLAYFIELD(xx, yy)
14749 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14753 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14755 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14757 ResetGfxAnimation(xx, yy);
14758 TEST_DrawLevelField(xx, yy);
14762 else if (IS_BELT_SWITCH(element))
14764 ToggleBeltSwitch(x, y);
14766 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14767 element == EL_SWITCHGATE_SWITCH_DOWN ||
14768 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14769 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14771 ToggleSwitchgateSwitch(x, y);
14773 else if (element == EL_LIGHT_SWITCH ||
14774 element == EL_LIGHT_SWITCH_ACTIVE)
14776 ToggleLightSwitch(x, y);
14778 else if (element == EL_TIMEGATE_SWITCH ||
14779 element == EL_DC_TIMEGATE_SWITCH)
14781 ActivateTimegateSwitch(x, y);
14783 else if (element == EL_BALLOON_SWITCH_LEFT ||
14784 element == EL_BALLOON_SWITCH_RIGHT ||
14785 element == EL_BALLOON_SWITCH_UP ||
14786 element == EL_BALLOON_SWITCH_DOWN ||
14787 element == EL_BALLOON_SWITCH_NONE ||
14788 element == EL_BALLOON_SWITCH_ANY)
14790 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14791 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14792 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14793 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14794 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14797 else if (element == EL_LAMP)
14799 Tile[x][y] = EL_LAMP_ACTIVE;
14800 game.lights_still_needed--;
14802 ResetGfxAnimation(x, y);
14803 TEST_DrawLevelField(x, y);
14805 else if (element == EL_TIME_ORB_FULL)
14807 Tile[x][y] = EL_TIME_ORB_EMPTY;
14809 if (level.time > 0 || level.use_time_orb_bug)
14811 TimeLeft += level.time_orb_time;
14812 game.no_time_limit = FALSE;
14814 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14816 DisplayGameControlValues();
14819 ResetGfxAnimation(x, y);
14820 TEST_DrawLevelField(x, y);
14822 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14823 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14827 game.ball_active = !game.ball_active;
14829 SCAN_PLAYFIELD(xx, yy)
14831 int e = Tile[xx][yy];
14833 if (game.ball_active)
14835 if (e == EL_EMC_MAGIC_BALL)
14836 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14837 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14838 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14842 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14843 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14844 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14845 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14850 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14851 player->index_bit, dig_side);
14853 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14854 player->index_bit, dig_side);
14856 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14857 player->index_bit, dig_side);
14863 if (!PLAYER_SWITCHING(player, x, y))
14865 player->is_switching = TRUE;
14866 player->switch_x = x;
14867 player->switch_y = y;
14869 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14870 player->index_bit, dig_side);
14871 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14872 player->index_bit, dig_side);
14874 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14875 player->index_bit, dig_side);
14876 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14877 player->index_bit, dig_side);
14880 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14881 player->index_bit, dig_side);
14882 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14883 player->index_bit, dig_side);
14885 return MP_NO_ACTION;
14888 player->push_delay = -1;
14890 if (is_player) // function can also be called by EL_PENGUIN
14892 if (Tile[x][y] != element) // really digged/collected something
14894 player->is_collecting = !player->is_digging;
14895 player->is_active = TRUE;
14897 player->last_removed_element = element;
14904 static boolean DigFieldByCE(int x, int y, int digging_element)
14906 int element = Tile[x][y];
14908 if (!IS_FREE(x, y))
14910 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14911 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14914 // no element can dig solid indestructible elements
14915 if (IS_INDESTRUCTIBLE(element) &&
14916 !IS_DIGGABLE(element) &&
14917 !IS_COLLECTIBLE(element))
14920 if (AmoebaNr[x][y] &&
14921 (element == EL_AMOEBA_FULL ||
14922 element == EL_BD_AMOEBA ||
14923 element == EL_AMOEBA_GROWING))
14925 AmoebaCnt[AmoebaNr[x][y]]--;
14926 AmoebaCnt2[AmoebaNr[x][y]]--;
14929 if (IS_MOVING(x, y))
14930 RemoveMovingField(x, y);
14934 TEST_DrawLevelField(x, y);
14937 // if digged element was about to explode, prevent the explosion
14938 ExplodeField[x][y] = EX_TYPE_NONE;
14940 PlayLevelSoundAction(x, y, action);
14943 Store[x][y] = EL_EMPTY;
14945 // this makes it possible to leave the removed element again
14946 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14947 Store[x][y] = element;
14952 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14954 int jx = player->jx, jy = player->jy;
14955 int x = jx + dx, y = jy + dy;
14956 int snap_direction = (dx == -1 ? MV_LEFT :
14957 dx == +1 ? MV_RIGHT :
14959 dy == +1 ? MV_DOWN : MV_NONE);
14960 boolean can_continue_snapping = (level.continuous_snapping &&
14961 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14963 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14966 if (!player->active || !IN_LEV_FIELD(x, y))
14974 if (player->MovPos == 0)
14975 player->is_pushing = FALSE;
14977 player->is_snapping = FALSE;
14979 if (player->MovPos == 0)
14981 player->is_moving = FALSE;
14982 player->is_digging = FALSE;
14983 player->is_collecting = FALSE;
14989 // prevent snapping with already pressed snap key when not allowed
14990 if (player->is_snapping && !can_continue_snapping)
14993 player->MovDir = snap_direction;
14995 if (player->MovPos == 0)
14997 player->is_moving = FALSE;
14998 player->is_digging = FALSE;
14999 player->is_collecting = FALSE;
15002 player->is_dropping = FALSE;
15003 player->is_dropping_pressed = FALSE;
15004 player->drop_pressed_delay = 0;
15006 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15009 player->is_snapping = TRUE;
15010 player->is_active = TRUE;
15012 if (player->MovPos == 0)
15014 player->is_moving = FALSE;
15015 player->is_digging = FALSE;
15016 player->is_collecting = FALSE;
15019 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15020 TEST_DrawLevelField(player->last_jx, player->last_jy);
15022 TEST_DrawLevelField(x, y);
15027 static boolean DropElement(struct PlayerInfo *player)
15029 int old_element, new_element;
15030 int dropx = player->jx, dropy = player->jy;
15031 int drop_direction = player->MovDir;
15032 int drop_side = drop_direction;
15033 int drop_element = get_next_dropped_element(player);
15035 /* do not drop an element on top of another element; when holding drop key
15036 pressed without moving, dropped element must move away before the next
15037 element can be dropped (this is especially important if the next element
15038 is dynamite, which can be placed on background for historical reasons) */
15039 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15042 if (IS_THROWABLE(drop_element))
15044 dropx += GET_DX_FROM_DIR(drop_direction);
15045 dropy += GET_DY_FROM_DIR(drop_direction);
15047 if (!IN_LEV_FIELD(dropx, dropy))
15051 old_element = Tile[dropx][dropy]; // old element at dropping position
15052 new_element = drop_element; // default: no change when dropping
15054 // check if player is active, not moving and ready to drop
15055 if (!player->active || player->MovPos || player->drop_delay > 0)
15058 // check if player has anything that can be dropped
15059 if (new_element == EL_UNDEFINED)
15062 // only set if player has anything that can be dropped
15063 player->is_dropping_pressed = TRUE;
15065 // check if drop key was pressed long enough for EM style dynamite
15066 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15069 // check if anything can be dropped at the current position
15070 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15073 // collected custom elements can only be dropped on empty fields
15074 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15077 if (old_element != EL_EMPTY)
15078 Back[dropx][dropy] = old_element; // store old element on this field
15080 ResetGfxAnimation(dropx, dropy);
15081 ResetRandomAnimationValue(dropx, dropy);
15083 if (player->inventory_size > 0 ||
15084 player->inventory_infinite_element != EL_UNDEFINED)
15086 if (player->inventory_size > 0)
15088 player->inventory_size--;
15090 DrawGameDoorValues();
15092 if (new_element == EL_DYNAMITE)
15093 new_element = EL_DYNAMITE_ACTIVE;
15094 else if (new_element == EL_EM_DYNAMITE)
15095 new_element = EL_EM_DYNAMITE_ACTIVE;
15096 else if (new_element == EL_SP_DISK_RED)
15097 new_element = EL_SP_DISK_RED_ACTIVE;
15100 Tile[dropx][dropy] = new_element;
15102 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15103 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15104 el2img(Tile[dropx][dropy]), 0);
15106 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15108 // needed if previous element just changed to "empty" in the last frame
15109 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15111 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15112 player->index_bit, drop_side);
15113 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15115 player->index_bit, drop_side);
15117 TestIfElementTouchesCustomElement(dropx, dropy);
15119 else // player is dropping a dyna bomb
15121 player->dynabombs_left--;
15123 Tile[dropx][dropy] = new_element;
15125 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15126 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15127 el2img(Tile[dropx][dropy]), 0);
15129 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15132 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15133 InitField_WithBug1(dropx, dropy, FALSE);
15135 new_element = Tile[dropx][dropy]; // element might have changed
15137 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15138 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15140 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15141 MovDir[dropx][dropy] = drop_direction;
15143 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15145 // do not cause impact style collision by dropping elements that can fall
15146 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15149 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15150 player->is_dropping = TRUE;
15152 player->drop_pressed_delay = 0;
15153 player->is_dropping_pressed = FALSE;
15155 player->drop_x = dropx;
15156 player->drop_y = dropy;
15161 // ----------------------------------------------------------------------------
15162 // game sound playing functions
15163 // ----------------------------------------------------------------------------
15165 static int *loop_sound_frame = NULL;
15166 static int *loop_sound_volume = NULL;
15168 void InitPlayLevelSound(void)
15170 int num_sounds = getSoundListSize();
15172 checked_free(loop_sound_frame);
15173 checked_free(loop_sound_volume);
15175 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15176 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15179 static void PlayLevelSound(int x, int y, int nr)
15181 int sx = SCREENX(x), sy = SCREENY(y);
15182 int volume, stereo_position;
15183 int max_distance = 8;
15184 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15186 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15187 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15190 if (!IN_LEV_FIELD(x, y) ||
15191 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15192 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15195 volume = SOUND_MAX_VOLUME;
15197 if (!IN_SCR_FIELD(sx, sy))
15199 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15200 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15202 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15205 stereo_position = (SOUND_MAX_LEFT +
15206 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15207 (SCR_FIELDX + 2 * max_distance));
15209 if (IS_LOOP_SOUND(nr))
15211 /* This assures that quieter loop sounds do not overwrite louder ones,
15212 while restarting sound volume comparison with each new game frame. */
15214 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15217 loop_sound_volume[nr] = volume;
15218 loop_sound_frame[nr] = FrameCounter;
15221 PlaySoundExt(nr, volume, stereo_position, type);
15224 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15226 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15227 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15228 y < LEVELY(BY1) ? LEVELY(BY1) :
15229 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15233 static void PlayLevelSoundAction(int x, int y, int action)
15235 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15238 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15240 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15242 if (sound_effect != SND_UNDEFINED)
15243 PlayLevelSound(x, y, sound_effect);
15246 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15249 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15251 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15252 PlayLevelSound(x, y, sound_effect);
15255 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15257 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15259 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15260 PlayLevelSound(x, y, sound_effect);
15263 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15265 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15267 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15268 StopSound(sound_effect);
15271 static int getLevelMusicNr(void)
15273 if (levelset.music[level_nr] != MUS_UNDEFINED)
15274 return levelset.music[level_nr]; // from config file
15276 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15279 static void FadeLevelSounds(void)
15284 static void FadeLevelMusic(void)
15286 int music_nr = getLevelMusicNr();
15287 char *curr_music = getCurrentlyPlayingMusicFilename();
15288 char *next_music = getMusicInfoEntryFilename(music_nr);
15290 if (!strEqual(curr_music, next_music))
15294 void FadeLevelSoundsAndMusic(void)
15300 static void PlayLevelMusic(void)
15302 int music_nr = getLevelMusicNr();
15303 char *curr_music = getCurrentlyPlayingMusicFilename();
15304 char *next_music = getMusicInfoEntryFilename(music_nr);
15306 if (!strEqual(curr_music, next_music))
15307 PlayMusicLoop(music_nr);
15310 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15312 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15314 int x = xx - offset;
15315 int y = yy - offset;
15320 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15324 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15328 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15332 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15336 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15340 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15344 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15347 case SOUND_android_clone:
15348 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15351 case SOUND_android_move:
15352 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15356 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15360 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15364 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15367 case SOUND_eater_eat:
15368 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15372 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15375 case SOUND_collect:
15376 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15379 case SOUND_diamond:
15380 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15384 // !!! CHECK THIS !!!
15386 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15388 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15392 case SOUND_wonderfall:
15393 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15397 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15401 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15405 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15409 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15413 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15417 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15421 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15425 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15428 case SOUND_exit_open:
15429 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15432 case SOUND_exit_leave:
15433 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15436 case SOUND_dynamite:
15437 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15441 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15445 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15449 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15453 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15457 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15461 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15465 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15470 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15472 int element = map_element_SP_to_RND(element_sp);
15473 int action = map_action_SP_to_RND(action_sp);
15474 int offset = (setup.sp_show_border_elements ? 0 : 1);
15475 int x = xx - offset;
15476 int y = yy - offset;
15478 PlayLevelSoundElementAction(x, y, element, action);
15481 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15483 int element = map_element_MM_to_RND(element_mm);
15484 int action = map_action_MM_to_RND(action_mm);
15486 int x = xx - offset;
15487 int y = yy - offset;
15489 if (!IS_MM_ELEMENT(element))
15490 element = EL_MM_DEFAULT;
15492 PlayLevelSoundElementAction(x, y, element, action);
15495 void PlaySound_MM(int sound_mm)
15497 int sound = map_sound_MM_to_RND(sound_mm);
15499 if (sound == SND_UNDEFINED)
15505 void PlaySoundLoop_MM(int sound_mm)
15507 int sound = map_sound_MM_to_RND(sound_mm);
15509 if (sound == SND_UNDEFINED)
15512 PlaySoundLoop(sound);
15515 void StopSound_MM(int sound_mm)
15517 int sound = map_sound_MM_to_RND(sound_mm);
15519 if (sound == SND_UNDEFINED)
15525 void RaiseScore(int value)
15527 game.score += value;
15529 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15531 DisplayGameControlValues();
15534 void RaiseScoreElement(int element)
15539 case EL_BD_DIAMOND:
15540 case EL_EMERALD_YELLOW:
15541 case EL_EMERALD_RED:
15542 case EL_EMERALD_PURPLE:
15543 case EL_SP_INFOTRON:
15544 RaiseScore(level.score[SC_EMERALD]);
15547 RaiseScore(level.score[SC_DIAMOND]);
15550 RaiseScore(level.score[SC_CRYSTAL]);
15553 RaiseScore(level.score[SC_PEARL]);
15556 case EL_BD_BUTTERFLY:
15557 case EL_SP_ELECTRON:
15558 RaiseScore(level.score[SC_BUG]);
15561 case EL_BD_FIREFLY:
15562 case EL_SP_SNIKSNAK:
15563 RaiseScore(level.score[SC_SPACESHIP]);
15566 case EL_DARK_YAMYAM:
15567 RaiseScore(level.score[SC_YAMYAM]);
15570 RaiseScore(level.score[SC_ROBOT]);
15573 RaiseScore(level.score[SC_PACMAN]);
15576 RaiseScore(level.score[SC_NUT]);
15579 case EL_EM_DYNAMITE:
15580 case EL_SP_DISK_RED:
15581 case EL_DYNABOMB_INCREASE_NUMBER:
15582 case EL_DYNABOMB_INCREASE_SIZE:
15583 case EL_DYNABOMB_INCREASE_POWER:
15584 RaiseScore(level.score[SC_DYNAMITE]);
15586 case EL_SHIELD_NORMAL:
15587 case EL_SHIELD_DEADLY:
15588 RaiseScore(level.score[SC_SHIELD]);
15590 case EL_EXTRA_TIME:
15591 RaiseScore(level.extra_time_score);
15605 case EL_DC_KEY_WHITE:
15606 RaiseScore(level.score[SC_KEY]);
15609 RaiseScore(element_info[element].collect_score);
15614 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15616 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15620 // prevent short reactivation of overlay buttons while closing door
15621 SetOverlayActive(FALSE);
15623 // door may still be open due to skipped or envelope style request
15624 CloseDoor(DOOR_CLOSE_1);
15627 if (network.enabled)
15628 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15632 FadeSkipNextFadeIn();
15634 SetGameStatus(GAME_MODE_MAIN);
15639 else // continue playing the game
15641 if (tape.playing && tape.deactivate_display)
15642 TapeDeactivateDisplayOff(TRUE);
15644 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15646 if (tape.playing && tape.deactivate_display)
15647 TapeDeactivateDisplayOn();
15651 void RequestQuitGame(boolean escape_key_pressed)
15653 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15654 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15655 level_editor_test_game);
15656 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15659 RequestQuitGameExt(skip_request, quick_quit,
15660 "Do you really want to quit the game?");
15663 void RequestRestartGame(char *message)
15665 game.restart_game_message = NULL;
15667 boolean has_started_game = hasStartedNetworkGame();
15668 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15670 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15672 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15676 // needed in case of envelope request to close game panel
15677 CloseDoor(DOOR_CLOSE_1);
15679 SetGameStatus(GAME_MODE_MAIN);
15685 void CheckGameOver(void)
15687 static boolean last_game_over = FALSE;
15688 static int game_over_delay = 0;
15689 int game_over_delay_value = 50;
15690 boolean game_over = checkGameFailed();
15692 // do not handle game over if request dialog is already active
15693 if (game.request_active)
15696 // do not ask to play again if game was never actually played
15697 if (!game.GamePlayed)
15702 last_game_over = FALSE;
15703 game_over_delay = game_over_delay_value;
15708 if (game_over_delay > 0)
15715 if (last_game_over != game_over)
15716 game.restart_game_message = (hasStartedNetworkGame() ?
15717 "Game over! Play it again?" :
15720 last_game_over = game_over;
15723 boolean checkGameSolved(void)
15725 // set for all game engines if level was solved
15726 return game.LevelSolved_GameEnd;
15729 boolean checkGameFailed(void)
15731 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15732 return (game_em.game_over && !game_em.level_solved);
15733 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15734 return (game_sp.game_over && !game_sp.level_solved);
15735 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15736 return (game_mm.game_over && !game_mm.level_solved);
15737 else // GAME_ENGINE_TYPE_RND
15738 return (game.GameOver && !game.LevelSolved);
15741 boolean checkGameEnded(void)
15743 return (checkGameSolved() || checkGameFailed());
15747 // ----------------------------------------------------------------------------
15748 // random generator functions
15749 // ----------------------------------------------------------------------------
15751 unsigned int InitEngineRandom_RND(int seed)
15753 game.num_random_calls = 0;
15755 return InitEngineRandom(seed);
15758 unsigned int RND(int max)
15762 game.num_random_calls++;
15764 return GetEngineRandom(max);
15771 // ----------------------------------------------------------------------------
15772 // game engine snapshot handling functions
15773 // ----------------------------------------------------------------------------
15775 struct EngineSnapshotInfo
15777 // runtime values for custom element collect score
15778 int collect_score[NUM_CUSTOM_ELEMENTS];
15780 // runtime values for group element choice position
15781 int choice_pos[NUM_GROUP_ELEMENTS];
15783 // runtime values for belt position animations
15784 int belt_graphic[4][NUM_BELT_PARTS];
15785 int belt_anim_mode[4][NUM_BELT_PARTS];
15788 static struct EngineSnapshotInfo engine_snapshot_rnd;
15789 static char *snapshot_level_identifier = NULL;
15790 static int snapshot_level_nr = -1;
15792 static void SaveEngineSnapshotValues_RND(void)
15794 static int belt_base_active_element[4] =
15796 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15797 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15798 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15799 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15803 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15805 int element = EL_CUSTOM_START + i;
15807 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15810 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15812 int element = EL_GROUP_START + i;
15814 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15817 for (i = 0; i < 4; i++)
15819 for (j = 0; j < NUM_BELT_PARTS; j++)
15821 int element = belt_base_active_element[i] + j;
15822 int graphic = el2img(element);
15823 int anim_mode = graphic_info[graphic].anim_mode;
15825 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15826 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15831 static void LoadEngineSnapshotValues_RND(void)
15833 unsigned int num_random_calls = game.num_random_calls;
15836 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15838 int element = EL_CUSTOM_START + i;
15840 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15843 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15845 int element = EL_GROUP_START + i;
15847 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15850 for (i = 0; i < 4; i++)
15852 for (j = 0; j < NUM_BELT_PARTS; j++)
15854 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15855 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15857 graphic_info[graphic].anim_mode = anim_mode;
15861 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15863 InitRND(tape.random_seed);
15864 for (i = 0; i < num_random_calls; i++)
15868 if (game.num_random_calls != num_random_calls)
15870 Error("number of random calls out of sync");
15871 Error("number of random calls should be %d", num_random_calls);
15872 Error("number of random calls is %d", game.num_random_calls);
15874 Fail("this should not happen -- please debug");
15878 void FreeEngineSnapshotSingle(void)
15880 FreeSnapshotSingle();
15882 setString(&snapshot_level_identifier, NULL);
15883 snapshot_level_nr = -1;
15886 void FreeEngineSnapshotList(void)
15888 FreeSnapshotList();
15891 static ListNode *SaveEngineSnapshotBuffers(void)
15893 ListNode *buffers = NULL;
15895 // copy some special values to a structure better suited for the snapshot
15897 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15898 SaveEngineSnapshotValues_RND();
15899 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15900 SaveEngineSnapshotValues_EM();
15901 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15902 SaveEngineSnapshotValues_SP(&buffers);
15903 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15904 SaveEngineSnapshotValues_MM(&buffers);
15906 // save values stored in special snapshot structure
15908 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15909 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15910 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15911 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15912 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15913 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15914 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15915 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15917 // save further RND engine values
15919 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15920 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15921 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15923 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15924 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15925 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15927 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15929 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15930 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15931 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15933 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15935 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15936 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15939 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15940 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15945 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15951 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15974 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15977 ListNode *node = engine_snapshot_list_rnd;
15980 while (node != NULL)
15982 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15987 Debug("game:playing:SaveEngineSnapshotBuffers",
15988 "size of engine snapshot: %d bytes", num_bytes);
15994 void SaveEngineSnapshotSingle(void)
15996 ListNode *buffers = SaveEngineSnapshotBuffers();
15998 // finally save all snapshot buffers to single snapshot
15999 SaveSnapshotSingle(buffers);
16001 // save level identification information
16002 setString(&snapshot_level_identifier, leveldir_current->identifier);
16003 snapshot_level_nr = level_nr;
16006 boolean CheckSaveEngineSnapshotToList(void)
16008 boolean save_snapshot =
16009 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16010 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16011 game.snapshot.changed_action) ||
16012 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16013 game.snapshot.collected_item));
16015 game.snapshot.changed_action = FALSE;
16016 game.snapshot.collected_item = FALSE;
16017 game.snapshot.save_snapshot = save_snapshot;
16019 return save_snapshot;
16022 void SaveEngineSnapshotToList(void)
16024 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16028 ListNode *buffers = SaveEngineSnapshotBuffers();
16030 // finally save all snapshot buffers to snapshot list
16031 SaveSnapshotToList(buffers);
16034 void SaveEngineSnapshotToListInitial(void)
16036 FreeEngineSnapshotList();
16038 SaveEngineSnapshotToList();
16041 static void LoadEngineSnapshotValues(void)
16043 // restore special values from snapshot structure
16045 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16046 LoadEngineSnapshotValues_RND();
16047 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16048 LoadEngineSnapshotValues_EM();
16049 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16050 LoadEngineSnapshotValues_SP();
16051 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16052 LoadEngineSnapshotValues_MM();
16055 void LoadEngineSnapshotSingle(void)
16057 LoadSnapshotSingle();
16059 LoadEngineSnapshotValues();
16062 static void LoadEngineSnapshot_Undo(int steps)
16064 LoadSnapshotFromList_Older(steps);
16066 LoadEngineSnapshotValues();
16069 static void LoadEngineSnapshot_Redo(int steps)
16071 LoadSnapshotFromList_Newer(steps);
16073 LoadEngineSnapshotValues();
16076 boolean CheckEngineSnapshotSingle(void)
16078 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16079 snapshot_level_nr == level_nr);
16082 boolean CheckEngineSnapshotList(void)
16084 return CheckSnapshotList();
16088 // ---------- new game button stuff -------------------------------------------
16095 boolean *setup_value;
16096 boolean allowed_on_tape;
16097 boolean is_touch_button;
16099 } gamebutton_info[NUM_GAME_BUTTONS] =
16102 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16103 GAME_CTRL_ID_STOP, NULL,
16104 TRUE, FALSE, "stop game"
16107 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16108 GAME_CTRL_ID_PAUSE, NULL,
16109 TRUE, FALSE, "pause game"
16112 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16113 GAME_CTRL_ID_PLAY, NULL,
16114 TRUE, FALSE, "play game"
16117 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16118 GAME_CTRL_ID_UNDO, NULL,
16119 TRUE, FALSE, "undo step"
16122 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16123 GAME_CTRL_ID_REDO, NULL,
16124 TRUE, FALSE, "redo step"
16127 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16128 GAME_CTRL_ID_SAVE, NULL,
16129 TRUE, FALSE, "save game"
16132 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16133 GAME_CTRL_ID_PAUSE2, NULL,
16134 TRUE, FALSE, "pause game"
16137 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16138 GAME_CTRL_ID_LOAD, NULL,
16139 TRUE, FALSE, "load game"
16142 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16143 GAME_CTRL_ID_PANEL_STOP, NULL,
16144 FALSE, FALSE, "stop game"
16147 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16148 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16149 FALSE, FALSE, "pause game"
16152 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16153 GAME_CTRL_ID_PANEL_PLAY, NULL,
16154 FALSE, FALSE, "play game"
16157 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16158 GAME_CTRL_ID_TOUCH_STOP, NULL,
16159 FALSE, TRUE, "stop game"
16162 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16163 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16164 FALSE, TRUE, "pause game"
16167 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16168 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16169 TRUE, FALSE, "background music on/off"
16172 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16173 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16174 TRUE, FALSE, "sound loops on/off"
16177 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16178 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16179 TRUE, FALSE, "normal sounds on/off"
16182 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16183 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16184 FALSE, FALSE, "background music on/off"
16187 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16188 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16189 FALSE, FALSE, "sound loops on/off"
16192 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16193 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16194 FALSE, FALSE, "normal sounds on/off"
16198 void CreateGameButtons(void)
16202 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16204 int graphic = gamebutton_info[i].graphic;
16205 struct GraphicInfo *gfx = &graphic_info[graphic];
16206 struct XY *pos = gamebutton_info[i].pos;
16207 struct GadgetInfo *gi;
16210 unsigned int event_mask;
16211 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16212 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16213 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16214 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16215 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16216 int gd_x = gfx->src_x;
16217 int gd_y = gfx->src_y;
16218 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16219 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16220 int gd_xa = gfx->src_x + gfx->active_xoffset;
16221 int gd_ya = gfx->src_y + gfx->active_yoffset;
16222 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16223 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16224 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16225 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16228 if (gfx->bitmap == NULL)
16230 game_gadget[id] = NULL;
16235 if (id == GAME_CTRL_ID_STOP ||
16236 id == GAME_CTRL_ID_PANEL_STOP ||
16237 id == GAME_CTRL_ID_TOUCH_STOP ||
16238 id == GAME_CTRL_ID_PLAY ||
16239 id == GAME_CTRL_ID_PANEL_PLAY ||
16240 id == GAME_CTRL_ID_SAVE ||
16241 id == GAME_CTRL_ID_LOAD)
16243 button_type = GD_TYPE_NORMAL_BUTTON;
16245 event_mask = GD_EVENT_RELEASED;
16247 else if (id == GAME_CTRL_ID_UNDO ||
16248 id == GAME_CTRL_ID_REDO)
16250 button_type = GD_TYPE_NORMAL_BUTTON;
16252 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16256 button_type = GD_TYPE_CHECK_BUTTON;
16257 checked = (gamebutton_info[i].setup_value != NULL ?
16258 *gamebutton_info[i].setup_value : FALSE);
16259 event_mask = GD_EVENT_PRESSED;
16262 gi = CreateGadget(GDI_CUSTOM_ID, id,
16263 GDI_IMAGE_ID, graphic,
16264 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16267 GDI_WIDTH, gfx->width,
16268 GDI_HEIGHT, gfx->height,
16269 GDI_TYPE, button_type,
16270 GDI_STATE, GD_BUTTON_UNPRESSED,
16271 GDI_CHECKED, checked,
16272 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16273 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16274 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16275 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16276 GDI_DIRECT_DRAW, FALSE,
16277 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16278 GDI_EVENT_MASK, event_mask,
16279 GDI_CALLBACK_ACTION, HandleGameButtons,
16283 Fail("cannot create gadget");
16285 game_gadget[id] = gi;
16289 void FreeGameButtons(void)
16293 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16294 FreeGadget(game_gadget[i]);
16297 static void UnmapGameButtonsAtSamePosition(int id)
16301 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16303 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16304 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16305 UnmapGadget(game_gadget[i]);
16308 static void UnmapGameButtonsAtSamePosition_All(void)
16310 if (setup.show_load_save_buttons)
16312 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16313 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16314 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16316 else if (setup.show_undo_redo_buttons)
16318 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16319 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16320 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16324 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16325 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16326 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16328 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16329 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16330 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16334 void MapLoadSaveButtons(void)
16336 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16337 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16339 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16340 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16343 void MapUndoRedoButtons(void)
16345 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16346 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16348 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16349 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16352 void ModifyPauseButtons(void)
16356 GAME_CTRL_ID_PAUSE,
16357 GAME_CTRL_ID_PAUSE2,
16358 GAME_CTRL_ID_PANEL_PAUSE,
16359 GAME_CTRL_ID_TOUCH_PAUSE,
16364 for (i = 0; ids[i] > -1; i++)
16365 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16368 static void MapGameButtonsExt(boolean on_tape)
16372 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16373 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16374 MapGadget(game_gadget[i]);
16376 UnmapGameButtonsAtSamePosition_All();
16378 RedrawGameButtons();
16381 static void UnmapGameButtonsExt(boolean on_tape)
16385 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16386 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16387 UnmapGadget(game_gadget[i]);
16390 static void RedrawGameButtonsExt(boolean on_tape)
16394 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16395 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16396 RedrawGadget(game_gadget[i]);
16399 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16404 gi->checked = state;
16407 static void RedrawSoundButtonGadget(int id)
16409 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16410 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16411 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16412 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16413 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16414 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16417 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16418 RedrawGadget(game_gadget[id2]);
16421 void MapGameButtons(void)
16423 MapGameButtonsExt(FALSE);
16426 void UnmapGameButtons(void)
16428 UnmapGameButtonsExt(FALSE);
16431 void RedrawGameButtons(void)
16433 RedrawGameButtonsExt(FALSE);
16436 void MapGameButtonsOnTape(void)
16438 MapGameButtonsExt(TRUE);
16441 void UnmapGameButtonsOnTape(void)
16443 UnmapGameButtonsExt(TRUE);
16446 void RedrawGameButtonsOnTape(void)
16448 RedrawGameButtonsExt(TRUE);
16451 static void GameUndoRedoExt(void)
16453 ClearPlayerAction();
16455 tape.pausing = TRUE;
16458 UpdateAndDisplayGameControlValues();
16460 DrawCompleteVideoDisplay();
16461 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16462 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16463 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16465 ModifyPauseButtons();
16470 static void GameUndo(int steps)
16472 if (!CheckEngineSnapshotList())
16475 int tape_property_bits = tape.property_bits;
16477 LoadEngineSnapshot_Undo(steps);
16479 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16484 static void GameRedo(int steps)
16486 if (!CheckEngineSnapshotList())
16489 int tape_property_bits = tape.property_bits;
16491 LoadEngineSnapshot_Redo(steps);
16493 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16498 static void HandleGameButtonsExt(int id, int button)
16500 static boolean game_undo_executed = FALSE;
16501 int steps = BUTTON_STEPSIZE(button);
16502 boolean handle_game_buttons =
16503 (game_status == GAME_MODE_PLAYING ||
16504 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16506 if (!handle_game_buttons)
16511 case GAME_CTRL_ID_STOP:
16512 case GAME_CTRL_ID_PANEL_STOP:
16513 case GAME_CTRL_ID_TOUCH_STOP:
16514 if (game_status == GAME_MODE_MAIN)
16520 RequestQuitGame(FALSE);
16524 case GAME_CTRL_ID_PAUSE:
16525 case GAME_CTRL_ID_PAUSE2:
16526 case GAME_CTRL_ID_PANEL_PAUSE:
16527 case GAME_CTRL_ID_TOUCH_PAUSE:
16528 if (network.enabled && game_status == GAME_MODE_PLAYING)
16531 SendToServer_ContinuePlaying();
16533 SendToServer_PausePlaying();
16536 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16538 game_undo_executed = FALSE;
16542 case GAME_CTRL_ID_PLAY:
16543 case GAME_CTRL_ID_PANEL_PLAY:
16544 if (game_status == GAME_MODE_MAIN)
16546 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16548 else if (tape.pausing)
16550 if (network.enabled)
16551 SendToServer_ContinuePlaying();
16553 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16557 case GAME_CTRL_ID_UNDO:
16558 // Important: When using "save snapshot when collecting an item" mode,
16559 // load last (current) snapshot for first "undo" after pressing "pause"
16560 // (else the last-but-one snapshot would be loaded, because the snapshot
16561 // pointer already points to the last snapshot when pressing "pause",
16562 // which is fine for "every step/move" mode, but not for "every collect")
16563 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16564 !game_undo_executed)
16567 game_undo_executed = TRUE;
16572 case GAME_CTRL_ID_REDO:
16576 case GAME_CTRL_ID_SAVE:
16580 case GAME_CTRL_ID_LOAD:
16584 case SOUND_CTRL_ID_MUSIC:
16585 case SOUND_CTRL_ID_PANEL_MUSIC:
16586 if (setup.sound_music)
16588 setup.sound_music = FALSE;
16592 else if (audio.music_available)
16594 setup.sound = setup.sound_music = TRUE;
16596 SetAudioMode(setup.sound);
16598 if (game_status == GAME_MODE_PLAYING)
16602 RedrawSoundButtonGadget(id);
16606 case SOUND_CTRL_ID_LOOPS:
16607 case SOUND_CTRL_ID_PANEL_LOOPS:
16608 if (setup.sound_loops)
16609 setup.sound_loops = FALSE;
16610 else if (audio.loops_available)
16612 setup.sound = setup.sound_loops = TRUE;
16614 SetAudioMode(setup.sound);
16617 RedrawSoundButtonGadget(id);
16621 case SOUND_CTRL_ID_SIMPLE:
16622 case SOUND_CTRL_ID_PANEL_SIMPLE:
16623 if (setup.sound_simple)
16624 setup.sound_simple = FALSE;
16625 else if (audio.sound_available)
16627 setup.sound = setup.sound_simple = TRUE;
16629 SetAudioMode(setup.sound);
16632 RedrawSoundButtonGadget(id);
16641 static void HandleGameButtons(struct GadgetInfo *gi)
16643 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16646 void HandleSoundButtonKeys(Key key)
16648 if (key == setup.shortcut.sound_simple)
16649 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16650 else if (key == setup.shortcut.sound_loops)
16651 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16652 else if (key == setup.shortcut.sound_music)
16653 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);