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)
4483 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4486 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4487 int actual_player_x, int actual_player_y)
4489 // this is used for non-R'n'D game engines to update certain engine values
4491 // needed to determine if sounds are played within the visible screen area
4492 scroll_x = actual_scroll_x;
4493 scroll_y = actual_scroll_y;
4495 // needed to get player position for "follow finger" playing input method
4496 local_player->jx = actual_player_x;
4497 local_player->jy = actual_player_y;
4500 void InitMovDir(int x, int y)
4502 int i, element = Tile[x][y];
4503 static int xy[4][2] =
4510 static int direction[3][4] =
4512 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4513 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4514 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4523 Tile[x][y] = EL_BUG;
4524 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4527 case EL_SPACESHIP_RIGHT:
4528 case EL_SPACESHIP_UP:
4529 case EL_SPACESHIP_LEFT:
4530 case EL_SPACESHIP_DOWN:
4531 Tile[x][y] = EL_SPACESHIP;
4532 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4535 case EL_BD_BUTTERFLY_RIGHT:
4536 case EL_BD_BUTTERFLY_UP:
4537 case EL_BD_BUTTERFLY_LEFT:
4538 case EL_BD_BUTTERFLY_DOWN:
4539 Tile[x][y] = EL_BD_BUTTERFLY;
4540 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4543 case EL_BD_FIREFLY_RIGHT:
4544 case EL_BD_FIREFLY_UP:
4545 case EL_BD_FIREFLY_LEFT:
4546 case EL_BD_FIREFLY_DOWN:
4547 Tile[x][y] = EL_BD_FIREFLY;
4548 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4551 case EL_PACMAN_RIGHT:
4553 case EL_PACMAN_LEFT:
4554 case EL_PACMAN_DOWN:
4555 Tile[x][y] = EL_PACMAN;
4556 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4559 case EL_YAMYAM_LEFT:
4560 case EL_YAMYAM_RIGHT:
4562 case EL_YAMYAM_DOWN:
4563 Tile[x][y] = EL_YAMYAM;
4564 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4567 case EL_SP_SNIKSNAK:
4568 MovDir[x][y] = MV_UP;
4571 case EL_SP_ELECTRON:
4572 MovDir[x][y] = MV_LEFT;
4579 Tile[x][y] = EL_MOLE;
4580 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4583 case EL_SPRING_LEFT:
4584 case EL_SPRING_RIGHT:
4585 Tile[x][y] = EL_SPRING;
4586 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4590 if (IS_CUSTOM_ELEMENT(element))
4592 struct ElementInfo *ei = &element_info[element];
4593 int move_direction_initial = ei->move_direction_initial;
4594 int move_pattern = ei->move_pattern;
4596 if (move_direction_initial == MV_START_PREVIOUS)
4598 if (MovDir[x][y] != MV_NONE)
4601 move_direction_initial = MV_START_AUTOMATIC;
4604 if (move_direction_initial == MV_START_RANDOM)
4605 MovDir[x][y] = 1 << RND(4);
4606 else if (move_direction_initial & MV_ANY_DIRECTION)
4607 MovDir[x][y] = move_direction_initial;
4608 else if (move_pattern == MV_ALL_DIRECTIONS ||
4609 move_pattern == MV_TURNING_LEFT ||
4610 move_pattern == MV_TURNING_RIGHT ||
4611 move_pattern == MV_TURNING_LEFT_RIGHT ||
4612 move_pattern == MV_TURNING_RIGHT_LEFT ||
4613 move_pattern == MV_TURNING_RANDOM)
4614 MovDir[x][y] = 1 << RND(4);
4615 else if (move_pattern == MV_HORIZONTAL)
4616 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4617 else if (move_pattern == MV_VERTICAL)
4618 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4619 else if (move_pattern & MV_ANY_DIRECTION)
4620 MovDir[x][y] = element_info[element].move_pattern;
4621 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4622 move_pattern == MV_ALONG_RIGHT_SIDE)
4624 // use random direction as default start direction
4625 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4626 MovDir[x][y] = 1 << RND(4);
4628 for (i = 0; i < NUM_DIRECTIONS; i++)
4630 int x1 = x + xy[i][0];
4631 int y1 = y + xy[i][1];
4633 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4635 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4636 MovDir[x][y] = direction[0][i];
4638 MovDir[x][y] = direction[1][i];
4647 MovDir[x][y] = 1 << RND(4);
4649 if (element != EL_BUG &&
4650 element != EL_SPACESHIP &&
4651 element != EL_BD_BUTTERFLY &&
4652 element != EL_BD_FIREFLY)
4655 for (i = 0; i < NUM_DIRECTIONS; i++)
4657 int x1 = x + xy[i][0];
4658 int y1 = y + xy[i][1];
4660 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4662 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4664 MovDir[x][y] = direction[0][i];
4667 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4668 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4670 MovDir[x][y] = direction[1][i];
4679 GfxDir[x][y] = MovDir[x][y];
4682 void InitAmoebaNr(int x, int y)
4685 int group_nr = AmoebaNeighbourNr(x, y);
4689 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4691 if (AmoebaCnt[i] == 0)
4699 AmoebaNr[x][y] = group_nr;
4700 AmoebaCnt[group_nr]++;
4701 AmoebaCnt2[group_nr]++;
4704 static void LevelSolved_SetFinalGameValues(void)
4706 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4707 game.score_time_final = (level.use_step_counter ? TimePlayed :
4708 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4710 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4711 game_em.lev->score :
4712 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4716 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4717 MM_HEALTH(game_mm.laser_overload_value) :
4720 game.LevelSolved_CountingTime = game.time_final;
4721 game.LevelSolved_CountingScore = game.score_final;
4722 game.LevelSolved_CountingHealth = game.health_final;
4725 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4727 game.LevelSolved_CountingTime = time;
4728 game.LevelSolved_CountingScore = score;
4729 game.LevelSolved_CountingHealth = health;
4731 game_panel_controls[GAME_PANEL_TIME].value = time;
4732 game_panel_controls[GAME_PANEL_SCORE].value = score;
4733 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4735 DisplayGameControlValues();
4738 static void LevelSolved(void)
4740 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4741 game.players_still_needed > 0)
4744 game.LevelSolved = TRUE;
4745 game.GameOver = TRUE;
4747 // needed here to display correct panel values while player walks into exit
4748 LevelSolved_SetFinalGameValues();
4753 static int time_count_steps;
4754 static int time, time_final;
4755 static float score, score_final; // needed for time score < 10 for 10 seconds
4756 static int health, health_final;
4757 static int game_over_delay_1 = 0;
4758 static int game_over_delay_2 = 0;
4759 static int game_over_delay_3 = 0;
4760 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4761 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4763 if (!game.LevelSolved_GameWon)
4767 // do not start end game actions before the player stops moving (to exit)
4768 if (local_player->active && local_player->MovPos)
4771 // calculate final game values after player finished walking into exit
4772 LevelSolved_SetFinalGameValues();
4774 game.LevelSolved_GameWon = TRUE;
4775 game.LevelSolved_SaveTape = tape.recording;
4776 game.LevelSolved_SaveScore = !tape.playing;
4780 LevelStats_incSolved(level_nr);
4782 SaveLevelSetup_SeriesInfo();
4785 if (tape.auto_play) // tape might already be stopped here
4786 tape.auto_play_level_solved = TRUE;
4790 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4791 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4792 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4794 time = time_final = game.time_final;
4795 score = score_final = game.score_final;
4796 health = health_final = game.health_final;
4798 // update game panel values before (delayed) counting of score (if any)
4799 LevelSolved_DisplayFinalGameValues(time, score, health);
4801 // if level has time score defined, calculate new final game values
4804 int time_final_max = 999;
4805 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4806 int time_frames = 0;
4807 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4808 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4813 time_frames = time_frames_left;
4815 else if (game.no_time_limit && TimePlayed < time_final_max)
4817 time_final = time_final_max;
4818 time_frames = time_frames_final_max - time_frames_played;
4821 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4823 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4825 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4828 score_final += health * time_score;
4831 game.score_final = score_final;
4832 game.health_final = health_final;
4835 // if not counting score after game, immediately update game panel values
4836 if (level_editor_test_game || !setup.count_score_after_game)
4839 score = score_final;
4841 LevelSolved_DisplayFinalGameValues(time, score, health);
4844 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4846 // check if last player has left the level
4847 if (game.exit_x >= 0 &&
4850 int x = game.exit_x;
4851 int y = game.exit_y;
4852 int element = Tile[x][y];
4854 // close exit door after last player
4855 if ((game.all_players_gone &&
4856 (element == EL_EXIT_OPEN ||
4857 element == EL_SP_EXIT_OPEN ||
4858 element == EL_STEEL_EXIT_OPEN)) ||
4859 element == EL_EM_EXIT_OPEN ||
4860 element == EL_EM_STEEL_EXIT_OPEN)
4864 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4865 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4866 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4867 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4868 EL_EM_STEEL_EXIT_CLOSING);
4870 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4873 // player disappears
4874 DrawLevelField(x, y);
4877 for (i = 0; i < MAX_PLAYERS; i++)
4879 struct PlayerInfo *player = &stored_player[i];
4881 if (player->present)
4883 RemovePlayer(player);
4885 // player disappears
4886 DrawLevelField(player->jx, player->jy);
4891 PlaySound(SND_GAME_WINNING);
4894 if (setup.count_score_after_game)
4896 if (time != time_final)
4898 if (game_over_delay_1 > 0)
4900 game_over_delay_1--;
4905 int time_to_go = ABS(time_final - time);
4906 int time_count_dir = (time < time_final ? +1 : -1);
4908 if (time_to_go < time_count_steps)
4909 time_count_steps = 1;
4911 time += time_count_steps * time_count_dir;
4912 score += time_count_steps * time_score;
4914 // set final score to correct rounding differences after counting score
4915 if (time == time_final)
4916 score = score_final;
4918 LevelSolved_DisplayFinalGameValues(time, score, health);
4920 if (time == time_final)
4921 StopSound(SND_GAME_LEVELTIME_BONUS);
4922 else if (setup.sound_loops)
4923 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4925 PlaySound(SND_GAME_LEVELTIME_BONUS);
4930 if (health != health_final)
4932 if (game_over_delay_2 > 0)
4934 game_over_delay_2--;
4939 int health_count_dir = (health < health_final ? +1 : -1);
4941 health += health_count_dir;
4942 score += time_score;
4944 LevelSolved_DisplayFinalGameValues(time, score, health);
4946 if (health == health_final)
4947 StopSound(SND_GAME_LEVELTIME_BONUS);
4948 else if (setup.sound_loops)
4949 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4951 PlaySound(SND_GAME_LEVELTIME_BONUS);
4957 game.panel.active = FALSE;
4959 if (game_over_delay_3 > 0)
4961 game_over_delay_3--;
4971 // used instead of "level_nr" (needed for network games)
4972 int last_level_nr = levelset.level_nr;
4973 boolean tape_saved = FALSE;
4975 game.LevelSolved_GameEnd = TRUE;
4977 if (game.LevelSolved_SaveTape)
4979 // make sure that request dialog to save tape does not open door again
4980 if (!global.use_envelope_request)
4981 CloseDoor(DOOR_CLOSE_1);
4984 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4986 // set unique basename for score tape (also saved in high score table)
4987 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4990 // if no tape is to be saved, close both doors simultaneously
4991 CloseDoor(DOOR_CLOSE_ALL);
4993 if (level_editor_test_game)
4995 SetGameStatus(GAME_MODE_MAIN);
5002 if (!game.LevelSolved_SaveScore)
5004 SetGameStatus(GAME_MODE_MAIN);
5011 if (level_nr == leveldir_current->handicap_level)
5013 leveldir_current->handicap_level++;
5015 SaveLevelSetup_SeriesInfo();
5018 // save score and score tape before potentially erasing tape below
5019 NewHighScore(last_level_nr, tape_saved);
5021 if (setup.increment_levels &&
5022 level_nr < leveldir_current->last_level &&
5025 level_nr++; // advance to next level
5026 TapeErase(); // start with empty tape
5028 if (setup.auto_play_next_level)
5030 LoadLevel(level_nr);
5032 SaveLevelSetup_SeriesInfo();
5036 if (scores.last_added >= 0 && setup.show_scores_after_game)
5038 SetGameStatus(GAME_MODE_SCORES);
5040 DrawHallOfFame(last_level_nr);
5042 else if (setup.auto_play_next_level && setup.increment_levels &&
5043 last_level_nr < leveldir_current->last_level &&
5046 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5050 SetGameStatus(GAME_MODE_MAIN);
5056 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5057 boolean one_score_entry_per_name)
5061 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5064 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5066 struct ScoreEntry *entry = &list->entry[i];
5067 boolean score_is_better = (new_entry->score > entry->score);
5068 boolean score_is_equal = (new_entry->score == entry->score);
5069 boolean time_is_better = (new_entry->time < entry->time);
5070 boolean time_is_equal = (new_entry->time == entry->time);
5071 boolean better_by_score = (score_is_better ||
5072 (score_is_equal && time_is_better));
5073 boolean better_by_time = (time_is_better ||
5074 (time_is_equal && score_is_better));
5075 boolean is_better = (level.rate_time_over_score ? better_by_time :
5077 boolean entry_is_empty = (entry->score == 0 &&
5080 // prevent adding server score entries if also existing in local score file
5081 // (special case: historic score entries have an empty tape basename entry)
5082 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5083 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5086 if (is_better || entry_is_empty)
5088 // player has made it to the hall of fame
5090 if (i < MAX_SCORE_ENTRIES - 1)
5092 int m = MAX_SCORE_ENTRIES - 1;
5095 if (one_score_entry_per_name)
5097 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5098 if (strEqual(list->entry[l].name, new_entry->name))
5101 if (m == i) // player's new highscore overwrites his old one
5105 for (l = m; l > i; l--)
5106 list->entry[l] = list->entry[l - 1];
5111 *entry = *new_entry;
5115 else if (one_score_entry_per_name &&
5116 strEqual(entry->name, new_entry->name))
5118 // player already in high score list with better score or time
5127 void NewHighScore(int level_nr, boolean tape_saved)
5129 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5130 boolean one_per_name = FALSE;
5132 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5133 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5135 new_entry.score = game.score_final;
5136 new_entry.time = game.score_time_final;
5138 LoadScore(level_nr);
5140 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5142 if (scores.last_added < 0)
5145 SaveScore(level_nr);
5147 // store last added local score entry (before merging server scores)
5148 scores.last_added_local = scores.last_added;
5150 if (!game.LevelSolved_SaveTape)
5153 SaveScoreTape(level_nr);
5155 if (setup.ask_for_using_api_server)
5157 setup.use_api_server =
5158 Request("Upload your score and tape to the high score server?", REQ_ASK);
5160 if (!setup.use_api_server)
5161 Request("Not using high score server! Use setup menu to enable again!",
5164 runtime.use_api_server = setup.use_api_server;
5166 // after asking for using API server once, do not ask again
5167 setup.ask_for_using_api_server = FALSE;
5169 SaveSetup_ServerSetup();
5172 SaveServerScore(level_nr, tape_saved);
5175 void MergeServerScore(void)
5177 struct ScoreEntry last_added_entry;
5178 boolean one_per_name = FALSE;
5181 if (scores.last_added >= 0)
5182 last_added_entry = scores.entry[scores.last_added];
5184 for (i = 0; i < server_scores.num_entries; i++)
5186 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5188 if (pos >= 0 && pos <= scores.last_added)
5189 scores.last_added++;
5192 if (scores.last_added >= MAX_SCORE_ENTRIES)
5194 scores.last_added = MAX_SCORE_ENTRIES - 1;
5195 scores.force_last_added = TRUE;
5197 scores.entry[scores.last_added] = last_added_entry;
5201 static int getElementMoveStepsizeExt(int x, int y, int direction)
5203 int element = Tile[x][y];
5204 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5205 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5206 int horiz_move = (dx != 0);
5207 int sign = (horiz_move ? dx : dy);
5208 int step = sign * element_info[element].move_stepsize;
5210 // special values for move stepsize for spring and things on conveyor belt
5213 if (CAN_FALL(element) &&
5214 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5215 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5216 else if (element == EL_SPRING)
5217 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5223 static int getElementMoveStepsize(int x, int y)
5225 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5228 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5230 if (player->GfxAction != action || player->GfxDir != dir)
5232 player->GfxAction = action;
5233 player->GfxDir = dir;
5235 player->StepFrame = 0;
5239 static void ResetGfxFrame(int x, int y)
5241 // profiling showed that "autotest" spends 10~20% of its time in this function
5242 if (DrawingDeactivatedField())
5245 int element = Tile[x][y];
5246 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5248 if (graphic_info[graphic].anim_global_sync)
5249 GfxFrame[x][y] = FrameCounter;
5250 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5251 GfxFrame[x][y] = CustomValue[x][y];
5252 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5253 GfxFrame[x][y] = element_info[element].collect_score;
5254 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5255 GfxFrame[x][y] = ChangeDelay[x][y];
5258 static void ResetGfxAnimation(int x, int y)
5260 GfxAction[x][y] = ACTION_DEFAULT;
5261 GfxDir[x][y] = MovDir[x][y];
5264 ResetGfxFrame(x, y);
5267 static void ResetRandomAnimationValue(int x, int y)
5269 GfxRandom[x][y] = INIT_GFX_RANDOM();
5272 static void InitMovingField(int x, int y, int direction)
5274 int element = Tile[x][y];
5275 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5276 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5279 boolean is_moving_before, is_moving_after;
5281 // check if element was/is moving or being moved before/after mode change
5282 is_moving_before = (WasJustMoving[x][y] != 0);
5283 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5285 // reset animation only for moving elements which change direction of moving
5286 // or which just started or stopped moving
5287 // (else CEs with property "can move" / "not moving" are reset each frame)
5288 if (is_moving_before != is_moving_after ||
5289 direction != MovDir[x][y])
5290 ResetGfxAnimation(x, y);
5292 MovDir[x][y] = direction;
5293 GfxDir[x][y] = direction;
5295 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5296 direction == MV_DOWN && CAN_FALL(element) ?
5297 ACTION_FALLING : ACTION_MOVING);
5299 // this is needed for CEs with property "can move" / "not moving"
5301 if (is_moving_after)
5303 if (Tile[newx][newy] == EL_EMPTY)
5304 Tile[newx][newy] = EL_BLOCKED;
5306 MovDir[newx][newy] = MovDir[x][y];
5308 CustomValue[newx][newy] = CustomValue[x][y];
5310 GfxFrame[newx][newy] = GfxFrame[x][y];
5311 GfxRandom[newx][newy] = GfxRandom[x][y];
5312 GfxAction[newx][newy] = GfxAction[x][y];
5313 GfxDir[newx][newy] = GfxDir[x][y];
5317 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5319 int direction = MovDir[x][y];
5320 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5321 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5327 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5329 int oldx = x, oldy = y;
5330 int direction = MovDir[x][y];
5332 if (direction == MV_LEFT)
5334 else if (direction == MV_RIGHT)
5336 else if (direction == MV_UP)
5338 else if (direction == MV_DOWN)
5341 *comes_from_x = oldx;
5342 *comes_from_y = oldy;
5345 static int MovingOrBlocked2Element(int x, int y)
5347 int element = Tile[x][y];
5349 if (element == EL_BLOCKED)
5353 Blocked2Moving(x, y, &oldx, &oldy);
5354 return Tile[oldx][oldy];
5360 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5362 // like MovingOrBlocked2Element(), but if element is moving
5363 // and (x,y) is the field the moving element is just leaving,
5364 // return EL_BLOCKED instead of the element value
5365 int element = Tile[x][y];
5367 if (IS_MOVING(x, y))
5369 if (element == EL_BLOCKED)
5373 Blocked2Moving(x, y, &oldx, &oldy);
5374 return Tile[oldx][oldy];
5383 static void RemoveField(int x, int y)
5385 Tile[x][y] = EL_EMPTY;
5391 CustomValue[x][y] = 0;
5394 ChangeDelay[x][y] = 0;
5395 ChangePage[x][y] = -1;
5396 Pushed[x][y] = FALSE;
5398 GfxElement[x][y] = EL_UNDEFINED;
5399 GfxAction[x][y] = ACTION_DEFAULT;
5400 GfxDir[x][y] = MV_NONE;
5403 static void RemoveMovingField(int x, int y)
5405 int oldx = x, oldy = y, newx = x, newy = y;
5406 int element = Tile[x][y];
5407 int next_element = EL_UNDEFINED;
5409 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5412 if (IS_MOVING(x, y))
5414 Moving2Blocked(x, y, &newx, &newy);
5416 if (Tile[newx][newy] != EL_BLOCKED)
5418 // element is moving, but target field is not free (blocked), but
5419 // already occupied by something different (example: acid pool);
5420 // in this case, only remove the moving field, but not the target
5422 RemoveField(oldx, oldy);
5424 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5426 TEST_DrawLevelField(oldx, oldy);
5431 else if (element == EL_BLOCKED)
5433 Blocked2Moving(x, y, &oldx, &oldy);
5434 if (!IS_MOVING(oldx, oldy))
5438 if (element == EL_BLOCKED &&
5439 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5440 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5441 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5442 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5443 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5444 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5445 next_element = get_next_element(Tile[oldx][oldy]);
5447 RemoveField(oldx, oldy);
5448 RemoveField(newx, newy);
5450 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5452 if (next_element != EL_UNDEFINED)
5453 Tile[oldx][oldy] = next_element;
5455 TEST_DrawLevelField(oldx, oldy);
5456 TEST_DrawLevelField(newx, newy);
5459 void DrawDynamite(int x, int y)
5461 int sx = SCREENX(x), sy = SCREENY(y);
5462 int graphic = el2img(Tile[x][y]);
5465 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5468 if (IS_WALKABLE_INSIDE(Back[x][y]))
5472 DrawLevelElement(x, y, Back[x][y]);
5473 else if (Store[x][y])
5474 DrawLevelElement(x, y, Store[x][y]);
5475 else if (game.use_masked_elements)
5476 DrawLevelElement(x, y, EL_EMPTY);
5478 frame = getGraphicAnimationFrameXY(graphic, x, y);
5480 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5481 DrawGraphicThruMask(sx, sy, graphic, frame);
5483 DrawGraphic(sx, sy, graphic, frame);
5486 static void CheckDynamite(int x, int y)
5488 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5492 if (MovDelay[x][y] != 0)
5495 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5501 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5506 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5508 boolean num_checked_players = 0;
5511 for (i = 0; i < MAX_PLAYERS; i++)
5513 if (stored_player[i].active)
5515 int sx = stored_player[i].jx;
5516 int sy = stored_player[i].jy;
5518 if (num_checked_players == 0)
5525 *sx1 = MIN(*sx1, sx);
5526 *sy1 = MIN(*sy1, sy);
5527 *sx2 = MAX(*sx2, sx);
5528 *sy2 = MAX(*sy2, sy);
5531 num_checked_players++;
5536 static boolean checkIfAllPlayersFitToScreen_RND(void)
5538 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5540 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5542 return (sx2 - sx1 < SCR_FIELDX &&
5543 sy2 - sy1 < SCR_FIELDY);
5546 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5548 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5550 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5552 *sx = (sx1 + sx2) / 2;
5553 *sy = (sy1 + sy2) / 2;
5556 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5557 boolean center_screen, boolean quick_relocation)
5559 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5560 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5561 boolean no_delay = (tape.warp_forward);
5562 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5563 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5564 int new_scroll_x, new_scroll_y;
5566 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5568 // case 1: quick relocation inside visible screen (without scrolling)
5575 if (!level.shifted_relocation || center_screen)
5577 // relocation _with_ centering of screen
5579 new_scroll_x = SCROLL_POSITION_X(x);
5580 new_scroll_y = SCROLL_POSITION_Y(y);
5584 // relocation _without_ centering of screen
5586 int center_scroll_x = SCROLL_POSITION_X(old_x);
5587 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5588 int offset_x = x + (scroll_x - center_scroll_x);
5589 int offset_y = y + (scroll_y - center_scroll_y);
5591 // for new screen position, apply previous offset to center position
5592 new_scroll_x = SCROLL_POSITION_X(offset_x);
5593 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5596 if (quick_relocation)
5598 // case 2: quick relocation (redraw without visible scrolling)
5600 scroll_x = new_scroll_x;
5601 scroll_y = new_scroll_y;
5608 // case 3: visible relocation (with scrolling to new position)
5610 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5612 SetVideoFrameDelay(wait_delay_value);
5614 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5616 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5617 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5619 if (dx == 0 && dy == 0) // no scrolling needed at all
5625 // set values for horizontal/vertical screen scrolling (half tile size)
5626 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5627 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5628 int pos_x = dx * TILEX / 2;
5629 int pos_y = dy * TILEY / 2;
5630 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5631 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5633 ScrollLevel(dx, dy);
5636 // scroll in two steps of half tile size to make things smoother
5637 BlitScreenToBitmapExt_RND(window, fx, fy);
5639 // scroll second step to align at full tile size
5640 BlitScreenToBitmap(window);
5646 SetVideoFrameDelay(frame_delay_value_old);
5649 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5651 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5652 int player_nr = GET_PLAYER_NR(el_player);
5653 struct PlayerInfo *player = &stored_player[player_nr];
5654 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5655 boolean no_delay = (tape.warp_forward);
5656 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5657 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5658 int old_jx = player->jx;
5659 int old_jy = player->jy;
5660 int old_element = Tile[old_jx][old_jy];
5661 int element = Tile[jx][jy];
5662 boolean player_relocated = (old_jx != jx || old_jy != jy);
5664 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5665 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5666 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5667 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5668 int leave_side_horiz = move_dir_horiz;
5669 int leave_side_vert = move_dir_vert;
5670 int enter_side = enter_side_horiz | enter_side_vert;
5671 int leave_side = leave_side_horiz | leave_side_vert;
5673 if (player->buried) // do not reanimate dead player
5676 if (!player_relocated) // no need to relocate the player
5679 if (IS_PLAYER(jx, jy)) // player already placed at new position
5681 RemoveField(jx, jy); // temporarily remove newly placed player
5682 DrawLevelField(jx, jy);
5685 if (player->present)
5687 while (player->MovPos)
5689 ScrollPlayer(player, SCROLL_GO_ON);
5690 ScrollScreen(NULL, SCROLL_GO_ON);
5692 AdvanceFrameAndPlayerCounters(player->index_nr);
5696 BackToFront_WithFrameDelay(wait_delay_value);
5699 DrawPlayer(player); // needed here only to cleanup last field
5700 DrawLevelField(player->jx, player->jy); // remove player graphic
5702 player->is_moving = FALSE;
5705 if (IS_CUSTOM_ELEMENT(old_element))
5706 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5708 player->index_bit, leave_side);
5710 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5712 player->index_bit, leave_side);
5714 Tile[jx][jy] = el_player;
5715 InitPlayerField(jx, jy, el_player, TRUE);
5717 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5718 possible that the relocation target field did not contain a player element,
5719 but a walkable element, to which the new player was relocated -- in this
5720 case, restore that (already initialized!) element on the player field */
5721 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5723 Tile[jx][jy] = element; // restore previously existing element
5726 // only visually relocate centered player
5727 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5728 FALSE, level.instant_relocation);
5730 TestIfPlayerTouchesBadThing(jx, jy);
5731 TestIfPlayerTouchesCustomElement(jx, jy);
5733 if (IS_CUSTOM_ELEMENT(element))
5734 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5735 player->index_bit, enter_side);
5737 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5738 player->index_bit, enter_side);
5740 if (player->is_switching)
5742 /* ensure that relocation while still switching an element does not cause
5743 a new element to be treated as also switched directly after relocation
5744 (this is important for teleporter switches that teleport the player to
5745 a place where another teleporter switch is in the same direction, which
5746 would then incorrectly be treated as immediately switched before the
5747 direction key that caused the switch was released) */
5749 player->switch_x += jx - old_jx;
5750 player->switch_y += jy - old_jy;
5754 static void Explode(int ex, int ey, int phase, int mode)
5760 // !!! eliminate this variable !!!
5761 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5763 if (game.explosions_delayed)
5765 ExplodeField[ex][ey] = mode;
5769 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5771 int center_element = Tile[ex][ey];
5772 int artwork_element, explosion_element; // set these values later
5774 // remove things displayed in background while burning dynamite
5775 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5778 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5780 // put moving element to center field (and let it explode there)
5781 center_element = MovingOrBlocked2Element(ex, ey);
5782 RemoveMovingField(ex, ey);
5783 Tile[ex][ey] = center_element;
5786 // now "center_element" is finally determined -- set related values now
5787 artwork_element = center_element; // for custom player artwork
5788 explosion_element = center_element; // for custom player artwork
5790 if (IS_PLAYER(ex, ey))
5792 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5794 artwork_element = stored_player[player_nr].artwork_element;
5796 if (level.use_explosion_element[player_nr])
5798 explosion_element = level.explosion_element[player_nr];
5799 artwork_element = explosion_element;
5803 if (mode == EX_TYPE_NORMAL ||
5804 mode == EX_TYPE_CENTER ||
5805 mode == EX_TYPE_CROSS)
5806 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5808 last_phase = element_info[explosion_element].explosion_delay + 1;
5810 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5812 int xx = x - ex + 1;
5813 int yy = y - ey + 1;
5816 if (!IN_LEV_FIELD(x, y) ||
5817 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5818 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5821 element = Tile[x][y];
5823 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5825 element = MovingOrBlocked2Element(x, y);
5827 if (!IS_EXPLOSION_PROOF(element))
5828 RemoveMovingField(x, y);
5831 // indestructible elements can only explode in center (but not flames)
5832 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5833 mode == EX_TYPE_BORDER)) ||
5834 element == EL_FLAMES)
5837 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5838 behaviour, for example when touching a yamyam that explodes to rocks
5839 with active deadly shield, a rock is created under the player !!! */
5840 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5842 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5843 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5844 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5846 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5849 if (IS_ACTIVE_BOMB(element))
5851 // re-activate things under the bomb like gate or penguin
5852 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5859 // save walkable background elements while explosion on same tile
5860 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5861 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5862 Back[x][y] = element;
5864 // ignite explodable elements reached by other explosion
5865 if (element == EL_EXPLOSION)
5866 element = Store2[x][y];
5868 if (AmoebaNr[x][y] &&
5869 (element == EL_AMOEBA_FULL ||
5870 element == EL_BD_AMOEBA ||
5871 element == EL_AMOEBA_GROWING))
5873 AmoebaCnt[AmoebaNr[x][y]]--;
5874 AmoebaCnt2[AmoebaNr[x][y]]--;
5879 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5881 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5883 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5885 if (PLAYERINFO(ex, ey)->use_murphy)
5886 Store[x][y] = EL_EMPTY;
5889 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5890 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5891 else if (IS_PLAYER_ELEMENT(center_element))
5892 Store[x][y] = EL_EMPTY;
5893 else if (center_element == EL_YAMYAM)
5894 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5895 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5896 Store[x][y] = element_info[center_element].content.e[xx][yy];
5898 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5899 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5900 // otherwise) -- FIX THIS !!!
5901 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5902 Store[x][y] = element_info[element].content.e[1][1];
5904 else if (!CAN_EXPLODE(element))
5905 Store[x][y] = element_info[element].content.e[1][1];
5908 Store[x][y] = EL_EMPTY;
5910 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5911 center_element == EL_AMOEBA_TO_DIAMOND)
5912 Store2[x][y] = element;
5914 Tile[x][y] = EL_EXPLOSION;
5915 GfxElement[x][y] = artwork_element;
5917 ExplodePhase[x][y] = 1;
5918 ExplodeDelay[x][y] = last_phase;
5923 if (center_element == EL_YAMYAM)
5924 game.yamyam_content_nr =
5925 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5937 GfxFrame[x][y] = 0; // restart explosion animation
5939 last_phase = ExplodeDelay[x][y];
5941 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5943 // this can happen if the player leaves an explosion just in time
5944 if (GfxElement[x][y] == EL_UNDEFINED)
5945 GfxElement[x][y] = EL_EMPTY;
5947 border_element = Store2[x][y];
5948 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5949 border_element = StorePlayer[x][y];
5951 if (phase == element_info[border_element].ignition_delay ||
5952 phase == last_phase)
5954 boolean border_explosion = FALSE;
5956 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5957 !PLAYER_EXPLOSION_PROTECTED(x, y))
5959 KillPlayerUnlessExplosionProtected(x, y);
5960 border_explosion = TRUE;
5962 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5964 Tile[x][y] = Store2[x][y];
5967 border_explosion = TRUE;
5969 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5971 AmoebaToDiamond(x, y);
5973 border_explosion = TRUE;
5976 // if an element just explodes due to another explosion (chain-reaction),
5977 // do not immediately end the new explosion when it was the last frame of
5978 // the explosion (as it would be done in the following "if"-statement!)
5979 if (border_explosion && phase == last_phase)
5983 if (phase == last_phase)
5987 element = Tile[x][y] = Store[x][y];
5988 Store[x][y] = Store2[x][y] = 0;
5989 GfxElement[x][y] = EL_UNDEFINED;
5991 // player can escape from explosions and might therefore be still alive
5992 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5993 element <= EL_PLAYER_IS_EXPLODING_4)
5995 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5996 int explosion_element = EL_PLAYER_1 + player_nr;
5997 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5998 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6000 if (level.use_explosion_element[player_nr])
6001 explosion_element = level.explosion_element[player_nr];
6003 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6004 element_info[explosion_element].content.e[xx][yy]);
6007 // restore probably existing indestructible background element
6008 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6009 element = Tile[x][y] = Back[x][y];
6012 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6013 GfxDir[x][y] = MV_NONE;
6014 ChangeDelay[x][y] = 0;
6015 ChangePage[x][y] = -1;
6017 CustomValue[x][y] = 0;
6019 InitField_WithBug2(x, y, FALSE);
6021 TEST_DrawLevelField(x, y);
6023 TestIfElementTouchesCustomElement(x, y);
6025 if (GFX_CRUMBLED(element))
6026 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6028 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6029 StorePlayer[x][y] = 0;
6031 if (IS_PLAYER_ELEMENT(element))
6032 RelocatePlayer(x, y, element);
6034 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6036 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6037 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6040 TEST_DrawLevelFieldCrumbled(x, y);
6042 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6044 DrawLevelElement(x, y, Back[x][y]);
6045 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6047 else if (IS_WALKABLE_UNDER(Back[x][y]))
6049 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6050 DrawLevelElementThruMask(x, y, Back[x][y]);
6052 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6053 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6057 static void DynaExplode(int ex, int ey)
6060 int dynabomb_element = Tile[ex][ey];
6061 int dynabomb_size = 1;
6062 boolean dynabomb_xl = FALSE;
6063 struct PlayerInfo *player;
6064 static int xy[4][2] =
6072 if (IS_ACTIVE_BOMB(dynabomb_element))
6074 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6075 dynabomb_size = player->dynabomb_size;
6076 dynabomb_xl = player->dynabomb_xl;
6077 player->dynabombs_left++;
6080 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6082 for (i = 0; i < NUM_DIRECTIONS; i++)
6084 for (j = 1; j <= dynabomb_size; j++)
6086 int x = ex + j * xy[i][0];
6087 int y = ey + j * xy[i][1];
6090 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6093 element = Tile[x][y];
6095 // do not restart explosions of fields with active bombs
6096 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6099 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6101 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6102 !IS_DIGGABLE(element) && !dynabomb_xl)
6108 void Bang(int x, int y)
6110 int element = MovingOrBlocked2Element(x, y);
6111 int explosion_type = EX_TYPE_NORMAL;
6113 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6115 struct PlayerInfo *player = PLAYERINFO(x, y);
6117 element = Tile[x][y] = player->initial_element;
6119 if (level.use_explosion_element[player->index_nr])
6121 int explosion_element = level.explosion_element[player->index_nr];
6123 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6124 explosion_type = EX_TYPE_CROSS;
6125 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6126 explosion_type = EX_TYPE_CENTER;
6134 case EL_BD_BUTTERFLY:
6137 case EL_DARK_YAMYAM:
6141 RaiseScoreElement(element);
6144 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6145 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6146 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6147 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6148 case EL_DYNABOMB_INCREASE_NUMBER:
6149 case EL_DYNABOMB_INCREASE_SIZE:
6150 case EL_DYNABOMB_INCREASE_POWER:
6151 explosion_type = EX_TYPE_DYNA;
6154 case EL_DC_LANDMINE:
6155 explosion_type = EX_TYPE_CENTER;
6160 case EL_LAMP_ACTIVE:
6161 case EL_AMOEBA_TO_DIAMOND:
6162 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6163 explosion_type = EX_TYPE_CENTER;
6167 if (element_info[element].explosion_type == EXPLODES_CROSS)
6168 explosion_type = EX_TYPE_CROSS;
6169 else if (element_info[element].explosion_type == EXPLODES_1X1)
6170 explosion_type = EX_TYPE_CENTER;
6174 if (explosion_type == EX_TYPE_DYNA)
6177 Explode(x, y, EX_PHASE_START, explosion_type);
6179 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6182 static void SplashAcid(int x, int y)
6184 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6185 (!IN_LEV_FIELD(x - 1, y - 2) ||
6186 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6187 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6189 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6190 (!IN_LEV_FIELD(x + 1, y - 2) ||
6191 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6192 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6194 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6197 static void InitBeltMovement(void)
6199 static int belt_base_element[4] =
6201 EL_CONVEYOR_BELT_1_LEFT,
6202 EL_CONVEYOR_BELT_2_LEFT,
6203 EL_CONVEYOR_BELT_3_LEFT,
6204 EL_CONVEYOR_BELT_4_LEFT
6206 static int belt_base_active_element[4] =
6208 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6209 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6210 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6211 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6216 // set frame order for belt animation graphic according to belt direction
6217 for (i = 0; i < NUM_BELTS; i++)
6221 for (j = 0; j < NUM_BELT_PARTS; j++)
6223 int element = belt_base_active_element[belt_nr] + j;
6224 int graphic_1 = el2img(element);
6225 int graphic_2 = el2panelimg(element);
6227 if (game.belt_dir[i] == MV_LEFT)
6229 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6230 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6234 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6235 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6240 SCAN_PLAYFIELD(x, y)
6242 int element = Tile[x][y];
6244 for (i = 0; i < NUM_BELTS; i++)
6246 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6248 int e_belt_nr = getBeltNrFromBeltElement(element);
6251 if (e_belt_nr == belt_nr)
6253 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6255 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6262 static void ToggleBeltSwitch(int x, int y)
6264 static int belt_base_element[4] =
6266 EL_CONVEYOR_BELT_1_LEFT,
6267 EL_CONVEYOR_BELT_2_LEFT,
6268 EL_CONVEYOR_BELT_3_LEFT,
6269 EL_CONVEYOR_BELT_4_LEFT
6271 static int belt_base_active_element[4] =
6273 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6274 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6275 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6276 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6278 static int belt_base_switch_element[4] =
6280 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6281 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6282 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6283 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6285 static int belt_move_dir[4] =
6293 int element = Tile[x][y];
6294 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6295 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6296 int belt_dir = belt_move_dir[belt_dir_nr];
6299 if (!IS_BELT_SWITCH(element))
6302 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6303 game.belt_dir[belt_nr] = belt_dir;
6305 if (belt_dir_nr == 3)
6308 // set frame order for belt animation graphic according to belt direction
6309 for (i = 0; i < NUM_BELT_PARTS; i++)
6311 int element = belt_base_active_element[belt_nr] + i;
6312 int graphic_1 = el2img(element);
6313 int graphic_2 = el2panelimg(element);
6315 if (belt_dir == MV_LEFT)
6317 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6318 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6322 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6323 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6327 SCAN_PLAYFIELD(xx, yy)
6329 int element = Tile[xx][yy];
6331 if (IS_BELT_SWITCH(element))
6333 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6335 if (e_belt_nr == belt_nr)
6337 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6338 TEST_DrawLevelField(xx, yy);
6341 else if (IS_BELT(element) && belt_dir != MV_NONE)
6343 int e_belt_nr = getBeltNrFromBeltElement(element);
6345 if (e_belt_nr == belt_nr)
6347 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6349 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6350 TEST_DrawLevelField(xx, yy);
6353 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6355 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6357 if (e_belt_nr == belt_nr)
6359 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6361 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6362 TEST_DrawLevelField(xx, yy);
6368 static void ToggleSwitchgateSwitch(int x, int y)
6372 game.switchgate_pos = !game.switchgate_pos;
6374 SCAN_PLAYFIELD(xx, yy)
6376 int element = Tile[xx][yy];
6378 if (element == EL_SWITCHGATE_SWITCH_UP)
6380 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6381 TEST_DrawLevelField(xx, yy);
6383 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6385 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6386 TEST_DrawLevelField(xx, yy);
6388 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6390 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6391 TEST_DrawLevelField(xx, yy);
6393 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6395 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6396 TEST_DrawLevelField(xx, yy);
6398 else if (element == EL_SWITCHGATE_OPEN ||
6399 element == EL_SWITCHGATE_OPENING)
6401 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6403 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6405 else if (element == EL_SWITCHGATE_CLOSED ||
6406 element == EL_SWITCHGATE_CLOSING)
6408 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6410 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6415 static int getInvisibleActiveFromInvisibleElement(int element)
6417 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6418 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6419 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6423 static int getInvisibleFromInvisibleActiveElement(int element)
6425 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6426 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6427 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6431 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6435 SCAN_PLAYFIELD(x, y)
6437 int element = Tile[x][y];
6439 if (element == EL_LIGHT_SWITCH &&
6440 game.light_time_left > 0)
6442 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6443 TEST_DrawLevelField(x, y);
6445 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6446 game.light_time_left == 0)
6448 Tile[x][y] = EL_LIGHT_SWITCH;
6449 TEST_DrawLevelField(x, y);
6451 else if (element == EL_EMC_DRIPPER &&
6452 game.light_time_left > 0)
6454 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6455 TEST_DrawLevelField(x, y);
6457 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6458 game.light_time_left == 0)
6460 Tile[x][y] = EL_EMC_DRIPPER;
6461 TEST_DrawLevelField(x, y);
6463 else if (element == EL_INVISIBLE_STEELWALL ||
6464 element == EL_INVISIBLE_WALL ||
6465 element == EL_INVISIBLE_SAND)
6467 if (game.light_time_left > 0)
6468 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6470 TEST_DrawLevelField(x, y);
6472 // uncrumble neighbour fields, if needed
6473 if (element == EL_INVISIBLE_SAND)
6474 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6476 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6477 element == EL_INVISIBLE_WALL_ACTIVE ||
6478 element == EL_INVISIBLE_SAND_ACTIVE)
6480 if (game.light_time_left == 0)
6481 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6483 TEST_DrawLevelField(x, y);
6485 // re-crumble neighbour fields, if needed
6486 if (element == EL_INVISIBLE_SAND)
6487 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6492 static void RedrawAllInvisibleElementsForLenses(void)
6496 SCAN_PLAYFIELD(x, y)
6498 int element = Tile[x][y];
6500 if (element == EL_EMC_DRIPPER &&
6501 game.lenses_time_left > 0)
6503 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6504 TEST_DrawLevelField(x, y);
6506 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6507 game.lenses_time_left == 0)
6509 Tile[x][y] = EL_EMC_DRIPPER;
6510 TEST_DrawLevelField(x, y);
6512 else if (element == EL_INVISIBLE_STEELWALL ||
6513 element == EL_INVISIBLE_WALL ||
6514 element == EL_INVISIBLE_SAND)
6516 if (game.lenses_time_left > 0)
6517 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6519 TEST_DrawLevelField(x, y);
6521 // uncrumble neighbour fields, if needed
6522 if (element == EL_INVISIBLE_SAND)
6523 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6525 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6526 element == EL_INVISIBLE_WALL_ACTIVE ||
6527 element == EL_INVISIBLE_SAND_ACTIVE)
6529 if (game.lenses_time_left == 0)
6530 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6532 TEST_DrawLevelField(x, y);
6534 // re-crumble neighbour fields, if needed
6535 if (element == EL_INVISIBLE_SAND)
6536 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6541 static void RedrawAllInvisibleElementsForMagnifier(void)
6545 SCAN_PLAYFIELD(x, y)
6547 int element = Tile[x][y];
6549 if (element == EL_EMC_FAKE_GRASS &&
6550 game.magnify_time_left > 0)
6552 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6553 TEST_DrawLevelField(x, y);
6555 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6556 game.magnify_time_left == 0)
6558 Tile[x][y] = EL_EMC_FAKE_GRASS;
6559 TEST_DrawLevelField(x, y);
6561 else if (IS_GATE_GRAY(element) &&
6562 game.magnify_time_left > 0)
6564 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6565 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6566 IS_EM_GATE_GRAY(element) ?
6567 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6568 IS_EMC_GATE_GRAY(element) ?
6569 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6570 IS_DC_GATE_GRAY(element) ?
6571 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6573 TEST_DrawLevelField(x, y);
6575 else if (IS_GATE_GRAY_ACTIVE(element) &&
6576 game.magnify_time_left == 0)
6578 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6579 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6580 IS_EM_GATE_GRAY_ACTIVE(element) ?
6581 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6582 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6583 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6584 IS_DC_GATE_GRAY_ACTIVE(element) ?
6585 EL_DC_GATE_WHITE_GRAY :
6587 TEST_DrawLevelField(x, y);
6592 static void ToggleLightSwitch(int x, int y)
6594 int element = Tile[x][y];
6596 game.light_time_left =
6597 (element == EL_LIGHT_SWITCH ?
6598 level.time_light * FRAMES_PER_SECOND : 0);
6600 RedrawAllLightSwitchesAndInvisibleElements();
6603 static void ActivateTimegateSwitch(int x, int y)
6607 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6609 SCAN_PLAYFIELD(xx, yy)
6611 int element = Tile[xx][yy];
6613 if (element == EL_TIMEGATE_CLOSED ||
6614 element == EL_TIMEGATE_CLOSING)
6616 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6617 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6621 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6623 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6624 TEST_DrawLevelField(xx, yy);
6630 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6631 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6634 static void Impact(int x, int y)
6636 boolean last_line = (y == lev_fieldy - 1);
6637 boolean object_hit = FALSE;
6638 boolean impact = (last_line || object_hit);
6639 int element = Tile[x][y];
6640 int smashed = EL_STEELWALL;
6642 if (!last_line) // check if element below was hit
6644 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6647 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6648 MovDir[x][y + 1] != MV_DOWN ||
6649 MovPos[x][y + 1] <= TILEY / 2));
6651 // do not smash moving elements that left the smashed field in time
6652 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6653 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6656 #if USE_QUICKSAND_IMPACT_BUGFIX
6657 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6659 RemoveMovingField(x, y + 1);
6660 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6661 Tile[x][y + 2] = EL_ROCK;
6662 TEST_DrawLevelField(x, y + 2);
6667 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6669 RemoveMovingField(x, y + 1);
6670 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6671 Tile[x][y + 2] = EL_ROCK;
6672 TEST_DrawLevelField(x, y + 2);
6679 smashed = MovingOrBlocked2Element(x, y + 1);
6681 impact = (last_line || object_hit);
6684 if (!last_line && smashed == EL_ACID) // element falls into acid
6686 SplashAcid(x, y + 1);
6690 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6691 // only reset graphic animation if graphic really changes after impact
6693 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6695 ResetGfxAnimation(x, y);
6696 TEST_DrawLevelField(x, y);
6699 if (impact && CAN_EXPLODE_IMPACT(element))
6704 else if (impact && element == EL_PEARL &&
6705 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6707 ResetGfxAnimation(x, y);
6709 Tile[x][y] = EL_PEARL_BREAKING;
6710 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6713 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6715 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6720 if (impact && element == EL_AMOEBA_DROP)
6722 if (object_hit && IS_PLAYER(x, y + 1))
6723 KillPlayerUnlessEnemyProtected(x, y + 1);
6724 else if (object_hit && smashed == EL_PENGUIN)
6728 Tile[x][y] = EL_AMOEBA_GROWING;
6729 Store[x][y] = EL_AMOEBA_WET;
6731 ResetRandomAnimationValue(x, y);
6736 if (object_hit) // check which object was hit
6738 if ((CAN_PASS_MAGIC_WALL(element) &&
6739 (smashed == EL_MAGIC_WALL ||
6740 smashed == EL_BD_MAGIC_WALL)) ||
6741 (CAN_PASS_DC_MAGIC_WALL(element) &&
6742 smashed == EL_DC_MAGIC_WALL))
6745 int activated_magic_wall =
6746 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6747 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6748 EL_DC_MAGIC_WALL_ACTIVE);
6750 // activate magic wall / mill
6751 SCAN_PLAYFIELD(xx, yy)
6753 if (Tile[xx][yy] == smashed)
6754 Tile[xx][yy] = activated_magic_wall;
6757 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6758 game.magic_wall_active = TRUE;
6760 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6761 SND_MAGIC_WALL_ACTIVATING :
6762 smashed == EL_BD_MAGIC_WALL ?
6763 SND_BD_MAGIC_WALL_ACTIVATING :
6764 SND_DC_MAGIC_WALL_ACTIVATING));
6767 if (IS_PLAYER(x, y + 1))
6769 if (CAN_SMASH_PLAYER(element))
6771 KillPlayerUnlessEnemyProtected(x, y + 1);
6775 else if (smashed == EL_PENGUIN)
6777 if (CAN_SMASH_PLAYER(element))
6783 else if (element == EL_BD_DIAMOND)
6785 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6791 else if (((element == EL_SP_INFOTRON ||
6792 element == EL_SP_ZONK) &&
6793 (smashed == EL_SP_SNIKSNAK ||
6794 smashed == EL_SP_ELECTRON ||
6795 smashed == EL_SP_DISK_ORANGE)) ||
6796 (element == EL_SP_INFOTRON &&
6797 smashed == EL_SP_DISK_YELLOW))
6802 else if (CAN_SMASH_EVERYTHING(element))
6804 if (IS_CLASSIC_ENEMY(smashed) ||
6805 CAN_EXPLODE_SMASHED(smashed))
6810 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6812 if (smashed == EL_LAMP ||
6813 smashed == EL_LAMP_ACTIVE)
6818 else if (smashed == EL_NUT)
6820 Tile[x][y + 1] = EL_NUT_BREAKING;
6821 PlayLevelSound(x, y, SND_NUT_BREAKING);
6822 RaiseScoreElement(EL_NUT);
6825 else if (smashed == EL_PEARL)
6827 ResetGfxAnimation(x, y);
6829 Tile[x][y + 1] = EL_PEARL_BREAKING;
6830 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6833 else if (smashed == EL_DIAMOND)
6835 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6836 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6839 else if (IS_BELT_SWITCH(smashed))
6841 ToggleBeltSwitch(x, y + 1);
6843 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6844 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6845 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6846 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6848 ToggleSwitchgateSwitch(x, y + 1);
6850 else if (smashed == EL_LIGHT_SWITCH ||
6851 smashed == EL_LIGHT_SWITCH_ACTIVE)
6853 ToggleLightSwitch(x, y + 1);
6857 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6859 CheckElementChangeBySide(x, y + 1, smashed, element,
6860 CE_SWITCHED, CH_SIDE_TOP);
6861 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6867 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6872 // play sound of magic wall / mill
6874 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6875 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6876 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6878 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6879 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6880 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6881 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6882 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6883 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6888 // play sound of object that hits the ground
6889 if (last_line || object_hit)
6890 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6893 static void TurnRoundExt(int x, int y)
6905 { 0, 0 }, { 0, 0 }, { 0, 0 },
6910 int left, right, back;
6914 { MV_DOWN, MV_UP, MV_RIGHT },
6915 { MV_UP, MV_DOWN, MV_LEFT },
6917 { MV_LEFT, MV_RIGHT, MV_DOWN },
6921 { MV_RIGHT, MV_LEFT, MV_UP }
6924 int element = Tile[x][y];
6925 int move_pattern = element_info[element].move_pattern;
6927 int old_move_dir = MovDir[x][y];
6928 int left_dir = turn[old_move_dir].left;
6929 int right_dir = turn[old_move_dir].right;
6930 int back_dir = turn[old_move_dir].back;
6932 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6933 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6934 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6935 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6937 int left_x = x + left_dx, left_y = y + left_dy;
6938 int right_x = x + right_dx, right_y = y + right_dy;
6939 int move_x = x + move_dx, move_y = y + move_dy;
6943 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6945 TestIfBadThingTouchesOtherBadThing(x, y);
6947 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6948 MovDir[x][y] = right_dir;
6949 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6950 MovDir[x][y] = left_dir;
6952 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6954 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6957 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6959 TestIfBadThingTouchesOtherBadThing(x, y);
6961 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6962 MovDir[x][y] = left_dir;
6963 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6964 MovDir[x][y] = right_dir;
6966 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6968 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6971 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6973 TestIfBadThingTouchesOtherBadThing(x, y);
6975 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6976 MovDir[x][y] = left_dir;
6977 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6978 MovDir[x][y] = right_dir;
6980 if (MovDir[x][y] != old_move_dir)
6983 else if (element == EL_YAMYAM)
6985 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6986 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6988 if (can_turn_left && can_turn_right)
6989 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6990 else if (can_turn_left)
6991 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6992 else if (can_turn_right)
6993 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6995 MovDir[x][y] = back_dir;
6997 MovDelay[x][y] = 16 + 16 * RND(3);
6999 else if (element == EL_DARK_YAMYAM)
7001 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7003 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7006 if (can_turn_left && can_turn_right)
7007 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7008 else if (can_turn_left)
7009 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7010 else if (can_turn_right)
7011 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7013 MovDir[x][y] = back_dir;
7015 MovDelay[x][y] = 16 + 16 * RND(3);
7017 else if (element == EL_PACMAN)
7019 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7020 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7022 if (can_turn_left && can_turn_right)
7023 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7024 else if (can_turn_left)
7025 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7026 else if (can_turn_right)
7027 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7029 MovDir[x][y] = back_dir;
7031 MovDelay[x][y] = 6 + RND(40);
7033 else if (element == EL_PIG)
7035 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7036 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7037 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7038 boolean should_turn_left, should_turn_right, should_move_on;
7040 int rnd = RND(rnd_value);
7042 should_turn_left = (can_turn_left &&
7044 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7045 y + back_dy + left_dy)));
7046 should_turn_right = (can_turn_right &&
7048 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7049 y + back_dy + right_dy)));
7050 should_move_on = (can_move_on &&
7053 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7054 y + move_dy + left_dy) ||
7055 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7056 y + move_dy + right_dy)));
7058 if (should_turn_left || should_turn_right || should_move_on)
7060 if (should_turn_left && should_turn_right && should_move_on)
7061 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7062 rnd < 2 * rnd_value / 3 ? right_dir :
7064 else if (should_turn_left && should_turn_right)
7065 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7066 else if (should_turn_left && should_move_on)
7067 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7068 else if (should_turn_right && should_move_on)
7069 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7070 else if (should_turn_left)
7071 MovDir[x][y] = left_dir;
7072 else if (should_turn_right)
7073 MovDir[x][y] = right_dir;
7074 else if (should_move_on)
7075 MovDir[x][y] = old_move_dir;
7077 else if (can_move_on && rnd > rnd_value / 8)
7078 MovDir[x][y] = old_move_dir;
7079 else if (can_turn_left && can_turn_right)
7080 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7081 else if (can_turn_left && rnd > rnd_value / 8)
7082 MovDir[x][y] = left_dir;
7083 else if (can_turn_right && rnd > rnd_value/8)
7084 MovDir[x][y] = right_dir;
7086 MovDir[x][y] = back_dir;
7088 xx = x + move_xy[MovDir[x][y]].dx;
7089 yy = y + move_xy[MovDir[x][y]].dy;
7091 if (!IN_LEV_FIELD(xx, yy) ||
7092 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7093 MovDir[x][y] = old_move_dir;
7097 else if (element == EL_DRAGON)
7099 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7100 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7101 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7103 int rnd = RND(rnd_value);
7105 if (can_move_on && rnd > rnd_value / 8)
7106 MovDir[x][y] = old_move_dir;
7107 else if (can_turn_left && can_turn_right)
7108 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7109 else if (can_turn_left && rnd > rnd_value / 8)
7110 MovDir[x][y] = left_dir;
7111 else if (can_turn_right && rnd > rnd_value / 8)
7112 MovDir[x][y] = right_dir;
7114 MovDir[x][y] = back_dir;
7116 xx = x + move_xy[MovDir[x][y]].dx;
7117 yy = y + move_xy[MovDir[x][y]].dy;
7119 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7120 MovDir[x][y] = old_move_dir;
7124 else if (element == EL_MOLE)
7126 boolean can_move_on =
7127 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7128 IS_AMOEBOID(Tile[move_x][move_y]) ||
7129 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7132 boolean can_turn_left =
7133 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7134 IS_AMOEBOID(Tile[left_x][left_y])));
7136 boolean can_turn_right =
7137 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7138 IS_AMOEBOID(Tile[right_x][right_y])));
7140 if (can_turn_left && can_turn_right)
7141 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7142 else if (can_turn_left)
7143 MovDir[x][y] = left_dir;
7145 MovDir[x][y] = right_dir;
7148 if (MovDir[x][y] != old_move_dir)
7151 else if (element == EL_BALLOON)
7153 MovDir[x][y] = game.wind_direction;
7156 else if (element == EL_SPRING)
7158 if (MovDir[x][y] & MV_HORIZONTAL)
7160 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7161 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7163 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7164 ResetGfxAnimation(move_x, move_y);
7165 TEST_DrawLevelField(move_x, move_y);
7167 MovDir[x][y] = back_dir;
7169 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7170 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7171 MovDir[x][y] = MV_NONE;
7176 else if (element == EL_ROBOT ||
7177 element == EL_SATELLITE ||
7178 element == EL_PENGUIN ||
7179 element == EL_EMC_ANDROID)
7181 int attr_x = -1, attr_y = -1;
7183 if (game.all_players_gone)
7185 attr_x = game.exit_x;
7186 attr_y = game.exit_y;
7192 for (i = 0; i < MAX_PLAYERS; i++)
7194 struct PlayerInfo *player = &stored_player[i];
7195 int jx = player->jx, jy = player->jy;
7197 if (!player->active)
7201 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7209 if (element == EL_ROBOT &&
7210 game.robot_wheel_x >= 0 &&
7211 game.robot_wheel_y >= 0 &&
7212 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7213 game.engine_version < VERSION_IDENT(3,1,0,0)))
7215 attr_x = game.robot_wheel_x;
7216 attr_y = game.robot_wheel_y;
7219 if (element == EL_PENGUIN)
7222 static int xy[4][2] =
7230 for (i = 0; i < NUM_DIRECTIONS; i++)
7232 int ex = x + xy[i][0];
7233 int ey = y + xy[i][1];
7235 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7236 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7237 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7238 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7247 MovDir[x][y] = MV_NONE;
7249 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7250 else if (attr_x > x)
7251 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7253 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7254 else if (attr_y > y)
7255 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7257 if (element == EL_ROBOT)
7261 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7262 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7263 Moving2Blocked(x, y, &newx, &newy);
7265 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7266 MovDelay[x][y] = 8 + 8 * !RND(3);
7268 MovDelay[x][y] = 16;
7270 else if (element == EL_PENGUIN)
7276 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7278 boolean first_horiz = RND(2);
7279 int new_move_dir = MovDir[x][y];
7282 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7283 Moving2Blocked(x, y, &newx, &newy);
7285 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7289 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7290 Moving2Blocked(x, y, &newx, &newy);
7292 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7295 MovDir[x][y] = old_move_dir;
7299 else if (element == EL_SATELLITE)
7305 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7307 boolean first_horiz = RND(2);
7308 int new_move_dir = MovDir[x][y];
7311 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7312 Moving2Blocked(x, y, &newx, &newy);
7314 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7318 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7319 Moving2Blocked(x, y, &newx, &newy);
7321 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7324 MovDir[x][y] = old_move_dir;
7328 else if (element == EL_EMC_ANDROID)
7330 static int check_pos[16] =
7332 -1, // 0 => (invalid)
7335 -1, // 3 => (invalid)
7337 0, // 5 => MV_LEFT | MV_UP
7338 2, // 6 => MV_RIGHT | MV_UP
7339 -1, // 7 => (invalid)
7341 6, // 9 => MV_LEFT | MV_DOWN
7342 4, // 10 => MV_RIGHT | MV_DOWN
7343 -1, // 11 => (invalid)
7344 -1, // 12 => (invalid)
7345 -1, // 13 => (invalid)
7346 -1, // 14 => (invalid)
7347 -1, // 15 => (invalid)
7355 { -1, -1, MV_LEFT | MV_UP },
7357 { +1, -1, MV_RIGHT | MV_UP },
7358 { +1, 0, MV_RIGHT },
7359 { +1, +1, MV_RIGHT | MV_DOWN },
7361 { -1, +1, MV_LEFT | MV_DOWN },
7364 int start_pos, check_order;
7365 boolean can_clone = FALSE;
7368 // check if there is any free field around current position
7369 for (i = 0; i < 8; i++)
7371 int newx = x + check_xy[i].dx;
7372 int newy = y + check_xy[i].dy;
7374 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7382 if (can_clone) // randomly find an element to clone
7386 start_pos = check_pos[RND(8)];
7387 check_order = (RND(2) ? -1 : +1);
7389 for (i = 0; i < 8; i++)
7391 int pos_raw = start_pos + i * check_order;
7392 int pos = (pos_raw + 8) % 8;
7393 int newx = x + check_xy[pos].dx;
7394 int newy = y + check_xy[pos].dy;
7396 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7398 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7399 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7401 Store[x][y] = Tile[newx][newy];
7410 if (can_clone) // randomly find a direction to move
7414 start_pos = check_pos[RND(8)];
7415 check_order = (RND(2) ? -1 : +1);
7417 for (i = 0; i < 8; i++)
7419 int pos_raw = start_pos + i * check_order;
7420 int pos = (pos_raw + 8) % 8;
7421 int newx = x + check_xy[pos].dx;
7422 int newy = y + check_xy[pos].dy;
7423 int new_move_dir = check_xy[pos].dir;
7425 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7427 MovDir[x][y] = new_move_dir;
7428 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7437 if (can_clone) // cloning and moving successful
7440 // cannot clone -- try to move towards player
7442 start_pos = check_pos[MovDir[x][y] & 0x0f];
7443 check_order = (RND(2) ? -1 : +1);
7445 for (i = 0; i < 3; i++)
7447 // first check start_pos, then previous/next or (next/previous) pos
7448 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7449 int pos = (pos_raw + 8) % 8;
7450 int newx = x + check_xy[pos].dx;
7451 int newy = y + check_xy[pos].dy;
7452 int new_move_dir = check_xy[pos].dir;
7454 if (IS_PLAYER(newx, newy))
7457 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7459 MovDir[x][y] = new_move_dir;
7460 MovDelay[x][y] = level.android_move_time * 8 + 1;
7467 else if (move_pattern == MV_TURNING_LEFT ||
7468 move_pattern == MV_TURNING_RIGHT ||
7469 move_pattern == MV_TURNING_LEFT_RIGHT ||
7470 move_pattern == MV_TURNING_RIGHT_LEFT ||
7471 move_pattern == MV_TURNING_RANDOM ||
7472 move_pattern == MV_ALL_DIRECTIONS)
7474 boolean can_turn_left =
7475 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7476 boolean can_turn_right =
7477 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7479 if (element_info[element].move_stepsize == 0) // "not moving"
7482 if (move_pattern == MV_TURNING_LEFT)
7483 MovDir[x][y] = left_dir;
7484 else if (move_pattern == MV_TURNING_RIGHT)
7485 MovDir[x][y] = right_dir;
7486 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7487 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7488 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7489 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7490 else if (move_pattern == MV_TURNING_RANDOM)
7491 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7492 can_turn_right && !can_turn_left ? right_dir :
7493 RND(2) ? left_dir : right_dir);
7494 else if (can_turn_left && can_turn_right)
7495 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7496 else if (can_turn_left)
7497 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7498 else if (can_turn_right)
7499 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7501 MovDir[x][y] = back_dir;
7503 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7505 else if (move_pattern == MV_HORIZONTAL ||
7506 move_pattern == MV_VERTICAL)
7508 if (move_pattern & old_move_dir)
7509 MovDir[x][y] = back_dir;
7510 else if (move_pattern == MV_HORIZONTAL)
7511 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7512 else if (move_pattern == MV_VERTICAL)
7513 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7515 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7517 else if (move_pattern & MV_ANY_DIRECTION)
7519 MovDir[x][y] = move_pattern;
7520 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7522 else if (move_pattern & MV_WIND_DIRECTION)
7524 MovDir[x][y] = game.wind_direction;
7525 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7527 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7529 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7530 MovDir[x][y] = left_dir;
7531 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7532 MovDir[x][y] = right_dir;
7534 if (MovDir[x][y] != old_move_dir)
7535 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7537 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7539 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7540 MovDir[x][y] = right_dir;
7541 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7542 MovDir[x][y] = left_dir;
7544 if (MovDir[x][y] != old_move_dir)
7545 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7547 else if (move_pattern == MV_TOWARDS_PLAYER ||
7548 move_pattern == MV_AWAY_FROM_PLAYER)
7550 int attr_x = -1, attr_y = -1;
7552 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7554 if (game.all_players_gone)
7556 attr_x = game.exit_x;
7557 attr_y = game.exit_y;
7563 for (i = 0; i < MAX_PLAYERS; i++)
7565 struct PlayerInfo *player = &stored_player[i];
7566 int jx = player->jx, jy = player->jy;
7568 if (!player->active)
7572 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7580 MovDir[x][y] = MV_NONE;
7582 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7583 else if (attr_x > x)
7584 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7586 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7587 else if (attr_y > y)
7588 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7590 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7592 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7594 boolean first_horiz = RND(2);
7595 int new_move_dir = MovDir[x][y];
7597 if (element_info[element].move_stepsize == 0) // "not moving"
7599 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7600 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7606 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7607 Moving2Blocked(x, y, &newx, &newy);
7609 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7613 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7614 Moving2Blocked(x, y, &newx, &newy);
7616 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7619 MovDir[x][y] = old_move_dir;
7622 else if (move_pattern == MV_WHEN_PUSHED ||
7623 move_pattern == MV_WHEN_DROPPED)
7625 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7626 MovDir[x][y] = MV_NONE;
7630 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7632 static int test_xy[7][2] =
7642 static int test_dir[7] =
7652 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7653 int move_preference = -1000000; // start with very low preference
7654 int new_move_dir = MV_NONE;
7655 int start_test = RND(4);
7658 for (i = 0; i < NUM_DIRECTIONS; i++)
7660 int move_dir = test_dir[start_test + i];
7661 int move_dir_preference;
7663 xx = x + test_xy[start_test + i][0];
7664 yy = y + test_xy[start_test + i][1];
7666 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7667 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7669 new_move_dir = move_dir;
7674 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7677 move_dir_preference = -1 * RunnerVisit[xx][yy];
7678 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7679 move_dir_preference = PlayerVisit[xx][yy];
7681 if (move_dir_preference > move_preference)
7683 // prefer field that has not been visited for the longest time
7684 move_preference = move_dir_preference;
7685 new_move_dir = move_dir;
7687 else if (move_dir_preference == move_preference &&
7688 move_dir == old_move_dir)
7690 // prefer last direction when all directions are preferred equally
7691 move_preference = move_dir_preference;
7692 new_move_dir = move_dir;
7696 MovDir[x][y] = new_move_dir;
7697 if (old_move_dir != new_move_dir)
7698 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7702 static void TurnRound(int x, int y)
7704 int direction = MovDir[x][y];
7708 GfxDir[x][y] = MovDir[x][y];
7710 if (direction != MovDir[x][y])
7714 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7716 ResetGfxFrame(x, y);
7719 static boolean JustBeingPushed(int x, int y)
7723 for (i = 0; i < MAX_PLAYERS; i++)
7725 struct PlayerInfo *player = &stored_player[i];
7727 if (player->active && player->is_pushing && player->MovPos)
7729 int next_jx = player->jx + (player->jx - player->last_jx);
7730 int next_jy = player->jy + (player->jy - player->last_jy);
7732 if (x == next_jx && y == next_jy)
7740 static void StartMoving(int x, int y)
7742 boolean started_moving = FALSE; // some elements can fall _and_ move
7743 int element = Tile[x][y];
7748 if (MovDelay[x][y] == 0)
7749 GfxAction[x][y] = ACTION_DEFAULT;
7751 if (CAN_FALL(element) && y < lev_fieldy - 1)
7753 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7754 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7755 if (JustBeingPushed(x, y))
7758 if (element == EL_QUICKSAND_FULL)
7760 if (IS_FREE(x, y + 1))
7762 InitMovingField(x, y, MV_DOWN);
7763 started_moving = TRUE;
7765 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7766 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7767 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7768 Store[x][y] = EL_ROCK;
7770 Store[x][y] = EL_ROCK;
7773 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7775 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7777 if (!MovDelay[x][y])
7779 MovDelay[x][y] = TILEY + 1;
7781 ResetGfxAnimation(x, y);
7782 ResetGfxAnimation(x, y + 1);
7787 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7788 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7795 Tile[x][y] = EL_QUICKSAND_EMPTY;
7796 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7797 Store[x][y + 1] = Store[x][y];
7800 PlayLevelSoundAction(x, y, ACTION_FILLING);
7802 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7804 if (!MovDelay[x][y])
7806 MovDelay[x][y] = TILEY + 1;
7808 ResetGfxAnimation(x, y);
7809 ResetGfxAnimation(x, y + 1);
7814 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7815 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7822 Tile[x][y] = EL_QUICKSAND_EMPTY;
7823 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7824 Store[x][y + 1] = Store[x][y];
7827 PlayLevelSoundAction(x, y, ACTION_FILLING);
7830 else if (element == EL_QUICKSAND_FAST_FULL)
7832 if (IS_FREE(x, y + 1))
7834 InitMovingField(x, y, MV_DOWN);
7835 started_moving = TRUE;
7837 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7838 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7839 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7840 Store[x][y] = EL_ROCK;
7842 Store[x][y] = EL_ROCK;
7845 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7847 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7849 if (!MovDelay[x][y])
7851 MovDelay[x][y] = TILEY + 1;
7853 ResetGfxAnimation(x, y);
7854 ResetGfxAnimation(x, y + 1);
7859 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7860 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7867 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7868 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7869 Store[x][y + 1] = Store[x][y];
7872 PlayLevelSoundAction(x, y, ACTION_FILLING);
7874 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7876 if (!MovDelay[x][y])
7878 MovDelay[x][y] = TILEY + 1;
7880 ResetGfxAnimation(x, y);
7881 ResetGfxAnimation(x, y + 1);
7886 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7887 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7894 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7895 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7896 Store[x][y + 1] = Store[x][y];
7899 PlayLevelSoundAction(x, y, ACTION_FILLING);
7902 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7903 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7905 InitMovingField(x, y, MV_DOWN);
7906 started_moving = TRUE;
7908 Tile[x][y] = EL_QUICKSAND_FILLING;
7909 Store[x][y] = element;
7911 PlayLevelSoundAction(x, y, ACTION_FILLING);
7913 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7914 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7916 InitMovingField(x, y, MV_DOWN);
7917 started_moving = TRUE;
7919 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7920 Store[x][y] = element;
7922 PlayLevelSoundAction(x, y, ACTION_FILLING);
7924 else if (element == EL_MAGIC_WALL_FULL)
7926 if (IS_FREE(x, y + 1))
7928 InitMovingField(x, y, MV_DOWN);
7929 started_moving = TRUE;
7931 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7932 Store[x][y] = EL_CHANGED(Store[x][y]);
7934 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7936 if (!MovDelay[x][y])
7937 MovDelay[x][y] = TILEY / 4 + 1;
7946 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7947 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7948 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7952 else if (element == EL_BD_MAGIC_WALL_FULL)
7954 if (IS_FREE(x, y + 1))
7956 InitMovingField(x, y, MV_DOWN);
7957 started_moving = TRUE;
7959 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7960 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7962 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7964 if (!MovDelay[x][y])
7965 MovDelay[x][y] = TILEY / 4 + 1;
7974 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7975 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7976 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7980 else if (element == EL_DC_MAGIC_WALL_FULL)
7982 if (IS_FREE(x, y + 1))
7984 InitMovingField(x, y, MV_DOWN);
7985 started_moving = TRUE;
7987 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7988 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7990 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7992 if (!MovDelay[x][y])
7993 MovDelay[x][y] = TILEY / 4 + 1;
8002 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8003 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8004 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8008 else if ((CAN_PASS_MAGIC_WALL(element) &&
8009 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8010 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8011 (CAN_PASS_DC_MAGIC_WALL(element) &&
8012 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8015 InitMovingField(x, y, MV_DOWN);
8016 started_moving = TRUE;
8019 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8020 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8021 EL_DC_MAGIC_WALL_FILLING);
8022 Store[x][y] = element;
8024 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8026 SplashAcid(x, y + 1);
8028 InitMovingField(x, y, MV_DOWN);
8029 started_moving = TRUE;
8031 Store[x][y] = EL_ACID;
8034 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8035 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8036 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8037 CAN_FALL(element) && WasJustFalling[x][y] &&
8038 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8040 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8041 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8042 (Tile[x][y + 1] == EL_BLOCKED)))
8044 /* this is needed for a special case not covered by calling "Impact()"
8045 from "ContinueMoving()": if an element moves to a tile directly below
8046 another element which was just falling on that tile (which was empty
8047 in the previous frame), the falling element above would just stop
8048 instead of smashing the element below (in previous version, the above
8049 element was just checked for "moving" instead of "falling", resulting
8050 in incorrect smashes caused by horizontal movement of the above
8051 element; also, the case of the player being the element to smash was
8052 simply not covered here... :-/ ) */
8054 CheckCollision[x][y] = 0;
8055 CheckImpact[x][y] = 0;
8059 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8061 if (MovDir[x][y] == MV_NONE)
8063 InitMovingField(x, y, MV_DOWN);
8064 started_moving = TRUE;
8067 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8069 if (WasJustFalling[x][y]) // prevent animation from being restarted
8070 MovDir[x][y] = MV_DOWN;
8072 InitMovingField(x, y, MV_DOWN);
8073 started_moving = TRUE;
8075 else if (element == EL_AMOEBA_DROP)
8077 Tile[x][y] = EL_AMOEBA_GROWING;
8078 Store[x][y] = EL_AMOEBA_WET;
8080 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8081 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8082 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8083 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8085 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8086 (IS_FREE(x - 1, y + 1) ||
8087 Tile[x - 1][y + 1] == EL_ACID));
8088 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8089 (IS_FREE(x + 1, y + 1) ||
8090 Tile[x + 1][y + 1] == EL_ACID));
8091 boolean can_fall_any = (can_fall_left || can_fall_right);
8092 boolean can_fall_both = (can_fall_left && can_fall_right);
8093 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8095 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8097 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8098 can_fall_right = FALSE;
8099 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8100 can_fall_left = FALSE;
8101 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8102 can_fall_right = FALSE;
8103 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8104 can_fall_left = FALSE;
8106 can_fall_any = (can_fall_left || can_fall_right);
8107 can_fall_both = FALSE;
8112 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8113 can_fall_right = FALSE; // slip down on left side
8115 can_fall_left = !(can_fall_right = RND(2));
8117 can_fall_both = FALSE;
8122 // if not determined otherwise, prefer left side for slipping down
8123 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8124 started_moving = TRUE;
8127 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8129 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8130 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8131 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8132 int belt_dir = game.belt_dir[belt_nr];
8134 if ((belt_dir == MV_LEFT && left_is_free) ||
8135 (belt_dir == MV_RIGHT && right_is_free))
8137 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8139 InitMovingField(x, y, belt_dir);
8140 started_moving = TRUE;
8142 Pushed[x][y] = TRUE;
8143 Pushed[nextx][y] = TRUE;
8145 GfxAction[x][y] = ACTION_DEFAULT;
8149 MovDir[x][y] = 0; // if element was moving, stop it
8154 // not "else if" because of elements that can fall and move (EL_SPRING)
8155 if (CAN_MOVE(element) && !started_moving)
8157 int move_pattern = element_info[element].move_pattern;
8160 Moving2Blocked(x, y, &newx, &newy);
8162 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8165 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8166 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8168 WasJustMoving[x][y] = 0;
8169 CheckCollision[x][y] = 0;
8171 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8173 if (Tile[x][y] != element) // element has changed
8177 if (!MovDelay[x][y]) // start new movement phase
8179 // all objects that can change their move direction after each step
8180 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8182 if (element != EL_YAMYAM &&
8183 element != EL_DARK_YAMYAM &&
8184 element != EL_PACMAN &&
8185 !(move_pattern & MV_ANY_DIRECTION) &&
8186 move_pattern != MV_TURNING_LEFT &&
8187 move_pattern != MV_TURNING_RIGHT &&
8188 move_pattern != MV_TURNING_LEFT_RIGHT &&
8189 move_pattern != MV_TURNING_RIGHT_LEFT &&
8190 move_pattern != MV_TURNING_RANDOM)
8194 if (MovDelay[x][y] && (element == EL_BUG ||
8195 element == EL_SPACESHIP ||
8196 element == EL_SP_SNIKSNAK ||
8197 element == EL_SP_ELECTRON ||
8198 element == EL_MOLE))
8199 TEST_DrawLevelField(x, y);
8203 if (MovDelay[x][y]) // wait some time before next movement
8207 if (element == EL_ROBOT ||
8208 element == EL_YAMYAM ||
8209 element == EL_DARK_YAMYAM)
8211 DrawLevelElementAnimationIfNeeded(x, y, element);
8212 PlayLevelSoundAction(x, y, ACTION_WAITING);
8214 else if (element == EL_SP_ELECTRON)
8215 DrawLevelElementAnimationIfNeeded(x, y, element);
8216 else if (element == EL_DRAGON)
8219 int dir = MovDir[x][y];
8220 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8221 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8222 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8223 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8224 dir == MV_UP ? IMG_FLAMES_1_UP :
8225 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8226 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8228 GfxAction[x][y] = ACTION_ATTACKING;
8230 if (IS_PLAYER(x, y))
8231 DrawPlayerField(x, y);
8233 TEST_DrawLevelField(x, y);
8235 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8237 for (i = 1; i <= 3; i++)
8239 int xx = x + i * dx;
8240 int yy = y + i * dy;
8241 int sx = SCREENX(xx);
8242 int sy = SCREENY(yy);
8243 int flame_graphic = graphic + (i - 1);
8245 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8250 int flamed = MovingOrBlocked2Element(xx, yy);
8252 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8255 RemoveMovingField(xx, yy);
8257 ChangeDelay[xx][yy] = 0;
8259 Tile[xx][yy] = EL_FLAMES;
8261 if (IN_SCR_FIELD(sx, sy))
8263 TEST_DrawLevelFieldCrumbled(xx, yy);
8264 DrawGraphic(sx, sy, flame_graphic, frame);
8269 if (Tile[xx][yy] == EL_FLAMES)
8270 Tile[xx][yy] = EL_EMPTY;
8271 TEST_DrawLevelField(xx, yy);
8276 if (MovDelay[x][y]) // element still has to wait some time
8278 PlayLevelSoundAction(x, y, ACTION_WAITING);
8284 // now make next step
8286 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8288 if (DONT_COLLIDE_WITH(element) &&
8289 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8290 !PLAYER_ENEMY_PROTECTED(newx, newy))
8292 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8297 else if (CAN_MOVE_INTO_ACID(element) &&
8298 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8299 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8300 (MovDir[x][y] == MV_DOWN ||
8301 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8303 SplashAcid(newx, newy);
8304 Store[x][y] = EL_ACID;
8306 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8308 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8309 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8310 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8311 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8314 TEST_DrawLevelField(x, y);
8316 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8317 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8318 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8320 game.friends_still_needed--;
8321 if (!game.friends_still_needed &&
8323 game.all_players_gone)
8328 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8330 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8331 TEST_DrawLevelField(newx, newy);
8333 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8335 else if (!IS_FREE(newx, newy))
8337 GfxAction[x][y] = ACTION_WAITING;
8339 if (IS_PLAYER(x, y))
8340 DrawPlayerField(x, y);
8342 TEST_DrawLevelField(x, y);
8347 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8349 if (IS_FOOD_PIG(Tile[newx][newy]))
8351 if (IS_MOVING(newx, newy))
8352 RemoveMovingField(newx, newy);
8355 Tile[newx][newy] = EL_EMPTY;
8356 TEST_DrawLevelField(newx, newy);
8359 PlayLevelSound(x, y, SND_PIG_DIGGING);
8361 else if (!IS_FREE(newx, newy))
8363 if (IS_PLAYER(x, y))
8364 DrawPlayerField(x, y);
8366 TEST_DrawLevelField(x, y);
8371 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8373 if (Store[x][y] != EL_EMPTY)
8375 boolean can_clone = FALSE;
8378 // check if element to clone is still there
8379 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8381 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8389 // cannot clone or target field not free anymore -- do not clone
8390 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8391 Store[x][y] = EL_EMPTY;
8394 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8396 if (IS_MV_DIAGONAL(MovDir[x][y]))
8398 int diagonal_move_dir = MovDir[x][y];
8399 int stored = Store[x][y];
8400 int change_delay = 8;
8403 // android is moving diagonally
8405 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8407 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8408 GfxElement[x][y] = EL_EMC_ANDROID;
8409 GfxAction[x][y] = ACTION_SHRINKING;
8410 GfxDir[x][y] = diagonal_move_dir;
8411 ChangeDelay[x][y] = change_delay;
8413 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8416 DrawLevelGraphicAnimation(x, y, graphic);
8417 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8419 if (Tile[newx][newy] == EL_ACID)
8421 SplashAcid(newx, newy);
8426 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8428 Store[newx][newy] = EL_EMC_ANDROID;
8429 GfxElement[newx][newy] = EL_EMC_ANDROID;
8430 GfxAction[newx][newy] = ACTION_GROWING;
8431 GfxDir[newx][newy] = diagonal_move_dir;
8432 ChangeDelay[newx][newy] = change_delay;
8434 graphic = el_act_dir2img(GfxElement[newx][newy],
8435 GfxAction[newx][newy], GfxDir[newx][newy]);
8437 DrawLevelGraphicAnimation(newx, newy, graphic);
8438 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8444 Tile[newx][newy] = EL_EMPTY;
8445 TEST_DrawLevelField(newx, newy);
8447 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8450 else if (!IS_FREE(newx, newy))
8455 else if (IS_CUSTOM_ELEMENT(element) &&
8456 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8458 if (!DigFieldByCE(newx, newy, element))
8461 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8463 RunnerVisit[x][y] = FrameCounter;
8464 PlayerVisit[x][y] /= 8; // expire player visit path
8467 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8469 if (!IS_FREE(newx, newy))
8471 if (IS_PLAYER(x, y))
8472 DrawPlayerField(x, y);
8474 TEST_DrawLevelField(x, y);
8480 boolean wanna_flame = !RND(10);
8481 int dx = newx - x, dy = newy - y;
8482 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8483 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8484 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8485 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8486 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8487 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8490 IS_CLASSIC_ENEMY(element1) ||
8491 IS_CLASSIC_ENEMY(element2)) &&
8492 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8493 element1 != EL_FLAMES && element2 != EL_FLAMES)
8495 ResetGfxAnimation(x, y);
8496 GfxAction[x][y] = ACTION_ATTACKING;
8498 if (IS_PLAYER(x, y))
8499 DrawPlayerField(x, y);
8501 TEST_DrawLevelField(x, y);
8503 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8505 MovDelay[x][y] = 50;
8507 Tile[newx][newy] = EL_FLAMES;
8508 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8509 Tile[newx1][newy1] = EL_FLAMES;
8510 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8511 Tile[newx2][newy2] = EL_FLAMES;
8517 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8518 Tile[newx][newy] == EL_DIAMOND)
8520 if (IS_MOVING(newx, newy))
8521 RemoveMovingField(newx, newy);
8524 Tile[newx][newy] = EL_EMPTY;
8525 TEST_DrawLevelField(newx, newy);
8528 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8530 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8531 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8533 if (AmoebaNr[newx][newy])
8535 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8536 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8537 Tile[newx][newy] == EL_BD_AMOEBA)
8538 AmoebaCnt[AmoebaNr[newx][newy]]--;
8541 if (IS_MOVING(newx, newy))
8543 RemoveMovingField(newx, newy);
8547 Tile[newx][newy] = EL_EMPTY;
8548 TEST_DrawLevelField(newx, newy);
8551 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8553 else if ((element == EL_PACMAN || element == EL_MOLE)
8554 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8556 if (AmoebaNr[newx][newy])
8558 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8559 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8560 Tile[newx][newy] == EL_BD_AMOEBA)
8561 AmoebaCnt[AmoebaNr[newx][newy]]--;
8564 if (element == EL_MOLE)
8566 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8567 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8569 ResetGfxAnimation(x, y);
8570 GfxAction[x][y] = ACTION_DIGGING;
8571 TEST_DrawLevelField(x, y);
8573 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8575 return; // wait for shrinking amoeba
8577 else // element == EL_PACMAN
8579 Tile[newx][newy] = EL_EMPTY;
8580 TEST_DrawLevelField(newx, newy);
8581 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8584 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8585 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8586 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8588 // wait for shrinking amoeba to completely disappear
8591 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8593 // object was running against a wall
8597 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8598 DrawLevelElementAnimation(x, y, element);
8600 if (DONT_TOUCH(element))
8601 TestIfBadThingTouchesPlayer(x, y);
8606 InitMovingField(x, y, MovDir[x][y]);
8608 PlayLevelSoundAction(x, y, ACTION_MOVING);
8612 ContinueMoving(x, y);
8615 void ContinueMoving(int x, int y)
8617 int element = Tile[x][y];
8618 struct ElementInfo *ei = &element_info[element];
8619 int direction = MovDir[x][y];
8620 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8621 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8622 int newx = x + dx, newy = y + dy;
8623 int stored = Store[x][y];
8624 int stored_new = Store[newx][newy];
8625 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8626 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8627 boolean last_line = (newy == lev_fieldy - 1);
8628 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8630 if (pushed_by_player) // special case: moving object pushed by player
8632 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8634 else if (use_step_delay) // special case: moving object has step delay
8636 if (!MovDelay[x][y])
8637 MovPos[x][y] += getElementMoveStepsize(x, y);
8642 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8646 TEST_DrawLevelField(x, y);
8648 return; // element is still waiting
8651 else // normal case: generically moving object
8653 MovPos[x][y] += getElementMoveStepsize(x, y);
8656 if (ABS(MovPos[x][y]) < TILEX)
8658 TEST_DrawLevelField(x, y);
8660 return; // element is still moving
8663 // element reached destination field
8665 Tile[x][y] = EL_EMPTY;
8666 Tile[newx][newy] = element;
8667 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8669 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8671 element = Tile[newx][newy] = EL_ACID;
8673 else if (element == EL_MOLE)
8675 Tile[x][y] = EL_SAND;
8677 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8679 else if (element == EL_QUICKSAND_FILLING)
8681 element = Tile[newx][newy] = get_next_element(element);
8682 Store[newx][newy] = Store[x][y];
8684 else if (element == EL_QUICKSAND_EMPTYING)
8686 Tile[x][y] = get_next_element(element);
8687 element = Tile[newx][newy] = Store[x][y];
8689 else if (element == EL_QUICKSAND_FAST_FILLING)
8691 element = Tile[newx][newy] = get_next_element(element);
8692 Store[newx][newy] = Store[x][y];
8694 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8696 Tile[x][y] = get_next_element(element);
8697 element = Tile[newx][newy] = Store[x][y];
8699 else if (element == EL_MAGIC_WALL_FILLING)
8701 element = Tile[newx][newy] = get_next_element(element);
8702 if (!game.magic_wall_active)
8703 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8704 Store[newx][newy] = Store[x][y];
8706 else if (element == EL_MAGIC_WALL_EMPTYING)
8708 Tile[x][y] = get_next_element(element);
8709 if (!game.magic_wall_active)
8710 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8711 element = Tile[newx][newy] = Store[x][y];
8713 InitField(newx, newy, FALSE);
8715 else if (element == EL_BD_MAGIC_WALL_FILLING)
8717 element = Tile[newx][newy] = get_next_element(element);
8718 if (!game.magic_wall_active)
8719 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8720 Store[newx][newy] = Store[x][y];
8722 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8724 Tile[x][y] = get_next_element(element);
8725 if (!game.magic_wall_active)
8726 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8727 element = Tile[newx][newy] = Store[x][y];
8729 InitField(newx, newy, FALSE);
8731 else if (element == EL_DC_MAGIC_WALL_FILLING)
8733 element = Tile[newx][newy] = get_next_element(element);
8734 if (!game.magic_wall_active)
8735 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8736 Store[newx][newy] = Store[x][y];
8738 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8740 Tile[x][y] = get_next_element(element);
8741 if (!game.magic_wall_active)
8742 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8743 element = Tile[newx][newy] = Store[x][y];
8745 InitField(newx, newy, FALSE);
8747 else if (element == EL_AMOEBA_DROPPING)
8749 Tile[x][y] = get_next_element(element);
8750 element = Tile[newx][newy] = Store[x][y];
8752 else if (element == EL_SOKOBAN_OBJECT)
8755 Tile[x][y] = Back[x][y];
8757 if (Back[newx][newy])
8758 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8760 Back[x][y] = Back[newx][newy] = 0;
8763 Store[x][y] = EL_EMPTY;
8768 MovDelay[newx][newy] = 0;
8770 if (CAN_CHANGE_OR_HAS_ACTION(element))
8772 // copy element change control values to new field
8773 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8774 ChangePage[newx][newy] = ChangePage[x][y];
8775 ChangeCount[newx][newy] = ChangeCount[x][y];
8776 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8779 CustomValue[newx][newy] = CustomValue[x][y];
8781 ChangeDelay[x][y] = 0;
8782 ChangePage[x][y] = -1;
8783 ChangeCount[x][y] = 0;
8784 ChangeEvent[x][y] = -1;
8786 CustomValue[x][y] = 0;
8788 // copy animation control values to new field
8789 GfxFrame[newx][newy] = GfxFrame[x][y];
8790 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8791 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8792 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8794 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8796 // some elements can leave other elements behind after moving
8797 if (ei->move_leave_element != EL_EMPTY &&
8798 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8799 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8801 int move_leave_element = ei->move_leave_element;
8803 // this makes it possible to leave the removed element again
8804 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8805 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8807 Tile[x][y] = move_leave_element;
8809 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8810 MovDir[x][y] = direction;
8812 InitField(x, y, FALSE);
8814 if (GFX_CRUMBLED(Tile[x][y]))
8815 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8817 if (IS_PLAYER_ELEMENT(move_leave_element))
8818 RelocatePlayer(x, y, move_leave_element);
8821 // do this after checking for left-behind element
8822 ResetGfxAnimation(x, y); // reset animation values for old field
8824 if (!CAN_MOVE(element) ||
8825 (CAN_FALL(element) && direction == MV_DOWN &&
8826 (element == EL_SPRING ||
8827 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8828 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8829 GfxDir[x][y] = MovDir[newx][newy] = 0;
8831 TEST_DrawLevelField(x, y);
8832 TEST_DrawLevelField(newx, newy);
8834 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8836 // prevent pushed element from moving on in pushed direction
8837 if (pushed_by_player && CAN_MOVE(element) &&
8838 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8839 !(element_info[element].move_pattern & direction))
8840 TurnRound(newx, newy);
8842 // prevent elements on conveyor belt from moving on in last direction
8843 if (pushed_by_conveyor && CAN_FALL(element) &&
8844 direction & MV_HORIZONTAL)
8845 MovDir[newx][newy] = 0;
8847 if (!pushed_by_player)
8849 int nextx = newx + dx, nexty = newy + dy;
8850 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8852 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8854 if (CAN_FALL(element) && direction == MV_DOWN)
8855 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8857 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8858 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8860 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8861 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8864 if (DONT_TOUCH(element)) // object may be nasty to player or others
8866 TestIfBadThingTouchesPlayer(newx, newy);
8867 TestIfBadThingTouchesFriend(newx, newy);
8869 if (!IS_CUSTOM_ELEMENT(element))
8870 TestIfBadThingTouchesOtherBadThing(newx, newy);
8872 else if (element == EL_PENGUIN)
8873 TestIfFriendTouchesBadThing(newx, newy);
8875 if (DONT_GET_HIT_BY(element))
8877 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8880 // give the player one last chance (one more frame) to move away
8881 if (CAN_FALL(element) && direction == MV_DOWN &&
8882 (last_line || (!IS_FREE(x, newy + 1) &&
8883 (!IS_PLAYER(x, newy + 1) ||
8884 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8887 if (pushed_by_player && !game.use_change_when_pushing_bug)
8889 int push_side = MV_DIR_OPPOSITE(direction);
8890 struct PlayerInfo *player = PLAYERINFO(x, y);
8892 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8893 player->index_bit, push_side);
8894 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8895 player->index_bit, push_side);
8898 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8899 MovDelay[newx][newy] = 1;
8901 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8903 TestIfElementTouchesCustomElement(x, y); // empty or new element
8904 TestIfElementHitsCustomElement(newx, newy, direction);
8905 TestIfPlayerTouchesCustomElement(newx, newy);
8906 TestIfElementTouchesCustomElement(newx, newy);
8908 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8909 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8910 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8911 MV_DIR_OPPOSITE(direction));
8914 int AmoebaNeighbourNr(int ax, int ay)
8917 int element = Tile[ax][ay];
8919 static int xy[4][2] =
8927 for (i = 0; i < NUM_DIRECTIONS; i++)
8929 int x = ax + xy[i][0];
8930 int y = ay + xy[i][1];
8932 if (!IN_LEV_FIELD(x, y))
8935 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8936 group_nr = AmoebaNr[x][y];
8942 static void AmoebaMerge(int ax, int ay)
8944 int i, x, y, xx, yy;
8945 int new_group_nr = AmoebaNr[ax][ay];
8946 static int xy[4][2] =
8954 if (new_group_nr == 0)
8957 for (i = 0; i < NUM_DIRECTIONS; i++)
8962 if (!IN_LEV_FIELD(x, y))
8965 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8966 Tile[x][y] == EL_BD_AMOEBA ||
8967 Tile[x][y] == EL_AMOEBA_DEAD) &&
8968 AmoebaNr[x][y] != new_group_nr)
8970 int old_group_nr = AmoebaNr[x][y];
8972 if (old_group_nr == 0)
8975 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8976 AmoebaCnt[old_group_nr] = 0;
8977 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8978 AmoebaCnt2[old_group_nr] = 0;
8980 SCAN_PLAYFIELD(xx, yy)
8982 if (AmoebaNr[xx][yy] == old_group_nr)
8983 AmoebaNr[xx][yy] = new_group_nr;
8989 void AmoebaToDiamond(int ax, int ay)
8993 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8995 int group_nr = AmoebaNr[ax][ay];
9000 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9001 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9007 SCAN_PLAYFIELD(x, y)
9009 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9012 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9016 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9017 SND_AMOEBA_TURNING_TO_GEM :
9018 SND_AMOEBA_TURNING_TO_ROCK));
9023 static int xy[4][2] =
9031 for (i = 0; i < NUM_DIRECTIONS; i++)
9036 if (!IN_LEV_FIELD(x, y))
9039 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9041 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9042 SND_AMOEBA_TURNING_TO_GEM :
9043 SND_AMOEBA_TURNING_TO_ROCK));
9050 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9053 int group_nr = AmoebaNr[ax][ay];
9054 boolean done = FALSE;
9059 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9060 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9066 SCAN_PLAYFIELD(x, y)
9068 if (AmoebaNr[x][y] == group_nr &&
9069 (Tile[x][y] == EL_AMOEBA_DEAD ||
9070 Tile[x][y] == EL_BD_AMOEBA ||
9071 Tile[x][y] == EL_AMOEBA_GROWING))
9074 Tile[x][y] = new_element;
9075 InitField(x, y, FALSE);
9076 TEST_DrawLevelField(x, y);
9082 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9083 SND_BD_AMOEBA_TURNING_TO_ROCK :
9084 SND_BD_AMOEBA_TURNING_TO_GEM));
9087 static void AmoebaGrowing(int x, int y)
9089 static unsigned int sound_delay = 0;
9090 static unsigned int sound_delay_value = 0;
9092 if (!MovDelay[x][y]) // start new growing cycle
9096 if (DelayReached(&sound_delay, sound_delay_value))
9098 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9099 sound_delay_value = 30;
9103 if (MovDelay[x][y]) // wait some time before growing bigger
9106 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9108 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9109 6 - MovDelay[x][y]);
9111 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9114 if (!MovDelay[x][y])
9116 Tile[x][y] = Store[x][y];
9118 TEST_DrawLevelField(x, y);
9123 static void AmoebaShrinking(int x, int y)
9125 static unsigned int sound_delay = 0;
9126 static unsigned int sound_delay_value = 0;
9128 if (!MovDelay[x][y]) // start new shrinking cycle
9132 if (DelayReached(&sound_delay, sound_delay_value))
9133 sound_delay_value = 30;
9136 if (MovDelay[x][y]) // wait some time before shrinking
9139 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9141 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9142 6 - MovDelay[x][y]);
9144 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9147 if (!MovDelay[x][y])
9149 Tile[x][y] = EL_EMPTY;
9150 TEST_DrawLevelField(x, y);
9152 // don't let mole enter this field in this cycle;
9153 // (give priority to objects falling to this field from above)
9159 static void AmoebaReproduce(int ax, int ay)
9162 int element = Tile[ax][ay];
9163 int graphic = el2img(element);
9164 int newax = ax, neway = ay;
9165 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9166 static int xy[4][2] =
9174 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9176 Tile[ax][ay] = EL_AMOEBA_DEAD;
9177 TEST_DrawLevelField(ax, ay);
9181 if (IS_ANIMATED(graphic))
9182 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9184 if (!MovDelay[ax][ay]) // start making new amoeba field
9185 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9187 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9190 if (MovDelay[ax][ay])
9194 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9197 int x = ax + xy[start][0];
9198 int y = ay + xy[start][1];
9200 if (!IN_LEV_FIELD(x, y))
9203 if (IS_FREE(x, y) ||
9204 CAN_GROW_INTO(Tile[x][y]) ||
9205 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9206 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9212 if (newax == ax && neway == ay)
9215 else // normal or "filled" (BD style) amoeba
9218 boolean waiting_for_player = FALSE;
9220 for (i = 0; i < NUM_DIRECTIONS; i++)
9222 int j = (start + i) % 4;
9223 int x = ax + xy[j][0];
9224 int y = ay + xy[j][1];
9226 if (!IN_LEV_FIELD(x, y))
9229 if (IS_FREE(x, y) ||
9230 CAN_GROW_INTO(Tile[x][y]) ||
9231 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9232 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9238 else if (IS_PLAYER(x, y))
9239 waiting_for_player = TRUE;
9242 if (newax == ax && neway == ay) // amoeba cannot grow
9244 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9246 Tile[ax][ay] = EL_AMOEBA_DEAD;
9247 TEST_DrawLevelField(ax, ay);
9248 AmoebaCnt[AmoebaNr[ax][ay]]--;
9250 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9252 if (element == EL_AMOEBA_FULL)
9253 AmoebaToDiamond(ax, ay);
9254 else if (element == EL_BD_AMOEBA)
9255 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9260 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9262 // amoeba gets larger by growing in some direction
9264 int new_group_nr = AmoebaNr[ax][ay];
9267 if (new_group_nr == 0)
9269 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9271 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9277 AmoebaNr[newax][neway] = new_group_nr;
9278 AmoebaCnt[new_group_nr]++;
9279 AmoebaCnt2[new_group_nr]++;
9281 // if amoeba touches other amoeba(s) after growing, unify them
9282 AmoebaMerge(newax, neway);
9284 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9286 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9292 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9293 (neway == lev_fieldy - 1 && newax != ax))
9295 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9296 Store[newax][neway] = element;
9298 else if (neway == ay || element == EL_EMC_DRIPPER)
9300 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9302 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9306 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9307 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9308 Store[ax][ay] = EL_AMOEBA_DROP;
9309 ContinueMoving(ax, ay);
9313 TEST_DrawLevelField(newax, neway);
9316 static void Life(int ax, int ay)
9320 int element = Tile[ax][ay];
9321 int graphic = el2img(element);
9322 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9324 boolean changed = FALSE;
9326 if (IS_ANIMATED(graphic))
9327 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9332 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9333 MovDelay[ax][ay] = life_time;
9335 if (MovDelay[ax][ay]) // wait some time before next cycle
9338 if (MovDelay[ax][ay])
9342 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9344 int xx = ax+x1, yy = ay+y1;
9345 int old_element = Tile[xx][yy];
9346 int num_neighbours = 0;
9348 if (!IN_LEV_FIELD(xx, yy))
9351 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9353 int x = xx+x2, y = yy+y2;
9355 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9358 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9359 boolean is_neighbour = FALSE;
9361 if (level.use_life_bugs)
9363 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9364 (IS_FREE(x, y) && Stop[x][y]));
9367 (Last[x][y] == element || is_player_cell);
9373 boolean is_free = FALSE;
9375 if (level.use_life_bugs)
9376 is_free = (IS_FREE(xx, yy));
9378 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9380 if (xx == ax && yy == ay) // field in the middle
9382 if (num_neighbours < life_parameter[0] ||
9383 num_neighbours > life_parameter[1])
9385 Tile[xx][yy] = EL_EMPTY;
9386 if (Tile[xx][yy] != old_element)
9387 TEST_DrawLevelField(xx, yy);
9388 Stop[xx][yy] = TRUE;
9392 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9393 { // free border field
9394 if (num_neighbours >= life_parameter[2] &&
9395 num_neighbours <= life_parameter[3])
9397 Tile[xx][yy] = element;
9398 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9399 if (Tile[xx][yy] != old_element)
9400 TEST_DrawLevelField(xx, yy);
9401 Stop[xx][yy] = TRUE;
9408 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9409 SND_GAME_OF_LIFE_GROWING);
9412 static void InitRobotWheel(int x, int y)
9414 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9417 static void RunRobotWheel(int x, int y)
9419 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9422 static void StopRobotWheel(int x, int y)
9424 if (game.robot_wheel_x == x &&
9425 game.robot_wheel_y == y)
9427 game.robot_wheel_x = -1;
9428 game.robot_wheel_y = -1;
9429 game.robot_wheel_active = FALSE;
9433 static void InitTimegateWheel(int x, int y)
9435 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9438 static void RunTimegateWheel(int x, int y)
9440 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9443 static void InitMagicBallDelay(int x, int y)
9445 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9448 static void ActivateMagicBall(int bx, int by)
9452 if (level.ball_random)
9454 int pos_border = RND(8); // select one of the eight border elements
9455 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9456 int xx = pos_content % 3;
9457 int yy = pos_content / 3;
9462 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9463 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9467 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9469 int xx = x - bx + 1;
9470 int yy = y - by + 1;
9472 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9473 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9477 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9480 static void CheckExit(int x, int y)
9482 if (game.gems_still_needed > 0 ||
9483 game.sokoban_fields_still_needed > 0 ||
9484 game.sokoban_objects_still_needed > 0 ||
9485 game.lights_still_needed > 0)
9487 int element = Tile[x][y];
9488 int graphic = el2img(element);
9490 if (IS_ANIMATED(graphic))
9491 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9496 // do not re-open exit door closed after last player
9497 if (game.all_players_gone)
9500 Tile[x][y] = EL_EXIT_OPENING;
9502 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9505 static void CheckExitEM(int x, int y)
9507 if (game.gems_still_needed > 0 ||
9508 game.sokoban_fields_still_needed > 0 ||
9509 game.sokoban_objects_still_needed > 0 ||
9510 game.lights_still_needed > 0)
9512 int element = Tile[x][y];
9513 int graphic = el2img(element);
9515 if (IS_ANIMATED(graphic))
9516 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9521 // do not re-open exit door closed after last player
9522 if (game.all_players_gone)
9525 Tile[x][y] = EL_EM_EXIT_OPENING;
9527 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9530 static void CheckExitSteel(int x, int y)
9532 if (game.gems_still_needed > 0 ||
9533 game.sokoban_fields_still_needed > 0 ||
9534 game.sokoban_objects_still_needed > 0 ||
9535 game.lights_still_needed > 0)
9537 int element = Tile[x][y];
9538 int graphic = el2img(element);
9540 if (IS_ANIMATED(graphic))
9541 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9546 // do not re-open exit door closed after last player
9547 if (game.all_players_gone)
9550 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9552 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9555 static void CheckExitSteelEM(int x, int y)
9557 if (game.gems_still_needed > 0 ||
9558 game.sokoban_fields_still_needed > 0 ||
9559 game.sokoban_objects_still_needed > 0 ||
9560 game.lights_still_needed > 0)
9562 int element = Tile[x][y];
9563 int graphic = el2img(element);
9565 if (IS_ANIMATED(graphic))
9566 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9571 // do not re-open exit door closed after last player
9572 if (game.all_players_gone)
9575 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9577 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9580 static void CheckExitSP(int x, int y)
9582 if (game.gems_still_needed > 0)
9584 int element = Tile[x][y];
9585 int graphic = el2img(element);
9587 if (IS_ANIMATED(graphic))
9588 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9593 // do not re-open exit door closed after last player
9594 if (game.all_players_gone)
9597 Tile[x][y] = EL_SP_EXIT_OPENING;
9599 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9602 static void CloseAllOpenTimegates(void)
9606 SCAN_PLAYFIELD(x, y)
9608 int element = Tile[x][y];
9610 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9612 Tile[x][y] = EL_TIMEGATE_CLOSING;
9614 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9619 static void DrawTwinkleOnField(int x, int y)
9621 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9624 if (Tile[x][y] == EL_BD_DIAMOND)
9627 if (MovDelay[x][y] == 0) // next animation frame
9628 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9630 if (MovDelay[x][y] != 0) // wait some time before next frame
9634 DrawLevelElementAnimation(x, y, Tile[x][y]);
9636 if (MovDelay[x][y] != 0)
9638 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9639 10 - MovDelay[x][y]);
9641 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9646 static void MauerWaechst(int x, int y)
9650 if (!MovDelay[x][y]) // next animation frame
9651 MovDelay[x][y] = 3 * delay;
9653 if (MovDelay[x][y]) // wait some time before next frame
9657 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9659 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9660 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9662 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9665 if (!MovDelay[x][y])
9667 if (MovDir[x][y] == MV_LEFT)
9669 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9670 TEST_DrawLevelField(x - 1, y);
9672 else if (MovDir[x][y] == MV_RIGHT)
9674 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9675 TEST_DrawLevelField(x + 1, y);
9677 else if (MovDir[x][y] == MV_UP)
9679 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9680 TEST_DrawLevelField(x, y - 1);
9684 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9685 TEST_DrawLevelField(x, y + 1);
9688 Tile[x][y] = Store[x][y];
9690 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9691 TEST_DrawLevelField(x, y);
9696 static void MauerAbleger(int ax, int ay)
9698 int element = Tile[ax][ay];
9699 int graphic = el2img(element);
9700 boolean oben_frei = FALSE, unten_frei = FALSE;
9701 boolean links_frei = FALSE, rechts_frei = FALSE;
9702 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9703 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9704 boolean new_wall = FALSE;
9706 if (IS_ANIMATED(graphic))
9707 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9709 if (!MovDelay[ax][ay]) // start building new wall
9710 MovDelay[ax][ay] = 6;
9712 if (MovDelay[ax][ay]) // wait some time before building new wall
9715 if (MovDelay[ax][ay])
9719 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9721 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9723 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9725 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9728 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9729 element == EL_EXPANDABLE_WALL_ANY)
9733 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9734 Store[ax][ay-1] = element;
9735 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9736 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9737 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9738 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9743 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9744 Store[ax][ay+1] = element;
9745 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9746 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9747 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9748 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9753 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9754 element == EL_EXPANDABLE_WALL_ANY ||
9755 element == EL_EXPANDABLE_WALL ||
9756 element == EL_BD_EXPANDABLE_WALL)
9760 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9761 Store[ax-1][ay] = element;
9762 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9763 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9764 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9765 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9771 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9772 Store[ax+1][ay] = element;
9773 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9774 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9775 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9776 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9781 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9782 TEST_DrawLevelField(ax, ay);
9784 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9786 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9787 unten_massiv = TRUE;
9788 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9789 links_massiv = TRUE;
9790 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9791 rechts_massiv = TRUE;
9793 if (((oben_massiv && unten_massiv) ||
9794 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9795 element == EL_EXPANDABLE_WALL) &&
9796 ((links_massiv && rechts_massiv) ||
9797 element == EL_EXPANDABLE_WALL_VERTICAL))
9798 Tile[ax][ay] = EL_WALL;
9801 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9804 static void MauerAblegerStahl(int ax, int ay)
9806 int element = Tile[ax][ay];
9807 int graphic = el2img(element);
9808 boolean oben_frei = FALSE, unten_frei = FALSE;
9809 boolean links_frei = FALSE, rechts_frei = FALSE;
9810 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9811 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9812 boolean new_wall = FALSE;
9814 if (IS_ANIMATED(graphic))
9815 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9817 if (!MovDelay[ax][ay]) // start building new wall
9818 MovDelay[ax][ay] = 6;
9820 if (MovDelay[ax][ay]) // wait some time before building new wall
9823 if (MovDelay[ax][ay])
9827 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9829 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9831 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9833 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9836 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9837 element == EL_EXPANDABLE_STEELWALL_ANY)
9841 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9842 Store[ax][ay-1] = element;
9843 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9844 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9845 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9846 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9851 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9852 Store[ax][ay+1] = element;
9853 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9854 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9855 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9856 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9861 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9862 element == EL_EXPANDABLE_STEELWALL_ANY)
9866 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9867 Store[ax-1][ay] = element;
9868 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9869 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9870 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9871 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9877 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9878 Store[ax+1][ay] = element;
9879 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9880 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9881 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9882 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9887 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9889 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9890 unten_massiv = TRUE;
9891 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9892 links_massiv = TRUE;
9893 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9894 rechts_massiv = TRUE;
9896 if (((oben_massiv && unten_massiv) ||
9897 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9898 ((links_massiv && rechts_massiv) ||
9899 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9900 Tile[ax][ay] = EL_STEELWALL;
9903 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9906 static void CheckForDragon(int x, int y)
9909 boolean dragon_found = FALSE;
9910 static int xy[4][2] =
9918 for (i = 0; i < NUM_DIRECTIONS; i++)
9920 for (j = 0; j < 4; j++)
9922 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9924 if (IN_LEV_FIELD(xx, yy) &&
9925 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9927 if (Tile[xx][yy] == EL_DRAGON)
9928 dragon_found = TRUE;
9937 for (i = 0; i < NUM_DIRECTIONS; i++)
9939 for (j = 0; j < 3; j++)
9941 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9943 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9945 Tile[xx][yy] = EL_EMPTY;
9946 TEST_DrawLevelField(xx, yy);
9955 static void InitBuggyBase(int x, int y)
9957 int element = Tile[x][y];
9958 int activating_delay = FRAMES_PER_SECOND / 4;
9961 (element == EL_SP_BUGGY_BASE ?
9962 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9963 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9965 element == EL_SP_BUGGY_BASE_ACTIVE ?
9966 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9969 static void WarnBuggyBase(int x, int y)
9972 static int xy[4][2] =
9980 for (i = 0; i < NUM_DIRECTIONS; i++)
9982 int xx = x + xy[i][0];
9983 int yy = y + xy[i][1];
9985 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9987 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9994 static void InitTrap(int x, int y)
9996 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9999 static void ActivateTrap(int x, int y)
10001 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10004 static void ChangeActiveTrap(int x, int y)
10006 int graphic = IMG_TRAP_ACTIVE;
10008 // if new animation frame was drawn, correct crumbled sand border
10009 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10010 TEST_DrawLevelFieldCrumbled(x, y);
10013 static int getSpecialActionElement(int element, int number, int base_element)
10015 return (element != EL_EMPTY ? element :
10016 number != -1 ? base_element + number - 1 :
10020 static int getModifiedActionNumber(int value_old, int operator, int operand,
10021 int value_min, int value_max)
10023 int value_new = (operator == CA_MODE_SET ? operand :
10024 operator == CA_MODE_ADD ? value_old + operand :
10025 operator == CA_MODE_SUBTRACT ? value_old - operand :
10026 operator == CA_MODE_MULTIPLY ? value_old * operand :
10027 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10028 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10031 return (value_new < value_min ? value_min :
10032 value_new > value_max ? value_max :
10036 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10038 struct ElementInfo *ei = &element_info[element];
10039 struct ElementChangeInfo *change = &ei->change_page[page];
10040 int target_element = change->target_element;
10041 int action_type = change->action_type;
10042 int action_mode = change->action_mode;
10043 int action_arg = change->action_arg;
10044 int action_element = change->action_element;
10047 if (!change->has_action)
10050 // ---------- determine action paramater values -----------------------------
10052 int level_time_value =
10053 (level.time > 0 ? TimeLeft :
10056 int action_arg_element_raw =
10057 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10058 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10059 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10060 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10061 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10062 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10063 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10065 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10067 int action_arg_direction =
10068 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10069 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10070 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10071 change->actual_trigger_side :
10072 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10073 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10076 int action_arg_number_min =
10077 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10080 int action_arg_number_max =
10081 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10082 action_type == CA_SET_LEVEL_GEMS ? 999 :
10083 action_type == CA_SET_LEVEL_TIME ? 9999 :
10084 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10085 action_type == CA_SET_CE_VALUE ? 9999 :
10086 action_type == CA_SET_CE_SCORE ? 9999 :
10089 int action_arg_number_reset =
10090 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10091 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10092 action_type == CA_SET_LEVEL_TIME ? level.time :
10093 action_type == CA_SET_LEVEL_SCORE ? 0 :
10094 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10095 action_type == CA_SET_CE_SCORE ? 0 :
10098 int action_arg_number =
10099 (action_arg <= CA_ARG_MAX ? action_arg :
10100 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10101 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10102 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10103 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10104 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10105 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10106 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10107 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10108 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10109 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10110 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10111 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10112 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10113 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10114 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10115 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10116 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10117 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10118 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10119 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10120 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10123 int action_arg_number_old =
10124 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10125 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10126 action_type == CA_SET_LEVEL_SCORE ? game.score :
10127 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10128 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10131 int action_arg_number_new =
10132 getModifiedActionNumber(action_arg_number_old,
10133 action_mode, action_arg_number,
10134 action_arg_number_min, action_arg_number_max);
10136 int trigger_player_bits =
10137 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10138 change->actual_trigger_player_bits : change->trigger_player);
10140 int action_arg_player_bits =
10141 (action_arg >= CA_ARG_PLAYER_1 &&
10142 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10143 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10144 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10147 // ---------- execute action -----------------------------------------------
10149 switch (action_type)
10156 // ---------- level actions ----------------------------------------------
10158 case CA_RESTART_LEVEL:
10160 game.restart_level = TRUE;
10165 case CA_SHOW_ENVELOPE:
10167 int element = getSpecialActionElement(action_arg_element,
10168 action_arg_number, EL_ENVELOPE_1);
10170 if (IS_ENVELOPE(element))
10171 local_player->show_envelope = element;
10176 case CA_SET_LEVEL_TIME:
10178 if (level.time > 0) // only modify limited time value
10180 TimeLeft = action_arg_number_new;
10182 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10184 DisplayGameControlValues();
10186 if (!TimeLeft && setup.time_limit)
10187 for (i = 0; i < MAX_PLAYERS; i++)
10188 KillPlayer(&stored_player[i]);
10194 case CA_SET_LEVEL_SCORE:
10196 game.score = action_arg_number_new;
10198 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10200 DisplayGameControlValues();
10205 case CA_SET_LEVEL_GEMS:
10207 game.gems_still_needed = action_arg_number_new;
10209 game.snapshot.collected_item = TRUE;
10211 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10213 DisplayGameControlValues();
10218 case CA_SET_LEVEL_WIND:
10220 game.wind_direction = action_arg_direction;
10225 case CA_SET_LEVEL_RANDOM_SEED:
10227 // ensure that setting a new random seed while playing is predictable
10228 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10233 // ---------- player actions ---------------------------------------------
10235 case CA_MOVE_PLAYER:
10236 case CA_MOVE_PLAYER_NEW:
10238 // automatically move to the next field in specified direction
10239 for (i = 0; i < MAX_PLAYERS; i++)
10240 if (trigger_player_bits & (1 << i))
10241 if (action_type == CA_MOVE_PLAYER ||
10242 stored_player[i].MovPos == 0)
10243 stored_player[i].programmed_action = action_arg_direction;
10248 case CA_EXIT_PLAYER:
10250 for (i = 0; i < MAX_PLAYERS; i++)
10251 if (action_arg_player_bits & (1 << i))
10252 ExitPlayer(&stored_player[i]);
10254 if (game.players_still_needed == 0)
10260 case CA_KILL_PLAYER:
10262 for (i = 0; i < MAX_PLAYERS; i++)
10263 if (action_arg_player_bits & (1 << i))
10264 KillPlayer(&stored_player[i]);
10269 case CA_SET_PLAYER_KEYS:
10271 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10272 int element = getSpecialActionElement(action_arg_element,
10273 action_arg_number, EL_KEY_1);
10275 if (IS_KEY(element))
10277 for (i = 0; i < MAX_PLAYERS; i++)
10279 if (trigger_player_bits & (1 << i))
10281 stored_player[i].key[KEY_NR(element)] = key_state;
10283 DrawGameDoorValues();
10291 case CA_SET_PLAYER_SPEED:
10293 for (i = 0; i < MAX_PLAYERS; i++)
10295 if (trigger_player_bits & (1 << i))
10297 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10299 if (action_arg == CA_ARG_SPEED_FASTER &&
10300 stored_player[i].cannot_move)
10302 action_arg_number = STEPSIZE_VERY_SLOW;
10304 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10305 action_arg == CA_ARG_SPEED_FASTER)
10307 action_arg_number = 2;
10308 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10311 else if (action_arg == CA_ARG_NUMBER_RESET)
10313 action_arg_number = level.initial_player_stepsize[i];
10317 getModifiedActionNumber(move_stepsize,
10320 action_arg_number_min,
10321 action_arg_number_max);
10323 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10330 case CA_SET_PLAYER_SHIELD:
10332 for (i = 0; i < MAX_PLAYERS; i++)
10334 if (trigger_player_bits & (1 << i))
10336 if (action_arg == CA_ARG_SHIELD_OFF)
10338 stored_player[i].shield_normal_time_left = 0;
10339 stored_player[i].shield_deadly_time_left = 0;
10341 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10343 stored_player[i].shield_normal_time_left = 999999;
10345 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10347 stored_player[i].shield_normal_time_left = 999999;
10348 stored_player[i].shield_deadly_time_left = 999999;
10356 case CA_SET_PLAYER_GRAVITY:
10358 for (i = 0; i < MAX_PLAYERS; i++)
10360 if (trigger_player_bits & (1 << i))
10362 stored_player[i].gravity =
10363 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10364 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10365 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10366 stored_player[i].gravity);
10373 case CA_SET_PLAYER_ARTWORK:
10375 for (i = 0; i < MAX_PLAYERS; i++)
10377 if (trigger_player_bits & (1 << i))
10379 int artwork_element = action_arg_element;
10381 if (action_arg == CA_ARG_ELEMENT_RESET)
10383 (level.use_artwork_element[i] ? level.artwork_element[i] :
10384 stored_player[i].element_nr);
10386 if (stored_player[i].artwork_element != artwork_element)
10387 stored_player[i].Frame = 0;
10389 stored_player[i].artwork_element = artwork_element;
10391 SetPlayerWaiting(&stored_player[i], FALSE);
10393 // set number of special actions for bored and sleeping animation
10394 stored_player[i].num_special_action_bored =
10395 get_num_special_action(artwork_element,
10396 ACTION_BORING_1, ACTION_BORING_LAST);
10397 stored_player[i].num_special_action_sleeping =
10398 get_num_special_action(artwork_element,
10399 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10406 case CA_SET_PLAYER_INVENTORY:
10408 for (i = 0; i < MAX_PLAYERS; i++)
10410 struct PlayerInfo *player = &stored_player[i];
10413 if (trigger_player_bits & (1 << i))
10415 int inventory_element = action_arg_element;
10417 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10418 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10419 action_arg == CA_ARG_ELEMENT_ACTION)
10421 int element = inventory_element;
10422 int collect_count = element_info[element].collect_count_initial;
10424 if (!IS_CUSTOM_ELEMENT(element))
10427 if (collect_count == 0)
10428 player->inventory_infinite_element = element;
10430 for (k = 0; k < collect_count; k++)
10431 if (player->inventory_size < MAX_INVENTORY_SIZE)
10432 player->inventory_element[player->inventory_size++] =
10435 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10436 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10437 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10439 if (player->inventory_infinite_element != EL_UNDEFINED &&
10440 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10441 action_arg_element_raw))
10442 player->inventory_infinite_element = EL_UNDEFINED;
10444 for (k = 0, j = 0; j < player->inventory_size; j++)
10446 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10447 action_arg_element_raw))
10448 player->inventory_element[k++] = player->inventory_element[j];
10451 player->inventory_size = k;
10453 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10455 if (player->inventory_size > 0)
10457 for (j = 0; j < player->inventory_size - 1; j++)
10458 player->inventory_element[j] = player->inventory_element[j + 1];
10460 player->inventory_size--;
10463 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10465 if (player->inventory_size > 0)
10466 player->inventory_size--;
10468 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10470 player->inventory_infinite_element = EL_UNDEFINED;
10471 player->inventory_size = 0;
10473 else if (action_arg == CA_ARG_INVENTORY_RESET)
10475 player->inventory_infinite_element = EL_UNDEFINED;
10476 player->inventory_size = 0;
10478 if (level.use_initial_inventory[i])
10480 for (j = 0; j < level.initial_inventory_size[i]; j++)
10482 int element = level.initial_inventory_content[i][j];
10483 int collect_count = element_info[element].collect_count_initial;
10485 if (!IS_CUSTOM_ELEMENT(element))
10488 if (collect_count == 0)
10489 player->inventory_infinite_element = element;
10491 for (k = 0; k < collect_count; k++)
10492 if (player->inventory_size < MAX_INVENTORY_SIZE)
10493 player->inventory_element[player->inventory_size++] =
10504 // ---------- CE actions -------------------------------------------------
10506 case CA_SET_CE_VALUE:
10508 int last_ce_value = CustomValue[x][y];
10510 CustomValue[x][y] = action_arg_number_new;
10512 if (CustomValue[x][y] != last_ce_value)
10514 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10515 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10517 if (CustomValue[x][y] == 0)
10519 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10520 ChangeCount[x][y] = 0; // allow at least one more change
10522 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10523 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10530 case CA_SET_CE_SCORE:
10532 int last_ce_score = ei->collect_score;
10534 ei->collect_score = action_arg_number_new;
10536 if (ei->collect_score != last_ce_score)
10538 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10539 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10541 if (ei->collect_score == 0)
10545 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10546 ChangeCount[x][y] = 0; // allow at least one more change
10548 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10549 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10552 This is a very special case that seems to be a mixture between
10553 CheckElementChange() and CheckTriggeredElementChange(): while
10554 the first one only affects single elements that are triggered
10555 directly, the second one affects multiple elements in the playfield
10556 that are triggered indirectly by another element. This is a third
10557 case: Changing the CE score always affects multiple identical CEs,
10558 so every affected CE must be checked, not only the single CE for
10559 which the CE score was changed in the first place (as every instance
10560 of that CE shares the same CE score, and therefore also can change)!
10562 SCAN_PLAYFIELD(xx, yy)
10564 if (Tile[xx][yy] == element)
10565 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10566 CE_SCORE_GETS_ZERO);
10574 case CA_SET_CE_ARTWORK:
10576 int artwork_element = action_arg_element;
10577 boolean reset_frame = FALSE;
10580 if (action_arg == CA_ARG_ELEMENT_RESET)
10581 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10584 if (ei->gfx_element != artwork_element)
10585 reset_frame = TRUE;
10587 ei->gfx_element = artwork_element;
10589 SCAN_PLAYFIELD(xx, yy)
10591 if (Tile[xx][yy] == element)
10595 ResetGfxAnimation(xx, yy);
10596 ResetRandomAnimationValue(xx, yy);
10599 TEST_DrawLevelField(xx, yy);
10606 // ---------- engine actions ---------------------------------------------
10608 case CA_SET_ENGINE_SCAN_MODE:
10610 InitPlayfieldScanMode(action_arg);
10620 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10622 int old_element = Tile[x][y];
10623 int new_element = GetElementFromGroupElement(element);
10624 int previous_move_direction = MovDir[x][y];
10625 int last_ce_value = CustomValue[x][y];
10626 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10627 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10628 boolean add_player_onto_element = (new_element_is_player &&
10629 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10630 IS_WALKABLE(old_element));
10632 if (!add_player_onto_element)
10634 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10635 RemoveMovingField(x, y);
10639 Tile[x][y] = new_element;
10641 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10642 MovDir[x][y] = previous_move_direction;
10644 if (element_info[new_element].use_last_ce_value)
10645 CustomValue[x][y] = last_ce_value;
10647 InitField_WithBug1(x, y, FALSE);
10649 new_element = Tile[x][y]; // element may have changed
10651 ResetGfxAnimation(x, y);
10652 ResetRandomAnimationValue(x, y);
10654 TEST_DrawLevelField(x, y);
10656 if (GFX_CRUMBLED(new_element))
10657 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10660 // check if element under the player changes from accessible to unaccessible
10661 // (needed for special case of dropping element which then changes)
10662 // (must be checked after creating new element for walkable group elements)
10663 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10664 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10671 // "ChangeCount" not set yet to allow "entered by player" change one time
10672 if (new_element_is_player)
10673 RelocatePlayer(x, y, new_element);
10676 ChangeCount[x][y]++; // count number of changes in the same frame
10678 TestIfBadThingTouchesPlayer(x, y);
10679 TestIfPlayerTouchesCustomElement(x, y);
10680 TestIfElementTouchesCustomElement(x, y);
10683 static void CreateField(int x, int y, int element)
10685 CreateFieldExt(x, y, element, FALSE);
10688 static void CreateElementFromChange(int x, int y, int element)
10690 element = GET_VALID_RUNTIME_ELEMENT(element);
10692 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10694 int old_element = Tile[x][y];
10696 // prevent changed element from moving in same engine frame
10697 // unless both old and new element can either fall or move
10698 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10699 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10703 CreateFieldExt(x, y, element, TRUE);
10706 static boolean ChangeElement(int x, int y, int element, int page)
10708 struct ElementInfo *ei = &element_info[element];
10709 struct ElementChangeInfo *change = &ei->change_page[page];
10710 int ce_value = CustomValue[x][y];
10711 int ce_score = ei->collect_score;
10712 int target_element;
10713 int old_element = Tile[x][y];
10715 // always use default change event to prevent running into a loop
10716 if (ChangeEvent[x][y] == -1)
10717 ChangeEvent[x][y] = CE_DELAY;
10719 if (ChangeEvent[x][y] == CE_DELAY)
10721 // reset actual trigger element, trigger player and action element
10722 change->actual_trigger_element = EL_EMPTY;
10723 change->actual_trigger_player = EL_EMPTY;
10724 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10725 change->actual_trigger_side = CH_SIDE_NONE;
10726 change->actual_trigger_ce_value = 0;
10727 change->actual_trigger_ce_score = 0;
10730 // do not change elements more than a specified maximum number of changes
10731 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10734 ChangeCount[x][y]++; // count number of changes in the same frame
10736 if (change->explode)
10743 if (change->use_target_content)
10745 boolean complete_replace = TRUE;
10746 boolean can_replace[3][3];
10749 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10752 boolean is_walkable;
10753 boolean is_diggable;
10754 boolean is_collectible;
10755 boolean is_removable;
10756 boolean is_destructible;
10757 int ex = x + xx - 1;
10758 int ey = y + yy - 1;
10759 int content_element = change->target_content.e[xx][yy];
10762 can_replace[xx][yy] = TRUE;
10764 if (ex == x && ey == y) // do not check changing element itself
10767 if (content_element == EL_EMPTY_SPACE)
10769 can_replace[xx][yy] = FALSE; // do not replace border with space
10774 if (!IN_LEV_FIELD(ex, ey))
10776 can_replace[xx][yy] = FALSE;
10777 complete_replace = FALSE;
10784 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10785 e = MovingOrBlocked2Element(ex, ey);
10787 is_empty = (IS_FREE(ex, ey) ||
10788 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10790 is_walkable = (is_empty || IS_WALKABLE(e));
10791 is_diggable = (is_empty || IS_DIGGABLE(e));
10792 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10793 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10794 is_removable = (is_diggable || is_collectible);
10796 can_replace[xx][yy] =
10797 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10798 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10799 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10800 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10801 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10802 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10803 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10805 if (!can_replace[xx][yy])
10806 complete_replace = FALSE;
10809 if (!change->only_if_complete || complete_replace)
10811 boolean something_has_changed = FALSE;
10813 if (change->only_if_complete && change->use_random_replace &&
10814 RND(100) < change->random_percentage)
10817 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10819 int ex = x + xx - 1;
10820 int ey = y + yy - 1;
10821 int content_element;
10823 if (can_replace[xx][yy] && (!change->use_random_replace ||
10824 RND(100) < change->random_percentage))
10826 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10827 RemoveMovingField(ex, ey);
10829 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10831 content_element = change->target_content.e[xx][yy];
10832 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10833 ce_value, ce_score);
10835 CreateElementFromChange(ex, ey, target_element);
10837 something_has_changed = TRUE;
10839 // for symmetry reasons, freeze newly created border elements
10840 if (ex != x || ey != y)
10841 Stop[ex][ey] = TRUE; // no more moving in this frame
10845 if (something_has_changed)
10847 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10848 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10854 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10855 ce_value, ce_score);
10857 if (element == EL_DIAGONAL_GROWING ||
10858 element == EL_DIAGONAL_SHRINKING)
10860 target_element = Store[x][y];
10862 Store[x][y] = EL_EMPTY;
10865 // special case: element changes to player (and may be kept if walkable)
10866 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10867 CreateElementFromChange(x, y, EL_EMPTY);
10869 CreateElementFromChange(x, y, target_element);
10871 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10872 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10875 // this uses direct change before indirect change
10876 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10881 static void HandleElementChange(int x, int y, int page)
10883 int element = MovingOrBlocked2Element(x, y);
10884 struct ElementInfo *ei = &element_info[element];
10885 struct ElementChangeInfo *change = &ei->change_page[page];
10886 boolean handle_action_before_change = FALSE;
10889 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10890 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10892 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10893 x, y, element, element_info[element].token_name);
10894 Debug("game:playing:HandleElementChange", "This should never happen!");
10898 // this can happen with classic bombs on walkable, changing elements
10899 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10904 if (ChangeDelay[x][y] == 0) // initialize element change
10906 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10908 if (change->can_change)
10910 // !!! not clear why graphic animation should be reset at all here !!!
10911 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10912 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10915 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10917 When using an animation frame delay of 1 (this only happens with
10918 "sp_zonk.moving.left/right" in the classic graphics), the default
10919 (non-moving) animation shows wrong animation frames (while the
10920 moving animation, like "sp_zonk.moving.left/right", is correct,
10921 so this graphical bug never shows up with the classic graphics).
10922 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10923 be drawn instead of the correct frames 0,1,2,3. This is caused by
10924 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10925 an element change: First when the change delay ("ChangeDelay[][]")
10926 counter has reached zero after decrementing, then a second time in
10927 the next frame (after "GfxFrame[][]" was already incremented) when
10928 "ChangeDelay[][]" is reset to the initial delay value again.
10930 This causes frame 0 to be drawn twice, while the last frame won't
10931 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10933 As some animations may already be cleverly designed around this bug
10934 (at least the "Snake Bite" snake tail animation does this), it cannot
10935 simply be fixed here without breaking such existing animations.
10936 Unfortunately, it cannot easily be detected if a graphics set was
10937 designed "before" or "after" the bug was fixed. As a workaround,
10938 a new graphics set option "game.graphics_engine_version" was added
10939 to be able to specify the game's major release version for which the
10940 graphics set was designed, which can then be used to decide if the
10941 bugfix should be used (version 4 and above) or not (version 3 or
10942 below, or if no version was specified at all, as with old sets).
10944 (The wrong/fixed animation frames can be tested with the test level set
10945 "test_gfxframe" and level "000", which contains a specially prepared
10946 custom element at level position (x/y) == (11/9) which uses the zonk
10947 animation mentioned above. Using "game.graphics_engine_version: 4"
10948 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10949 This can also be seen from the debug output for this test element.)
10952 // when a custom element is about to change (for example by change delay),
10953 // do not reset graphic animation when the custom element is moving
10954 if (game.graphics_engine_version < 4 &&
10957 ResetGfxAnimation(x, y);
10958 ResetRandomAnimationValue(x, y);
10961 if (change->pre_change_function)
10962 change->pre_change_function(x, y);
10966 ChangeDelay[x][y]--;
10968 if (ChangeDelay[x][y] != 0) // continue element change
10970 if (change->can_change)
10972 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10974 if (IS_ANIMATED(graphic))
10975 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10977 if (change->change_function)
10978 change->change_function(x, y);
10981 else // finish element change
10983 if (ChangePage[x][y] != -1) // remember page from delayed change
10985 page = ChangePage[x][y];
10986 ChangePage[x][y] = -1;
10988 change = &ei->change_page[page];
10991 if (IS_MOVING(x, y)) // never change a running system ;-)
10993 ChangeDelay[x][y] = 1; // try change after next move step
10994 ChangePage[x][y] = page; // remember page to use for change
10999 // special case: set new level random seed before changing element
11000 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11001 handle_action_before_change = TRUE;
11003 if (change->has_action && handle_action_before_change)
11004 ExecuteCustomElementAction(x, y, element, page);
11006 if (change->can_change)
11008 if (ChangeElement(x, y, element, page))
11010 if (change->post_change_function)
11011 change->post_change_function(x, y);
11015 if (change->has_action && !handle_action_before_change)
11016 ExecuteCustomElementAction(x, y, element, page);
11020 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11021 int trigger_element,
11023 int trigger_player,
11027 boolean change_done_any = FALSE;
11028 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11031 if (!(trigger_events[trigger_element][trigger_event]))
11034 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11036 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11038 int element = EL_CUSTOM_START + i;
11039 boolean change_done = FALSE;
11042 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11043 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11046 for (p = 0; p < element_info[element].num_change_pages; p++)
11048 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11050 if (change->can_change_or_has_action &&
11051 change->has_event[trigger_event] &&
11052 change->trigger_side & trigger_side &&
11053 change->trigger_player & trigger_player &&
11054 change->trigger_page & trigger_page_bits &&
11055 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11057 change->actual_trigger_element = trigger_element;
11058 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11059 change->actual_trigger_player_bits = trigger_player;
11060 change->actual_trigger_side = trigger_side;
11061 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11062 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11064 if ((change->can_change && !change_done) || change->has_action)
11068 SCAN_PLAYFIELD(x, y)
11070 if (Tile[x][y] == element)
11072 if (change->can_change && !change_done)
11074 // if element already changed in this frame, not only prevent
11075 // another element change (checked in ChangeElement()), but
11076 // also prevent additional element actions for this element
11078 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11079 !level.use_action_after_change_bug)
11082 ChangeDelay[x][y] = 1;
11083 ChangeEvent[x][y] = trigger_event;
11085 HandleElementChange(x, y, p);
11087 else if (change->has_action)
11089 // if element already changed in this frame, not only prevent
11090 // another element change (checked in ChangeElement()), but
11091 // also prevent additional element actions for this element
11093 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11094 !level.use_action_after_change_bug)
11097 ExecuteCustomElementAction(x, y, element, p);
11098 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11103 if (change->can_change)
11105 change_done = TRUE;
11106 change_done_any = TRUE;
11113 RECURSION_LOOP_DETECTION_END();
11115 return change_done_any;
11118 static boolean CheckElementChangeExt(int x, int y,
11120 int trigger_element,
11122 int trigger_player,
11125 boolean change_done = FALSE;
11128 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11129 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11132 if (Tile[x][y] == EL_BLOCKED)
11134 Blocked2Moving(x, y, &x, &y);
11135 element = Tile[x][y];
11138 // check if element has already changed or is about to change after moving
11139 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11140 Tile[x][y] != element) ||
11142 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11143 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11144 ChangePage[x][y] != -1)))
11147 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11149 for (p = 0; p < element_info[element].num_change_pages; p++)
11151 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11153 /* check trigger element for all events where the element that is checked
11154 for changing interacts with a directly adjacent element -- this is
11155 different to element changes that affect other elements to change on the
11156 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11157 boolean check_trigger_element =
11158 (trigger_event == CE_NEXT_TO_X ||
11159 trigger_event == CE_TOUCHING_X ||
11160 trigger_event == CE_HITTING_X ||
11161 trigger_event == CE_HIT_BY_X ||
11162 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11164 if (change->can_change_or_has_action &&
11165 change->has_event[trigger_event] &&
11166 change->trigger_side & trigger_side &&
11167 change->trigger_player & trigger_player &&
11168 (!check_trigger_element ||
11169 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11171 change->actual_trigger_element = trigger_element;
11172 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11173 change->actual_trigger_player_bits = trigger_player;
11174 change->actual_trigger_side = trigger_side;
11175 change->actual_trigger_ce_value = CustomValue[x][y];
11176 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11178 // special case: trigger element not at (x,y) position for some events
11179 if (check_trigger_element)
11191 { 0, 0 }, { 0, 0 }, { 0, 0 },
11195 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11196 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11198 change->actual_trigger_ce_value = CustomValue[xx][yy];
11199 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11202 if (change->can_change && !change_done)
11204 ChangeDelay[x][y] = 1;
11205 ChangeEvent[x][y] = trigger_event;
11207 HandleElementChange(x, y, p);
11209 change_done = TRUE;
11211 else if (change->has_action)
11213 ExecuteCustomElementAction(x, y, element, p);
11214 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11219 RECURSION_LOOP_DETECTION_END();
11221 return change_done;
11224 static void PlayPlayerSound(struct PlayerInfo *player)
11226 int jx = player->jx, jy = player->jy;
11227 int sound_element = player->artwork_element;
11228 int last_action = player->last_action_waiting;
11229 int action = player->action_waiting;
11231 if (player->is_waiting)
11233 if (action != last_action)
11234 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11236 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11240 if (action != last_action)
11241 StopSound(element_info[sound_element].sound[last_action]);
11243 if (last_action == ACTION_SLEEPING)
11244 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11248 static void PlayAllPlayersSound(void)
11252 for (i = 0; i < MAX_PLAYERS; i++)
11253 if (stored_player[i].active)
11254 PlayPlayerSound(&stored_player[i]);
11257 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11259 boolean last_waiting = player->is_waiting;
11260 int move_dir = player->MovDir;
11262 player->dir_waiting = move_dir;
11263 player->last_action_waiting = player->action_waiting;
11267 if (!last_waiting) // not waiting -> waiting
11269 player->is_waiting = TRUE;
11271 player->frame_counter_bored =
11273 game.player_boring_delay_fixed +
11274 GetSimpleRandom(game.player_boring_delay_random);
11275 player->frame_counter_sleeping =
11277 game.player_sleeping_delay_fixed +
11278 GetSimpleRandom(game.player_sleeping_delay_random);
11280 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11283 if (game.player_sleeping_delay_fixed +
11284 game.player_sleeping_delay_random > 0 &&
11285 player->anim_delay_counter == 0 &&
11286 player->post_delay_counter == 0 &&
11287 FrameCounter >= player->frame_counter_sleeping)
11288 player->is_sleeping = TRUE;
11289 else if (game.player_boring_delay_fixed +
11290 game.player_boring_delay_random > 0 &&
11291 FrameCounter >= player->frame_counter_bored)
11292 player->is_bored = TRUE;
11294 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11295 player->is_bored ? ACTION_BORING :
11298 if (player->is_sleeping && player->use_murphy)
11300 // special case for sleeping Murphy when leaning against non-free tile
11302 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11303 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11304 !IS_MOVING(player->jx - 1, player->jy)))
11305 move_dir = MV_LEFT;
11306 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11307 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11308 !IS_MOVING(player->jx + 1, player->jy)))
11309 move_dir = MV_RIGHT;
11311 player->is_sleeping = FALSE;
11313 player->dir_waiting = move_dir;
11316 if (player->is_sleeping)
11318 if (player->num_special_action_sleeping > 0)
11320 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11322 int last_special_action = player->special_action_sleeping;
11323 int num_special_action = player->num_special_action_sleeping;
11324 int special_action =
11325 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11326 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11327 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11328 last_special_action + 1 : ACTION_SLEEPING);
11329 int special_graphic =
11330 el_act_dir2img(player->artwork_element, special_action, move_dir);
11332 player->anim_delay_counter =
11333 graphic_info[special_graphic].anim_delay_fixed +
11334 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11335 player->post_delay_counter =
11336 graphic_info[special_graphic].post_delay_fixed +
11337 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11339 player->special_action_sleeping = special_action;
11342 if (player->anim_delay_counter > 0)
11344 player->action_waiting = player->special_action_sleeping;
11345 player->anim_delay_counter--;
11347 else if (player->post_delay_counter > 0)
11349 player->post_delay_counter--;
11353 else if (player->is_bored)
11355 if (player->num_special_action_bored > 0)
11357 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11359 int special_action =
11360 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11361 int special_graphic =
11362 el_act_dir2img(player->artwork_element, special_action, move_dir);
11364 player->anim_delay_counter =
11365 graphic_info[special_graphic].anim_delay_fixed +
11366 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11367 player->post_delay_counter =
11368 graphic_info[special_graphic].post_delay_fixed +
11369 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11371 player->special_action_bored = special_action;
11374 if (player->anim_delay_counter > 0)
11376 player->action_waiting = player->special_action_bored;
11377 player->anim_delay_counter--;
11379 else if (player->post_delay_counter > 0)
11381 player->post_delay_counter--;
11386 else if (last_waiting) // waiting -> not waiting
11388 player->is_waiting = FALSE;
11389 player->is_bored = FALSE;
11390 player->is_sleeping = FALSE;
11392 player->frame_counter_bored = -1;
11393 player->frame_counter_sleeping = -1;
11395 player->anim_delay_counter = 0;
11396 player->post_delay_counter = 0;
11398 player->dir_waiting = player->MovDir;
11399 player->action_waiting = ACTION_DEFAULT;
11401 player->special_action_bored = ACTION_DEFAULT;
11402 player->special_action_sleeping = ACTION_DEFAULT;
11406 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11408 if ((!player->is_moving && player->was_moving) ||
11409 (player->MovPos == 0 && player->was_moving) ||
11410 (player->is_snapping && !player->was_snapping) ||
11411 (player->is_dropping && !player->was_dropping))
11413 if (!CheckSaveEngineSnapshotToList())
11416 player->was_moving = FALSE;
11417 player->was_snapping = TRUE;
11418 player->was_dropping = TRUE;
11422 if (player->is_moving)
11423 player->was_moving = TRUE;
11425 if (!player->is_snapping)
11426 player->was_snapping = FALSE;
11428 if (!player->is_dropping)
11429 player->was_dropping = FALSE;
11432 static struct MouseActionInfo mouse_action_last = { 0 };
11433 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11434 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11437 CheckSaveEngineSnapshotToList();
11439 mouse_action_last = mouse_action;
11442 static void CheckSingleStepMode(struct PlayerInfo *player)
11444 if (tape.single_step && tape.recording && !tape.pausing)
11446 // as it is called "single step mode", just return to pause mode when the
11447 // player stopped moving after one tile (or never starts moving at all)
11448 // (reverse logic needed here in case single step mode used in team mode)
11449 if (player->is_moving ||
11450 player->is_pushing ||
11451 player->is_dropping_pressed ||
11452 player->effective_mouse_action.button)
11453 game.enter_single_step_mode = FALSE;
11456 CheckSaveEngineSnapshot(player);
11459 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11461 int left = player_action & JOY_LEFT;
11462 int right = player_action & JOY_RIGHT;
11463 int up = player_action & JOY_UP;
11464 int down = player_action & JOY_DOWN;
11465 int button1 = player_action & JOY_BUTTON_1;
11466 int button2 = player_action & JOY_BUTTON_2;
11467 int dx = (left ? -1 : right ? 1 : 0);
11468 int dy = (up ? -1 : down ? 1 : 0);
11470 if (!player->active || tape.pausing)
11476 SnapField(player, dx, dy);
11480 DropElement(player);
11482 MovePlayer(player, dx, dy);
11485 CheckSingleStepMode(player);
11487 SetPlayerWaiting(player, FALSE);
11489 return player_action;
11493 // no actions for this player (no input at player's configured device)
11495 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11496 SnapField(player, 0, 0);
11497 CheckGravityMovementWhenNotMoving(player);
11499 if (player->MovPos == 0)
11500 SetPlayerWaiting(player, TRUE);
11502 if (player->MovPos == 0) // needed for tape.playing
11503 player->is_moving = FALSE;
11505 player->is_dropping = FALSE;
11506 player->is_dropping_pressed = FALSE;
11507 player->drop_pressed_delay = 0;
11509 CheckSingleStepMode(player);
11515 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11518 if (!tape.use_mouse_actions)
11521 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11522 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11523 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11526 static void SetTapeActionFromMouseAction(byte *tape_action,
11527 struct MouseActionInfo *mouse_action)
11529 if (!tape.use_mouse_actions)
11532 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11533 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11534 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11537 static void CheckLevelSolved(void)
11539 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11541 if (game_em.level_solved &&
11542 !game_em.game_over) // game won
11546 game_em.game_over = TRUE;
11548 game.all_players_gone = TRUE;
11551 if (game_em.game_over) // game lost
11552 game.all_players_gone = TRUE;
11554 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11556 if (game_sp.level_solved &&
11557 !game_sp.game_over) // game won
11561 game_sp.game_over = TRUE;
11563 game.all_players_gone = TRUE;
11566 if (game_sp.game_over) // game lost
11567 game.all_players_gone = TRUE;
11569 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11571 if (game_mm.level_solved &&
11572 !game_mm.game_over) // game won
11576 game_mm.game_over = TRUE;
11578 game.all_players_gone = TRUE;
11581 if (game_mm.game_over) // game lost
11582 game.all_players_gone = TRUE;
11586 static void CheckLevelTime(void)
11590 if (TimeFrames >= FRAMES_PER_SECOND)
11595 for (i = 0; i < MAX_PLAYERS; i++)
11597 struct PlayerInfo *player = &stored_player[i];
11599 if (SHIELD_ON(player))
11601 player->shield_normal_time_left--;
11603 if (player->shield_deadly_time_left > 0)
11604 player->shield_deadly_time_left--;
11608 if (!game.LevelSolved && !level.use_step_counter)
11616 if (TimeLeft <= 10 && setup.time_limit)
11617 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11619 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11620 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11622 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11624 if (!TimeLeft && setup.time_limit)
11626 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11627 game_em.lev->killed_out_of_time = TRUE;
11629 for (i = 0; i < MAX_PLAYERS; i++)
11630 KillPlayer(&stored_player[i]);
11633 else if (game.no_time_limit && !game.all_players_gone)
11635 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11638 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11641 if (tape.recording || tape.playing)
11642 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11645 if (tape.recording || tape.playing)
11646 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11648 UpdateAndDisplayGameControlValues();
11651 void AdvanceFrameAndPlayerCounters(int player_nr)
11655 // advance frame counters (global frame counter and time frame counter)
11659 // advance player counters (counters for move delay, move animation etc.)
11660 for (i = 0; i < MAX_PLAYERS; i++)
11662 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11663 int move_delay_value = stored_player[i].move_delay_value;
11664 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11666 if (!advance_player_counters) // not all players may be affected
11669 if (move_frames == 0) // less than one move per game frame
11671 int stepsize = TILEX / move_delay_value;
11672 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11673 int count = (stored_player[i].is_moving ?
11674 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11676 if (count % delay == 0)
11680 stored_player[i].Frame += move_frames;
11682 if (stored_player[i].MovPos != 0)
11683 stored_player[i].StepFrame += move_frames;
11685 if (stored_player[i].move_delay > 0)
11686 stored_player[i].move_delay--;
11688 // due to bugs in previous versions, counter must count up, not down
11689 if (stored_player[i].push_delay != -1)
11690 stored_player[i].push_delay++;
11692 if (stored_player[i].drop_delay > 0)
11693 stored_player[i].drop_delay--;
11695 if (stored_player[i].is_dropping_pressed)
11696 stored_player[i].drop_pressed_delay++;
11700 void StartGameActions(boolean init_network_game, boolean record_tape,
11703 unsigned int new_random_seed = InitRND(random_seed);
11706 TapeStartRecording(new_random_seed);
11708 if (init_network_game)
11710 SendToServer_LevelFile();
11711 SendToServer_StartPlaying();
11719 static void GameActionsExt(void)
11722 static unsigned int game_frame_delay = 0;
11724 unsigned int game_frame_delay_value;
11725 byte *recorded_player_action;
11726 byte summarized_player_action = 0;
11727 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11730 // detect endless loops, caused by custom element programming
11731 if (recursion_loop_detected && recursion_loop_depth == 0)
11733 char *message = getStringCat3("Internal Error! Element ",
11734 EL_NAME(recursion_loop_element),
11735 " caused endless loop! Quit the game?");
11737 Warn("element '%s' caused endless loop in game engine",
11738 EL_NAME(recursion_loop_element));
11740 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11742 recursion_loop_detected = FALSE; // if game should be continued
11749 if (game.restart_level)
11750 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11752 CheckLevelSolved();
11754 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11757 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11760 if (game_status != GAME_MODE_PLAYING) // status might have changed
11763 game_frame_delay_value =
11764 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11766 if (tape.playing && tape.warp_forward && !tape.pausing)
11767 game_frame_delay_value = 0;
11769 SetVideoFrameDelay(game_frame_delay_value);
11771 // (de)activate virtual buttons depending on current game status
11772 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11774 if (game.all_players_gone) // if no players there to be controlled anymore
11775 SetOverlayActive(FALSE);
11776 else if (!tape.playing) // if game continues after tape stopped playing
11777 SetOverlayActive(TRUE);
11782 // ---------- main game synchronization point ----------
11784 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11786 Debug("game:playing:skip", "skip == %d", skip);
11789 // ---------- main game synchronization point ----------
11791 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11795 if (network_playing && !network_player_action_received)
11797 // try to get network player actions in time
11799 // last chance to get network player actions without main loop delay
11800 HandleNetworking();
11802 // game was quit by network peer
11803 if (game_status != GAME_MODE_PLAYING)
11806 // check if network player actions still missing and game still running
11807 if (!network_player_action_received && !checkGameEnded())
11808 return; // failed to get network player actions in time
11810 // do not yet reset "network_player_action_received" (for tape.pausing)
11816 // at this point we know that we really continue executing the game
11818 network_player_action_received = FALSE;
11820 // when playing tape, read previously recorded player input from tape data
11821 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11823 local_player->effective_mouse_action = local_player->mouse_action;
11825 if (recorded_player_action != NULL)
11826 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11827 recorded_player_action);
11829 // TapePlayAction() may return NULL when toggling to "pause before death"
11833 if (tape.set_centered_player)
11835 game.centered_player_nr_next = tape.centered_player_nr_next;
11836 game.set_centered_player = TRUE;
11839 for (i = 0; i < MAX_PLAYERS; i++)
11841 summarized_player_action |= stored_player[i].action;
11843 if (!network_playing && (game.team_mode || tape.playing))
11844 stored_player[i].effective_action = stored_player[i].action;
11847 if (network_playing && !checkGameEnded())
11848 SendToServer_MovePlayer(summarized_player_action);
11850 // summarize all actions at local players mapped input device position
11851 // (this allows using different input devices in single player mode)
11852 if (!network.enabled && !game.team_mode)
11853 stored_player[map_player_action[local_player->index_nr]].effective_action =
11854 summarized_player_action;
11856 // summarize all actions at centered player in local team mode
11857 if (tape.recording &&
11858 setup.team_mode && !network.enabled &&
11859 setup.input_on_focus &&
11860 game.centered_player_nr != -1)
11862 for (i = 0; i < MAX_PLAYERS; i++)
11863 stored_player[map_player_action[i]].effective_action =
11864 (i == game.centered_player_nr ? summarized_player_action : 0);
11867 if (recorded_player_action != NULL)
11868 for (i = 0; i < MAX_PLAYERS; i++)
11869 stored_player[i].effective_action = recorded_player_action[i];
11871 for (i = 0; i < MAX_PLAYERS; i++)
11873 tape_action[i] = stored_player[i].effective_action;
11875 /* (this may happen in the RND game engine if a player was not present on
11876 the playfield on level start, but appeared later from a custom element */
11877 if (setup.team_mode &&
11880 !tape.player_participates[i])
11881 tape.player_participates[i] = TRUE;
11884 SetTapeActionFromMouseAction(tape_action,
11885 &local_player->effective_mouse_action);
11887 // only record actions from input devices, but not programmed actions
11888 if (tape.recording)
11889 TapeRecordAction(tape_action);
11891 // remember if game was played (especially after tape stopped playing)
11892 if (!tape.playing && summarized_player_action)
11893 game.GamePlayed = TRUE;
11895 #if USE_NEW_PLAYER_ASSIGNMENTS
11896 // !!! also map player actions in single player mode !!!
11897 // if (game.team_mode)
11900 byte mapped_action[MAX_PLAYERS];
11902 #if DEBUG_PLAYER_ACTIONS
11903 for (i = 0; i < MAX_PLAYERS; i++)
11904 DebugContinued("", "%d, ", stored_player[i].effective_action);
11907 for (i = 0; i < MAX_PLAYERS; i++)
11908 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11910 for (i = 0; i < MAX_PLAYERS; i++)
11911 stored_player[i].effective_action = mapped_action[i];
11913 #if DEBUG_PLAYER_ACTIONS
11914 DebugContinued("", "=> ");
11915 for (i = 0; i < MAX_PLAYERS; i++)
11916 DebugContinued("", "%d, ", stored_player[i].effective_action);
11917 DebugContinued("game:playing:player", "\n");
11920 #if DEBUG_PLAYER_ACTIONS
11923 for (i = 0; i < MAX_PLAYERS; i++)
11924 DebugContinued("", "%d, ", stored_player[i].effective_action);
11925 DebugContinued("game:playing:player", "\n");
11930 for (i = 0; i < MAX_PLAYERS; i++)
11932 // allow engine snapshot in case of changed movement attempt
11933 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11934 (stored_player[i].effective_action & KEY_MOTION))
11935 game.snapshot.changed_action = TRUE;
11937 // allow engine snapshot in case of snapping/dropping attempt
11938 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11939 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11940 game.snapshot.changed_action = TRUE;
11942 game.snapshot.last_action[i] = stored_player[i].effective_action;
11945 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11947 GameActions_EM_Main();
11949 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11951 GameActions_SP_Main();
11953 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11955 GameActions_MM_Main();
11959 GameActions_RND_Main();
11962 BlitScreenToBitmap(backbuffer);
11964 CheckLevelSolved();
11967 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11969 if (global.show_frames_per_second)
11971 static unsigned int fps_counter = 0;
11972 static int fps_frames = 0;
11973 unsigned int fps_delay_ms = Counter() - fps_counter;
11977 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11979 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11982 fps_counter = Counter();
11984 // always draw FPS to screen after FPS value was updated
11985 redraw_mask |= REDRAW_FPS;
11988 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11989 if (GetDrawDeactivationMask() == REDRAW_NONE)
11990 redraw_mask |= REDRAW_FPS;
11994 static void GameActions_CheckSaveEngineSnapshot(void)
11996 if (!game.snapshot.save_snapshot)
11999 // clear flag for saving snapshot _before_ saving snapshot
12000 game.snapshot.save_snapshot = FALSE;
12002 SaveEngineSnapshotToList();
12005 void GameActions(void)
12009 GameActions_CheckSaveEngineSnapshot();
12012 void GameActions_EM_Main(void)
12014 byte effective_action[MAX_PLAYERS];
12015 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12018 for (i = 0; i < MAX_PLAYERS; i++)
12019 effective_action[i] = stored_player[i].effective_action;
12021 GameActions_EM(effective_action, warp_mode);
12024 void GameActions_SP_Main(void)
12026 byte effective_action[MAX_PLAYERS];
12027 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12030 for (i = 0; i < MAX_PLAYERS; i++)
12031 effective_action[i] = stored_player[i].effective_action;
12033 GameActions_SP(effective_action, warp_mode);
12035 for (i = 0; i < MAX_PLAYERS; i++)
12037 if (stored_player[i].force_dropping)
12038 stored_player[i].action |= KEY_BUTTON_DROP;
12040 stored_player[i].force_dropping = FALSE;
12044 void GameActions_MM_Main(void)
12046 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12048 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12051 void GameActions_RND_Main(void)
12056 void GameActions_RND(void)
12058 static struct MouseActionInfo mouse_action_last = { 0 };
12059 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12060 int magic_wall_x = 0, magic_wall_y = 0;
12061 int i, x, y, element, graphic, last_gfx_frame;
12063 InitPlayfieldScanModeVars();
12065 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12067 SCAN_PLAYFIELD(x, y)
12069 ChangeCount[x][y] = 0;
12070 ChangeEvent[x][y] = -1;
12074 if (game.set_centered_player)
12076 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12078 // switching to "all players" only possible if all players fit to screen
12079 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12081 game.centered_player_nr_next = game.centered_player_nr;
12082 game.set_centered_player = FALSE;
12085 // do not switch focus to non-existing (or non-active) player
12086 if (game.centered_player_nr_next >= 0 &&
12087 !stored_player[game.centered_player_nr_next].active)
12089 game.centered_player_nr_next = game.centered_player_nr;
12090 game.set_centered_player = FALSE;
12094 if (game.set_centered_player &&
12095 ScreenMovPos == 0) // screen currently aligned at tile position
12099 if (game.centered_player_nr_next == -1)
12101 setScreenCenteredToAllPlayers(&sx, &sy);
12105 sx = stored_player[game.centered_player_nr_next].jx;
12106 sy = stored_player[game.centered_player_nr_next].jy;
12109 game.centered_player_nr = game.centered_player_nr_next;
12110 game.set_centered_player = FALSE;
12112 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12113 DrawGameDoorValues();
12116 // check single step mode (set flag and clear again if any player is active)
12117 game.enter_single_step_mode =
12118 (tape.single_step && tape.recording && !tape.pausing);
12120 for (i = 0; i < MAX_PLAYERS; i++)
12122 int actual_player_action = stored_player[i].effective_action;
12125 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12126 - rnd_equinox_tetrachloride 048
12127 - rnd_equinox_tetrachloride_ii 096
12128 - rnd_emanuel_schmieg 002
12129 - doctor_sloan_ww 001, 020
12131 if (stored_player[i].MovPos == 0)
12132 CheckGravityMovement(&stored_player[i]);
12135 // overwrite programmed action with tape action
12136 if (stored_player[i].programmed_action)
12137 actual_player_action = stored_player[i].programmed_action;
12139 PlayerActions(&stored_player[i], actual_player_action);
12141 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12144 // single step pause mode may already have been toggled by "ScrollPlayer()"
12145 if (game.enter_single_step_mode && !tape.pausing)
12146 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12148 ScrollScreen(NULL, SCROLL_GO_ON);
12150 /* for backwards compatibility, the following code emulates a fixed bug that
12151 occured when pushing elements (causing elements that just made their last
12152 pushing step to already (if possible) make their first falling step in the
12153 same game frame, which is bad); this code is also needed to use the famous
12154 "spring push bug" which is used in older levels and might be wanted to be
12155 used also in newer levels, but in this case the buggy pushing code is only
12156 affecting the "spring" element and no other elements */
12158 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12160 for (i = 0; i < MAX_PLAYERS; i++)
12162 struct PlayerInfo *player = &stored_player[i];
12163 int x = player->jx;
12164 int y = player->jy;
12166 if (player->active && player->is_pushing && player->is_moving &&
12168 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12169 Tile[x][y] == EL_SPRING))
12171 ContinueMoving(x, y);
12173 // continue moving after pushing (this is actually a bug)
12174 if (!IS_MOVING(x, y))
12175 Stop[x][y] = FALSE;
12180 SCAN_PLAYFIELD(x, y)
12182 Last[x][y] = Tile[x][y];
12184 ChangeCount[x][y] = 0;
12185 ChangeEvent[x][y] = -1;
12187 // this must be handled before main playfield loop
12188 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12191 if (MovDelay[x][y] <= 0)
12195 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12198 if (MovDelay[x][y] <= 0)
12200 int element = Store[x][y];
12201 int move_direction = MovDir[x][y];
12202 int player_index_bit = Store2[x][y];
12208 TEST_DrawLevelField(x, y);
12210 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12212 if (IS_ENVELOPE(element))
12213 local_player->show_envelope = element;
12218 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12220 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12222 Debug("game:playing:GameActions_RND", "This should never happen!");
12224 ChangePage[x][y] = -1;
12228 Stop[x][y] = FALSE;
12229 if (WasJustMoving[x][y] > 0)
12230 WasJustMoving[x][y]--;
12231 if (WasJustFalling[x][y] > 0)
12232 WasJustFalling[x][y]--;
12233 if (CheckCollision[x][y] > 0)
12234 CheckCollision[x][y]--;
12235 if (CheckImpact[x][y] > 0)
12236 CheckImpact[x][y]--;
12240 /* reset finished pushing action (not done in ContinueMoving() to allow
12241 continuous pushing animation for elements with zero push delay) */
12242 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12244 ResetGfxAnimation(x, y);
12245 TEST_DrawLevelField(x, y);
12249 if (IS_BLOCKED(x, y))
12253 Blocked2Moving(x, y, &oldx, &oldy);
12254 if (!IS_MOVING(oldx, oldy))
12256 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12257 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12258 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12259 Debug("game:playing:GameActions_RND", "This should never happen!");
12265 if (mouse_action.button)
12267 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12268 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12270 x = mouse_action.lx;
12271 y = mouse_action.ly;
12272 element = Tile[x][y];
12276 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12277 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12281 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12282 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12286 SCAN_PLAYFIELD(x, y)
12288 element = Tile[x][y];
12289 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12290 last_gfx_frame = GfxFrame[x][y];
12292 ResetGfxFrame(x, y);
12294 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12295 DrawLevelGraphicAnimation(x, y, graphic);
12297 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12298 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12299 ResetRandomAnimationValue(x, y);
12301 SetRandomAnimationValue(x, y);
12303 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12305 if (IS_INACTIVE(element))
12307 if (IS_ANIMATED(graphic))
12308 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12313 // this may take place after moving, so 'element' may have changed
12314 if (IS_CHANGING(x, y) &&
12315 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12317 int page = element_info[element].event_page_nr[CE_DELAY];
12319 HandleElementChange(x, y, page);
12321 element = Tile[x][y];
12322 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12325 CheckNextToConditions(x, y);
12327 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12331 element = Tile[x][y];
12332 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12334 if (IS_ANIMATED(graphic) &&
12335 !IS_MOVING(x, y) &&
12337 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12339 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12340 TEST_DrawTwinkleOnField(x, y);
12342 else if (element == EL_ACID)
12345 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12347 else if ((element == EL_EXIT_OPEN ||
12348 element == EL_EM_EXIT_OPEN ||
12349 element == EL_SP_EXIT_OPEN ||
12350 element == EL_STEEL_EXIT_OPEN ||
12351 element == EL_EM_STEEL_EXIT_OPEN ||
12352 element == EL_SP_TERMINAL ||
12353 element == EL_SP_TERMINAL_ACTIVE ||
12354 element == EL_EXTRA_TIME ||
12355 element == EL_SHIELD_NORMAL ||
12356 element == EL_SHIELD_DEADLY) &&
12357 IS_ANIMATED(graphic))
12358 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12359 else if (IS_MOVING(x, y))
12360 ContinueMoving(x, y);
12361 else if (IS_ACTIVE_BOMB(element))
12362 CheckDynamite(x, y);
12363 else if (element == EL_AMOEBA_GROWING)
12364 AmoebaGrowing(x, y);
12365 else if (element == EL_AMOEBA_SHRINKING)
12366 AmoebaShrinking(x, y);
12368 #if !USE_NEW_AMOEBA_CODE
12369 else if (IS_AMOEBALIVE(element))
12370 AmoebaReproduce(x, y);
12373 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12375 else if (element == EL_EXIT_CLOSED)
12377 else if (element == EL_EM_EXIT_CLOSED)
12379 else if (element == EL_STEEL_EXIT_CLOSED)
12380 CheckExitSteel(x, y);
12381 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12382 CheckExitSteelEM(x, y);
12383 else if (element == EL_SP_EXIT_CLOSED)
12385 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12386 element == EL_EXPANDABLE_STEELWALL_GROWING)
12387 MauerWaechst(x, y);
12388 else if (element == EL_EXPANDABLE_WALL ||
12389 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12390 element == EL_EXPANDABLE_WALL_VERTICAL ||
12391 element == EL_EXPANDABLE_WALL_ANY ||
12392 element == EL_BD_EXPANDABLE_WALL)
12393 MauerAbleger(x, y);
12394 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12395 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12396 element == EL_EXPANDABLE_STEELWALL_ANY)
12397 MauerAblegerStahl(x, y);
12398 else if (element == EL_FLAMES)
12399 CheckForDragon(x, y);
12400 else if (element == EL_EXPLOSION)
12401 ; // drawing of correct explosion animation is handled separately
12402 else if (element == EL_ELEMENT_SNAPPING ||
12403 element == EL_DIAGONAL_SHRINKING ||
12404 element == EL_DIAGONAL_GROWING)
12406 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12408 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12410 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12411 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12413 if (IS_BELT_ACTIVE(element))
12414 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12416 if (game.magic_wall_active)
12418 int jx = local_player->jx, jy = local_player->jy;
12420 // play the element sound at the position nearest to the player
12421 if ((element == EL_MAGIC_WALL_FULL ||
12422 element == EL_MAGIC_WALL_ACTIVE ||
12423 element == EL_MAGIC_WALL_EMPTYING ||
12424 element == EL_BD_MAGIC_WALL_FULL ||
12425 element == EL_BD_MAGIC_WALL_ACTIVE ||
12426 element == EL_BD_MAGIC_WALL_EMPTYING ||
12427 element == EL_DC_MAGIC_WALL_FULL ||
12428 element == EL_DC_MAGIC_WALL_ACTIVE ||
12429 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12430 ABS(x - jx) + ABS(y - jy) <
12431 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12439 #if USE_NEW_AMOEBA_CODE
12440 // new experimental amoeba growth stuff
12441 if (!(FrameCounter % 8))
12443 static unsigned int random = 1684108901;
12445 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12447 x = RND(lev_fieldx);
12448 y = RND(lev_fieldy);
12449 element = Tile[x][y];
12451 if (!IS_PLAYER(x,y) &&
12452 (element == EL_EMPTY ||
12453 CAN_GROW_INTO(element) ||
12454 element == EL_QUICKSAND_EMPTY ||
12455 element == EL_QUICKSAND_FAST_EMPTY ||
12456 element == EL_ACID_SPLASH_LEFT ||
12457 element == EL_ACID_SPLASH_RIGHT))
12459 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12460 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12461 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12462 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12463 Tile[x][y] = EL_AMOEBA_DROP;
12466 random = random * 129 + 1;
12471 game.explosions_delayed = FALSE;
12473 SCAN_PLAYFIELD(x, y)
12475 element = Tile[x][y];
12477 if (ExplodeField[x][y])
12478 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12479 else if (element == EL_EXPLOSION)
12480 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12482 ExplodeField[x][y] = EX_TYPE_NONE;
12485 game.explosions_delayed = TRUE;
12487 if (game.magic_wall_active)
12489 if (!(game.magic_wall_time_left % 4))
12491 int element = Tile[magic_wall_x][magic_wall_y];
12493 if (element == EL_BD_MAGIC_WALL_FULL ||
12494 element == EL_BD_MAGIC_WALL_ACTIVE ||
12495 element == EL_BD_MAGIC_WALL_EMPTYING)
12496 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12497 else if (element == EL_DC_MAGIC_WALL_FULL ||
12498 element == EL_DC_MAGIC_WALL_ACTIVE ||
12499 element == EL_DC_MAGIC_WALL_EMPTYING)
12500 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12502 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12505 if (game.magic_wall_time_left > 0)
12507 game.magic_wall_time_left--;
12509 if (!game.magic_wall_time_left)
12511 SCAN_PLAYFIELD(x, y)
12513 element = Tile[x][y];
12515 if (element == EL_MAGIC_WALL_ACTIVE ||
12516 element == EL_MAGIC_WALL_FULL)
12518 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12519 TEST_DrawLevelField(x, y);
12521 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12522 element == EL_BD_MAGIC_WALL_FULL)
12524 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12525 TEST_DrawLevelField(x, y);
12527 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12528 element == EL_DC_MAGIC_WALL_FULL)
12530 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12531 TEST_DrawLevelField(x, y);
12535 game.magic_wall_active = FALSE;
12540 if (game.light_time_left > 0)
12542 game.light_time_left--;
12544 if (game.light_time_left == 0)
12545 RedrawAllLightSwitchesAndInvisibleElements();
12548 if (game.timegate_time_left > 0)
12550 game.timegate_time_left--;
12552 if (game.timegate_time_left == 0)
12553 CloseAllOpenTimegates();
12556 if (game.lenses_time_left > 0)
12558 game.lenses_time_left--;
12560 if (game.lenses_time_left == 0)
12561 RedrawAllInvisibleElementsForLenses();
12564 if (game.magnify_time_left > 0)
12566 game.magnify_time_left--;
12568 if (game.magnify_time_left == 0)
12569 RedrawAllInvisibleElementsForMagnifier();
12572 for (i = 0; i < MAX_PLAYERS; i++)
12574 struct PlayerInfo *player = &stored_player[i];
12576 if (SHIELD_ON(player))
12578 if (player->shield_deadly_time_left)
12579 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12580 else if (player->shield_normal_time_left)
12581 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12585 #if USE_DELAYED_GFX_REDRAW
12586 SCAN_PLAYFIELD(x, y)
12588 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12590 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12591 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12593 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12594 DrawLevelField(x, y);
12596 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12597 DrawLevelFieldCrumbled(x, y);
12599 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12600 DrawLevelFieldCrumbledNeighbours(x, y);
12602 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12603 DrawTwinkleOnField(x, y);
12606 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12611 PlayAllPlayersSound();
12613 for (i = 0; i < MAX_PLAYERS; i++)
12615 struct PlayerInfo *player = &stored_player[i];
12617 if (player->show_envelope != 0 && (!player->active ||
12618 player->MovPos == 0))
12620 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12622 player->show_envelope = 0;
12626 // use random number generator in every frame to make it less predictable
12627 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12630 mouse_action_last = mouse_action;
12633 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12635 int min_x = x, min_y = y, max_x = x, max_y = y;
12636 int scr_fieldx = getScreenFieldSizeX();
12637 int scr_fieldy = getScreenFieldSizeY();
12640 for (i = 0; i < MAX_PLAYERS; i++)
12642 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12644 if (!stored_player[i].active || &stored_player[i] == player)
12647 min_x = MIN(min_x, jx);
12648 min_y = MIN(min_y, jy);
12649 max_x = MAX(max_x, jx);
12650 max_y = MAX(max_y, jy);
12653 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12656 static boolean AllPlayersInVisibleScreen(void)
12660 for (i = 0; i < MAX_PLAYERS; i++)
12662 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12664 if (!stored_player[i].active)
12667 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12674 void ScrollLevel(int dx, int dy)
12676 int scroll_offset = 2 * TILEX_VAR;
12679 BlitBitmap(drawto_field, drawto_field,
12680 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12681 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12682 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12683 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12684 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12685 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12689 x = (dx == 1 ? BX1 : BX2);
12690 for (y = BY1; y <= BY2; y++)
12691 DrawScreenField(x, y);
12696 y = (dy == 1 ? BY1 : BY2);
12697 for (x = BX1; x <= BX2; x++)
12698 DrawScreenField(x, y);
12701 redraw_mask |= REDRAW_FIELD;
12704 static boolean canFallDown(struct PlayerInfo *player)
12706 int jx = player->jx, jy = player->jy;
12708 return (IN_LEV_FIELD(jx, jy + 1) &&
12709 (IS_FREE(jx, jy + 1) ||
12710 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12711 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12712 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12715 static boolean canPassField(int x, int y, int move_dir)
12717 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12718 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12719 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12720 int nextx = x + dx;
12721 int nexty = y + dy;
12722 int element = Tile[x][y];
12724 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12725 !CAN_MOVE(element) &&
12726 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12727 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12728 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12731 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12733 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12734 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12735 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12739 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12740 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12741 (IS_DIGGABLE(Tile[newx][newy]) ||
12742 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12743 canPassField(newx, newy, move_dir)));
12746 static void CheckGravityMovement(struct PlayerInfo *player)
12748 if (player->gravity && !player->programmed_action)
12750 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12751 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12752 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12753 int jx = player->jx, jy = player->jy;
12754 boolean player_is_moving_to_valid_field =
12755 (!player_is_snapping &&
12756 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12757 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12758 boolean player_can_fall_down = canFallDown(player);
12760 if (player_can_fall_down &&
12761 !player_is_moving_to_valid_field)
12762 player->programmed_action = MV_DOWN;
12766 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12768 return CheckGravityMovement(player);
12770 if (player->gravity && !player->programmed_action)
12772 int jx = player->jx, jy = player->jy;
12773 boolean field_under_player_is_free =
12774 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12775 boolean player_is_standing_on_valid_field =
12776 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12777 (IS_WALKABLE(Tile[jx][jy]) &&
12778 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12780 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12781 player->programmed_action = MV_DOWN;
12786 MovePlayerOneStep()
12787 -----------------------------------------------------------------------------
12788 dx, dy: direction (non-diagonal) to try to move the player to
12789 real_dx, real_dy: direction as read from input device (can be diagonal)
12792 boolean MovePlayerOneStep(struct PlayerInfo *player,
12793 int dx, int dy, int real_dx, int real_dy)
12795 int jx = player->jx, jy = player->jy;
12796 int new_jx = jx + dx, new_jy = jy + dy;
12798 boolean player_can_move = !player->cannot_move;
12800 if (!player->active || (!dx && !dy))
12801 return MP_NO_ACTION;
12803 player->MovDir = (dx < 0 ? MV_LEFT :
12804 dx > 0 ? MV_RIGHT :
12806 dy > 0 ? MV_DOWN : MV_NONE);
12808 if (!IN_LEV_FIELD(new_jx, new_jy))
12809 return MP_NO_ACTION;
12811 if (!player_can_move)
12813 if (player->MovPos == 0)
12815 player->is_moving = FALSE;
12816 player->is_digging = FALSE;
12817 player->is_collecting = FALSE;
12818 player->is_snapping = FALSE;
12819 player->is_pushing = FALSE;
12823 if (!network.enabled && game.centered_player_nr == -1 &&
12824 !AllPlayersInSight(player, new_jx, new_jy))
12825 return MP_NO_ACTION;
12827 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12828 if (can_move != MP_MOVING)
12831 // check if DigField() has caused relocation of the player
12832 if (player->jx != jx || player->jy != jy)
12833 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12835 StorePlayer[jx][jy] = 0;
12836 player->last_jx = jx;
12837 player->last_jy = jy;
12838 player->jx = new_jx;
12839 player->jy = new_jy;
12840 StorePlayer[new_jx][new_jy] = player->element_nr;
12842 if (player->move_delay_value_next != -1)
12844 player->move_delay_value = player->move_delay_value_next;
12845 player->move_delay_value_next = -1;
12849 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12851 player->step_counter++;
12853 PlayerVisit[jx][jy] = FrameCounter;
12855 player->is_moving = TRUE;
12858 // should better be called in MovePlayer(), but this breaks some tapes
12859 ScrollPlayer(player, SCROLL_INIT);
12865 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12867 int jx = player->jx, jy = player->jy;
12868 int old_jx = jx, old_jy = jy;
12869 int moved = MP_NO_ACTION;
12871 if (!player->active)
12876 if (player->MovPos == 0)
12878 player->is_moving = FALSE;
12879 player->is_digging = FALSE;
12880 player->is_collecting = FALSE;
12881 player->is_snapping = FALSE;
12882 player->is_pushing = FALSE;
12888 if (player->move_delay > 0)
12891 player->move_delay = -1; // set to "uninitialized" value
12893 // store if player is automatically moved to next field
12894 player->is_auto_moving = (player->programmed_action != MV_NONE);
12896 // remove the last programmed player action
12897 player->programmed_action = 0;
12899 if (player->MovPos)
12901 // should only happen if pre-1.2 tape recordings are played
12902 // this is only for backward compatibility
12904 int original_move_delay_value = player->move_delay_value;
12907 Debug("game:playing:MovePlayer",
12908 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12912 // scroll remaining steps with finest movement resolution
12913 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12915 while (player->MovPos)
12917 ScrollPlayer(player, SCROLL_GO_ON);
12918 ScrollScreen(NULL, SCROLL_GO_ON);
12920 AdvanceFrameAndPlayerCounters(player->index_nr);
12923 BackToFront_WithFrameDelay(0);
12926 player->move_delay_value = original_move_delay_value;
12929 player->is_active = FALSE;
12931 if (player->last_move_dir & MV_HORIZONTAL)
12933 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12934 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12938 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12939 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12942 if (!moved && !player->is_active)
12944 player->is_moving = FALSE;
12945 player->is_digging = FALSE;
12946 player->is_collecting = FALSE;
12947 player->is_snapping = FALSE;
12948 player->is_pushing = FALSE;
12954 if (moved & MP_MOVING && !ScreenMovPos &&
12955 (player->index_nr == game.centered_player_nr ||
12956 game.centered_player_nr == -1))
12958 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12960 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12962 // actual player has left the screen -- scroll in that direction
12963 if (jx != old_jx) // player has moved horizontally
12964 scroll_x += (jx - old_jx);
12965 else // player has moved vertically
12966 scroll_y += (jy - old_jy);
12970 int offset_raw = game.scroll_delay_value;
12972 if (jx != old_jx) // player has moved horizontally
12974 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12975 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12976 int new_scroll_x = jx - MIDPOSX + offset_x;
12978 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12979 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12980 scroll_x = new_scroll_x;
12982 // don't scroll over playfield boundaries
12983 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12985 // don't scroll more than one field at a time
12986 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12988 // don't scroll against the player's moving direction
12989 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12990 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12991 scroll_x = old_scroll_x;
12993 else // player has moved vertically
12995 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12996 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12997 int new_scroll_y = jy - MIDPOSY + offset_y;
12999 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13000 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13001 scroll_y = new_scroll_y;
13003 // don't scroll over playfield boundaries
13004 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13006 // don't scroll more than one field at a time
13007 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13009 // don't scroll against the player's moving direction
13010 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13011 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13012 scroll_y = old_scroll_y;
13016 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13018 if (!network.enabled && game.centered_player_nr == -1 &&
13019 !AllPlayersInVisibleScreen())
13021 scroll_x = old_scroll_x;
13022 scroll_y = old_scroll_y;
13026 ScrollScreen(player, SCROLL_INIT);
13027 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13032 player->StepFrame = 0;
13034 if (moved & MP_MOVING)
13036 if (old_jx != jx && old_jy == jy)
13037 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13038 else if (old_jx == jx && old_jy != jy)
13039 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13041 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13043 player->last_move_dir = player->MovDir;
13044 player->is_moving = TRUE;
13045 player->is_snapping = FALSE;
13046 player->is_switching = FALSE;
13047 player->is_dropping = FALSE;
13048 player->is_dropping_pressed = FALSE;
13049 player->drop_pressed_delay = 0;
13052 // should better be called here than above, but this breaks some tapes
13053 ScrollPlayer(player, SCROLL_INIT);
13058 CheckGravityMovementWhenNotMoving(player);
13060 player->is_moving = FALSE;
13062 /* at this point, the player is allowed to move, but cannot move right now
13063 (e.g. because of something blocking the way) -- ensure that the player
13064 is also allowed to move in the next frame (in old versions before 3.1.1,
13065 the player was forced to wait again for eight frames before next try) */
13067 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13068 player->move_delay = 0; // allow direct movement in the next frame
13071 if (player->move_delay == -1) // not yet initialized by DigField()
13072 player->move_delay = player->move_delay_value;
13074 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13076 TestIfPlayerTouchesBadThing(jx, jy);
13077 TestIfPlayerTouchesCustomElement(jx, jy);
13080 if (!player->active)
13081 RemovePlayer(player);
13086 void ScrollPlayer(struct PlayerInfo *player, int mode)
13088 int jx = player->jx, jy = player->jy;
13089 int last_jx = player->last_jx, last_jy = player->last_jy;
13090 int move_stepsize = TILEX / player->move_delay_value;
13092 if (!player->active)
13095 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13098 if (mode == SCROLL_INIT)
13100 player->actual_frame_counter = FrameCounter;
13101 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13103 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13104 Tile[last_jx][last_jy] == EL_EMPTY)
13106 int last_field_block_delay = 0; // start with no blocking at all
13107 int block_delay_adjustment = player->block_delay_adjustment;
13109 // if player blocks last field, add delay for exactly one move
13110 if (player->block_last_field)
13112 last_field_block_delay += player->move_delay_value;
13114 // when blocking enabled, prevent moving up despite gravity
13115 if (player->gravity && player->MovDir == MV_UP)
13116 block_delay_adjustment = -1;
13119 // add block delay adjustment (also possible when not blocking)
13120 last_field_block_delay += block_delay_adjustment;
13122 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13123 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13126 if (player->MovPos != 0) // player has not yet reached destination
13129 else if (!FrameReached(&player->actual_frame_counter, 1))
13132 if (player->MovPos != 0)
13134 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13135 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13137 // before DrawPlayer() to draw correct player graphic for this case
13138 if (player->MovPos == 0)
13139 CheckGravityMovement(player);
13142 if (player->MovPos == 0) // player reached destination field
13144 if (player->move_delay_reset_counter > 0)
13146 player->move_delay_reset_counter--;
13148 if (player->move_delay_reset_counter == 0)
13150 // continue with normal speed after quickly moving through gate
13151 HALVE_PLAYER_SPEED(player);
13153 // be able to make the next move without delay
13154 player->move_delay = 0;
13158 player->last_jx = jx;
13159 player->last_jy = jy;
13161 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13162 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13163 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13164 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13165 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13166 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13167 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13168 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13170 ExitPlayer(player);
13172 if (game.players_still_needed == 0 &&
13173 (game.friends_still_needed == 0 ||
13174 IS_SP_ELEMENT(Tile[jx][jy])))
13178 // this breaks one level: "machine", level 000
13180 int move_direction = player->MovDir;
13181 int enter_side = MV_DIR_OPPOSITE(move_direction);
13182 int leave_side = move_direction;
13183 int old_jx = last_jx;
13184 int old_jy = last_jy;
13185 int old_element = Tile[old_jx][old_jy];
13186 int new_element = Tile[jx][jy];
13188 if (IS_CUSTOM_ELEMENT(old_element))
13189 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13191 player->index_bit, leave_side);
13193 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13194 CE_PLAYER_LEAVES_X,
13195 player->index_bit, leave_side);
13197 if (IS_CUSTOM_ELEMENT(new_element))
13198 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13199 player->index_bit, enter_side);
13201 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13202 CE_PLAYER_ENTERS_X,
13203 player->index_bit, enter_side);
13205 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13206 CE_MOVE_OF_X, move_direction);
13209 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13211 TestIfPlayerTouchesBadThing(jx, jy);
13212 TestIfPlayerTouchesCustomElement(jx, jy);
13214 /* needed because pushed element has not yet reached its destination,
13215 so it would trigger a change event at its previous field location */
13216 if (!player->is_pushing)
13217 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13219 if (level.finish_dig_collect &&
13220 (player->is_digging || player->is_collecting))
13222 int last_element = player->last_removed_element;
13223 int move_direction = player->MovDir;
13224 int enter_side = MV_DIR_OPPOSITE(move_direction);
13225 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13226 CE_PLAYER_COLLECTS_X);
13228 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13229 player->index_bit, enter_side);
13231 player->last_removed_element = EL_UNDEFINED;
13234 if (!player->active)
13235 RemovePlayer(player);
13238 if (level.use_step_counter)
13248 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13249 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13251 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13253 DisplayGameControlValues();
13255 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13256 for (i = 0; i < MAX_PLAYERS; i++)
13257 KillPlayer(&stored_player[i]);
13259 else if (game.no_time_limit && !game.all_players_gone)
13261 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13263 DisplayGameControlValues();
13267 if (tape.single_step && tape.recording && !tape.pausing &&
13268 !player->programmed_action)
13269 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13271 if (!player->programmed_action)
13272 CheckSaveEngineSnapshot(player);
13276 void ScrollScreen(struct PlayerInfo *player, int mode)
13278 static unsigned int screen_frame_counter = 0;
13280 if (mode == SCROLL_INIT)
13282 // set scrolling step size according to actual player's moving speed
13283 ScrollStepSize = TILEX / player->move_delay_value;
13285 screen_frame_counter = FrameCounter;
13286 ScreenMovDir = player->MovDir;
13287 ScreenMovPos = player->MovPos;
13288 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13291 else if (!FrameReached(&screen_frame_counter, 1))
13296 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13297 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13298 redraw_mask |= REDRAW_FIELD;
13301 ScreenMovDir = MV_NONE;
13304 void CheckNextToConditions(int x, int y)
13306 int element = Tile[x][y];
13308 if (IS_PLAYER(x, y))
13309 TestIfPlayerNextToCustomElement(x, y);
13311 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13312 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13313 TestIfElementNextToCustomElement(x, y);
13316 void TestIfPlayerNextToCustomElement(int x, int y)
13318 static int xy[4][2] =
13325 static int trigger_sides[4][2] =
13327 // center side border side
13328 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13329 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13330 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13331 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13335 if (!IS_PLAYER(x, y))
13338 struct PlayerInfo *player = PLAYERINFO(x, y);
13340 if (player->is_moving)
13343 for (i = 0; i < NUM_DIRECTIONS; i++)
13345 int xx = x + xy[i][0];
13346 int yy = y + xy[i][1];
13347 int border_side = trigger_sides[i][1];
13348 int border_element;
13350 if (!IN_LEV_FIELD(xx, yy))
13353 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13354 continue; // center and border element not connected
13356 border_element = Tile[xx][yy];
13358 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13359 player->index_bit, border_side);
13360 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13361 CE_PLAYER_NEXT_TO_X,
13362 player->index_bit, border_side);
13364 /* use player element that is initially defined in the level playfield,
13365 not the player element that corresponds to the runtime player number
13366 (example: a level that contains EL_PLAYER_3 as the only player would
13367 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13369 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13370 CE_NEXT_TO_X, border_side);
13374 void TestIfPlayerTouchesCustomElement(int x, int y)
13376 static int xy[4][2] =
13383 static int trigger_sides[4][2] =
13385 // center side border side
13386 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13387 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13388 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13389 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13391 static int touch_dir[4] =
13393 MV_LEFT | MV_RIGHT,
13398 int center_element = Tile[x][y]; // should always be non-moving!
13401 for (i = 0; i < NUM_DIRECTIONS; i++)
13403 int xx = x + xy[i][0];
13404 int yy = y + xy[i][1];
13405 int center_side = trigger_sides[i][0];
13406 int border_side = trigger_sides[i][1];
13407 int border_element;
13409 if (!IN_LEV_FIELD(xx, yy))
13412 if (IS_PLAYER(x, y)) // player found at center element
13414 struct PlayerInfo *player = PLAYERINFO(x, y);
13416 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13417 border_element = Tile[xx][yy]; // may be moving!
13418 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13419 border_element = Tile[xx][yy];
13420 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13421 border_element = MovingOrBlocked2Element(xx, yy);
13423 continue; // center and border element do not touch
13425 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13426 player->index_bit, border_side);
13427 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13428 CE_PLAYER_TOUCHES_X,
13429 player->index_bit, border_side);
13432 /* use player element that is initially defined in the level playfield,
13433 not the player element that corresponds to the runtime player number
13434 (example: a level that contains EL_PLAYER_3 as the only player would
13435 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13436 int player_element = PLAYERINFO(x, y)->initial_element;
13438 CheckElementChangeBySide(xx, yy, border_element, player_element,
13439 CE_TOUCHING_X, border_side);
13442 else if (IS_PLAYER(xx, yy)) // player found at border element
13444 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13446 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13448 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13449 continue; // center and border element do not touch
13452 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13453 player->index_bit, center_side);
13454 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13455 CE_PLAYER_TOUCHES_X,
13456 player->index_bit, center_side);
13459 /* use player element that is initially defined in the level playfield,
13460 not the player element that corresponds to the runtime player number
13461 (example: a level that contains EL_PLAYER_3 as the only player would
13462 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13463 int player_element = PLAYERINFO(xx, yy)->initial_element;
13465 CheckElementChangeBySide(x, y, center_element, player_element,
13466 CE_TOUCHING_X, center_side);
13474 void TestIfElementNextToCustomElement(int x, int y)
13476 static int xy[4][2] =
13483 static int trigger_sides[4][2] =
13485 // center side border side
13486 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13487 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13488 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13489 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13491 int center_element = Tile[x][y]; // should always be non-moving!
13494 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13497 for (i = 0; i < NUM_DIRECTIONS; i++)
13499 int xx = x + xy[i][0];
13500 int yy = y + xy[i][1];
13501 int border_side = trigger_sides[i][1];
13502 int border_element;
13504 if (!IN_LEV_FIELD(xx, yy))
13507 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13508 continue; // center and border element not connected
13510 border_element = Tile[xx][yy];
13512 // check for change of center element (but change it only once)
13513 if (CheckElementChangeBySide(x, y, center_element, border_element,
13514 CE_NEXT_TO_X, border_side))
13519 void TestIfElementTouchesCustomElement(int x, int y)
13521 static int xy[4][2] =
13528 static int trigger_sides[4][2] =
13530 // center side border side
13531 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13532 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13533 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13534 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13536 static int touch_dir[4] =
13538 MV_LEFT | MV_RIGHT,
13543 boolean change_center_element = FALSE;
13544 int center_element = Tile[x][y]; // should always be non-moving!
13545 int border_element_old[NUM_DIRECTIONS];
13548 for (i = 0; i < NUM_DIRECTIONS; i++)
13550 int xx = x + xy[i][0];
13551 int yy = y + xy[i][1];
13552 int border_element;
13554 border_element_old[i] = -1;
13556 if (!IN_LEV_FIELD(xx, yy))
13559 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13560 border_element = Tile[xx][yy]; // may be moving!
13561 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13562 border_element = Tile[xx][yy];
13563 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13564 border_element = MovingOrBlocked2Element(xx, yy);
13566 continue; // center and border element do not touch
13568 border_element_old[i] = border_element;
13571 for (i = 0; i < NUM_DIRECTIONS; i++)
13573 int xx = x + xy[i][0];
13574 int yy = y + xy[i][1];
13575 int center_side = trigger_sides[i][0];
13576 int border_element = border_element_old[i];
13578 if (border_element == -1)
13581 // check for change of border element
13582 CheckElementChangeBySide(xx, yy, border_element, center_element,
13583 CE_TOUCHING_X, center_side);
13585 // (center element cannot be player, so we dont have to check this here)
13588 for (i = 0; i < NUM_DIRECTIONS; i++)
13590 int xx = x + xy[i][0];
13591 int yy = y + xy[i][1];
13592 int border_side = trigger_sides[i][1];
13593 int border_element = border_element_old[i];
13595 if (border_element == -1)
13598 // check for change of center element (but change it only once)
13599 if (!change_center_element)
13600 change_center_element =
13601 CheckElementChangeBySide(x, y, center_element, border_element,
13602 CE_TOUCHING_X, border_side);
13604 if (IS_PLAYER(xx, yy))
13606 /* use player element that is initially defined in the level playfield,
13607 not the player element that corresponds to the runtime player number
13608 (example: a level that contains EL_PLAYER_3 as the only player would
13609 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13610 int player_element = PLAYERINFO(xx, yy)->initial_element;
13612 CheckElementChangeBySide(x, y, center_element, player_element,
13613 CE_TOUCHING_X, border_side);
13618 void TestIfElementHitsCustomElement(int x, int y, int direction)
13620 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13621 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13622 int hitx = x + dx, hity = y + dy;
13623 int hitting_element = Tile[x][y];
13624 int touched_element;
13626 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13629 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13630 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13632 if (IN_LEV_FIELD(hitx, hity))
13634 int opposite_direction = MV_DIR_OPPOSITE(direction);
13635 int hitting_side = direction;
13636 int touched_side = opposite_direction;
13637 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13638 MovDir[hitx][hity] != direction ||
13639 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13645 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13646 CE_HITTING_X, touched_side);
13648 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13649 CE_HIT_BY_X, hitting_side);
13651 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13652 CE_HIT_BY_SOMETHING, opposite_direction);
13654 if (IS_PLAYER(hitx, hity))
13656 /* use player element that is initially defined in the level playfield,
13657 not the player element that corresponds to the runtime player number
13658 (example: a level that contains EL_PLAYER_3 as the only player would
13659 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13660 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13662 CheckElementChangeBySide(x, y, hitting_element, player_element,
13663 CE_HITTING_X, touched_side);
13668 // "hitting something" is also true when hitting the playfield border
13669 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13670 CE_HITTING_SOMETHING, direction);
13673 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13675 int i, kill_x = -1, kill_y = -1;
13677 int bad_element = -1;
13678 static int test_xy[4][2] =
13685 static int test_dir[4] =
13693 for (i = 0; i < NUM_DIRECTIONS; i++)
13695 int test_x, test_y, test_move_dir, test_element;
13697 test_x = good_x + test_xy[i][0];
13698 test_y = good_y + test_xy[i][1];
13700 if (!IN_LEV_FIELD(test_x, test_y))
13704 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13706 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13708 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13709 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13711 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13712 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13716 bad_element = test_element;
13722 if (kill_x != -1 || kill_y != -1)
13724 if (IS_PLAYER(good_x, good_y))
13726 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13728 if (player->shield_deadly_time_left > 0 &&
13729 !IS_INDESTRUCTIBLE(bad_element))
13730 Bang(kill_x, kill_y);
13731 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13732 KillPlayer(player);
13735 Bang(good_x, good_y);
13739 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13741 int i, kill_x = -1, kill_y = -1;
13742 int bad_element = Tile[bad_x][bad_y];
13743 static int test_xy[4][2] =
13750 static int touch_dir[4] =
13752 MV_LEFT | MV_RIGHT,
13757 static int test_dir[4] =
13765 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13768 for (i = 0; i < NUM_DIRECTIONS; i++)
13770 int test_x, test_y, test_move_dir, test_element;
13772 test_x = bad_x + test_xy[i][0];
13773 test_y = bad_y + test_xy[i][1];
13775 if (!IN_LEV_FIELD(test_x, test_y))
13779 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13781 test_element = Tile[test_x][test_y];
13783 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13784 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13786 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13787 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13789 // good thing is player or penguin that does not move away
13790 if (IS_PLAYER(test_x, test_y))
13792 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13794 if (bad_element == EL_ROBOT && player->is_moving)
13795 continue; // robot does not kill player if he is moving
13797 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13799 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13800 continue; // center and border element do not touch
13808 else if (test_element == EL_PENGUIN)
13818 if (kill_x != -1 || kill_y != -1)
13820 if (IS_PLAYER(kill_x, kill_y))
13822 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13824 if (player->shield_deadly_time_left > 0 &&
13825 !IS_INDESTRUCTIBLE(bad_element))
13826 Bang(bad_x, bad_y);
13827 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13828 KillPlayer(player);
13831 Bang(kill_x, kill_y);
13835 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13837 int bad_element = Tile[bad_x][bad_y];
13838 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13839 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13840 int test_x = bad_x + dx, test_y = bad_y + dy;
13841 int test_move_dir, test_element;
13842 int kill_x = -1, kill_y = -1;
13844 if (!IN_LEV_FIELD(test_x, test_y))
13848 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13850 test_element = Tile[test_x][test_y];
13852 if (test_move_dir != bad_move_dir)
13854 // good thing can be player or penguin that does not move away
13855 if (IS_PLAYER(test_x, test_y))
13857 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13859 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13860 player as being hit when he is moving towards the bad thing, because
13861 the "get hit by" condition would be lost after the player stops) */
13862 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13863 return; // player moves away from bad thing
13868 else if (test_element == EL_PENGUIN)
13875 if (kill_x != -1 || kill_y != -1)
13877 if (IS_PLAYER(kill_x, kill_y))
13879 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13881 if (player->shield_deadly_time_left > 0 &&
13882 !IS_INDESTRUCTIBLE(bad_element))
13883 Bang(bad_x, bad_y);
13884 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13885 KillPlayer(player);
13888 Bang(kill_x, kill_y);
13892 void TestIfPlayerTouchesBadThing(int x, int y)
13894 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13897 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13899 TestIfGoodThingHitsBadThing(x, y, move_dir);
13902 void TestIfBadThingTouchesPlayer(int x, int y)
13904 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13907 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13909 TestIfBadThingHitsGoodThing(x, y, move_dir);
13912 void TestIfFriendTouchesBadThing(int x, int y)
13914 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13917 void TestIfBadThingTouchesFriend(int x, int y)
13919 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13922 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13924 int i, kill_x = bad_x, kill_y = bad_y;
13925 static int xy[4][2] =
13933 for (i = 0; i < NUM_DIRECTIONS; i++)
13937 x = bad_x + xy[i][0];
13938 y = bad_y + xy[i][1];
13939 if (!IN_LEV_FIELD(x, y))
13942 element = Tile[x][y];
13943 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13944 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13952 if (kill_x != bad_x || kill_y != bad_y)
13953 Bang(bad_x, bad_y);
13956 void KillPlayer(struct PlayerInfo *player)
13958 int jx = player->jx, jy = player->jy;
13960 if (!player->active)
13964 Debug("game:playing:KillPlayer",
13965 "0: killed == %d, active == %d, reanimated == %d",
13966 player->killed, player->active, player->reanimated);
13969 /* the following code was introduced to prevent an infinite loop when calling
13971 -> CheckTriggeredElementChangeExt()
13972 -> ExecuteCustomElementAction()
13974 -> (infinitely repeating the above sequence of function calls)
13975 which occurs when killing the player while having a CE with the setting
13976 "kill player X when explosion of <player X>"; the solution using a new
13977 field "player->killed" was chosen for backwards compatibility, although
13978 clever use of the fields "player->active" etc. would probably also work */
13980 if (player->killed)
13984 player->killed = TRUE;
13986 // remove accessible field at the player's position
13987 Tile[jx][jy] = EL_EMPTY;
13989 // deactivate shield (else Bang()/Explode() would not work right)
13990 player->shield_normal_time_left = 0;
13991 player->shield_deadly_time_left = 0;
13994 Debug("game:playing:KillPlayer",
13995 "1: killed == %d, active == %d, reanimated == %d",
13996 player->killed, player->active, player->reanimated);
14002 Debug("game:playing:KillPlayer",
14003 "2: killed == %d, active == %d, reanimated == %d",
14004 player->killed, player->active, player->reanimated);
14007 if (player->reanimated) // killed player may have been reanimated
14008 player->killed = player->reanimated = FALSE;
14010 BuryPlayer(player);
14013 static void KillPlayerUnlessEnemyProtected(int x, int y)
14015 if (!PLAYER_ENEMY_PROTECTED(x, y))
14016 KillPlayer(PLAYERINFO(x, y));
14019 static void KillPlayerUnlessExplosionProtected(int x, int y)
14021 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14022 KillPlayer(PLAYERINFO(x, y));
14025 void BuryPlayer(struct PlayerInfo *player)
14027 int jx = player->jx, jy = player->jy;
14029 if (!player->active)
14032 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14033 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14035 RemovePlayer(player);
14037 player->buried = TRUE;
14039 if (game.all_players_gone)
14040 game.GameOver = TRUE;
14043 void RemovePlayer(struct PlayerInfo *player)
14045 int jx = player->jx, jy = player->jy;
14046 int i, found = FALSE;
14048 player->present = FALSE;
14049 player->active = FALSE;
14051 // required for some CE actions (even if the player is not active anymore)
14052 player->MovPos = 0;
14054 if (!ExplodeField[jx][jy])
14055 StorePlayer[jx][jy] = 0;
14057 if (player->is_moving)
14058 TEST_DrawLevelField(player->last_jx, player->last_jy);
14060 for (i = 0; i < MAX_PLAYERS; i++)
14061 if (stored_player[i].active)
14066 game.all_players_gone = TRUE;
14067 game.GameOver = TRUE;
14070 game.exit_x = game.robot_wheel_x = jx;
14071 game.exit_y = game.robot_wheel_y = jy;
14074 void ExitPlayer(struct PlayerInfo *player)
14076 DrawPlayer(player); // needed here only to cleanup last field
14077 RemovePlayer(player);
14079 if (game.players_still_needed > 0)
14080 game.players_still_needed--;
14083 static void SetFieldForSnapping(int x, int y, int element, int direction,
14084 int player_index_bit)
14086 struct ElementInfo *ei = &element_info[element];
14087 int direction_bit = MV_DIR_TO_BIT(direction);
14088 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14089 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14090 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14092 Tile[x][y] = EL_ELEMENT_SNAPPING;
14093 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14094 MovDir[x][y] = direction;
14095 Store[x][y] = element;
14096 Store2[x][y] = player_index_bit;
14098 ResetGfxAnimation(x, y);
14100 GfxElement[x][y] = element;
14101 GfxAction[x][y] = action;
14102 GfxDir[x][y] = direction;
14103 GfxFrame[x][y] = -1;
14106 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14107 int player_index_bit)
14109 TestIfElementTouchesCustomElement(x, y); // for empty space
14111 if (level.finish_dig_collect)
14113 int dig_side = MV_DIR_OPPOSITE(direction);
14114 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14115 CE_PLAYER_COLLECTS_X);
14117 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14118 player_index_bit, dig_side);
14119 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14120 player_index_bit, dig_side);
14125 =============================================================================
14126 checkDiagonalPushing()
14127 -----------------------------------------------------------------------------
14128 check if diagonal input device direction results in pushing of object
14129 (by checking if the alternative direction is walkable, diggable, ...)
14130 =============================================================================
14133 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14134 int x, int y, int real_dx, int real_dy)
14136 int jx, jy, dx, dy, xx, yy;
14138 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14141 // diagonal direction: check alternative direction
14146 xx = jx + (dx == 0 ? real_dx : 0);
14147 yy = jy + (dy == 0 ? real_dy : 0);
14149 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14153 =============================================================================
14155 -----------------------------------------------------------------------------
14156 x, y: field next to player (non-diagonal) to try to dig to
14157 real_dx, real_dy: direction as read from input device (can be diagonal)
14158 =============================================================================
14161 static int DigField(struct PlayerInfo *player,
14162 int oldx, int oldy, int x, int y,
14163 int real_dx, int real_dy, int mode)
14165 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14166 boolean player_was_pushing = player->is_pushing;
14167 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14168 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14169 int jx = oldx, jy = oldy;
14170 int dx = x - jx, dy = y - jy;
14171 int nextx = x + dx, nexty = y + dy;
14172 int move_direction = (dx == -1 ? MV_LEFT :
14173 dx == +1 ? MV_RIGHT :
14175 dy == +1 ? MV_DOWN : MV_NONE);
14176 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14177 int dig_side = MV_DIR_OPPOSITE(move_direction);
14178 int old_element = Tile[jx][jy];
14179 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14182 if (is_player) // function can also be called by EL_PENGUIN
14184 if (player->MovPos == 0)
14186 player->is_digging = FALSE;
14187 player->is_collecting = FALSE;
14190 if (player->MovPos == 0) // last pushing move finished
14191 player->is_pushing = FALSE;
14193 if (mode == DF_NO_PUSH) // player just stopped pushing
14195 player->is_switching = FALSE;
14196 player->push_delay = -1;
14198 return MP_NO_ACTION;
14202 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14203 old_element = Back[jx][jy];
14205 // in case of element dropped at player position, check background
14206 else if (Back[jx][jy] != EL_EMPTY &&
14207 game.engine_version >= VERSION_IDENT(2,2,0,0))
14208 old_element = Back[jx][jy];
14210 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14211 return MP_NO_ACTION; // field has no opening in this direction
14213 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14214 return MP_NO_ACTION; // field has no opening in this direction
14216 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14220 Tile[jx][jy] = player->artwork_element;
14221 InitMovingField(jx, jy, MV_DOWN);
14222 Store[jx][jy] = EL_ACID;
14223 ContinueMoving(jx, jy);
14224 BuryPlayer(player);
14226 return MP_DONT_RUN_INTO;
14229 if (player_can_move && DONT_RUN_INTO(element))
14231 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14233 return MP_DONT_RUN_INTO;
14236 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14237 return MP_NO_ACTION;
14239 collect_count = element_info[element].collect_count_initial;
14241 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14242 return MP_NO_ACTION;
14244 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14245 player_can_move = player_can_move_or_snap;
14247 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14248 game.engine_version >= VERSION_IDENT(2,2,0,0))
14250 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14251 player->index_bit, dig_side);
14252 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14253 player->index_bit, dig_side);
14255 if (element == EL_DC_LANDMINE)
14258 if (Tile[x][y] != element) // field changed by snapping
14261 return MP_NO_ACTION;
14264 if (player->gravity && is_player && !player->is_auto_moving &&
14265 canFallDown(player) && move_direction != MV_DOWN &&
14266 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14267 return MP_NO_ACTION; // player cannot walk here due to gravity
14269 if (player_can_move &&
14270 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14272 int sound_element = SND_ELEMENT(element);
14273 int sound_action = ACTION_WALKING;
14275 if (IS_RND_GATE(element))
14277 if (!player->key[RND_GATE_NR(element)])
14278 return MP_NO_ACTION;
14280 else if (IS_RND_GATE_GRAY(element))
14282 if (!player->key[RND_GATE_GRAY_NR(element)])
14283 return MP_NO_ACTION;
14285 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14287 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14288 return MP_NO_ACTION;
14290 else if (element == EL_EXIT_OPEN ||
14291 element == EL_EM_EXIT_OPEN ||
14292 element == EL_EM_EXIT_OPENING ||
14293 element == EL_STEEL_EXIT_OPEN ||
14294 element == EL_EM_STEEL_EXIT_OPEN ||
14295 element == EL_EM_STEEL_EXIT_OPENING ||
14296 element == EL_SP_EXIT_OPEN ||
14297 element == EL_SP_EXIT_OPENING)
14299 sound_action = ACTION_PASSING; // player is passing exit
14301 else if (element == EL_EMPTY)
14303 sound_action = ACTION_MOVING; // nothing to walk on
14306 // play sound from background or player, whatever is available
14307 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14308 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14310 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14312 else if (player_can_move &&
14313 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14315 if (!ACCESS_FROM(element, opposite_direction))
14316 return MP_NO_ACTION; // field not accessible from this direction
14318 if (CAN_MOVE(element)) // only fixed elements can be passed!
14319 return MP_NO_ACTION;
14321 if (IS_EM_GATE(element))
14323 if (!player->key[EM_GATE_NR(element)])
14324 return MP_NO_ACTION;
14326 else if (IS_EM_GATE_GRAY(element))
14328 if (!player->key[EM_GATE_GRAY_NR(element)])
14329 return MP_NO_ACTION;
14331 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14333 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14334 return MP_NO_ACTION;
14336 else if (IS_EMC_GATE(element))
14338 if (!player->key[EMC_GATE_NR(element)])
14339 return MP_NO_ACTION;
14341 else if (IS_EMC_GATE_GRAY(element))
14343 if (!player->key[EMC_GATE_GRAY_NR(element)])
14344 return MP_NO_ACTION;
14346 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14348 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (element == EL_DC_GATE_WHITE ||
14352 element == EL_DC_GATE_WHITE_GRAY ||
14353 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14355 if (player->num_white_keys == 0)
14356 return MP_NO_ACTION;
14358 player->num_white_keys--;
14360 else if (IS_SP_PORT(element))
14362 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14363 element == EL_SP_GRAVITY_PORT_RIGHT ||
14364 element == EL_SP_GRAVITY_PORT_UP ||
14365 element == EL_SP_GRAVITY_PORT_DOWN)
14366 player->gravity = !player->gravity;
14367 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14368 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14369 element == EL_SP_GRAVITY_ON_PORT_UP ||
14370 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14371 player->gravity = TRUE;
14372 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14373 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14374 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14375 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14376 player->gravity = FALSE;
14379 // automatically move to the next field with double speed
14380 player->programmed_action = move_direction;
14382 if (player->move_delay_reset_counter == 0)
14384 player->move_delay_reset_counter = 2; // two double speed steps
14386 DOUBLE_PLAYER_SPEED(player);
14389 PlayLevelSoundAction(x, y, ACTION_PASSING);
14391 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14395 if (mode != DF_SNAP)
14397 GfxElement[x][y] = GFX_ELEMENT(element);
14398 player->is_digging = TRUE;
14401 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14403 // use old behaviour for old levels (digging)
14404 if (!level.finish_dig_collect)
14406 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14407 player->index_bit, dig_side);
14409 // if digging triggered player relocation, finish digging tile
14410 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14411 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14414 if (mode == DF_SNAP)
14416 if (level.block_snap_field)
14417 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14419 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14421 // use old behaviour for old levels (snapping)
14422 if (!level.finish_dig_collect)
14423 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14424 player->index_bit, dig_side);
14427 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14431 if (is_player && mode != DF_SNAP)
14433 GfxElement[x][y] = element;
14434 player->is_collecting = TRUE;
14437 if (element == EL_SPEED_PILL)
14439 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14441 else if (element == EL_EXTRA_TIME && level.time > 0)
14443 TimeLeft += level.extra_time;
14445 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14447 DisplayGameControlValues();
14449 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14451 player->shield_normal_time_left += level.shield_normal_time;
14452 if (element == EL_SHIELD_DEADLY)
14453 player->shield_deadly_time_left += level.shield_deadly_time;
14455 else if (element == EL_DYNAMITE ||
14456 element == EL_EM_DYNAMITE ||
14457 element == EL_SP_DISK_RED)
14459 if (player->inventory_size < MAX_INVENTORY_SIZE)
14460 player->inventory_element[player->inventory_size++] = element;
14462 DrawGameDoorValues();
14464 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14466 player->dynabomb_count++;
14467 player->dynabombs_left++;
14469 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14471 player->dynabomb_size++;
14473 else if (element == EL_DYNABOMB_INCREASE_POWER)
14475 player->dynabomb_xl = TRUE;
14477 else if (IS_KEY(element))
14479 player->key[KEY_NR(element)] = TRUE;
14481 DrawGameDoorValues();
14483 else if (element == EL_DC_KEY_WHITE)
14485 player->num_white_keys++;
14487 // display white keys?
14488 // DrawGameDoorValues();
14490 else if (IS_ENVELOPE(element))
14492 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14494 if (!wait_for_snapping)
14495 player->show_envelope = element;
14497 else if (element == EL_EMC_LENSES)
14499 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14501 RedrawAllInvisibleElementsForLenses();
14503 else if (element == EL_EMC_MAGNIFIER)
14505 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14507 RedrawAllInvisibleElementsForMagnifier();
14509 else if (IS_DROPPABLE(element) ||
14510 IS_THROWABLE(element)) // can be collected and dropped
14514 if (collect_count == 0)
14515 player->inventory_infinite_element = element;
14517 for (i = 0; i < collect_count; i++)
14518 if (player->inventory_size < MAX_INVENTORY_SIZE)
14519 player->inventory_element[player->inventory_size++] = element;
14521 DrawGameDoorValues();
14523 else if (collect_count > 0)
14525 game.gems_still_needed -= collect_count;
14526 if (game.gems_still_needed < 0)
14527 game.gems_still_needed = 0;
14529 game.snapshot.collected_item = TRUE;
14531 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14533 DisplayGameControlValues();
14536 RaiseScoreElement(element);
14537 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14539 // use old behaviour for old levels (collecting)
14540 if (!level.finish_dig_collect && is_player)
14542 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14543 player->index_bit, dig_side);
14545 // if collecting triggered player relocation, finish collecting tile
14546 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14547 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14550 if (mode == DF_SNAP)
14552 if (level.block_snap_field)
14553 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14555 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14557 // use old behaviour for old levels (snapping)
14558 if (!level.finish_dig_collect)
14559 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14560 player->index_bit, dig_side);
14563 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14565 if (mode == DF_SNAP && element != EL_BD_ROCK)
14566 return MP_NO_ACTION;
14568 if (CAN_FALL(element) && dy)
14569 return MP_NO_ACTION;
14571 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14572 !(element == EL_SPRING && level.use_spring_bug))
14573 return MP_NO_ACTION;
14575 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14576 ((move_direction & MV_VERTICAL &&
14577 ((element_info[element].move_pattern & MV_LEFT &&
14578 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14579 (element_info[element].move_pattern & MV_RIGHT &&
14580 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14581 (move_direction & MV_HORIZONTAL &&
14582 ((element_info[element].move_pattern & MV_UP &&
14583 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14584 (element_info[element].move_pattern & MV_DOWN &&
14585 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14586 return MP_NO_ACTION;
14588 // do not push elements already moving away faster than player
14589 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14590 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14591 return MP_NO_ACTION;
14593 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14595 if (player->push_delay_value == -1 || !player_was_pushing)
14596 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14598 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14600 if (player->push_delay_value == -1)
14601 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14603 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14605 if (!player->is_pushing)
14606 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14609 player->is_pushing = TRUE;
14610 player->is_active = TRUE;
14612 if (!(IN_LEV_FIELD(nextx, nexty) &&
14613 (IS_FREE(nextx, nexty) ||
14614 (IS_SB_ELEMENT(element) &&
14615 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14616 (IS_CUSTOM_ELEMENT(element) &&
14617 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14618 return MP_NO_ACTION;
14620 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14621 return MP_NO_ACTION;
14623 if (player->push_delay == -1) // new pushing; restart delay
14624 player->push_delay = 0;
14626 if (player->push_delay < player->push_delay_value &&
14627 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14628 element != EL_SPRING && element != EL_BALLOON)
14630 // make sure that there is no move delay before next try to push
14631 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14632 player->move_delay = 0;
14634 return MP_NO_ACTION;
14637 if (IS_CUSTOM_ELEMENT(element) &&
14638 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14640 if (!DigFieldByCE(nextx, nexty, element))
14641 return MP_NO_ACTION;
14644 if (IS_SB_ELEMENT(element))
14646 boolean sokoban_task_solved = FALSE;
14648 if (element == EL_SOKOBAN_FIELD_FULL)
14650 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14652 IncrementSokobanFieldsNeeded();
14653 IncrementSokobanObjectsNeeded();
14656 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14658 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14660 DecrementSokobanFieldsNeeded();
14661 DecrementSokobanObjectsNeeded();
14663 // sokoban object was pushed from empty field to sokoban field
14664 if (Back[x][y] == EL_EMPTY)
14665 sokoban_task_solved = TRUE;
14668 Tile[x][y] = EL_SOKOBAN_OBJECT;
14670 if (Back[x][y] == Back[nextx][nexty])
14671 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14672 else if (Back[x][y] != 0)
14673 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14676 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14679 if (sokoban_task_solved &&
14680 game.sokoban_fields_still_needed == 0 &&
14681 game.sokoban_objects_still_needed == 0 &&
14682 level.auto_exit_sokoban)
14684 game.players_still_needed = 0;
14688 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14692 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14694 InitMovingField(x, y, move_direction);
14695 GfxAction[x][y] = ACTION_PUSHING;
14697 if (mode == DF_SNAP)
14698 ContinueMoving(x, y);
14700 MovPos[x][y] = (dx != 0 ? dx : dy);
14702 Pushed[x][y] = TRUE;
14703 Pushed[nextx][nexty] = TRUE;
14705 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14706 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14708 player->push_delay_value = -1; // get new value later
14710 // check for element change _after_ element has been pushed
14711 if (game.use_change_when_pushing_bug)
14713 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14714 player->index_bit, dig_side);
14715 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14716 player->index_bit, dig_side);
14719 else if (IS_SWITCHABLE(element))
14721 if (PLAYER_SWITCHING(player, x, y))
14723 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14724 player->index_bit, dig_side);
14729 player->is_switching = TRUE;
14730 player->switch_x = x;
14731 player->switch_y = y;
14733 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14735 if (element == EL_ROBOT_WHEEL)
14737 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14739 game.robot_wheel_x = x;
14740 game.robot_wheel_y = y;
14741 game.robot_wheel_active = TRUE;
14743 TEST_DrawLevelField(x, y);
14745 else if (element == EL_SP_TERMINAL)
14749 SCAN_PLAYFIELD(xx, yy)
14751 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14755 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14757 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14759 ResetGfxAnimation(xx, yy);
14760 TEST_DrawLevelField(xx, yy);
14764 else if (IS_BELT_SWITCH(element))
14766 ToggleBeltSwitch(x, y);
14768 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14769 element == EL_SWITCHGATE_SWITCH_DOWN ||
14770 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14771 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14773 ToggleSwitchgateSwitch(x, y);
14775 else if (element == EL_LIGHT_SWITCH ||
14776 element == EL_LIGHT_SWITCH_ACTIVE)
14778 ToggleLightSwitch(x, y);
14780 else if (element == EL_TIMEGATE_SWITCH ||
14781 element == EL_DC_TIMEGATE_SWITCH)
14783 ActivateTimegateSwitch(x, y);
14785 else if (element == EL_BALLOON_SWITCH_LEFT ||
14786 element == EL_BALLOON_SWITCH_RIGHT ||
14787 element == EL_BALLOON_SWITCH_UP ||
14788 element == EL_BALLOON_SWITCH_DOWN ||
14789 element == EL_BALLOON_SWITCH_NONE ||
14790 element == EL_BALLOON_SWITCH_ANY)
14792 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14793 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14794 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14795 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14796 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14799 else if (element == EL_LAMP)
14801 Tile[x][y] = EL_LAMP_ACTIVE;
14802 game.lights_still_needed--;
14804 ResetGfxAnimation(x, y);
14805 TEST_DrawLevelField(x, y);
14807 else if (element == EL_TIME_ORB_FULL)
14809 Tile[x][y] = EL_TIME_ORB_EMPTY;
14811 if (level.time > 0 || level.use_time_orb_bug)
14813 TimeLeft += level.time_orb_time;
14814 game.no_time_limit = FALSE;
14816 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14818 DisplayGameControlValues();
14821 ResetGfxAnimation(x, y);
14822 TEST_DrawLevelField(x, y);
14824 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14825 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14829 game.ball_active = !game.ball_active;
14831 SCAN_PLAYFIELD(xx, yy)
14833 int e = Tile[xx][yy];
14835 if (game.ball_active)
14837 if (e == EL_EMC_MAGIC_BALL)
14838 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14839 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14840 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14844 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14845 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14846 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14847 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14852 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14853 player->index_bit, dig_side);
14855 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14856 player->index_bit, dig_side);
14858 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14859 player->index_bit, dig_side);
14865 if (!PLAYER_SWITCHING(player, x, y))
14867 player->is_switching = TRUE;
14868 player->switch_x = x;
14869 player->switch_y = y;
14871 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14872 player->index_bit, dig_side);
14873 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14874 player->index_bit, dig_side);
14876 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14877 player->index_bit, dig_side);
14878 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14879 player->index_bit, dig_side);
14882 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14883 player->index_bit, dig_side);
14884 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14885 player->index_bit, dig_side);
14887 return MP_NO_ACTION;
14890 player->push_delay = -1;
14892 if (is_player) // function can also be called by EL_PENGUIN
14894 if (Tile[x][y] != element) // really digged/collected something
14896 player->is_collecting = !player->is_digging;
14897 player->is_active = TRUE;
14899 player->last_removed_element = element;
14906 static boolean DigFieldByCE(int x, int y, int digging_element)
14908 int element = Tile[x][y];
14910 if (!IS_FREE(x, y))
14912 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14913 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14916 // no element can dig solid indestructible elements
14917 if (IS_INDESTRUCTIBLE(element) &&
14918 !IS_DIGGABLE(element) &&
14919 !IS_COLLECTIBLE(element))
14922 if (AmoebaNr[x][y] &&
14923 (element == EL_AMOEBA_FULL ||
14924 element == EL_BD_AMOEBA ||
14925 element == EL_AMOEBA_GROWING))
14927 AmoebaCnt[AmoebaNr[x][y]]--;
14928 AmoebaCnt2[AmoebaNr[x][y]]--;
14931 if (IS_MOVING(x, y))
14932 RemoveMovingField(x, y);
14936 TEST_DrawLevelField(x, y);
14939 // if digged element was about to explode, prevent the explosion
14940 ExplodeField[x][y] = EX_TYPE_NONE;
14942 PlayLevelSoundAction(x, y, action);
14945 Store[x][y] = EL_EMPTY;
14947 // this makes it possible to leave the removed element again
14948 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14949 Store[x][y] = element;
14954 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14956 int jx = player->jx, jy = player->jy;
14957 int x = jx + dx, y = jy + dy;
14958 int snap_direction = (dx == -1 ? MV_LEFT :
14959 dx == +1 ? MV_RIGHT :
14961 dy == +1 ? MV_DOWN : MV_NONE);
14962 boolean can_continue_snapping = (level.continuous_snapping &&
14963 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14965 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14968 if (!player->active || !IN_LEV_FIELD(x, y))
14976 if (player->MovPos == 0)
14977 player->is_pushing = FALSE;
14979 player->is_snapping = FALSE;
14981 if (player->MovPos == 0)
14983 player->is_moving = FALSE;
14984 player->is_digging = FALSE;
14985 player->is_collecting = FALSE;
14991 // prevent snapping with already pressed snap key when not allowed
14992 if (player->is_snapping && !can_continue_snapping)
14995 player->MovDir = snap_direction;
14997 if (player->MovPos == 0)
14999 player->is_moving = FALSE;
15000 player->is_digging = FALSE;
15001 player->is_collecting = FALSE;
15004 player->is_dropping = FALSE;
15005 player->is_dropping_pressed = FALSE;
15006 player->drop_pressed_delay = 0;
15008 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15011 player->is_snapping = TRUE;
15012 player->is_active = TRUE;
15014 if (player->MovPos == 0)
15016 player->is_moving = FALSE;
15017 player->is_digging = FALSE;
15018 player->is_collecting = FALSE;
15021 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15022 TEST_DrawLevelField(player->last_jx, player->last_jy);
15024 TEST_DrawLevelField(x, y);
15029 static boolean DropElement(struct PlayerInfo *player)
15031 int old_element, new_element;
15032 int dropx = player->jx, dropy = player->jy;
15033 int drop_direction = player->MovDir;
15034 int drop_side = drop_direction;
15035 int drop_element = get_next_dropped_element(player);
15037 /* do not drop an element on top of another element; when holding drop key
15038 pressed without moving, dropped element must move away before the next
15039 element can be dropped (this is especially important if the next element
15040 is dynamite, which can be placed on background for historical reasons) */
15041 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15044 if (IS_THROWABLE(drop_element))
15046 dropx += GET_DX_FROM_DIR(drop_direction);
15047 dropy += GET_DY_FROM_DIR(drop_direction);
15049 if (!IN_LEV_FIELD(dropx, dropy))
15053 old_element = Tile[dropx][dropy]; // old element at dropping position
15054 new_element = drop_element; // default: no change when dropping
15056 // check if player is active, not moving and ready to drop
15057 if (!player->active || player->MovPos || player->drop_delay > 0)
15060 // check if player has anything that can be dropped
15061 if (new_element == EL_UNDEFINED)
15064 // only set if player has anything that can be dropped
15065 player->is_dropping_pressed = TRUE;
15067 // check if drop key was pressed long enough for EM style dynamite
15068 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15071 // check if anything can be dropped at the current position
15072 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15075 // collected custom elements can only be dropped on empty fields
15076 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15079 if (old_element != EL_EMPTY)
15080 Back[dropx][dropy] = old_element; // store old element on this field
15082 ResetGfxAnimation(dropx, dropy);
15083 ResetRandomAnimationValue(dropx, dropy);
15085 if (player->inventory_size > 0 ||
15086 player->inventory_infinite_element != EL_UNDEFINED)
15088 if (player->inventory_size > 0)
15090 player->inventory_size--;
15092 DrawGameDoorValues();
15094 if (new_element == EL_DYNAMITE)
15095 new_element = EL_DYNAMITE_ACTIVE;
15096 else if (new_element == EL_EM_DYNAMITE)
15097 new_element = EL_EM_DYNAMITE_ACTIVE;
15098 else if (new_element == EL_SP_DISK_RED)
15099 new_element = EL_SP_DISK_RED_ACTIVE;
15102 Tile[dropx][dropy] = new_element;
15104 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15105 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15106 el2img(Tile[dropx][dropy]), 0);
15108 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15110 // needed if previous element just changed to "empty" in the last frame
15111 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15113 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15114 player->index_bit, drop_side);
15115 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15117 player->index_bit, drop_side);
15119 TestIfElementTouchesCustomElement(dropx, dropy);
15121 else // player is dropping a dyna bomb
15123 player->dynabombs_left--;
15125 Tile[dropx][dropy] = new_element;
15127 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15128 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15129 el2img(Tile[dropx][dropy]), 0);
15131 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15134 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15135 InitField_WithBug1(dropx, dropy, FALSE);
15137 new_element = Tile[dropx][dropy]; // element might have changed
15139 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15140 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15142 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15143 MovDir[dropx][dropy] = drop_direction;
15145 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15147 // do not cause impact style collision by dropping elements that can fall
15148 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15151 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15152 player->is_dropping = TRUE;
15154 player->drop_pressed_delay = 0;
15155 player->is_dropping_pressed = FALSE;
15157 player->drop_x = dropx;
15158 player->drop_y = dropy;
15163 // ----------------------------------------------------------------------------
15164 // game sound playing functions
15165 // ----------------------------------------------------------------------------
15167 static int *loop_sound_frame = NULL;
15168 static int *loop_sound_volume = NULL;
15170 void InitPlayLevelSound(void)
15172 int num_sounds = getSoundListSize();
15174 checked_free(loop_sound_frame);
15175 checked_free(loop_sound_volume);
15177 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15178 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15181 static void PlayLevelSound(int x, int y, int nr)
15183 int sx = SCREENX(x), sy = SCREENY(y);
15184 int volume, stereo_position;
15185 int max_distance = 8;
15186 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15188 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15189 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15192 if (!IN_LEV_FIELD(x, y) ||
15193 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15194 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15197 volume = SOUND_MAX_VOLUME;
15199 if (!IN_SCR_FIELD(sx, sy))
15201 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15202 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15204 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15207 stereo_position = (SOUND_MAX_LEFT +
15208 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15209 (SCR_FIELDX + 2 * max_distance));
15211 if (IS_LOOP_SOUND(nr))
15213 /* This assures that quieter loop sounds do not overwrite louder ones,
15214 while restarting sound volume comparison with each new game frame. */
15216 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15219 loop_sound_volume[nr] = volume;
15220 loop_sound_frame[nr] = FrameCounter;
15223 PlaySoundExt(nr, volume, stereo_position, type);
15226 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15228 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15229 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15230 y < LEVELY(BY1) ? LEVELY(BY1) :
15231 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15235 static void PlayLevelSoundAction(int x, int y, int action)
15237 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15240 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15242 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15244 if (sound_effect != SND_UNDEFINED)
15245 PlayLevelSound(x, y, sound_effect);
15248 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15251 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15253 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15254 PlayLevelSound(x, y, sound_effect);
15257 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15259 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15261 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15262 PlayLevelSound(x, y, sound_effect);
15265 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15267 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15269 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15270 StopSound(sound_effect);
15273 static int getLevelMusicNr(void)
15275 if (levelset.music[level_nr] != MUS_UNDEFINED)
15276 return levelset.music[level_nr]; // from config file
15278 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15281 static void FadeLevelSounds(void)
15286 static void FadeLevelMusic(void)
15288 int music_nr = getLevelMusicNr();
15289 char *curr_music = getCurrentlyPlayingMusicFilename();
15290 char *next_music = getMusicInfoEntryFilename(music_nr);
15292 if (!strEqual(curr_music, next_music))
15296 void FadeLevelSoundsAndMusic(void)
15302 static void PlayLevelMusic(void)
15304 int music_nr = getLevelMusicNr();
15305 char *curr_music = getCurrentlyPlayingMusicFilename();
15306 char *next_music = getMusicInfoEntryFilename(music_nr);
15308 if (!strEqual(curr_music, next_music))
15309 PlayMusicLoop(music_nr);
15312 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15314 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15316 int x = xx - offset;
15317 int y = yy - offset;
15322 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15326 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15330 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15334 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15338 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15342 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15346 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15349 case SOUND_android_clone:
15350 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15353 case SOUND_android_move:
15354 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15358 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15362 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15366 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15369 case SOUND_eater_eat:
15370 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15374 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15377 case SOUND_collect:
15378 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15381 case SOUND_diamond:
15382 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15386 // !!! CHECK THIS !!!
15388 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15390 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15394 case SOUND_wonderfall:
15395 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15399 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15403 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15407 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15411 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15415 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15419 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15423 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15427 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15430 case SOUND_exit_open:
15431 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15434 case SOUND_exit_leave:
15435 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15438 case SOUND_dynamite:
15439 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15443 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15447 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15451 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15455 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15459 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15463 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15467 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15472 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15474 int element = map_element_SP_to_RND(element_sp);
15475 int action = map_action_SP_to_RND(action_sp);
15476 int offset = (setup.sp_show_border_elements ? 0 : 1);
15477 int x = xx - offset;
15478 int y = yy - offset;
15480 PlayLevelSoundElementAction(x, y, element, action);
15483 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15485 int element = map_element_MM_to_RND(element_mm);
15486 int action = map_action_MM_to_RND(action_mm);
15488 int x = xx - offset;
15489 int y = yy - offset;
15491 if (!IS_MM_ELEMENT(element))
15492 element = EL_MM_DEFAULT;
15494 PlayLevelSoundElementAction(x, y, element, action);
15497 void PlaySound_MM(int sound_mm)
15499 int sound = map_sound_MM_to_RND(sound_mm);
15501 if (sound == SND_UNDEFINED)
15507 void PlaySoundLoop_MM(int sound_mm)
15509 int sound = map_sound_MM_to_RND(sound_mm);
15511 if (sound == SND_UNDEFINED)
15514 PlaySoundLoop(sound);
15517 void StopSound_MM(int sound_mm)
15519 int sound = map_sound_MM_to_RND(sound_mm);
15521 if (sound == SND_UNDEFINED)
15527 void RaiseScore(int value)
15529 game.score += value;
15531 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15533 DisplayGameControlValues();
15536 void RaiseScoreElement(int element)
15541 case EL_BD_DIAMOND:
15542 case EL_EMERALD_YELLOW:
15543 case EL_EMERALD_RED:
15544 case EL_EMERALD_PURPLE:
15545 case EL_SP_INFOTRON:
15546 RaiseScore(level.score[SC_EMERALD]);
15549 RaiseScore(level.score[SC_DIAMOND]);
15552 RaiseScore(level.score[SC_CRYSTAL]);
15555 RaiseScore(level.score[SC_PEARL]);
15558 case EL_BD_BUTTERFLY:
15559 case EL_SP_ELECTRON:
15560 RaiseScore(level.score[SC_BUG]);
15563 case EL_BD_FIREFLY:
15564 case EL_SP_SNIKSNAK:
15565 RaiseScore(level.score[SC_SPACESHIP]);
15568 case EL_DARK_YAMYAM:
15569 RaiseScore(level.score[SC_YAMYAM]);
15572 RaiseScore(level.score[SC_ROBOT]);
15575 RaiseScore(level.score[SC_PACMAN]);
15578 RaiseScore(level.score[SC_NUT]);
15581 case EL_EM_DYNAMITE:
15582 case EL_SP_DISK_RED:
15583 case EL_DYNABOMB_INCREASE_NUMBER:
15584 case EL_DYNABOMB_INCREASE_SIZE:
15585 case EL_DYNABOMB_INCREASE_POWER:
15586 RaiseScore(level.score[SC_DYNAMITE]);
15588 case EL_SHIELD_NORMAL:
15589 case EL_SHIELD_DEADLY:
15590 RaiseScore(level.score[SC_SHIELD]);
15592 case EL_EXTRA_TIME:
15593 RaiseScore(level.extra_time_score);
15607 case EL_DC_KEY_WHITE:
15608 RaiseScore(level.score[SC_KEY]);
15611 RaiseScore(element_info[element].collect_score);
15616 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15618 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15622 // prevent short reactivation of overlay buttons while closing door
15623 SetOverlayActive(FALSE);
15625 // door may still be open due to skipped or envelope style request
15626 CloseDoor(DOOR_CLOSE_1);
15629 if (network.enabled)
15630 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15634 FadeSkipNextFadeIn();
15636 SetGameStatus(GAME_MODE_MAIN);
15641 else // continue playing the game
15643 if (tape.playing && tape.deactivate_display)
15644 TapeDeactivateDisplayOff(TRUE);
15646 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15648 if (tape.playing && tape.deactivate_display)
15649 TapeDeactivateDisplayOn();
15653 void RequestQuitGame(boolean escape_key_pressed)
15655 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15656 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15657 level_editor_test_game);
15658 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15661 RequestQuitGameExt(skip_request, quick_quit,
15662 "Do you really want to quit the game?");
15665 void RequestRestartGame(char *message)
15667 game.restart_game_message = NULL;
15669 boolean has_started_game = hasStartedNetworkGame();
15670 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15672 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15674 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15678 // needed in case of envelope request to close game panel
15679 CloseDoor(DOOR_CLOSE_1);
15681 SetGameStatus(GAME_MODE_MAIN);
15687 void CheckGameOver(void)
15689 static boolean last_game_over = FALSE;
15690 static int game_over_delay = 0;
15691 int game_over_delay_value = 50;
15692 boolean game_over = checkGameFailed();
15694 // do not handle game over if request dialog is already active
15695 if (game.request_active)
15698 // do not ask to play again if game was never actually played
15699 if (!game.GamePlayed)
15704 last_game_over = FALSE;
15705 game_over_delay = game_over_delay_value;
15710 if (game_over_delay > 0)
15717 if (last_game_over != game_over)
15718 game.restart_game_message = (hasStartedNetworkGame() ?
15719 "Game over! Play it again?" :
15722 last_game_over = game_over;
15725 boolean checkGameSolved(void)
15727 // set for all game engines if level was solved
15728 return game.LevelSolved_GameEnd;
15731 boolean checkGameFailed(void)
15733 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15734 return (game_em.game_over && !game_em.level_solved);
15735 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15736 return (game_sp.game_over && !game_sp.level_solved);
15737 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15738 return (game_mm.game_over && !game_mm.level_solved);
15739 else // GAME_ENGINE_TYPE_RND
15740 return (game.GameOver && !game.LevelSolved);
15743 boolean checkGameEnded(void)
15745 return (checkGameSolved() || checkGameFailed());
15749 // ----------------------------------------------------------------------------
15750 // random generator functions
15751 // ----------------------------------------------------------------------------
15753 unsigned int InitEngineRandom_RND(int seed)
15755 game.num_random_calls = 0;
15757 return InitEngineRandom(seed);
15760 unsigned int RND(int max)
15764 game.num_random_calls++;
15766 return GetEngineRandom(max);
15773 // ----------------------------------------------------------------------------
15774 // game engine snapshot handling functions
15775 // ----------------------------------------------------------------------------
15777 struct EngineSnapshotInfo
15779 // runtime values for custom element collect score
15780 int collect_score[NUM_CUSTOM_ELEMENTS];
15782 // runtime values for group element choice position
15783 int choice_pos[NUM_GROUP_ELEMENTS];
15785 // runtime values for belt position animations
15786 int belt_graphic[4][NUM_BELT_PARTS];
15787 int belt_anim_mode[4][NUM_BELT_PARTS];
15790 static struct EngineSnapshotInfo engine_snapshot_rnd;
15791 static char *snapshot_level_identifier = NULL;
15792 static int snapshot_level_nr = -1;
15794 static void SaveEngineSnapshotValues_RND(void)
15796 static int belt_base_active_element[4] =
15798 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15799 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15800 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15801 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15805 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15807 int element = EL_CUSTOM_START + i;
15809 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15812 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15814 int element = EL_GROUP_START + i;
15816 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15819 for (i = 0; i < 4; i++)
15821 for (j = 0; j < NUM_BELT_PARTS; j++)
15823 int element = belt_base_active_element[i] + j;
15824 int graphic = el2img(element);
15825 int anim_mode = graphic_info[graphic].anim_mode;
15827 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15828 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15833 static void LoadEngineSnapshotValues_RND(void)
15835 unsigned int num_random_calls = game.num_random_calls;
15838 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15840 int element = EL_CUSTOM_START + i;
15842 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15845 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15847 int element = EL_GROUP_START + i;
15849 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15852 for (i = 0; i < 4; i++)
15854 for (j = 0; j < NUM_BELT_PARTS; j++)
15856 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15857 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15859 graphic_info[graphic].anim_mode = anim_mode;
15863 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15865 InitRND(tape.random_seed);
15866 for (i = 0; i < num_random_calls; i++)
15870 if (game.num_random_calls != num_random_calls)
15872 Error("number of random calls out of sync");
15873 Error("number of random calls should be %d", num_random_calls);
15874 Error("number of random calls is %d", game.num_random_calls);
15876 Fail("this should not happen -- please debug");
15880 void FreeEngineSnapshotSingle(void)
15882 FreeSnapshotSingle();
15884 setString(&snapshot_level_identifier, NULL);
15885 snapshot_level_nr = -1;
15888 void FreeEngineSnapshotList(void)
15890 FreeSnapshotList();
15893 static ListNode *SaveEngineSnapshotBuffers(void)
15895 ListNode *buffers = NULL;
15897 // copy some special values to a structure better suited for the snapshot
15899 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15900 SaveEngineSnapshotValues_RND();
15901 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15902 SaveEngineSnapshotValues_EM();
15903 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15904 SaveEngineSnapshotValues_SP(&buffers);
15905 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15906 SaveEngineSnapshotValues_MM(&buffers);
15908 // save values stored in special snapshot structure
15910 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15911 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15912 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15913 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15914 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15915 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15916 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15917 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15919 // save further RND engine values
15921 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15922 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15923 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15925 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15927 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15928 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15929 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15931 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15933 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15935 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15937 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15940 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15945 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15951 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15966 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15976 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15979 ListNode *node = engine_snapshot_list_rnd;
15982 while (node != NULL)
15984 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15989 Debug("game:playing:SaveEngineSnapshotBuffers",
15990 "size of engine snapshot: %d bytes", num_bytes);
15996 void SaveEngineSnapshotSingle(void)
15998 ListNode *buffers = SaveEngineSnapshotBuffers();
16000 // finally save all snapshot buffers to single snapshot
16001 SaveSnapshotSingle(buffers);
16003 // save level identification information
16004 setString(&snapshot_level_identifier, leveldir_current->identifier);
16005 snapshot_level_nr = level_nr;
16008 boolean CheckSaveEngineSnapshotToList(void)
16010 boolean save_snapshot =
16011 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16012 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16013 game.snapshot.changed_action) ||
16014 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16015 game.snapshot.collected_item));
16017 game.snapshot.changed_action = FALSE;
16018 game.snapshot.collected_item = FALSE;
16019 game.snapshot.save_snapshot = save_snapshot;
16021 return save_snapshot;
16024 void SaveEngineSnapshotToList(void)
16026 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16030 ListNode *buffers = SaveEngineSnapshotBuffers();
16032 // finally save all snapshot buffers to snapshot list
16033 SaveSnapshotToList(buffers);
16036 void SaveEngineSnapshotToListInitial(void)
16038 FreeEngineSnapshotList();
16040 SaveEngineSnapshotToList();
16043 static void LoadEngineSnapshotValues(void)
16045 // restore special values from snapshot structure
16047 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16048 LoadEngineSnapshotValues_RND();
16049 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16050 LoadEngineSnapshotValues_EM();
16051 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16052 LoadEngineSnapshotValues_SP();
16053 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16054 LoadEngineSnapshotValues_MM();
16057 void LoadEngineSnapshotSingle(void)
16059 LoadSnapshotSingle();
16061 LoadEngineSnapshotValues();
16064 static void LoadEngineSnapshot_Undo(int steps)
16066 LoadSnapshotFromList_Older(steps);
16068 LoadEngineSnapshotValues();
16071 static void LoadEngineSnapshot_Redo(int steps)
16073 LoadSnapshotFromList_Newer(steps);
16075 LoadEngineSnapshotValues();
16078 boolean CheckEngineSnapshotSingle(void)
16080 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16081 snapshot_level_nr == level_nr);
16084 boolean CheckEngineSnapshotList(void)
16086 return CheckSnapshotList();
16090 // ---------- new game button stuff -------------------------------------------
16097 boolean *setup_value;
16098 boolean allowed_on_tape;
16099 boolean is_touch_button;
16101 } gamebutton_info[NUM_GAME_BUTTONS] =
16104 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16105 GAME_CTRL_ID_STOP, NULL,
16106 TRUE, FALSE, "stop game"
16109 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16110 GAME_CTRL_ID_PAUSE, NULL,
16111 TRUE, FALSE, "pause game"
16114 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16115 GAME_CTRL_ID_PLAY, NULL,
16116 TRUE, FALSE, "play game"
16119 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16120 GAME_CTRL_ID_UNDO, NULL,
16121 TRUE, FALSE, "undo step"
16124 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16125 GAME_CTRL_ID_REDO, NULL,
16126 TRUE, FALSE, "redo step"
16129 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16130 GAME_CTRL_ID_SAVE, NULL,
16131 TRUE, FALSE, "save game"
16134 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16135 GAME_CTRL_ID_PAUSE2, NULL,
16136 TRUE, FALSE, "pause game"
16139 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16140 GAME_CTRL_ID_LOAD, NULL,
16141 TRUE, FALSE, "load game"
16144 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16145 GAME_CTRL_ID_PANEL_STOP, NULL,
16146 FALSE, FALSE, "stop game"
16149 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16150 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16151 FALSE, FALSE, "pause game"
16154 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16155 GAME_CTRL_ID_PANEL_PLAY, NULL,
16156 FALSE, FALSE, "play game"
16159 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16160 GAME_CTRL_ID_TOUCH_STOP, NULL,
16161 FALSE, TRUE, "stop game"
16164 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16165 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16166 FALSE, TRUE, "pause game"
16169 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16170 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16171 TRUE, FALSE, "background music on/off"
16174 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16175 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16176 TRUE, FALSE, "sound loops on/off"
16179 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16180 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16181 TRUE, FALSE, "normal sounds on/off"
16184 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16185 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16186 FALSE, FALSE, "background music on/off"
16189 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16190 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16191 FALSE, FALSE, "sound loops on/off"
16194 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16195 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16196 FALSE, FALSE, "normal sounds on/off"
16200 void CreateGameButtons(void)
16204 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16206 int graphic = gamebutton_info[i].graphic;
16207 struct GraphicInfo *gfx = &graphic_info[graphic];
16208 struct XY *pos = gamebutton_info[i].pos;
16209 struct GadgetInfo *gi;
16212 unsigned int event_mask;
16213 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16214 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16215 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16216 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16217 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16218 int gd_x = gfx->src_x;
16219 int gd_y = gfx->src_y;
16220 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16221 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16222 int gd_xa = gfx->src_x + gfx->active_xoffset;
16223 int gd_ya = gfx->src_y + gfx->active_yoffset;
16224 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16225 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16226 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16227 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16230 if (gfx->bitmap == NULL)
16232 game_gadget[id] = NULL;
16237 if (id == GAME_CTRL_ID_STOP ||
16238 id == GAME_CTRL_ID_PANEL_STOP ||
16239 id == GAME_CTRL_ID_TOUCH_STOP ||
16240 id == GAME_CTRL_ID_PLAY ||
16241 id == GAME_CTRL_ID_PANEL_PLAY ||
16242 id == GAME_CTRL_ID_SAVE ||
16243 id == GAME_CTRL_ID_LOAD)
16245 button_type = GD_TYPE_NORMAL_BUTTON;
16247 event_mask = GD_EVENT_RELEASED;
16249 else if (id == GAME_CTRL_ID_UNDO ||
16250 id == GAME_CTRL_ID_REDO)
16252 button_type = GD_TYPE_NORMAL_BUTTON;
16254 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16258 button_type = GD_TYPE_CHECK_BUTTON;
16259 checked = (gamebutton_info[i].setup_value != NULL ?
16260 *gamebutton_info[i].setup_value : FALSE);
16261 event_mask = GD_EVENT_PRESSED;
16264 gi = CreateGadget(GDI_CUSTOM_ID, id,
16265 GDI_IMAGE_ID, graphic,
16266 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16269 GDI_WIDTH, gfx->width,
16270 GDI_HEIGHT, gfx->height,
16271 GDI_TYPE, button_type,
16272 GDI_STATE, GD_BUTTON_UNPRESSED,
16273 GDI_CHECKED, checked,
16274 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16275 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16276 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16277 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16278 GDI_DIRECT_DRAW, FALSE,
16279 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16280 GDI_EVENT_MASK, event_mask,
16281 GDI_CALLBACK_ACTION, HandleGameButtons,
16285 Fail("cannot create gadget");
16287 game_gadget[id] = gi;
16291 void FreeGameButtons(void)
16295 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16296 FreeGadget(game_gadget[i]);
16299 static void UnmapGameButtonsAtSamePosition(int id)
16303 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16305 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16306 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16307 UnmapGadget(game_gadget[i]);
16310 static void UnmapGameButtonsAtSamePosition_All(void)
16312 if (setup.show_load_save_buttons)
16314 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16315 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16316 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16318 else if (setup.show_undo_redo_buttons)
16320 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16321 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16322 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16326 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16327 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16328 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16330 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16331 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16332 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16336 void MapLoadSaveButtons(void)
16338 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16339 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16341 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16342 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16345 void MapUndoRedoButtons(void)
16347 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16348 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16350 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16351 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16354 void ModifyPauseButtons(void)
16358 GAME_CTRL_ID_PAUSE,
16359 GAME_CTRL_ID_PAUSE2,
16360 GAME_CTRL_ID_PANEL_PAUSE,
16361 GAME_CTRL_ID_TOUCH_PAUSE,
16366 for (i = 0; ids[i] > -1; i++)
16367 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16370 static void MapGameButtonsExt(boolean on_tape)
16374 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16375 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16376 MapGadget(game_gadget[i]);
16378 UnmapGameButtonsAtSamePosition_All();
16380 RedrawGameButtons();
16383 static void UnmapGameButtonsExt(boolean on_tape)
16387 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16388 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16389 UnmapGadget(game_gadget[i]);
16392 static void RedrawGameButtonsExt(boolean on_tape)
16396 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16397 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16398 RedrawGadget(game_gadget[i]);
16401 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16406 gi->checked = state;
16409 static void RedrawSoundButtonGadget(int id)
16411 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16412 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16413 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16414 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16415 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16416 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16419 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16420 RedrawGadget(game_gadget[id2]);
16423 void MapGameButtons(void)
16425 MapGameButtonsExt(FALSE);
16428 void UnmapGameButtons(void)
16430 UnmapGameButtonsExt(FALSE);
16433 void RedrawGameButtons(void)
16435 RedrawGameButtonsExt(FALSE);
16438 void MapGameButtonsOnTape(void)
16440 MapGameButtonsExt(TRUE);
16443 void UnmapGameButtonsOnTape(void)
16445 UnmapGameButtonsExt(TRUE);
16448 void RedrawGameButtonsOnTape(void)
16450 RedrawGameButtonsExt(TRUE);
16453 static void GameUndoRedoExt(void)
16455 ClearPlayerAction();
16457 tape.pausing = TRUE;
16460 UpdateAndDisplayGameControlValues();
16462 DrawCompleteVideoDisplay();
16463 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16464 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16465 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16467 ModifyPauseButtons();
16472 static void GameUndo(int steps)
16474 if (!CheckEngineSnapshotList())
16477 int tape_property_bits = tape.property_bits;
16479 LoadEngineSnapshot_Undo(steps);
16481 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16486 static void GameRedo(int steps)
16488 if (!CheckEngineSnapshotList())
16491 int tape_property_bits = tape.property_bits;
16493 LoadEngineSnapshot_Redo(steps);
16495 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16500 static void HandleGameButtonsExt(int id, int button)
16502 static boolean game_undo_executed = FALSE;
16503 int steps = BUTTON_STEPSIZE(button);
16504 boolean handle_game_buttons =
16505 (game_status == GAME_MODE_PLAYING ||
16506 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16508 if (!handle_game_buttons)
16513 case GAME_CTRL_ID_STOP:
16514 case GAME_CTRL_ID_PANEL_STOP:
16515 case GAME_CTRL_ID_TOUCH_STOP:
16516 if (game_status == GAME_MODE_MAIN)
16522 RequestQuitGame(FALSE);
16526 case GAME_CTRL_ID_PAUSE:
16527 case GAME_CTRL_ID_PAUSE2:
16528 case GAME_CTRL_ID_PANEL_PAUSE:
16529 case GAME_CTRL_ID_TOUCH_PAUSE:
16530 if (network.enabled && game_status == GAME_MODE_PLAYING)
16533 SendToServer_ContinuePlaying();
16535 SendToServer_PausePlaying();
16538 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16540 game_undo_executed = FALSE;
16544 case GAME_CTRL_ID_PLAY:
16545 case GAME_CTRL_ID_PANEL_PLAY:
16546 if (game_status == GAME_MODE_MAIN)
16548 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16550 else if (tape.pausing)
16552 if (network.enabled)
16553 SendToServer_ContinuePlaying();
16555 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16559 case GAME_CTRL_ID_UNDO:
16560 // Important: When using "save snapshot when collecting an item" mode,
16561 // load last (current) snapshot for first "undo" after pressing "pause"
16562 // (else the last-but-one snapshot would be loaded, because the snapshot
16563 // pointer already points to the last snapshot when pressing "pause",
16564 // which is fine for "every step/move" mode, but not for "every collect")
16565 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16566 !game_undo_executed)
16569 game_undo_executed = TRUE;
16574 case GAME_CTRL_ID_REDO:
16578 case GAME_CTRL_ID_SAVE:
16582 case GAME_CTRL_ID_LOAD:
16586 case SOUND_CTRL_ID_MUSIC:
16587 case SOUND_CTRL_ID_PANEL_MUSIC:
16588 if (setup.sound_music)
16590 setup.sound_music = FALSE;
16594 else if (audio.music_available)
16596 setup.sound = setup.sound_music = TRUE;
16598 SetAudioMode(setup.sound);
16600 if (game_status == GAME_MODE_PLAYING)
16604 RedrawSoundButtonGadget(id);
16608 case SOUND_CTRL_ID_LOOPS:
16609 case SOUND_CTRL_ID_PANEL_LOOPS:
16610 if (setup.sound_loops)
16611 setup.sound_loops = FALSE;
16612 else if (audio.loops_available)
16614 setup.sound = setup.sound_loops = TRUE;
16616 SetAudioMode(setup.sound);
16619 RedrawSoundButtonGadget(id);
16623 case SOUND_CTRL_ID_SIMPLE:
16624 case SOUND_CTRL_ID_PANEL_SIMPLE:
16625 if (setup.sound_simple)
16626 setup.sound_simple = FALSE;
16627 else if (audio.sound_available)
16629 setup.sound = setup.sound_simple = TRUE;
16631 SetAudioMode(setup.sound);
16634 RedrawSoundButtonGadget(id);
16643 static void HandleGameButtons(struct GadgetInfo *gi)
16645 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16648 void HandleSoundButtonKeys(Key key)
16650 if (key == setup.shortcut.sound_simple)
16651 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16652 else if (key == setup.shortcut.sound_loops)
16653 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16654 else if (key == setup.shortcut.sound_music)
16655 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);