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 struct XY xy_topdown[] =
1565 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1567 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1568 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1569 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1570 IS_JUST_CHANGING(x, y))
1572 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1574 // static variables for playfield scan mode (scanning forward or backward)
1575 static int playfield_scan_start_x = 0;
1576 static int playfield_scan_start_y = 0;
1577 static int playfield_scan_delta_x = 1;
1578 static int playfield_scan_delta_y = 1;
1580 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1581 (y) >= 0 && (y) <= lev_fieldy - 1; \
1582 (y) += playfield_scan_delta_y) \
1583 for ((x) = playfield_scan_start_x; \
1584 (x) >= 0 && (x) <= lev_fieldx - 1; \
1585 (x) += playfield_scan_delta_x)
1588 void DEBUG_SetMaximumDynamite(void)
1592 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1593 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1594 local_player->inventory_element[local_player->inventory_size++] =
1599 static void InitPlayfieldScanModeVars(void)
1601 if (game.use_reverse_scan_direction)
1603 playfield_scan_start_x = lev_fieldx - 1;
1604 playfield_scan_start_y = lev_fieldy - 1;
1606 playfield_scan_delta_x = -1;
1607 playfield_scan_delta_y = -1;
1611 playfield_scan_start_x = 0;
1612 playfield_scan_start_y = 0;
1614 playfield_scan_delta_x = 1;
1615 playfield_scan_delta_y = 1;
1619 static void InitPlayfieldScanMode(int mode)
1621 game.use_reverse_scan_direction =
1622 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1624 InitPlayfieldScanModeVars();
1627 static int get_move_delay_from_stepsize(int move_stepsize)
1630 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1632 // make sure that stepsize value is always a power of 2
1633 move_stepsize = (1 << log_2(move_stepsize));
1635 return TILEX / move_stepsize;
1638 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1641 int player_nr = player->index_nr;
1642 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1643 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1645 // do no immediately change move delay -- the player might just be moving
1646 player->move_delay_value_next = move_delay;
1648 // information if player can move must be set separately
1649 player->cannot_move = cannot_move;
1653 player->move_delay = game.initial_move_delay[player_nr];
1654 player->move_delay_value = game.initial_move_delay_value[player_nr];
1656 player->move_delay_value_next = -1;
1658 player->move_delay_reset_counter = 0;
1662 void GetPlayerConfig(void)
1664 GameFrameDelay = setup.game_frame_delay;
1666 if (!audio.sound_available)
1667 setup.sound_simple = FALSE;
1669 if (!audio.loops_available)
1670 setup.sound_loops = FALSE;
1672 if (!audio.music_available)
1673 setup.sound_music = FALSE;
1675 if (!video.fullscreen_available)
1676 setup.fullscreen = FALSE;
1678 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1680 SetAudioMode(setup.sound);
1683 int GetElementFromGroupElement(int element)
1685 if (IS_GROUP_ELEMENT(element))
1687 struct ElementGroupInfo *group = element_info[element].group;
1688 int last_anim_random_frame = gfx.anim_random_frame;
1691 if (group->choice_mode == ANIM_RANDOM)
1692 gfx.anim_random_frame = RND(group->num_elements_resolved);
1694 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1695 group->choice_mode, 0,
1698 if (group->choice_mode == ANIM_RANDOM)
1699 gfx.anim_random_frame = last_anim_random_frame;
1701 group->choice_pos++;
1703 element = group->element_resolved[element_pos];
1709 static void IncrementSokobanFieldsNeeded(void)
1711 if (level.sb_fields_needed)
1712 game.sokoban_fields_still_needed++;
1715 static void IncrementSokobanObjectsNeeded(void)
1717 if (level.sb_objects_needed)
1718 game.sokoban_objects_still_needed++;
1721 static void DecrementSokobanFieldsNeeded(void)
1723 if (game.sokoban_fields_still_needed > 0)
1724 game.sokoban_fields_still_needed--;
1727 static void DecrementSokobanObjectsNeeded(void)
1729 if (game.sokoban_objects_still_needed > 0)
1730 game.sokoban_objects_still_needed--;
1733 static void InitPlayerField(int x, int y, int element, boolean init_game)
1735 if (element == EL_SP_MURPHY)
1739 if (stored_player[0].present)
1741 Tile[x][y] = EL_SP_MURPHY_CLONE;
1747 stored_player[0].initial_element = element;
1748 stored_player[0].use_murphy = TRUE;
1750 if (!level.use_artwork_element[0])
1751 stored_player[0].artwork_element = EL_SP_MURPHY;
1754 Tile[x][y] = EL_PLAYER_1;
1760 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1761 int jx = player->jx, jy = player->jy;
1763 player->present = TRUE;
1765 player->block_last_field = (element == EL_SP_MURPHY ?
1766 level.sp_block_last_field :
1767 level.block_last_field);
1769 // ---------- initialize player's last field block delay ------------------
1771 // always start with reliable default value (no adjustment needed)
1772 player->block_delay_adjustment = 0;
1774 // special case 1: in Supaplex, Murphy blocks last field one more frame
1775 if (player->block_last_field && element == EL_SP_MURPHY)
1776 player->block_delay_adjustment = 1;
1778 // special case 2: in game engines before 3.1.1, blocking was different
1779 if (game.use_block_last_field_bug)
1780 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1782 if (!network.enabled || player->connected_network)
1784 player->active = TRUE;
1786 // remove potentially duplicate players
1787 if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y])
1788 StorePlayer[jx][jy] = 0;
1790 StorePlayer[x][y] = Tile[x][y];
1792 #if DEBUG_INIT_PLAYER
1793 Debug("game:init:player", "- player element %d activated",
1794 player->element_nr);
1795 Debug("game:init:player", " (local player is %d and currently %s)",
1796 local_player->element_nr,
1797 local_player->active ? "active" : "not active");
1801 Tile[x][y] = EL_EMPTY;
1803 player->jx = player->last_jx = x;
1804 player->jy = player->last_jy = y;
1807 // always check if player was just killed and should be reanimated
1809 int player_nr = GET_PLAYER_NR(element);
1810 struct PlayerInfo *player = &stored_player[player_nr];
1812 if (player->active && player->killed)
1813 player->reanimated = TRUE; // if player was just killed, reanimate him
1817 static void InitField(int x, int y, boolean init_game)
1819 int element = Tile[x][y];
1828 InitPlayerField(x, y, element, init_game);
1831 case EL_SOKOBAN_FIELD_PLAYER:
1832 element = Tile[x][y] = EL_PLAYER_1;
1833 InitField(x, y, init_game);
1835 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1836 InitField(x, y, init_game);
1839 case EL_SOKOBAN_FIELD_EMPTY:
1840 IncrementSokobanFieldsNeeded();
1843 case EL_SOKOBAN_OBJECT:
1844 IncrementSokobanObjectsNeeded();
1848 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1849 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1850 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1851 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1852 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1853 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1854 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1855 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1856 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1857 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1866 case EL_SPACESHIP_RIGHT:
1867 case EL_SPACESHIP_UP:
1868 case EL_SPACESHIP_LEFT:
1869 case EL_SPACESHIP_DOWN:
1870 case EL_BD_BUTTERFLY:
1871 case EL_BD_BUTTERFLY_RIGHT:
1872 case EL_BD_BUTTERFLY_UP:
1873 case EL_BD_BUTTERFLY_LEFT:
1874 case EL_BD_BUTTERFLY_DOWN:
1876 case EL_BD_FIREFLY_RIGHT:
1877 case EL_BD_FIREFLY_UP:
1878 case EL_BD_FIREFLY_LEFT:
1879 case EL_BD_FIREFLY_DOWN:
1880 case EL_PACMAN_RIGHT:
1882 case EL_PACMAN_LEFT:
1883 case EL_PACMAN_DOWN:
1885 case EL_YAMYAM_LEFT:
1886 case EL_YAMYAM_RIGHT:
1888 case EL_YAMYAM_DOWN:
1889 case EL_DARK_YAMYAM:
1892 case EL_SP_SNIKSNAK:
1893 case EL_SP_ELECTRON:
1899 case EL_SPRING_LEFT:
1900 case EL_SPRING_RIGHT:
1904 case EL_AMOEBA_FULL:
1909 case EL_AMOEBA_DROP:
1910 if (y == lev_fieldy - 1)
1912 Tile[x][y] = EL_AMOEBA_GROWING;
1913 Store[x][y] = EL_AMOEBA_WET;
1917 case EL_DYNAMITE_ACTIVE:
1918 case EL_SP_DISK_RED_ACTIVE:
1919 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1920 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1921 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1922 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1923 MovDelay[x][y] = 96;
1926 case EL_EM_DYNAMITE_ACTIVE:
1927 MovDelay[x][y] = 32;
1931 game.lights_still_needed++;
1935 game.friends_still_needed++;
1940 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1943 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1944 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1945 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1946 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1947 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1948 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1949 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1950 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1951 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1952 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1953 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1954 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1957 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1958 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1959 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1961 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1963 game.belt_dir[belt_nr] = belt_dir;
1964 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1966 else // more than one switch -- set it like the first switch
1968 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1973 case EL_LIGHT_SWITCH_ACTIVE:
1975 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1978 case EL_INVISIBLE_STEELWALL:
1979 case EL_INVISIBLE_WALL:
1980 case EL_INVISIBLE_SAND:
1981 if (game.light_time_left > 0 ||
1982 game.lenses_time_left > 0)
1983 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1986 case EL_EMC_MAGIC_BALL:
1987 if (game.ball_active)
1988 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1991 case EL_EMC_MAGIC_BALL_SWITCH:
1992 if (game.ball_active)
1993 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1996 case EL_TRIGGER_PLAYER:
1997 case EL_TRIGGER_ELEMENT:
1998 case EL_TRIGGER_CE_VALUE:
1999 case EL_TRIGGER_CE_SCORE:
2001 case EL_ANY_ELEMENT:
2002 case EL_CURRENT_CE_VALUE:
2003 case EL_CURRENT_CE_SCORE:
2020 // reference elements should not be used on the playfield
2021 Tile[x][y] = EL_EMPTY;
2025 if (IS_CUSTOM_ELEMENT(element))
2027 if (CAN_MOVE(element))
2030 if (!element_info[element].use_last_ce_value || init_game)
2031 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2033 else if (IS_GROUP_ELEMENT(element))
2035 Tile[x][y] = GetElementFromGroupElement(element);
2037 InitField(x, y, init_game);
2039 else if (IS_EMPTY_ELEMENT(element))
2041 GfxElementEmpty[x][y] = element;
2042 Tile[x][y] = EL_EMPTY;
2044 if (element_info[element].use_gfx_element)
2045 game.use_masked_elements = TRUE;
2052 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2055 static void InitField_WithBug1(int x, int y, boolean init_game)
2057 InitField(x, y, init_game);
2059 // not needed to call InitMovDir() -- already done by InitField()!
2060 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2061 CAN_MOVE(Tile[x][y]))
2065 static void InitField_WithBug2(int x, int y, boolean init_game)
2067 int old_element = Tile[x][y];
2069 InitField(x, y, init_game);
2071 // not needed to call InitMovDir() -- already done by InitField()!
2072 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2073 CAN_MOVE(old_element) &&
2074 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2077 /* this case is in fact a combination of not less than three bugs:
2078 first, it calls InitMovDir() for elements that can move, although this is
2079 already done by InitField(); then, it checks the element that was at this
2080 field _before_ the call to InitField() (which can change it); lastly, it
2081 was not called for "mole with direction" elements, which were treated as
2082 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2086 static int get_key_element_from_nr(int key_nr)
2088 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2089 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2090 EL_EM_KEY_1 : EL_KEY_1);
2092 return key_base_element + key_nr;
2095 static int get_next_dropped_element(struct PlayerInfo *player)
2097 return (player->inventory_size > 0 ?
2098 player->inventory_element[player->inventory_size - 1] :
2099 player->inventory_infinite_element != EL_UNDEFINED ?
2100 player->inventory_infinite_element :
2101 player->dynabombs_left > 0 ?
2102 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2106 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2108 // pos >= 0: get element from bottom of the stack;
2109 // pos < 0: get element from top of the stack
2113 int min_inventory_size = -pos;
2114 int inventory_pos = player->inventory_size - min_inventory_size;
2115 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2117 return (player->inventory_size >= min_inventory_size ?
2118 player->inventory_element[inventory_pos] :
2119 player->inventory_infinite_element != EL_UNDEFINED ?
2120 player->inventory_infinite_element :
2121 player->dynabombs_left >= min_dynabombs_left ?
2122 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 int min_dynabombs_left = pos + 1;
2128 int min_inventory_size = pos + 1 - player->dynabombs_left;
2129 int inventory_pos = pos - player->dynabombs_left;
2131 return (player->inventory_infinite_element != EL_UNDEFINED ?
2132 player->inventory_infinite_element :
2133 player->dynabombs_left >= min_dynabombs_left ?
2134 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2135 player->inventory_size >= min_inventory_size ?
2136 player->inventory_element[inventory_pos] :
2141 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2143 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2144 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2147 if (gpo1->sort_priority != gpo2->sort_priority)
2148 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2150 compare_result = gpo1->nr - gpo2->nr;
2152 return compare_result;
2155 int getPlayerInventorySize(int player_nr)
2157 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2158 return game_em.ply[player_nr]->dynamite;
2159 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2160 return game_sp.red_disk_count;
2162 return stored_player[player_nr].inventory_size;
2165 static void InitGameControlValues(void)
2169 for (i = 0; game_panel_controls[i].nr != -1; i++)
2171 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2172 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2173 struct TextPosInfo *pos = gpc->pos;
2175 int type = gpc->type;
2179 Error("'game_panel_controls' structure corrupted at %d", i);
2181 Fail("this should not happen -- please debug");
2184 // force update of game controls after initialization
2185 gpc->value = gpc->last_value = -1;
2186 gpc->frame = gpc->last_frame = -1;
2187 gpc->gfx_frame = -1;
2189 // determine panel value width for later calculation of alignment
2190 if (type == TYPE_INTEGER || type == TYPE_STRING)
2192 pos->width = pos->size * getFontWidth(pos->font);
2193 pos->height = getFontHeight(pos->font);
2195 else if (type == TYPE_ELEMENT)
2197 pos->width = pos->size;
2198 pos->height = pos->size;
2201 // fill structure for game panel draw order
2203 gpo->sort_priority = pos->sort_priority;
2206 // sort game panel controls according to sort_priority and control number
2207 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2208 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2211 static void UpdatePlayfieldElementCount(void)
2213 boolean use_element_count = FALSE;
2216 // first check if it is needed at all to calculate playfield element count
2217 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2218 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2219 use_element_count = TRUE;
2221 if (!use_element_count)
2224 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2225 element_info[i].element_count = 0;
2227 SCAN_PLAYFIELD(x, y)
2229 element_info[Tile[x][y]].element_count++;
2232 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2233 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2234 if (IS_IN_GROUP(j, i))
2235 element_info[EL_GROUP_START + i].element_count +=
2236 element_info[j].element_count;
2239 static void UpdateGameControlValues(void)
2242 int time = (game.LevelSolved ?
2243 game.LevelSolved_CountingTime :
2244 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2247 game_sp.time_played :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 game_mm.energy_left :
2250 game.no_level_time_limit ? TimePlayed : TimeLeft);
2251 int score = (game.LevelSolved ?
2252 game.LevelSolved_CountingScore :
2253 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2254 game_em.lev->score :
2255 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2257 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2260 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2261 game_em.lev->gems_needed :
2262 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2263 game_sp.infotrons_still_needed :
2264 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2265 game_mm.kettles_still_needed :
2266 game.gems_still_needed);
2267 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2268 game_em.lev->gems_needed > 0 :
2269 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2270 game_sp.infotrons_still_needed > 0 :
2271 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2272 game_mm.kettles_still_needed > 0 ||
2273 game_mm.lights_still_needed > 0 :
2274 game.gems_still_needed > 0 ||
2275 game.sokoban_fields_still_needed > 0 ||
2276 game.sokoban_objects_still_needed > 0 ||
2277 game.lights_still_needed > 0);
2278 int health = (game.LevelSolved ?
2279 game.LevelSolved_CountingHealth :
2280 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2281 MM_HEALTH(game_mm.laser_overload_value) :
2283 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2285 UpdatePlayfieldElementCount();
2287 // update game panel control values
2289 // used instead of "level_nr" (for network games)
2290 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2291 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2293 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2294 for (i = 0; i < MAX_NUM_KEYS; i++)
2295 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2296 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2299 if (game.centered_player_nr == -1)
2301 for (i = 0; i < MAX_PLAYERS; i++)
2303 // only one player in Supaplex game engine
2304 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2307 for (k = 0; k < MAX_NUM_KEYS; k++)
2309 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2311 if (game_em.ply[i]->keys & (1 << k))
2312 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2313 get_key_element_from_nr(k);
2315 else if (stored_player[i].key[k])
2316 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2317 get_key_element_from_nr(k);
2320 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2321 getPlayerInventorySize(i);
2323 if (stored_player[i].num_white_keys > 0)
2324 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2327 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2328 stored_player[i].num_white_keys;
2333 int player_nr = game.centered_player_nr;
2335 for (k = 0; k < MAX_NUM_KEYS; k++)
2337 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2339 if (game_em.ply[player_nr]->keys & (1 << k))
2340 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2341 get_key_element_from_nr(k);
2343 else if (stored_player[player_nr].key[k])
2344 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2345 get_key_element_from_nr(k);
2348 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2349 getPlayerInventorySize(player_nr);
2351 if (stored_player[player_nr].num_white_keys > 0)
2352 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2354 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2355 stored_player[player_nr].num_white_keys;
2358 // re-arrange keys on game panel, if needed or if defined by style settings
2359 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2361 int nr = GAME_PANEL_KEY_1 + i;
2362 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2363 struct TextPosInfo *pos = gpc->pos;
2365 // skip check if key is not in the player's inventory
2366 if (gpc->value == EL_EMPTY)
2369 // check if keys should be arranged on panel from left to right
2370 if (pos->style == STYLE_LEFTMOST_POSITION)
2372 // check previous key positions (left from current key)
2373 for (k = 0; k < i; k++)
2375 int nr_new = GAME_PANEL_KEY_1 + k;
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2380 gpc->value = EL_EMPTY;
2387 // check if "undefined" keys can be placed at some other position
2388 if (pos->x == -1 && pos->y == -1)
2390 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2392 // 1st try: display key at the same position as normal or EM keys
2393 if (game_panel_controls[nr_new].value == EL_EMPTY)
2395 game_panel_controls[nr_new].value = gpc->value;
2399 // 2nd try: display key at the next free position in the key panel
2400 for (k = 0; k < STD_NUM_KEYS; k++)
2402 nr_new = GAME_PANEL_KEY_1 + k;
2404 if (game_panel_controls[nr_new].value == EL_EMPTY)
2406 game_panel_controls[nr_new].value = gpc->value;
2415 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2417 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2418 get_inventory_element_from_pos(local_player, i);
2419 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2420 get_inventory_element_from_pos(local_player, -i - 1);
2423 game_panel_controls[GAME_PANEL_SCORE].value = score;
2424 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2426 game_panel_controls[GAME_PANEL_TIME].value = time;
2428 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2429 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2430 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2432 if (level.time == 0)
2433 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2435 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2437 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2438 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2440 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2442 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2443 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2445 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2446 local_player->shield_normal_time_left;
2447 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2448 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2450 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2451 local_player->shield_deadly_time_left;
2453 game_panel_controls[GAME_PANEL_EXIT].value =
2454 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2456 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2457 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2458 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2459 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2460 EL_EMC_MAGIC_BALL_SWITCH);
2462 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2463 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2464 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2465 game.light_time_left;
2467 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2468 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2469 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2470 game.timegate_time_left;
2472 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2473 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2475 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2476 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2477 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2478 game.lenses_time_left;
2480 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2481 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2482 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2483 game.magnify_time_left;
2485 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2486 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2487 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2488 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2489 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2490 EL_BALLOON_SWITCH_NONE);
2492 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2493 local_player->dynabomb_count;
2494 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2495 local_player->dynabomb_size;
2496 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2497 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2499 game_panel_controls[GAME_PANEL_PENGUINS].value =
2500 game.friends_still_needed;
2502 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2503 game.sokoban_objects_still_needed;
2504 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2505 game.sokoban_fields_still_needed;
2507 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2508 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2510 for (i = 0; i < NUM_BELTS; i++)
2512 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2513 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2514 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2515 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2516 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2519 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2520 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2521 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2522 game.magic_wall_time_left;
2524 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2525 local_player->gravity;
2527 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2528 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2530 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2531 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2532 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2533 game.panel.element[i].id : EL_UNDEFINED);
2535 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2536 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2537 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2538 element_info[game.panel.element_count[i].id].element_count : 0);
2540 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2541 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2542 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2543 element_info[game.panel.ce_score[i].id].collect_score : 0);
2545 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2546 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2547 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2548 element_info[game.panel.ce_score_element[i].id].collect_score :
2551 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2552 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2553 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2555 // update game panel control frames
2557 for (i = 0; game_panel_controls[i].nr != -1; i++)
2559 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2561 if (gpc->type == TYPE_ELEMENT)
2563 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2565 int last_anim_random_frame = gfx.anim_random_frame;
2566 int element = gpc->value;
2567 int graphic = el2panelimg(element);
2568 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2570 graphic_info[graphic].anim_global_anim_sync ?
2571 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2573 if (gpc->value != gpc->last_value)
2576 gpc->gfx_random = init_gfx_random;
2582 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2583 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2584 gpc->gfx_random = init_gfx_random;
2587 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2588 gfx.anim_random_frame = gpc->gfx_random;
2590 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2591 gpc->gfx_frame = element_info[element].collect_score;
2593 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2595 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2596 gfx.anim_random_frame = last_anim_random_frame;
2599 else if (gpc->type == TYPE_GRAPHIC)
2601 if (gpc->graphic != IMG_UNDEFINED)
2603 int last_anim_random_frame = gfx.anim_random_frame;
2604 int graphic = gpc->graphic;
2605 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2607 graphic_info[graphic].anim_global_anim_sync ?
2608 getGlobalAnimSyncFrame() : INIT_GFX_RANDOM());
2610 if (gpc->value != gpc->last_value)
2613 gpc->gfx_random = init_gfx_random;
2619 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2620 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2621 gpc->gfx_random = init_gfx_random;
2624 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2625 gfx.anim_random_frame = gpc->gfx_random;
2627 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2629 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2630 gfx.anim_random_frame = last_anim_random_frame;
2636 static void DisplayGameControlValues(void)
2638 boolean redraw_panel = FALSE;
2641 for (i = 0; game_panel_controls[i].nr != -1; i++)
2643 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2645 if (PANEL_DEACTIVATED(gpc->pos))
2648 if (gpc->value == gpc->last_value &&
2649 gpc->frame == gpc->last_frame)
2652 redraw_panel = TRUE;
2658 // copy default game door content to main double buffer
2660 // !!! CHECK AGAIN !!!
2661 SetPanelBackground();
2662 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2663 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2665 // redraw game control buttons
2666 RedrawGameButtons();
2668 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2670 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2672 int nr = game_panel_order[i].nr;
2673 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2674 struct TextPosInfo *pos = gpc->pos;
2675 int type = gpc->type;
2676 int value = gpc->value;
2677 int frame = gpc->frame;
2678 int size = pos->size;
2679 int font = pos->font;
2680 boolean draw_masked = pos->draw_masked;
2681 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2683 if (PANEL_DEACTIVATED(pos))
2686 if (pos->class == get_hash_from_key("extra_panel_items") &&
2687 !setup.prefer_extra_panel_items)
2690 gpc->last_value = value;
2691 gpc->last_frame = frame;
2693 if (type == TYPE_INTEGER)
2695 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2696 nr == GAME_PANEL_INVENTORY_COUNT ||
2697 nr == GAME_PANEL_SCORE ||
2698 nr == GAME_PANEL_HIGHSCORE ||
2699 nr == GAME_PANEL_TIME)
2701 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2703 if (use_dynamic_size) // use dynamic number of digits
2705 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2706 nr == GAME_PANEL_INVENTORY_COUNT ||
2707 nr == GAME_PANEL_TIME ? 1000 : 100000);
2708 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2709 nr == GAME_PANEL_INVENTORY_COUNT ||
2710 nr == GAME_PANEL_TIME ? 1 : 2);
2711 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2712 nr == GAME_PANEL_INVENTORY_COUNT ||
2713 nr == GAME_PANEL_TIME ? 3 : 5);
2714 int size2 = size1 + size_add;
2715 int font1 = pos->font;
2716 int font2 = pos->font_alt;
2718 size = (value < value_change ? size1 : size2);
2719 font = (value < value_change ? font1 : font2);
2723 // correct text size if "digits" is zero or less
2725 size = strlen(int2str(value, size));
2727 // dynamically correct text alignment
2728 pos->width = size * getFontWidth(font);
2730 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2731 int2str(value, size), font, mask_mode);
2733 else if (type == TYPE_ELEMENT)
2735 int element, graphic;
2739 int dst_x = PANEL_XPOS(pos);
2740 int dst_y = PANEL_YPOS(pos);
2742 if (value != EL_UNDEFINED && value != EL_EMPTY)
2745 graphic = el2panelimg(value);
2748 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2749 element, EL_NAME(element), size);
2752 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2755 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2758 width = graphic_info[graphic].width * size / TILESIZE;
2759 height = graphic_info[graphic].height * size / TILESIZE;
2762 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2765 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2769 else if (type == TYPE_GRAPHIC)
2771 int graphic = gpc->graphic;
2772 int graphic_active = gpc->graphic_active;
2776 int dst_x = PANEL_XPOS(pos);
2777 int dst_y = PANEL_YPOS(pos);
2778 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2779 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2781 if (graphic != IMG_UNDEFINED && !skip)
2783 if (pos->style == STYLE_REVERSE)
2784 value = 100 - value;
2786 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2788 if (pos->direction & MV_HORIZONTAL)
2790 width = graphic_info[graphic_active].width * value / 100;
2791 height = graphic_info[graphic_active].height;
2793 if (pos->direction == MV_LEFT)
2795 src_x += graphic_info[graphic_active].width - width;
2796 dst_x += graphic_info[graphic_active].width - width;
2801 width = graphic_info[graphic_active].width;
2802 height = graphic_info[graphic_active].height * value / 100;
2804 if (pos->direction == MV_UP)
2806 src_y += graphic_info[graphic_active].height - height;
2807 dst_y += graphic_info[graphic_active].height - height;
2812 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2815 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2818 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2820 if (pos->direction & MV_HORIZONTAL)
2822 if (pos->direction == MV_RIGHT)
2829 dst_x = PANEL_XPOS(pos);
2832 width = graphic_info[graphic].width - width;
2836 if (pos->direction == MV_DOWN)
2843 dst_y = PANEL_YPOS(pos);
2846 height = graphic_info[graphic].height - height;
2850 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2853 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2857 else if (type == TYPE_STRING)
2859 boolean active = (value != 0);
2860 char *state_normal = "off";
2861 char *state_active = "on";
2862 char *state = (active ? state_active : state_normal);
2863 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2864 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2865 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2866 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2868 if (nr == GAME_PANEL_GRAVITY_STATE)
2870 int font1 = pos->font; // (used for normal state)
2871 int font2 = pos->font_alt; // (used for active state)
2873 font = (active ? font2 : font1);
2882 // don't truncate output if "chars" is zero or less
2885 // dynamically correct text alignment
2886 pos->width = size * getFontWidth(font);
2889 s_cut = getStringCopyN(s, size);
2891 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2892 s_cut, font, mask_mode);
2898 redraw_mask |= REDRAW_DOOR_1;
2901 SetGameStatus(GAME_MODE_PLAYING);
2904 void UpdateAndDisplayGameControlValues(void)
2906 if (tape.deactivate_display)
2909 UpdateGameControlValues();
2910 DisplayGameControlValues();
2913 void UpdateGameDoorValues(void)
2915 UpdateGameControlValues();
2918 void DrawGameDoorValues(void)
2920 DisplayGameControlValues();
2924 // ============================================================================
2926 // ----------------------------------------------------------------------------
2927 // initialize game engine due to level / tape version number
2928 // ============================================================================
2930 static void InitGameEngine(void)
2932 int i, j, k, l, x, y;
2934 // set game engine from tape file when re-playing, else from level file
2935 game.engine_version = (tape.playing ? tape.engine_version :
2936 level.game_version);
2938 // set single or multi-player game mode (needed for re-playing tapes)
2939 game.team_mode = setup.team_mode;
2943 int num_players = 0;
2945 for (i = 0; i < MAX_PLAYERS; i++)
2946 if (tape.player_participates[i])
2949 // multi-player tapes contain input data for more than one player
2950 game.team_mode = (num_players > 1);
2954 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2955 level.game_version);
2956 Debug("game:init:level", " tape.file_version == %06d",
2958 Debug("game:init:level", " tape.game_version == %06d",
2960 Debug("game:init:level", " tape.engine_version == %06d",
2961 tape.engine_version);
2962 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2963 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2966 // --------------------------------------------------------------------------
2967 // set flags for bugs and changes according to active game engine version
2968 // --------------------------------------------------------------------------
2972 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2974 Bug was introduced in version:
2977 Bug was fixed in version:
2981 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2982 but the property "can fall" was missing, which caused some levels to be
2983 unsolvable. This was fixed in version 4.2.0.0.
2985 Affected levels/tapes:
2986 An example for a tape that was fixed by this bugfix is tape 029 from the
2987 level set "rnd_sam_bateman".
2988 The wrong behaviour will still be used for all levels or tapes that were
2989 created/recorded with it. An example for this is tape 023 from the level
2990 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2993 boolean use_amoeba_dropping_cannot_fall_bug =
2994 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2995 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2997 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2998 tape.game_version < VERSION_IDENT(4,2,0,0)));
3001 Summary of bugfix/change:
3002 Fixed move speed of elements entering or leaving magic wall.
3004 Fixed/changed in version:
3008 Before 2.0.1, move speed of elements entering or leaving magic wall was
3009 twice as fast as it is now.
3010 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3012 Affected levels/tapes:
3013 The first condition is generally needed for all levels/tapes before version
3014 2.0.1, which might use the old behaviour before it was changed; known tapes
3015 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3018 above, but before it was known that this change would break tapes like the
3019 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3020 although the engine version while recording maybe was before 2.0.1. There
3021 are a lot of tapes that are affected by this exception, like tape 006 from
3022 the level set "rnd_conor_mancone".
3025 boolean use_old_move_stepsize_for_magic_wall =
3026 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3028 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3029 tape.game_version < VERSION_IDENT(4,2,0,0)));
3032 Summary of bugfix/change:
3033 Fixed handling for custom elements that change when pushed by the player.
3035 Fixed/changed in version:
3039 Before 3.1.0, custom elements that "change when pushing" changed directly
3040 after the player started pushing them (until then handled in "DigField()").
3041 Since 3.1.0, these custom elements are not changed until the "pushing"
3042 move of the element is finished (now handled in "ContinueMoving()").
3044 Affected levels/tapes:
3045 The first condition is generally needed for all levels/tapes before version
3046 3.1.0, which might use the old behaviour before it was changed; known tapes
3047 that are affected are some tapes from the level set "Walpurgis Gardens" by
3049 The second condition is an exception from the above case and is needed for
3050 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3051 above (including some development versions of 3.1.0), but before it was
3052 known that this change would break tapes like the above and was fixed in
3053 3.1.1, so that the changed behaviour was active although the engine version
3054 while recording maybe was before 3.1.0. There is at least one tape that is
3055 affected by this exception, which is the tape for the one-level set "Bug
3056 Machine" by Juergen Bonhagen.
3059 game.use_change_when_pushing_bug =
3060 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3062 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3063 tape.game_version < VERSION_IDENT(3,1,1,0)));
3066 Summary of bugfix/change:
3067 Fixed handling for blocking the field the player leaves when moving.
3069 Fixed/changed in version:
3073 Before 3.1.1, when "block last field when moving" was enabled, the field
3074 the player is leaving when moving was blocked for the time of the move,
3075 and was directly unblocked afterwards. This resulted in the last field
3076 being blocked for exactly one less than the number of frames of one player
3077 move. Additionally, even when blocking was disabled, the last field was
3078 blocked for exactly one frame.
3079 Since 3.1.1, due to changes in player movement handling, the last field
3080 is not blocked at all when blocking is disabled. When blocking is enabled,
3081 the last field is blocked for exactly the number of frames of one player
3082 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3083 last field is blocked for exactly one more than the number of frames of
3086 Affected levels/tapes:
3087 (!!! yet to be determined -- probably many !!!)
3090 game.use_block_last_field_bug =
3091 (game.engine_version < VERSION_IDENT(3,1,1,0));
3093 /* various special flags and settings for native Emerald Mine game engine */
3095 game_em.use_single_button =
3096 (game.engine_version > VERSION_IDENT(4,0,0,2));
3098 game_em.use_snap_key_bug =
3099 (game.engine_version < VERSION_IDENT(4,0,1,0));
3101 game_em.use_random_bug =
3102 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3104 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3106 game_em.use_old_explosions = use_old_em_engine;
3107 game_em.use_old_android = use_old_em_engine;
3108 game_em.use_old_push_elements = use_old_em_engine;
3109 game_em.use_old_push_into_acid = use_old_em_engine;
3111 game_em.use_wrap_around = !use_old_em_engine;
3113 // --------------------------------------------------------------------------
3115 // set maximal allowed number of custom element changes per game frame
3116 game.max_num_changes_per_frame = 1;
3118 // default scan direction: scan playfield from top/left to bottom/right
3119 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3121 // dynamically adjust element properties according to game engine version
3122 InitElementPropertiesEngine(game.engine_version);
3124 // ---------- initialize special element properties -------------------------
3126 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3127 if (use_amoeba_dropping_cannot_fall_bug)
3128 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3130 // ---------- initialize player's initial move delay ------------------------
3132 // dynamically adjust player properties according to level information
3133 for (i = 0; i < MAX_PLAYERS; i++)
3134 game.initial_move_delay_value[i] =
3135 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3137 // dynamically adjust player properties according to game engine version
3138 for (i = 0; i < MAX_PLAYERS; i++)
3139 game.initial_move_delay[i] =
3140 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3141 game.initial_move_delay_value[i] : 0);
3143 // ---------- initialize player's initial push delay ------------------------
3145 // dynamically adjust player properties according to game engine version
3146 game.initial_push_delay_value =
3147 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3149 // ---------- initialize changing elements ----------------------------------
3151 // initialize changing elements information
3152 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3154 struct ElementInfo *ei = &element_info[i];
3156 // this pointer might have been changed in the level editor
3157 ei->change = &ei->change_page[0];
3159 if (!IS_CUSTOM_ELEMENT(i))
3161 ei->change->target_element = EL_EMPTY_SPACE;
3162 ei->change->delay_fixed = 0;
3163 ei->change->delay_random = 0;
3164 ei->change->delay_frames = 1;
3167 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3169 ei->has_change_event[j] = FALSE;
3171 ei->event_page_nr[j] = 0;
3172 ei->event_page[j] = &ei->change_page[0];
3176 // add changing elements from pre-defined list
3177 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3179 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3180 struct ElementInfo *ei = &element_info[ch_delay->element];
3182 ei->change->target_element = ch_delay->target_element;
3183 ei->change->delay_fixed = ch_delay->change_delay;
3185 ei->change->pre_change_function = ch_delay->pre_change_function;
3186 ei->change->change_function = ch_delay->change_function;
3187 ei->change->post_change_function = ch_delay->post_change_function;
3189 ei->change->can_change = TRUE;
3190 ei->change->can_change_or_has_action = TRUE;
3192 ei->has_change_event[CE_DELAY] = TRUE;
3194 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3195 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3198 // ---------- initialize internal run-time variables ------------------------
3200 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3202 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3204 for (j = 0; j < ei->num_change_pages; j++)
3206 ei->change_page[j].can_change_or_has_action =
3207 (ei->change_page[j].can_change |
3208 ei->change_page[j].has_action);
3212 // add change events from custom element configuration
3213 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3215 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3217 for (j = 0; j < ei->num_change_pages; j++)
3219 if (!ei->change_page[j].can_change_or_has_action)
3222 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3224 // only add event page for the first page found with this event
3225 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3227 ei->has_change_event[k] = TRUE;
3229 ei->event_page_nr[k] = j;
3230 ei->event_page[k] = &ei->change_page[j];
3236 // ---------- initialize reference elements in change conditions ------------
3238 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3240 int element = EL_CUSTOM_START + i;
3241 struct ElementInfo *ei = &element_info[element];
3243 for (j = 0; j < ei->num_change_pages; j++)
3245 int trigger_element = ei->change_page[j].initial_trigger_element;
3247 if (trigger_element >= EL_PREV_CE_8 &&
3248 trigger_element <= EL_NEXT_CE_8)
3249 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3251 ei->change_page[j].trigger_element = trigger_element;
3255 // ---------- initialize run-time trigger player and element ----------------
3257 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3259 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3261 for (j = 0; j < ei->num_change_pages; j++)
3263 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3264 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3265 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3266 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3267 ei->change_page[j].actual_trigger_ce_value = 0;
3268 ei->change_page[j].actual_trigger_ce_score = 0;
3272 // ---------- initialize trigger events -------------------------------------
3274 // initialize trigger events information
3275 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3276 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3277 trigger_events[i][j] = FALSE;
3279 // add trigger events from element change event properties
3280 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3282 struct ElementInfo *ei = &element_info[i];
3284 for (j = 0; j < ei->num_change_pages; j++)
3286 if (!ei->change_page[j].can_change_or_has_action)
3289 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3291 int trigger_element = ei->change_page[j].trigger_element;
3293 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3295 if (ei->change_page[j].has_event[k])
3297 if (IS_GROUP_ELEMENT(trigger_element))
3299 struct ElementGroupInfo *group =
3300 element_info[trigger_element].group;
3302 for (l = 0; l < group->num_elements_resolved; l++)
3303 trigger_events[group->element_resolved[l]][k] = TRUE;
3305 else if (trigger_element == EL_ANY_ELEMENT)
3306 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3307 trigger_events[l][k] = TRUE;
3309 trigger_events[trigger_element][k] = TRUE;
3316 // ---------- initialize push delay -----------------------------------------
3318 // initialize push delay values to default
3319 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3321 if (!IS_CUSTOM_ELEMENT(i))
3323 // set default push delay values (corrected since version 3.0.7-1)
3324 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3326 element_info[i].push_delay_fixed = 2;
3327 element_info[i].push_delay_random = 8;
3331 element_info[i].push_delay_fixed = 8;
3332 element_info[i].push_delay_random = 8;
3337 // set push delay value for certain elements from pre-defined list
3338 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3340 int e = push_delay_list[i].element;
3342 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3343 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3346 // set push delay value for Supaplex elements for newer engine versions
3347 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3349 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3351 if (IS_SP_ELEMENT(i))
3353 // set SP push delay to just enough to push under a falling zonk
3354 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3356 element_info[i].push_delay_fixed = delay;
3357 element_info[i].push_delay_random = 0;
3362 // ---------- initialize move stepsize --------------------------------------
3364 // initialize move stepsize values to default
3365 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3366 if (!IS_CUSTOM_ELEMENT(i))
3367 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3369 // set move stepsize value for certain elements from pre-defined list
3370 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3372 int e = move_stepsize_list[i].element;
3374 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3376 // set move stepsize value for certain elements for older engine versions
3377 if (use_old_move_stepsize_for_magic_wall)
3379 if (e == EL_MAGIC_WALL_FILLING ||
3380 e == EL_MAGIC_WALL_EMPTYING ||
3381 e == EL_BD_MAGIC_WALL_FILLING ||
3382 e == EL_BD_MAGIC_WALL_EMPTYING)
3383 element_info[e].move_stepsize *= 2;
3387 // ---------- initialize collect score --------------------------------------
3389 // initialize collect score values for custom elements from initial value
3390 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3391 if (IS_CUSTOM_ELEMENT(i))
3392 element_info[i].collect_score = element_info[i].collect_score_initial;
3394 // ---------- initialize collect count --------------------------------------
3396 // initialize collect count values for non-custom elements
3397 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3398 if (!IS_CUSTOM_ELEMENT(i))
3399 element_info[i].collect_count_initial = 0;
3401 // add collect count values for all elements from pre-defined list
3402 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3403 element_info[collect_count_list[i].element].collect_count_initial =
3404 collect_count_list[i].count;
3406 // ---------- initialize access direction -----------------------------------
3408 // initialize access direction values to default (access from every side)
3409 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3410 if (!IS_CUSTOM_ELEMENT(i))
3411 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3413 // set access direction value for certain elements from pre-defined list
3414 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3415 element_info[access_direction_list[i].element].access_direction =
3416 access_direction_list[i].direction;
3418 // ---------- initialize explosion content ----------------------------------
3419 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3421 if (IS_CUSTOM_ELEMENT(i))
3424 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3426 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3428 element_info[i].content.e[x][y] =
3429 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3430 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3431 i == EL_PLAYER_3 ? EL_EMERALD :
3432 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3433 i == EL_MOLE ? EL_EMERALD_RED :
3434 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3435 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3436 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3437 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3438 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3439 i == EL_WALL_EMERALD ? EL_EMERALD :
3440 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3441 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3442 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3443 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3444 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3445 i == EL_WALL_PEARL ? EL_PEARL :
3446 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3451 // ---------- initialize recursion detection --------------------------------
3452 recursion_loop_depth = 0;
3453 recursion_loop_detected = FALSE;
3454 recursion_loop_element = EL_UNDEFINED;
3456 // ---------- initialize graphics engine ------------------------------------
3457 game.scroll_delay_value =
3458 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3459 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3460 !setup.forced_scroll_delay ? 0 :
3461 setup.scroll_delay ? setup.scroll_delay_value : 0);
3462 game.scroll_delay_value =
3463 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3465 // ---------- initialize game engine snapshots ------------------------------
3466 for (i = 0; i < MAX_PLAYERS; i++)
3467 game.snapshot.last_action[i] = 0;
3468 game.snapshot.changed_action = FALSE;
3469 game.snapshot.collected_item = FALSE;
3470 game.snapshot.mode =
3471 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3472 SNAPSHOT_MODE_EVERY_STEP :
3473 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3474 SNAPSHOT_MODE_EVERY_MOVE :
3475 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3476 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3477 game.snapshot.save_snapshot = FALSE;
3479 // ---------- initialize level time for Supaplex engine ---------------------
3480 // Supaplex levels with time limit currently unsupported -- should be added
3481 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3484 // ---------- initialize flags for handling game actions --------------------
3486 // set flags for game actions to default values
3487 game.use_key_actions = TRUE;
3488 game.use_mouse_actions = FALSE;
3490 // when using Mirror Magic game engine, handle mouse events only
3491 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3493 game.use_key_actions = FALSE;
3494 game.use_mouse_actions = TRUE;
3497 // check for custom elements with mouse click events
3498 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3500 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3502 int element = EL_CUSTOM_START + i;
3504 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3505 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3506 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3507 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3508 game.use_mouse_actions = TRUE;
3513 static int get_num_special_action(int element, int action_first,
3516 int num_special_action = 0;
3519 for (i = action_first; i <= action_last; i++)
3521 boolean found = FALSE;
3523 for (j = 0; j < NUM_DIRECTIONS; j++)
3524 if (el_act_dir2img(element, i, j) !=
3525 el_act_dir2img(element, ACTION_DEFAULT, j))
3529 num_special_action++;
3534 return num_special_action;
3538 // ============================================================================
3540 // ----------------------------------------------------------------------------
3541 // initialize and start new game
3542 // ============================================================================
3544 #if DEBUG_INIT_PLAYER
3545 static void DebugPrintPlayerStatus(char *message)
3552 Debug("game:init:player", "%s:", message);
3554 for (i = 0; i < MAX_PLAYERS; i++)
3556 struct PlayerInfo *player = &stored_player[i];
3558 Debug("game:init:player",
3559 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3563 player->connected_locally,
3564 player->connected_network,
3566 (local_player == player ? " (local player)" : ""));
3573 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3574 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3575 int fade_mask = REDRAW_FIELD;
3577 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3578 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3579 int initial_move_dir = MV_DOWN;
3582 // required here to update video display before fading (FIX THIS)
3583 DrawMaskedBorder(REDRAW_DOOR_2);
3585 if (!game.restart_level)
3586 CloseDoor(DOOR_CLOSE_1);
3588 SetGameStatus(GAME_MODE_PLAYING);
3590 if (level_editor_test_game)
3591 FadeSkipNextFadeOut();
3593 FadeSetEnterScreen();
3596 fade_mask = REDRAW_ALL;
3598 FadeLevelSoundsAndMusic();
3600 ExpireSoundLoops(TRUE);
3604 if (level_editor_test_game)
3605 FadeSkipNextFadeIn();
3607 // needed if different viewport properties defined for playing
3608 ChangeViewportPropertiesIfNeeded();
3612 DrawCompleteVideoDisplay();
3614 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3617 InitGameControlValues();
3621 // initialize tape actions from game when recording tape
3622 tape.use_key_actions = game.use_key_actions;
3623 tape.use_mouse_actions = game.use_mouse_actions;
3625 // initialize visible playfield size when recording tape (for team mode)
3626 tape.scr_fieldx = SCR_FIELDX;
3627 tape.scr_fieldy = SCR_FIELDY;
3630 // don't play tapes over network
3631 network_playing = (network.enabled && !tape.playing);
3633 for (i = 0; i < MAX_PLAYERS; i++)
3635 struct PlayerInfo *player = &stored_player[i];
3637 player->index_nr = i;
3638 player->index_bit = (1 << i);
3639 player->element_nr = EL_PLAYER_1 + i;
3641 player->present = FALSE;
3642 player->active = FALSE;
3643 player->mapped = FALSE;
3645 player->killed = FALSE;
3646 player->reanimated = FALSE;
3647 player->buried = FALSE;
3650 player->effective_action = 0;
3651 player->programmed_action = 0;
3652 player->snap_action = 0;
3654 player->mouse_action.lx = 0;
3655 player->mouse_action.ly = 0;
3656 player->mouse_action.button = 0;
3657 player->mouse_action.button_hint = 0;
3659 player->effective_mouse_action.lx = 0;
3660 player->effective_mouse_action.ly = 0;
3661 player->effective_mouse_action.button = 0;
3662 player->effective_mouse_action.button_hint = 0;
3664 for (j = 0; j < MAX_NUM_KEYS; j++)
3665 player->key[j] = FALSE;
3667 player->num_white_keys = 0;
3669 player->dynabomb_count = 0;
3670 player->dynabomb_size = 1;
3671 player->dynabombs_left = 0;
3672 player->dynabomb_xl = FALSE;
3674 player->MovDir = initial_move_dir;
3677 player->GfxDir = initial_move_dir;
3678 player->GfxAction = ACTION_DEFAULT;
3680 player->StepFrame = 0;
3682 player->initial_element = player->element_nr;
3683 player->artwork_element =
3684 (level.use_artwork_element[i] ? level.artwork_element[i] :
3685 player->element_nr);
3686 player->use_murphy = FALSE;
3688 player->block_last_field = FALSE; // initialized in InitPlayerField()
3689 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3691 player->gravity = level.initial_player_gravity[i];
3693 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3695 player->actual_frame_counter.count = 0;
3696 player->actual_frame_counter.value = 1;
3698 player->step_counter = 0;
3700 player->last_move_dir = initial_move_dir;
3702 player->is_active = FALSE;
3704 player->is_waiting = FALSE;
3705 player->is_moving = FALSE;
3706 player->is_auto_moving = FALSE;
3707 player->is_digging = FALSE;
3708 player->is_snapping = FALSE;
3709 player->is_collecting = FALSE;
3710 player->is_pushing = FALSE;
3711 player->is_switching = FALSE;
3712 player->is_dropping = FALSE;
3713 player->is_dropping_pressed = FALSE;
3715 player->is_bored = FALSE;
3716 player->is_sleeping = FALSE;
3718 player->was_waiting = TRUE;
3719 player->was_moving = FALSE;
3720 player->was_snapping = FALSE;
3721 player->was_dropping = FALSE;
3723 player->force_dropping = FALSE;
3725 player->frame_counter_bored = -1;
3726 player->frame_counter_sleeping = -1;
3728 player->anim_delay_counter = 0;
3729 player->post_delay_counter = 0;
3731 player->dir_waiting = initial_move_dir;
3732 player->action_waiting = ACTION_DEFAULT;
3733 player->last_action_waiting = ACTION_DEFAULT;
3734 player->special_action_bored = ACTION_DEFAULT;
3735 player->special_action_sleeping = ACTION_DEFAULT;
3737 player->switch_x = -1;
3738 player->switch_y = -1;
3740 player->drop_x = -1;
3741 player->drop_y = -1;
3743 player->show_envelope = 0;
3745 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3747 player->push_delay = -1; // initialized when pushing starts
3748 player->push_delay_value = game.initial_push_delay_value;
3750 player->drop_delay = 0;
3751 player->drop_pressed_delay = 0;
3753 player->last_jx = -1;
3754 player->last_jy = -1;
3758 player->shield_normal_time_left = 0;
3759 player->shield_deadly_time_left = 0;
3761 player->last_removed_element = EL_UNDEFINED;
3763 player->inventory_infinite_element = EL_UNDEFINED;
3764 player->inventory_size = 0;
3766 if (level.use_initial_inventory[i])
3768 for (j = 0; j < level.initial_inventory_size[i]; j++)
3770 int element = level.initial_inventory_content[i][j];
3771 int collect_count = element_info[element].collect_count_initial;
3774 if (!IS_CUSTOM_ELEMENT(element))
3777 if (collect_count == 0)
3778 player->inventory_infinite_element = element;
3780 for (k = 0; k < collect_count; k++)
3781 if (player->inventory_size < MAX_INVENTORY_SIZE)
3782 player->inventory_element[player->inventory_size++] = element;
3786 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3787 SnapField(player, 0, 0);
3789 map_player_action[i] = i;
3792 network_player_action_received = FALSE;
3794 // initial null action
3795 if (network_playing)
3796 SendToServer_MovePlayer(MV_NONE);
3801 TimeLeft = level.time;
3804 ScreenMovDir = MV_NONE;
3808 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3810 game.robot_wheel_x = -1;
3811 game.robot_wheel_y = -1;
3816 game.all_players_gone = FALSE;
3818 game.LevelSolved = FALSE;
3819 game.GameOver = FALSE;
3821 game.GamePlayed = !tape.playing;
3823 game.LevelSolved_GameWon = FALSE;
3824 game.LevelSolved_GameEnd = FALSE;
3825 game.LevelSolved_SaveTape = FALSE;
3826 game.LevelSolved_SaveScore = FALSE;
3828 game.LevelSolved_CountingTime = 0;
3829 game.LevelSolved_CountingScore = 0;
3830 game.LevelSolved_CountingHealth = 0;
3832 game.panel.active = TRUE;
3834 game.no_level_time_limit = (level.time == 0);
3835 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3837 game.yamyam_content_nr = 0;
3838 game.robot_wheel_active = FALSE;
3839 game.magic_wall_active = FALSE;
3840 game.magic_wall_time_left = 0;
3841 game.light_time_left = 0;
3842 game.timegate_time_left = 0;
3843 game.switchgate_pos = 0;
3844 game.wind_direction = level.wind_direction_initial;
3846 game.time_final = 0;
3847 game.score_time_final = 0;
3850 game.score_final = 0;
3852 game.health = MAX_HEALTH;
3853 game.health_final = MAX_HEALTH;
3855 game.gems_still_needed = level.gems_needed;
3856 game.sokoban_fields_still_needed = 0;
3857 game.sokoban_objects_still_needed = 0;
3858 game.lights_still_needed = 0;
3859 game.players_still_needed = 0;
3860 game.friends_still_needed = 0;
3862 game.lenses_time_left = 0;
3863 game.magnify_time_left = 0;
3865 game.ball_active = level.ball_active_initial;
3866 game.ball_content_nr = 0;
3868 game.explosions_delayed = TRUE;
3870 game.envelope_active = FALSE;
3872 // special case: set custom artwork setting to initial value
3873 game.use_masked_elements = game.use_masked_elements_initial;
3875 for (i = 0; i < NUM_BELTS; i++)
3877 game.belt_dir[i] = MV_NONE;
3878 game.belt_dir_nr[i] = 3; // not moving, next moving left
3881 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3882 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3884 #if DEBUG_INIT_PLAYER
3885 DebugPrintPlayerStatus("Player status at level initialization");
3888 SCAN_PLAYFIELD(x, y)
3890 Tile[x][y] = Last[x][y] = level.field[x][y];
3891 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3892 ChangeDelay[x][y] = 0;
3893 ChangePage[x][y] = -1;
3894 CustomValue[x][y] = 0; // initialized in InitField()
3895 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3897 WasJustMoving[x][y] = 0;
3898 WasJustFalling[x][y] = 0;
3899 CheckCollision[x][y] = 0;
3900 CheckImpact[x][y] = 0;
3902 Pushed[x][y] = FALSE;
3904 ChangeCount[x][y] = 0;
3905 ChangeEvent[x][y] = -1;
3907 ExplodePhase[x][y] = 0;
3908 ExplodeDelay[x][y] = 0;
3909 ExplodeField[x][y] = EX_TYPE_NONE;
3911 RunnerVisit[x][y] = 0;
3912 PlayerVisit[x][y] = 0;
3915 GfxRandom[x][y] = INIT_GFX_RANDOM();
3916 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3917 GfxElement[x][y] = EL_UNDEFINED;
3918 GfxElementEmpty[x][y] = EL_EMPTY;
3919 GfxAction[x][y] = ACTION_DEFAULT;
3920 GfxDir[x][y] = MV_NONE;
3921 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3924 SCAN_PLAYFIELD(x, y)
3926 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3928 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3931 InitField(x, y, TRUE);
3933 ResetGfxAnimation(x, y);
3938 for (i = 0; i < MAX_PLAYERS; i++)
3940 struct PlayerInfo *player = &stored_player[i];
3942 // set number of special actions for bored and sleeping animation
3943 player->num_special_action_bored =
3944 get_num_special_action(player->artwork_element,
3945 ACTION_BORING_1, ACTION_BORING_LAST);
3946 player->num_special_action_sleeping =
3947 get_num_special_action(player->artwork_element,
3948 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3951 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3952 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3954 // initialize type of slippery elements
3955 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3957 if (!IS_CUSTOM_ELEMENT(i))
3959 // default: elements slip down either to the left or right randomly
3960 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3962 // SP style elements prefer to slip down on the left side
3963 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3964 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3966 // BD style elements prefer to slip down on the left side
3967 if (game.emulation == EMU_BOULDERDASH)
3968 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3972 // initialize explosion and ignition delay
3973 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3975 if (!IS_CUSTOM_ELEMENT(i))
3978 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3979 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3980 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3981 int last_phase = (num_phase + 1) * delay;
3982 int half_phase = (num_phase / 2) * delay;
3984 element_info[i].explosion_delay = last_phase - 1;
3985 element_info[i].ignition_delay = half_phase;
3987 if (i == EL_BLACK_ORB)
3988 element_info[i].ignition_delay = 1;
3992 // correct non-moving belts to start moving left
3993 for (i = 0; i < NUM_BELTS; i++)
3994 if (game.belt_dir[i] == MV_NONE)
3995 game.belt_dir_nr[i] = 3; // not moving, next moving left
3997 #if USE_NEW_PLAYER_ASSIGNMENTS
3998 // use preferred player also in local single-player mode
3999 if (!network.enabled && !game.team_mode)
4001 int new_index_nr = setup.network_player_nr;
4003 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
4005 for (i = 0; i < MAX_PLAYERS; i++)
4006 stored_player[i].connected_locally = FALSE;
4008 stored_player[new_index_nr].connected_locally = TRUE;
4012 for (i = 0; i < MAX_PLAYERS; i++)
4014 stored_player[i].connected = FALSE;
4016 // in network game mode, the local player might not be the first player
4017 if (stored_player[i].connected_locally)
4018 local_player = &stored_player[i];
4021 if (!network.enabled)
4022 local_player->connected = TRUE;
4026 for (i = 0; i < MAX_PLAYERS; i++)
4027 stored_player[i].connected = tape.player_participates[i];
4029 else if (network.enabled)
4031 // add team mode players connected over the network (needed for correct
4032 // assignment of player figures from level to locally playing players)
4034 for (i = 0; i < MAX_PLAYERS; i++)
4035 if (stored_player[i].connected_network)
4036 stored_player[i].connected = TRUE;
4038 else if (game.team_mode)
4040 // try to guess locally connected team mode players (needed for correct
4041 // assignment of player figures from level to locally playing players)
4043 for (i = 0; i < MAX_PLAYERS; i++)
4044 if (setup.input[i].use_joystick ||
4045 setup.input[i].key.left != KSYM_UNDEFINED)
4046 stored_player[i].connected = TRUE;
4049 #if DEBUG_INIT_PLAYER
4050 DebugPrintPlayerStatus("Player status after level initialization");
4053 #if DEBUG_INIT_PLAYER
4054 Debug("game:init:player", "Reassigning players ...");
4057 // check if any connected player was not found in playfield
4058 for (i = 0; i < MAX_PLAYERS; i++)
4060 struct PlayerInfo *player = &stored_player[i];
4062 if (player->connected && !player->present)
4064 struct PlayerInfo *field_player = NULL;
4066 #if DEBUG_INIT_PLAYER
4067 Debug("game:init:player",
4068 "- looking for field player for player %d ...", i + 1);
4071 // assign first free player found that is present in the playfield
4073 // first try: look for unmapped playfield player that is not connected
4074 for (j = 0; j < MAX_PLAYERS; j++)
4075 if (field_player == NULL &&
4076 stored_player[j].present &&
4077 !stored_player[j].mapped &&
4078 !stored_player[j].connected)
4079 field_player = &stored_player[j];
4081 // second try: look for *any* unmapped playfield player
4082 for (j = 0; j < MAX_PLAYERS; j++)
4083 if (field_player == NULL &&
4084 stored_player[j].present &&
4085 !stored_player[j].mapped)
4086 field_player = &stored_player[j];
4088 if (field_player != NULL)
4090 int jx = field_player->jx, jy = field_player->jy;
4092 #if DEBUG_INIT_PLAYER
4093 Debug("game:init:player", "- found player %d",
4094 field_player->index_nr + 1);
4097 player->present = FALSE;
4098 player->active = FALSE;
4100 field_player->present = TRUE;
4101 field_player->active = TRUE;
4104 player->initial_element = field_player->initial_element;
4105 player->artwork_element = field_player->artwork_element;
4107 player->block_last_field = field_player->block_last_field;
4108 player->block_delay_adjustment = field_player->block_delay_adjustment;
4111 StorePlayer[jx][jy] = field_player->element_nr;
4113 field_player->jx = field_player->last_jx = jx;
4114 field_player->jy = field_player->last_jy = jy;
4116 if (local_player == player)
4117 local_player = field_player;
4119 map_player_action[field_player->index_nr] = i;
4121 field_player->mapped = TRUE;
4123 #if DEBUG_INIT_PLAYER
4124 Debug("game:init:player", "- map_player_action[%d] == %d",
4125 field_player->index_nr + 1, i + 1);
4130 if (player->connected && player->present)
4131 player->mapped = TRUE;
4134 #if DEBUG_INIT_PLAYER
4135 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4140 // check if any connected player was not found in playfield
4141 for (i = 0; i < MAX_PLAYERS; i++)
4143 struct PlayerInfo *player = &stored_player[i];
4145 if (player->connected && !player->present)
4147 for (j = 0; j < MAX_PLAYERS; j++)
4149 struct PlayerInfo *field_player = &stored_player[j];
4150 int jx = field_player->jx, jy = field_player->jy;
4152 // assign first free player found that is present in the playfield
4153 if (field_player->present && !field_player->connected)
4155 player->present = TRUE;
4156 player->active = TRUE;
4158 field_player->present = FALSE;
4159 field_player->active = FALSE;
4161 player->initial_element = field_player->initial_element;
4162 player->artwork_element = field_player->artwork_element;
4164 player->block_last_field = field_player->block_last_field;
4165 player->block_delay_adjustment = field_player->block_delay_adjustment;
4167 StorePlayer[jx][jy] = player->element_nr;
4169 player->jx = player->last_jx = jx;
4170 player->jy = player->last_jy = jy;
4180 Debug("game:init:player", "local_player->present == %d",
4181 local_player->present);
4184 // set focus to local player for network games, else to all players
4185 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4186 game.centered_player_nr_next = game.centered_player_nr;
4187 game.set_centered_player = FALSE;
4188 game.set_centered_player_wrap = FALSE;
4190 if (network_playing && tape.recording)
4192 // store client dependent player focus when recording network games
4193 tape.centered_player_nr_next = game.centered_player_nr_next;
4194 tape.set_centered_player = TRUE;
4199 // when playing a tape, eliminate all players who do not participate
4201 #if USE_NEW_PLAYER_ASSIGNMENTS
4203 if (!game.team_mode)
4205 for (i = 0; i < MAX_PLAYERS; i++)
4207 if (stored_player[i].active &&
4208 !tape.player_participates[map_player_action[i]])
4210 struct PlayerInfo *player = &stored_player[i];
4211 int jx = player->jx, jy = player->jy;
4213 #if DEBUG_INIT_PLAYER
4214 Debug("game:init:player", "Removing player %d at (%d, %d)",
4218 player->active = FALSE;
4219 StorePlayer[jx][jy] = 0;
4220 Tile[jx][jy] = EL_EMPTY;
4227 for (i = 0; i < MAX_PLAYERS; i++)
4229 if (stored_player[i].active &&
4230 !tape.player_participates[i])
4232 struct PlayerInfo *player = &stored_player[i];
4233 int jx = player->jx, jy = player->jy;
4235 player->active = FALSE;
4236 StorePlayer[jx][jy] = 0;
4237 Tile[jx][jy] = EL_EMPTY;
4242 else if (!network.enabled && !game.team_mode) // && !tape.playing
4244 // when in single player mode, eliminate all but the local player
4246 for (i = 0; i < MAX_PLAYERS; i++)
4248 struct PlayerInfo *player = &stored_player[i];
4250 if (player->active && player != local_player)
4252 int jx = player->jx, jy = player->jy;
4254 player->active = FALSE;
4255 player->present = FALSE;
4257 StorePlayer[jx][jy] = 0;
4258 Tile[jx][jy] = EL_EMPTY;
4263 for (i = 0; i < MAX_PLAYERS; i++)
4264 if (stored_player[i].active)
4265 game.players_still_needed++;
4267 if (level.solved_by_one_player)
4268 game.players_still_needed = 1;
4270 // when recording the game, store which players take part in the game
4273 #if USE_NEW_PLAYER_ASSIGNMENTS
4274 for (i = 0; i < MAX_PLAYERS; i++)
4275 if (stored_player[i].connected)
4276 tape.player_participates[i] = TRUE;
4278 for (i = 0; i < MAX_PLAYERS; i++)
4279 if (stored_player[i].active)
4280 tape.player_participates[i] = TRUE;
4284 #if DEBUG_INIT_PLAYER
4285 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4288 if (BorderElement == EL_EMPTY)
4291 SBX_Right = lev_fieldx - SCR_FIELDX;
4293 SBY_Lower = lev_fieldy - SCR_FIELDY;
4298 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4300 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4303 if (full_lev_fieldx <= SCR_FIELDX)
4304 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4305 if (full_lev_fieldy <= SCR_FIELDY)
4306 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4308 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4310 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4313 // if local player not found, look for custom element that might create
4314 // the player (make some assumptions about the right custom element)
4315 if (!local_player->present)
4317 int start_x = 0, start_y = 0;
4318 int found_rating = 0;
4319 int found_element = EL_UNDEFINED;
4320 int player_nr = local_player->index_nr;
4322 SCAN_PLAYFIELD(x, y)
4324 int element = Tile[x][y];
4329 if (level.use_start_element[player_nr] &&
4330 level.start_element[player_nr] == element &&
4337 found_element = element;
4340 if (!IS_CUSTOM_ELEMENT(element))
4343 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 single target
4348 content = element_info[element].change_page[i].target_element;
4349 is_player = IS_PLAYER_ELEMENT(content);
4351 if (is_player && (found_rating < 3 ||
4352 (found_rating == 3 && element < found_element)))
4358 found_element = element;
4363 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4365 // check for player created from custom element as explosion content
4366 content = element_info[element].content.e[xx][yy];
4367 is_player = IS_PLAYER_ELEMENT(content);
4369 if (is_player && (found_rating < 2 ||
4370 (found_rating == 2 && element < found_element)))
4372 start_x = x + xx - 1;
4373 start_y = y + yy - 1;
4376 found_element = element;
4379 if (!CAN_CHANGE(element))
4382 for (i = 0; i < element_info[element].num_change_pages; i++)
4384 // check for player created from custom element as extended target
4386 element_info[element].change_page[i].target_content.e[xx][yy];
4388 is_player = IS_PLAYER_ELEMENT(content);
4390 if (is_player && (found_rating < 1 ||
4391 (found_rating == 1 && element < found_element)))
4393 start_x = x + xx - 1;
4394 start_y = y + yy - 1;
4397 found_element = element;
4403 scroll_x = SCROLL_POSITION_X(start_x);
4404 scroll_y = SCROLL_POSITION_Y(start_y);
4408 scroll_x = SCROLL_POSITION_X(local_player->jx);
4409 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4412 // !!! FIX THIS (START) !!!
4413 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4415 InitGameEngine_EM();
4417 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4419 InitGameEngine_SP();
4421 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4423 InitGameEngine_MM();
4427 DrawLevel(REDRAW_FIELD);
4430 // after drawing the level, correct some elements
4431 if (game.timegate_time_left == 0)
4432 CloseAllOpenTimegates();
4435 // blit playfield from scroll buffer to normal back buffer for fading in
4436 BlitScreenToBitmap(backbuffer);
4437 // !!! FIX THIS (END) !!!
4439 DrawMaskedBorder(fade_mask);
4444 // full screen redraw is required at this point in the following cases:
4445 // - special editor door undrawn when game was started from level editor
4446 // - drawing area (playfield) was changed and has to be removed completely
4447 redraw_mask = REDRAW_ALL;
4451 if (!game.restart_level)
4453 // copy default game door content to main double buffer
4455 // !!! CHECK AGAIN !!!
4456 SetPanelBackground();
4457 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4458 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4461 SetPanelBackground();
4462 SetDrawBackgroundMask(REDRAW_DOOR_1);
4464 UpdateAndDisplayGameControlValues();
4466 if (!game.restart_level)
4472 CreateGameButtons();
4477 // copy actual game door content to door double buffer for OpenDoor()
4478 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4480 OpenDoor(DOOR_OPEN_ALL);
4482 KeyboardAutoRepeatOffUnlessAutoplay();
4484 #if DEBUG_INIT_PLAYER
4485 DebugPrintPlayerStatus("Player status (final)");
4494 if (!game.restart_level && !tape.playing)
4496 LevelStats_incPlayed(level_nr);
4498 SaveLevelSetup_SeriesInfo();
4501 game.restart_level = FALSE;
4502 game.restart_game_message = NULL;
4504 game.request_active = FALSE;
4505 game.request_active_or_moving = FALSE;
4507 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4508 InitGameActions_MM();
4510 SaveEngineSnapshotToListInitial();
4512 if (!game.restart_level)
4514 PlaySound(SND_GAME_STARTING);
4516 if (setup.sound_music)
4520 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4523 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4524 int actual_player_x, int actual_player_y)
4526 // this is used for non-R'n'D game engines to update certain engine values
4528 // needed to determine if sounds are played within the visible screen area
4529 scroll_x = actual_scroll_x;
4530 scroll_y = actual_scroll_y;
4532 // needed to get player position for "follow finger" playing input method
4533 local_player->jx = actual_player_x;
4534 local_player->jy = actual_player_y;
4537 void InitMovDir(int x, int y)
4539 int i, element = Tile[x][y];
4540 static int xy[4][2] =
4547 static int direction[3][4] =
4549 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4550 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4551 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4560 Tile[x][y] = EL_BUG;
4561 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4564 case EL_SPACESHIP_RIGHT:
4565 case EL_SPACESHIP_UP:
4566 case EL_SPACESHIP_LEFT:
4567 case EL_SPACESHIP_DOWN:
4568 Tile[x][y] = EL_SPACESHIP;
4569 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4572 case EL_BD_BUTTERFLY_RIGHT:
4573 case EL_BD_BUTTERFLY_UP:
4574 case EL_BD_BUTTERFLY_LEFT:
4575 case EL_BD_BUTTERFLY_DOWN:
4576 Tile[x][y] = EL_BD_BUTTERFLY;
4577 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4580 case EL_BD_FIREFLY_RIGHT:
4581 case EL_BD_FIREFLY_UP:
4582 case EL_BD_FIREFLY_LEFT:
4583 case EL_BD_FIREFLY_DOWN:
4584 Tile[x][y] = EL_BD_FIREFLY;
4585 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4588 case EL_PACMAN_RIGHT:
4590 case EL_PACMAN_LEFT:
4591 case EL_PACMAN_DOWN:
4592 Tile[x][y] = EL_PACMAN;
4593 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4596 case EL_YAMYAM_LEFT:
4597 case EL_YAMYAM_RIGHT:
4599 case EL_YAMYAM_DOWN:
4600 Tile[x][y] = EL_YAMYAM;
4601 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4604 case EL_SP_SNIKSNAK:
4605 MovDir[x][y] = MV_UP;
4608 case EL_SP_ELECTRON:
4609 MovDir[x][y] = MV_LEFT;
4616 Tile[x][y] = EL_MOLE;
4617 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4620 case EL_SPRING_LEFT:
4621 case EL_SPRING_RIGHT:
4622 Tile[x][y] = EL_SPRING;
4623 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4627 if (IS_CUSTOM_ELEMENT(element))
4629 struct ElementInfo *ei = &element_info[element];
4630 int move_direction_initial = ei->move_direction_initial;
4631 int move_pattern = ei->move_pattern;
4633 if (move_direction_initial == MV_START_PREVIOUS)
4635 if (MovDir[x][y] != MV_NONE)
4638 move_direction_initial = MV_START_AUTOMATIC;
4641 if (move_direction_initial == MV_START_RANDOM)
4642 MovDir[x][y] = 1 << RND(4);
4643 else if (move_direction_initial & MV_ANY_DIRECTION)
4644 MovDir[x][y] = move_direction_initial;
4645 else if (move_pattern == MV_ALL_DIRECTIONS ||
4646 move_pattern == MV_TURNING_LEFT ||
4647 move_pattern == MV_TURNING_RIGHT ||
4648 move_pattern == MV_TURNING_LEFT_RIGHT ||
4649 move_pattern == MV_TURNING_RIGHT_LEFT ||
4650 move_pattern == MV_TURNING_RANDOM)
4651 MovDir[x][y] = 1 << RND(4);
4652 else if (move_pattern == MV_HORIZONTAL)
4653 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4654 else if (move_pattern == MV_VERTICAL)
4655 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4656 else if (move_pattern & MV_ANY_DIRECTION)
4657 MovDir[x][y] = element_info[element].move_pattern;
4658 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4659 move_pattern == MV_ALONG_RIGHT_SIDE)
4661 // use random direction as default start direction
4662 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4663 MovDir[x][y] = 1 << RND(4);
4665 for (i = 0; i < NUM_DIRECTIONS; i++)
4667 int x1 = x + xy[i][0];
4668 int y1 = y + xy[i][1];
4670 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4672 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4673 MovDir[x][y] = direction[0][i];
4675 MovDir[x][y] = direction[1][i];
4684 MovDir[x][y] = 1 << RND(4);
4686 if (element != EL_BUG &&
4687 element != EL_SPACESHIP &&
4688 element != EL_BD_BUTTERFLY &&
4689 element != EL_BD_FIREFLY)
4692 for (i = 0; i < NUM_DIRECTIONS; i++)
4694 int x1 = x + xy[i][0];
4695 int y1 = y + xy[i][1];
4697 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4699 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4701 MovDir[x][y] = direction[0][i];
4704 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4705 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4707 MovDir[x][y] = direction[1][i];
4716 GfxDir[x][y] = MovDir[x][y];
4719 void InitAmoebaNr(int x, int y)
4722 int group_nr = AmoebaNeighbourNr(x, y);
4726 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4728 if (AmoebaCnt[i] == 0)
4736 AmoebaNr[x][y] = group_nr;
4737 AmoebaCnt[group_nr]++;
4738 AmoebaCnt2[group_nr]++;
4741 static void LevelSolved_SetFinalGameValues(void)
4743 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4744 game.score_time_final = (level.use_step_counter ? TimePlayed :
4745 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4747 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4748 game_em.lev->score :
4749 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4753 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4754 MM_HEALTH(game_mm.laser_overload_value) :
4757 game.LevelSolved_CountingTime = game.time_final;
4758 game.LevelSolved_CountingScore = game.score_final;
4759 game.LevelSolved_CountingHealth = game.health_final;
4762 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4764 game.LevelSolved_CountingTime = time;
4765 game.LevelSolved_CountingScore = score;
4766 game.LevelSolved_CountingHealth = health;
4768 game_panel_controls[GAME_PANEL_TIME].value = time;
4769 game_panel_controls[GAME_PANEL_SCORE].value = score;
4770 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4772 DisplayGameControlValues();
4775 static void LevelSolved(void)
4777 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4778 game.players_still_needed > 0)
4781 game.LevelSolved = TRUE;
4782 game.GameOver = TRUE;
4786 // needed here to display correct panel values while player walks into exit
4787 LevelSolved_SetFinalGameValues();
4792 static int time_count_steps;
4793 static int time, time_final;
4794 static float score, score_final; // needed for time score < 10 for 10 seconds
4795 static int health, health_final;
4796 static int game_over_delay_1 = 0;
4797 static int game_over_delay_2 = 0;
4798 static int game_over_delay_3 = 0;
4799 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4800 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4802 if (!game.LevelSolved_GameWon)
4806 // do not start end game actions before the player stops moving (to exit)
4807 if (local_player->active && local_player->MovPos)
4810 // calculate final game values after player finished walking into exit
4811 LevelSolved_SetFinalGameValues();
4813 game.LevelSolved_GameWon = TRUE;
4814 game.LevelSolved_SaveTape = tape.recording;
4815 game.LevelSolved_SaveScore = !tape.playing;
4819 LevelStats_incSolved(level_nr);
4821 SaveLevelSetup_SeriesInfo();
4824 if (tape.auto_play) // tape might already be stopped here
4825 tape.auto_play_level_solved = TRUE;
4829 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4830 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4831 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4833 time = time_final = game.time_final;
4834 score = score_final = game.score_final;
4835 health = health_final = game.health_final;
4837 // update game panel values before (delayed) counting of score (if any)
4838 LevelSolved_DisplayFinalGameValues(time, score, health);
4840 // if level has time score defined, calculate new final game values
4843 int time_final_max = 999;
4844 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4845 int time_frames = 0;
4846 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4847 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4852 time_frames = time_frames_left;
4854 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4856 time_final = time_final_max;
4857 time_frames = time_frames_final_max - time_frames_played;
4860 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4862 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4864 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4867 score_final += health * time_score;
4870 game.score_final = score_final;
4871 game.health_final = health_final;
4874 // if not counting score after game, immediately update game panel values
4875 if (level_editor_test_game || !setup.count_score_after_game)
4878 score = score_final;
4880 LevelSolved_DisplayFinalGameValues(time, score, health);
4883 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4885 // check if last player has left the level
4886 if (game.exit_x >= 0 &&
4889 int x = game.exit_x;
4890 int y = game.exit_y;
4891 int element = Tile[x][y];
4893 // close exit door after last player
4894 if ((game.all_players_gone &&
4895 (element == EL_EXIT_OPEN ||
4896 element == EL_SP_EXIT_OPEN ||
4897 element == EL_STEEL_EXIT_OPEN)) ||
4898 element == EL_EM_EXIT_OPEN ||
4899 element == EL_EM_STEEL_EXIT_OPEN)
4903 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4904 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4905 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4906 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4907 EL_EM_STEEL_EXIT_CLOSING);
4909 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4912 // player disappears
4913 DrawLevelField(x, y);
4916 for (i = 0; i < MAX_PLAYERS; i++)
4918 struct PlayerInfo *player = &stored_player[i];
4920 if (player->present)
4922 RemovePlayer(player);
4924 // player disappears
4925 DrawLevelField(player->jx, player->jy);
4930 PlaySound(SND_GAME_WINNING);
4933 if (setup.count_score_after_game)
4935 if (time != time_final)
4937 if (game_over_delay_1 > 0)
4939 game_over_delay_1--;
4944 int time_to_go = ABS(time_final - time);
4945 int time_count_dir = (time < time_final ? +1 : -1);
4947 if (time_to_go < time_count_steps)
4948 time_count_steps = 1;
4950 time += time_count_steps * time_count_dir;
4951 score += time_count_steps * time_score;
4953 // set final score to correct rounding differences after counting score
4954 if (time == time_final)
4955 score = score_final;
4957 LevelSolved_DisplayFinalGameValues(time, score, health);
4959 if (time == time_final)
4960 StopSound(SND_GAME_LEVELTIME_BONUS);
4961 else if (setup.sound_loops)
4962 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4964 PlaySound(SND_GAME_LEVELTIME_BONUS);
4969 if (health != health_final)
4971 if (game_over_delay_2 > 0)
4973 game_over_delay_2--;
4978 int health_count_dir = (health < health_final ? +1 : -1);
4980 health += health_count_dir;
4981 score += time_score;
4983 LevelSolved_DisplayFinalGameValues(time, score, health);
4985 if (health == health_final)
4986 StopSound(SND_GAME_LEVELTIME_BONUS);
4987 else if (setup.sound_loops)
4988 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4990 PlaySound(SND_GAME_LEVELTIME_BONUS);
4996 game.panel.active = FALSE;
4998 if (game_over_delay_3 > 0)
5000 game_over_delay_3--;
5010 // used instead of "level_nr" (needed for network games)
5011 int last_level_nr = levelset.level_nr;
5012 boolean tape_saved = FALSE;
5014 game.LevelSolved_GameEnd = TRUE;
5016 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5018 // make sure that request dialog to save tape does not open door again
5019 if (!global.use_envelope_request)
5020 CloseDoor(DOOR_CLOSE_1);
5023 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5025 // set unique basename for score tape (also saved in high score table)
5026 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5029 // if no tape is to be saved, close both doors simultaneously
5030 CloseDoor(DOOR_CLOSE_ALL);
5032 if (level_editor_test_game || score_info_tape_play)
5034 SetGameStatus(GAME_MODE_MAIN);
5041 if (!game.LevelSolved_SaveScore)
5043 SetGameStatus(GAME_MODE_MAIN);
5050 if (level_nr == leveldir_current->handicap_level)
5052 leveldir_current->handicap_level++;
5054 SaveLevelSetup_SeriesInfo();
5057 // save score and score tape before potentially erasing tape below
5058 NewHighScore(last_level_nr, tape_saved);
5060 if (setup.increment_levels &&
5061 level_nr < leveldir_current->last_level &&
5064 level_nr++; // advance to next level
5065 TapeErase(); // start with empty tape
5067 if (setup.auto_play_next_level)
5069 scores.continue_playing = TRUE;
5070 scores.next_level_nr = level_nr;
5072 LoadLevel(level_nr);
5074 SaveLevelSetup_SeriesInfo();
5078 if (scores.last_added >= 0 && setup.show_scores_after_game)
5080 SetGameStatus(GAME_MODE_SCORES);
5082 DrawHallOfFame(last_level_nr);
5084 else if (scores.continue_playing)
5086 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5090 SetGameStatus(GAME_MODE_MAIN);
5096 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5097 boolean one_score_entry_per_name)
5101 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5104 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5106 struct ScoreEntry *entry = &list->entry[i];
5107 boolean score_is_better = (new_entry->score > entry->score);
5108 boolean score_is_equal = (new_entry->score == entry->score);
5109 boolean time_is_better = (new_entry->time < entry->time);
5110 boolean time_is_equal = (new_entry->time == entry->time);
5111 boolean better_by_score = (score_is_better ||
5112 (score_is_equal && time_is_better));
5113 boolean better_by_time = (time_is_better ||
5114 (time_is_equal && score_is_better));
5115 boolean is_better = (level.rate_time_over_score ? better_by_time :
5117 boolean entry_is_empty = (entry->score == 0 &&
5120 // prevent adding server score entries if also existing in local score file
5121 // (special case: historic score entries have an empty tape basename entry)
5122 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5123 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5125 // add fields from server score entry not stored in local score entry
5126 // (currently, this means setting platform, version and country fields;
5127 // in rare cases, this may also correct an invalid score value, as
5128 // historic scores might have been truncated to 16-bit values locally)
5129 *entry = *new_entry;
5134 if (is_better || entry_is_empty)
5136 // player has made it to the hall of fame
5138 if (i < MAX_SCORE_ENTRIES - 1)
5140 int m = MAX_SCORE_ENTRIES - 1;
5143 if (one_score_entry_per_name)
5145 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5146 if (strEqual(list->entry[l].name, new_entry->name))
5149 if (m == i) // player's new highscore overwrites his old one
5153 for (l = m; l > i; l--)
5154 list->entry[l] = list->entry[l - 1];
5159 *entry = *new_entry;
5163 else if (one_score_entry_per_name &&
5164 strEqual(entry->name, new_entry->name))
5166 // player already in high score list with better score or time
5172 // special case: new score is beyond the last high score list position
5173 return MAX_SCORE_ENTRIES;
5176 void NewHighScore(int level_nr, boolean tape_saved)
5178 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5179 boolean one_per_name = FALSE;
5181 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5182 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5184 new_entry.score = game.score_final;
5185 new_entry.time = game.score_time_final;
5187 LoadScore(level_nr);
5189 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5191 if (scores.last_added >= MAX_SCORE_ENTRIES)
5193 scores.last_added = MAX_SCORE_ENTRIES - 1;
5194 scores.force_last_added = TRUE;
5196 scores.entry[scores.last_added] = new_entry;
5198 // store last added local score entry (before merging server scores)
5199 scores.last_added_local = scores.last_added;
5204 if (scores.last_added < 0)
5207 SaveScore(level_nr);
5209 // store last added local score entry (before merging server scores)
5210 scores.last_added_local = scores.last_added;
5212 if (!game.LevelSolved_SaveTape)
5215 SaveScoreTape(level_nr);
5217 if (setup.ask_for_using_api_server)
5219 setup.use_api_server =
5220 Request("Upload your score and tape to the high score server?", REQ_ASK);
5222 if (!setup.use_api_server)
5223 Request("Not using high score server! Use setup menu to enable again!",
5226 runtime.use_api_server = setup.use_api_server;
5228 // after asking for using API server once, do not ask again
5229 setup.ask_for_using_api_server = FALSE;
5231 SaveSetup_ServerSetup();
5234 SaveServerScore(level_nr, tape_saved);
5237 void MergeServerScore(void)
5239 struct ScoreEntry last_added_entry;
5240 boolean one_per_name = FALSE;
5243 if (scores.last_added >= 0)
5244 last_added_entry = scores.entry[scores.last_added];
5246 for (i = 0; i < server_scores.num_entries; i++)
5248 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5250 if (pos >= 0 && pos <= scores.last_added)
5251 scores.last_added++;
5254 if (scores.last_added >= MAX_SCORE_ENTRIES)
5256 scores.last_added = MAX_SCORE_ENTRIES - 1;
5257 scores.force_last_added = TRUE;
5259 scores.entry[scores.last_added] = last_added_entry;
5263 static int getElementMoveStepsizeExt(int x, int y, int direction)
5265 int element = Tile[x][y];
5266 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5267 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5268 int horiz_move = (dx != 0);
5269 int sign = (horiz_move ? dx : dy);
5270 int step = sign * element_info[element].move_stepsize;
5272 // special values for move stepsize for spring and things on conveyor belt
5275 if (CAN_FALL(element) &&
5276 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5277 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5278 else if (element == EL_SPRING)
5279 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5285 static int getElementMoveStepsize(int x, int y)
5287 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5290 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5292 if (player->GfxAction != action || player->GfxDir != dir)
5294 player->GfxAction = action;
5295 player->GfxDir = dir;
5297 player->StepFrame = 0;
5301 static void ResetGfxFrame(int x, int y)
5303 // profiling showed that "autotest" spends 10~20% of its time in this function
5304 if (DrawingDeactivatedField())
5307 int element = Tile[x][y];
5308 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5310 if (graphic_info[graphic].anim_global_sync)
5311 GfxFrame[x][y] = FrameCounter;
5312 else if (graphic_info[graphic].anim_global_anim_sync)
5313 GfxFrame[x][y] = getGlobalAnimSyncFrame();
5314 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5315 GfxFrame[x][y] = CustomValue[x][y];
5316 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5317 GfxFrame[x][y] = element_info[element].collect_score;
5318 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5319 GfxFrame[x][y] = ChangeDelay[x][y];
5322 static void ResetGfxAnimation(int x, int y)
5324 GfxAction[x][y] = ACTION_DEFAULT;
5325 GfxDir[x][y] = MovDir[x][y];
5328 ResetGfxFrame(x, y);
5331 static void ResetRandomAnimationValue(int x, int y)
5333 GfxRandom[x][y] = INIT_GFX_RANDOM();
5336 static void InitMovingField(int x, int y, int direction)
5338 int element = Tile[x][y];
5339 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5340 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5343 boolean is_moving_before, is_moving_after;
5345 // check if element was/is moving or being moved before/after mode change
5346 is_moving_before = (WasJustMoving[x][y] != 0);
5347 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5349 // reset animation only for moving elements which change direction of moving
5350 // or which just started or stopped moving
5351 // (else CEs with property "can move" / "not moving" are reset each frame)
5352 if (is_moving_before != is_moving_after ||
5353 direction != MovDir[x][y])
5354 ResetGfxAnimation(x, y);
5356 MovDir[x][y] = direction;
5357 GfxDir[x][y] = direction;
5359 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5360 direction == MV_DOWN && CAN_FALL(element) ?
5361 ACTION_FALLING : ACTION_MOVING);
5363 // this is needed for CEs with property "can move" / "not moving"
5365 if (is_moving_after)
5367 if (Tile[newx][newy] == EL_EMPTY)
5368 Tile[newx][newy] = EL_BLOCKED;
5370 MovDir[newx][newy] = MovDir[x][y];
5372 CustomValue[newx][newy] = CustomValue[x][y];
5374 GfxFrame[newx][newy] = GfxFrame[x][y];
5375 GfxRandom[newx][newy] = GfxRandom[x][y];
5376 GfxAction[newx][newy] = GfxAction[x][y];
5377 GfxDir[newx][newy] = GfxDir[x][y];
5381 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5383 int direction = MovDir[x][y];
5384 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5385 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5391 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5393 int oldx = x, oldy = y;
5394 int direction = MovDir[x][y];
5396 if (direction == MV_LEFT)
5398 else if (direction == MV_RIGHT)
5400 else if (direction == MV_UP)
5402 else if (direction == MV_DOWN)
5405 *comes_from_x = oldx;
5406 *comes_from_y = oldy;
5409 static int MovingOrBlocked2Element(int x, int y)
5411 int element = Tile[x][y];
5413 if (element == EL_BLOCKED)
5417 Blocked2Moving(x, y, &oldx, &oldy);
5418 return Tile[oldx][oldy];
5424 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5426 // like MovingOrBlocked2Element(), but if element is moving
5427 // and (x,y) is the field the moving element is just leaving,
5428 // return EL_BLOCKED instead of the element value
5429 int element = Tile[x][y];
5431 if (IS_MOVING(x, y))
5433 if (element == EL_BLOCKED)
5437 Blocked2Moving(x, y, &oldx, &oldy);
5438 return Tile[oldx][oldy];
5447 static void RemoveField(int x, int y)
5449 Tile[x][y] = EL_EMPTY;
5455 CustomValue[x][y] = 0;
5458 ChangeDelay[x][y] = 0;
5459 ChangePage[x][y] = -1;
5460 Pushed[x][y] = FALSE;
5462 GfxElement[x][y] = EL_UNDEFINED;
5463 GfxAction[x][y] = ACTION_DEFAULT;
5464 GfxDir[x][y] = MV_NONE;
5467 static void RemoveMovingField(int x, int y)
5469 int oldx = x, oldy = y, newx = x, newy = y;
5470 int element = Tile[x][y];
5471 int next_element = EL_UNDEFINED;
5473 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5476 if (IS_MOVING(x, y))
5478 Moving2Blocked(x, y, &newx, &newy);
5480 if (Tile[newx][newy] != EL_BLOCKED)
5482 // element is moving, but target field is not free (blocked), but
5483 // already occupied by something different (example: acid pool);
5484 // in this case, only remove the moving field, but not the target
5486 RemoveField(oldx, oldy);
5488 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5490 TEST_DrawLevelField(oldx, oldy);
5495 else if (element == EL_BLOCKED)
5497 Blocked2Moving(x, y, &oldx, &oldy);
5498 if (!IS_MOVING(oldx, oldy))
5502 if (element == EL_BLOCKED &&
5503 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5504 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5505 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5506 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5507 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5508 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5509 next_element = get_next_element(Tile[oldx][oldy]);
5511 RemoveField(oldx, oldy);
5512 RemoveField(newx, newy);
5514 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5516 if (next_element != EL_UNDEFINED)
5517 Tile[oldx][oldy] = next_element;
5519 TEST_DrawLevelField(oldx, oldy);
5520 TEST_DrawLevelField(newx, newy);
5523 void DrawDynamite(int x, int y)
5525 int sx = SCREENX(x), sy = SCREENY(y);
5526 int graphic = el2img(Tile[x][y]);
5529 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5532 if (IS_WALKABLE_INSIDE(Back[x][y]))
5536 DrawLevelElement(x, y, Back[x][y]);
5537 else if (Store[x][y])
5538 DrawLevelElement(x, y, Store[x][y]);
5539 else if (game.use_masked_elements)
5540 DrawLevelElement(x, y, EL_EMPTY);
5542 frame = getGraphicAnimationFrameXY(graphic, x, y);
5544 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5545 DrawGraphicThruMask(sx, sy, graphic, frame);
5547 DrawGraphic(sx, sy, graphic, frame);
5550 static void CheckDynamite(int x, int y)
5552 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5556 if (MovDelay[x][y] != 0)
5559 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5565 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5570 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5572 boolean num_checked_players = 0;
5575 for (i = 0; i < MAX_PLAYERS; i++)
5577 if (stored_player[i].active)
5579 int sx = stored_player[i].jx;
5580 int sy = stored_player[i].jy;
5582 if (num_checked_players == 0)
5589 *sx1 = MIN(*sx1, sx);
5590 *sy1 = MIN(*sy1, sy);
5591 *sx2 = MAX(*sx2, sx);
5592 *sy2 = MAX(*sy2, sy);
5595 num_checked_players++;
5600 static boolean checkIfAllPlayersFitToScreen_RND(void)
5602 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5604 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5606 return (sx2 - sx1 < SCR_FIELDX &&
5607 sy2 - sy1 < SCR_FIELDY);
5610 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5612 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5614 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5616 *sx = (sx1 + sx2) / 2;
5617 *sy = (sy1 + sy2) / 2;
5620 static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
5621 boolean center_screen, boolean quick_relocation)
5623 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5624 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5625 boolean no_delay = (tape.warp_forward);
5626 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5627 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5628 int new_scroll_x, new_scroll_y;
5630 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5632 // case 1: quick relocation inside visible screen (without scrolling)
5639 if (!level.shifted_relocation || center_screen)
5641 // relocation _with_ centering of screen
5643 new_scroll_x = SCROLL_POSITION_X(x);
5644 new_scroll_y = SCROLL_POSITION_Y(y);
5648 // relocation _without_ centering of screen
5650 int center_scroll_x = SCROLL_POSITION_X(old_x);
5651 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5652 int offset_x = x + (scroll_x - center_scroll_x);
5653 int offset_y = y + (scroll_y - center_scroll_y);
5655 // for new screen position, apply previous offset to center position
5656 new_scroll_x = SCROLL_POSITION_X(offset_x);
5657 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5660 if (quick_relocation)
5662 // case 2: quick relocation (redraw without visible scrolling)
5664 scroll_x = new_scroll_x;
5665 scroll_y = new_scroll_y;
5672 // case 3: visible relocation (with scrolling to new position)
5674 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5676 SetVideoFrameDelay(wait_delay_value);
5678 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5680 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5681 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5683 if (dx == 0 && dy == 0) // no scrolling needed at all
5689 // set values for horizontal/vertical screen scrolling (half tile size)
5690 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5691 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5692 int pos_x = dx * TILEX / 2;
5693 int pos_y = dy * TILEY / 2;
5694 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5695 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5697 ScrollLevel(dx, dy);
5700 // scroll in two steps of half tile size to make things smoother
5701 BlitScreenToBitmapExt_RND(window, fx, fy);
5703 // scroll second step to align at full tile size
5704 BlitScreenToBitmap(window);
5710 SetVideoFrameDelay(frame_delay_value_old);
5713 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5715 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5716 int player_nr = GET_PLAYER_NR(el_player);
5717 struct PlayerInfo *player = &stored_player[player_nr];
5718 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5719 boolean no_delay = (tape.warp_forward);
5720 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5721 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5722 int old_jx = player->jx;
5723 int old_jy = player->jy;
5724 int old_element = Tile[old_jx][old_jy];
5725 int element = Tile[jx][jy];
5726 boolean player_relocated = (old_jx != jx || old_jy != jy);
5728 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5729 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5730 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5731 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5732 int leave_side_horiz = move_dir_horiz;
5733 int leave_side_vert = move_dir_vert;
5734 int enter_side = enter_side_horiz | enter_side_vert;
5735 int leave_side = leave_side_horiz | leave_side_vert;
5737 if (player->buried) // do not reanimate dead player
5740 if (!player_relocated) // no need to relocate the player
5743 if (IS_PLAYER(jx, jy)) // player already placed at new position
5745 RemoveField(jx, jy); // temporarily remove newly placed player
5746 DrawLevelField(jx, jy);
5749 if (player->present)
5751 while (player->MovPos)
5753 ScrollPlayer(player, SCROLL_GO_ON);
5754 ScrollScreen(NULL, SCROLL_GO_ON);
5756 AdvanceFrameAndPlayerCounters(player->index_nr);
5760 BackToFront_WithFrameDelay(wait_delay_value);
5763 DrawPlayer(player); // needed here only to cleanup last field
5764 DrawLevelField(player->jx, player->jy); // remove player graphic
5766 player->is_moving = FALSE;
5769 if (IS_CUSTOM_ELEMENT(old_element))
5770 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5772 player->index_bit, leave_side);
5774 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5776 player->index_bit, leave_side);
5778 Tile[jx][jy] = el_player;
5779 InitPlayerField(jx, jy, el_player, TRUE);
5781 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5782 possible that the relocation target field did not contain a player element,
5783 but a walkable element, to which the new player was relocated -- in this
5784 case, restore that (already initialized!) element on the player field */
5785 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5787 Tile[jx][jy] = element; // restore previously existing element
5790 // only visually relocate centered player
5791 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy,
5792 FALSE, level.instant_relocation);
5794 TestIfPlayerTouchesBadThing(jx, jy);
5795 TestIfPlayerTouchesCustomElement(jx, jy);
5797 if (IS_CUSTOM_ELEMENT(element))
5798 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5799 player->index_bit, enter_side);
5801 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5802 player->index_bit, enter_side);
5804 if (player->is_switching)
5806 /* ensure that relocation while still switching an element does not cause
5807 a new element to be treated as also switched directly after relocation
5808 (this is important for teleporter switches that teleport the player to
5809 a place where another teleporter switch is in the same direction, which
5810 would then incorrectly be treated as immediately switched before the
5811 direction key that caused the switch was released) */
5813 player->switch_x += jx - old_jx;
5814 player->switch_y += jy - old_jy;
5818 static void Explode(int ex, int ey, int phase, int mode)
5824 // !!! eliminate this variable !!!
5825 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5827 if (game.explosions_delayed)
5829 ExplodeField[ex][ey] = mode;
5833 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5835 int center_element = Tile[ex][ey];
5836 int artwork_element, explosion_element; // set these values later
5838 // remove things displayed in background while burning dynamite
5839 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5842 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5844 // put moving element to center field (and let it explode there)
5845 center_element = MovingOrBlocked2Element(ex, ey);
5846 RemoveMovingField(ex, ey);
5847 Tile[ex][ey] = center_element;
5850 // now "center_element" is finally determined -- set related values now
5851 artwork_element = center_element; // for custom player artwork
5852 explosion_element = center_element; // for custom player artwork
5854 if (IS_PLAYER(ex, ey))
5856 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5858 artwork_element = stored_player[player_nr].artwork_element;
5860 if (level.use_explosion_element[player_nr])
5862 explosion_element = level.explosion_element[player_nr];
5863 artwork_element = explosion_element;
5867 if (mode == EX_TYPE_NORMAL ||
5868 mode == EX_TYPE_CENTER ||
5869 mode == EX_TYPE_CROSS)
5870 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5872 last_phase = element_info[explosion_element].explosion_delay + 1;
5874 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5876 int xx = x - ex + 1;
5877 int yy = y - ey + 1;
5880 if (!IN_LEV_FIELD(x, y) ||
5881 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5882 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5885 element = Tile[x][y];
5887 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5889 element = MovingOrBlocked2Element(x, y);
5891 if (!IS_EXPLOSION_PROOF(element))
5892 RemoveMovingField(x, y);
5895 // indestructible elements can only explode in center (but not flames)
5896 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5897 mode == EX_TYPE_BORDER)) ||
5898 element == EL_FLAMES)
5901 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5902 behaviour, for example when touching a yamyam that explodes to rocks
5903 with active deadly shield, a rock is created under the player !!! */
5904 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5906 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5907 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5908 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5910 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5913 if (IS_ACTIVE_BOMB(element))
5915 // re-activate things under the bomb like gate or penguin
5916 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5923 // save walkable background elements while explosion on same tile
5924 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5925 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5926 Back[x][y] = element;
5928 // ignite explodable elements reached by other explosion
5929 if (element == EL_EXPLOSION)
5930 element = Store2[x][y];
5932 if (AmoebaNr[x][y] &&
5933 (element == EL_AMOEBA_FULL ||
5934 element == EL_BD_AMOEBA ||
5935 element == EL_AMOEBA_GROWING))
5937 AmoebaCnt[AmoebaNr[x][y]]--;
5938 AmoebaCnt2[AmoebaNr[x][y]]--;
5943 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5945 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5947 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5949 if (PLAYERINFO(ex, ey)->use_murphy)
5950 Store[x][y] = EL_EMPTY;
5953 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5954 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5955 else if (IS_PLAYER_ELEMENT(center_element))
5956 Store[x][y] = EL_EMPTY;
5957 else if (center_element == EL_YAMYAM)
5958 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5959 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5960 Store[x][y] = element_info[center_element].content.e[xx][yy];
5962 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5963 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5964 // otherwise) -- FIX THIS !!!
5965 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5966 Store[x][y] = element_info[element].content.e[1][1];
5968 else if (!CAN_EXPLODE(element))
5969 Store[x][y] = element_info[element].content.e[1][1];
5972 Store[x][y] = EL_EMPTY;
5974 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5975 center_element == EL_AMOEBA_TO_DIAMOND)
5976 Store2[x][y] = element;
5978 Tile[x][y] = EL_EXPLOSION;
5979 GfxElement[x][y] = artwork_element;
5981 ExplodePhase[x][y] = 1;
5982 ExplodeDelay[x][y] = last_phase;
5987 if (center_element == EL_YAMYAM)
5988 game.yamyam_content_nr =
5989 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
6001 GfxFrame[x][y] = 0; // restart explosion animation
6003 last_phase = ExplodeDelay[x][y];
6005 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
6007 // this can happen if the player leaves an explosion just in time
6008 if (GfxElement[x][y] == EL_UNDEFINED)
6009 GfxElement[x][y] = EL_EMPTY;
6011 border_element = Store2[x][y];
6012 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6013 border_element = StorePlayer[x][y];
6015 if (phase == element_info[border_element].ignition_delay ||
6016 phase == last_phase)
6018 boolean border_explosion = FALSE;
6020 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6021 !PLAYER_EXPLOSION_PROTECTED(x, y))
6023 KillPlayerUnlessExplosionProtected(x, y);
6024 border_explosion = TRUE;
6026 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6028 Tile[x][y] = Store2[x][y];
6031 border_explosion = TRUE;
6033 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6035 AmoebaToDiamond(x, y);
6037 border_explosion = TRUE;
6040 // if an element just explodes due to another explosion (chain-reaction),
6041 // do not immediately end the new explosion when it was the last frame of
6042 // the explosion (as it would be done in the following "if"-statement!)
6043 if (border_explosion && phase == last_phase)
6047 // this can happen if the player was just killed by an explosion
6048 if (GfxElement[x][y] == EL_UNDEFINED)
6049 GfxElement[x][y] = EL_EMPTY;
6051 if (phase == last_phase)
6055 element = Tile[x][y] = Store[x][y];
6056 Store[x][y] = Store2[x][y] = 0;
6057 GfxElement[x][y] = EL_UNDEFINED;
6059 // player can escape from explosions and might therefore be still alive
6060 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6061 element <= EL_PLAYER_IS_EXPLODING_4)
6063 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6064 int explosion_element = EL_PLAYER_1 + player_nr;
6065 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6066 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6068 if (level.use_explosion_element[player_nr])
6069 explosion_element = level.explosion_element[player_nr];
6071 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6072 element_info[explosion_element].content.e[xx][yy]);
6075 // restore probably existing indestructible background element
6076 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6077 element = Tile[x][y] = Back[x][y];
6080 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6081 GfxDir[x][y] = MV_NONE;
6082 ChangeDelay[x][y] = 0;
6083 ChangePage[x][y] = -1;
6085 CustomValue[x][y] = 0;
6087 InitField_WithBug2(x, y, FALSE);
6089 TEST_DrawLevelField(x, y);
6091 TestIfElementTouchesCustomElement(x, y);
6093 if (GFX_CRUMBLED(element))
6094 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6096 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6097 StorePlayer[x][y] = 0;
6099 if (IS_PLAYER_ELEMENT(element))
6100 RelocatePlayer(x, y, element);
6102 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6104 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6105 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6108 TEST_DrawLevelFieldCrumbled(x, y);
6110 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6112 DrawLevelElement(x, y, Back[x][y]);
6113 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6115 else if (IS_WALKABLE_UNDER(Back[x][y]))
6117 DrawLevelGraphic(x, y, graphic, frame);
6118 DrawLevelElementThruMask(x, y, Back[x][y]);
6120 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6121 DrawLevelGraphic(x, y, graphic, frame);
6125 static void DynaExplode(int ex, int ey)
6128 int dynabomb_element = Tile[ex][ey];
6129 int dynabomb_size = 1;
6130 boolean dynabomb_xl = FALSE;
6131 struct PlayerInfo *player;
6132 struct XY *xy = xy_topdown;
6134 if (IS_ACTIVE_BOMB(dynabomb_element))
6136 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6137 dynabomb_size = player->dynabomb_size;
6138 dynabomb_xl = player->dynabomb_xl;
6139 player->dynabombs_left++;
6142 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6144 for (i = 0; i < NUM_DIRECTIONS; i++)
6146 for (j = 1; j <= dynabomb_size; j++)
6148 int x = ex + j * xy[i].x;
6149 int y = ey + j * xy[i].y;
6152 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6155 element = Tile[x][y];
6157 // do not restart explosions of fields with active bombs
6158 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6161 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6163 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6164 !IS_DIGGABLE(element) && !dynabomb_xl)
6170 void Bang(int x, int y)
6172 int element = MovingOrBlocked2Element(x, y);
6173 int explosion_type = EX_TYPE_NORMAL;
6175 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6177 struct PlayerInfo *player = PLAYERINFO(x, y);
6179 element = Tile[x][y] = player->initial_element;
6181 if (level.use_explosion_element[player->index_nr])
6183 int explosion_element = level.explosion_element[player->index_nr];
6185 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6186 explosion_type = EX_TYPE_CROSS;
6187 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6188 explosion_type = EX_TYPE_CENTER;
6196 case EL_BD_BUTTERFLY:
6199 case EL_DARK_YAMYAM:
6203 RaiseScoreElement(element);
6206 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6207 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6208 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6209 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6210 case EL_DYNABOMB_INCREASE_NUMBER:
6211 case EL_DYNABOMB_INCREASE_SIZE:
6212 case EL_DYNABOMB_INCREASE_POWER:
6213 explosion_type = EX_TYPE_DYNA;
6216 case EL_DC_LANDMINE:
6217 explosion_type = EX_TYPE_CENTER;
6222 case EL_LAMP_ACTIVE:
6223 case EL_AMOEBA_TO_DIAMOND:
6224 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6225 explosion_type = EX_TYPE_CENTER;
6229 if (element_info[element].explosion_type == EXPLODES_CROSS)
6230 explosion_type = EX_TYPE_CROSS;
6231 else if (element_info[element].explosion_type == EXPLODES_1X1)
6232 explosion_type = EX_TYPE_CENTER;
6236 if (explosion_type == EX_TYPE_DYNA)
6239 Explode(x, y, EX_PHASE_START, explosion_type);
6241 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6244 static void SplashAcid(int x, int y)
6246 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6247 (!IN_LEV_FIELD(x - 1, y - 2) ||
6248 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6249 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6251 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6252 (!IN_LEV_FIELD(x + 1, y - 2) ||
6253 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6254 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6256 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6259 static void InitBeltMovement(void)
6261 static int belt_base_element[4] =
6263 EL_CONVEYOR_BELT_1_LEFT,
6264 EL_CONVEYOR_BELT_2_LEFT,
6265 EL_CONVEYOR_BELT_3_LEFT,
6266 EL_CONVEYOR_BELT_4_LEFT
6268 static int belt_base_active_element[4] =
6270 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6271 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6272 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6273 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6278 // set frame order for belt animation graphic according to belt direction
6279 for (i = 0; i < NUM_BELTS; i++)
6283 for (j = 0; j < NUM_BELT_PARTS; j++)
6285 int element = belt_base_active_element[belt_nr] + j;
6286 int graphic_1 = el2img(element);
6287 int graphic_2 = el2panelimg(element);
6289 if (game.belt_dir[i] == MV_LEFT)
6291 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6292 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6296 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6297 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6302 SCAN_PLAYFIELD(x, y)
6304 int element = Tile[x][y];
6306 for (i = 0; i < NUM_BELTS; i++)
6308 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6310 int e_belt_nr = getBeltNrFromBeltElement(element);
6313 if (e_belt_nr == belt_nr)
6315 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6317 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6324 static void ToggleBeltSwitch(int x, int y)
6326 static int belt_base_element[4] =
6328 EL_CONVEYOR_BELT_1_LEFT,
6329 EL_CONVEYOR_BELT_2_LEFT,
6330 EL_CONVEYOR_BELT_3_LEFT,
6331 EL_CONVEYOR_BELT_4_LEFT
6333 static int belt_base_active_element[4] =
6335 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6336 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6337 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6338 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6340 static int belt_base_switch_element[4] =
6342 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6343 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6344 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6345 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6347 static int belt_move_dir[4] =
6355 int element = Tile[x][y];
6356 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6357 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6358 int belt_dir = belt_move_dir[belt_dir_nr];
6361 if (!IS_BELT_SWITCH(element))
6364 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6365 game.belt_dir[belt_nr] = belt_dir;
6367 if (belt_dir_nr == 3)
6370 // set frame order for belt animation graphic according to belt direction
6371 for (i = 0; i < NUM_BELT_PARTS; i++)
6373 int element = belt_base_active_element[belt_nr] + i;
6374 int graphic_1 = el2img(element);
6375 int graphic_2 = el2panelimg(element);
6377 if (belt_dir == MV_LEFT)
6379 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6380 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6384 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6385 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6389 SCAN_PLAYFIELD(xx, yy)
6391 int element = Tile[xx][yy];
6393 if (IS_BELT_SWITCH(element))
6395 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6397 if (e_belt_nr == belt_nr)
6399 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6400 TEST_DrawLevelField(xx, yy);
6403 else if (IS_BELT(element) && belt_dir != MV_NONE)
6405 int e_belt_nr = getBeltNrFromBeltElement(element);
6407 if (e_belt_nr == belt_nr)
6409 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6411 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6412 TEST_DrawLevelField(xx, yy);
6415 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6417 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6419 if (e_belt_nr == belt_nr)
6421 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6423 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6424 TEST_DrawLevelField(xx, yy);
6430 static void ToggleSwitchgateSwitch(void)
6434 game.switchgate_pos = !game.switchgate_pos;
6436 SCAN_PLAYFIELD(xx, yy)
6438 int element = Tile[xx][yy];
6440 if (element == EL_SWITCHGATE_SWITCH_UP)
6442 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6443 TEST_DrawLevelField(xx, yy);
6445 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6447 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6448 TEST_DrawLevelField(xx, yy);
6450 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6452 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6453 TEST_DrawLevelField(xx, yy);
6455 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6457 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6458 TEST_DrawLevelField(xx, yy);
6460 else if (element == EL_SWITCHGATE_OPEN ||
6461 element == EL_SWITCHGATE_OPENING)
6463 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6465 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6467 else if (element == EL_SWITCHGATE_CLOSED ||
6468 element == EL_SWITCHGATE_CLOSING)
6470 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6472 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6477 static int getInvisibleActiveFromInvisibleElement(int element)
6479 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6480 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6481 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6485 static int getInvisibleFromInvisibleActiveElement(int element)
6487 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6488 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6489 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6493 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6497 SCAN_PLAYFIELD(x, y)
6499 int element = Tile[x][y];
6501 if (element == EL_LIGHT_SWITCH &&
6502 game.light_time_left > 0)
6504 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6505 TEST_DrawLevelField(x, y);
6507 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6508 game.light_time_left == 0)
6510 Tile[x][y] = EL_LIGHT_SWITCH;
6511 TEST_DrawLevelField(x, y);
6513 else if (element == EL_EMC_DRIPPER &&
6514 game.light_time_left > 0)
6516 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6517 TEST_DrawLevelField(x, y);
6519 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6520 game.light_time_left == 0)
6522 Tile[x][y] = EL_EMC_DRIPPER;
6523 TEST_DrawLevelField(x, y);
6525 else if (element == EL_INVISIBLE_STEELWALL ||
6526 element == EL_INVISIBLE_WALL ||
6527 element == EL_INVISIBLE_SAND)
6529 if (game.light_time_left > 0)
6530 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6532 TEST_DrawLevelField(x, y);
6534 // uncrumble neighbour fields, if needed
6535 if (element == EL_INVISIBLE_SAND)
6536 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6538 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6539 element == EL_INVISIBLE_WALL_ACTIVE ||
6540 element == EL_INVISIBLE_SAND_ACTIVE)
6542 if (game.light_time_left == 0)
6543 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6545 TEST_DrawLevelField(x, y);
6547 // re-crumble neighbour fields, if needed
6548 if (element == EL_INVISIBLE_SAND)
6549 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6554 static void RedrawAllInvisibleElementsForLenses(void)
6558 SCAN_PLAYFIELD(x, y)
6560 int element = Tile[x][y];
6562 if (element == EL_EMC_DRIPPER &&
6563 game.lenses_time_left > 0)
6565 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6566 TEST_DrawLevelField(x, y);
6568 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6569 game.lenses_time_left == 0)
6571 Tile[x][y] = EL_EMC_DRIPPER;
6572 TEST_DrawLevelField(x, y);
6574 else if (element == EL_INVISIBLE_STEELWALL ||
6575 element == EL_INVISIBLE_WALL ||
6576 element == EL_INVISIBLE_SAND)
6578 if (game.lenses_time_left > 0)
6579 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6581 TEST_DrawLevelField(x, y);
6583 // uncrumble neighbour fields, if needed
6584 if (element == EL_INVISIBLE_SAND)
6585 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6587 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6588 element == EL_INVISIBLE_WALL_ACTIVE ||
6589 element == EL_INVISIBLE_SAND_ACTIVE)
6591 if (game.lenses_time_left == 0)
6592 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6594 TEST_DrawLevelField(x, y);
6596 // re-crumble neighbour fields, if needed
6597 if (element == EL_INVISIBLE_SAND)
6598 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6603 static void RedrawAllInvisibleElementsForMagnifier(void)
6607 SCAN_PLAYFIELD(x, y)
6609 int element = Tile[x][y];
6611 if (element == EL_EMC_FAKE_GRASS &&
6612 game.magnify_time_left > 0)
6614 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6615 TEST_DrawLevelField(x, y);
6617 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6618 game.magnify_time_left == 0)
6620 Tile[x][y] = EL_EMC_FAKE_GRASS;
6621 TEST_DrawLevelField(x, y);
6623 else if (IS_GATE_GRAY(element) &&
6624 game.magnify_time_left > 0)
6626 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6627 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6628 IS_EM_GATE_GRAY(element) ?
6629 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6630 IS_EMC_GATE_GRAY(element) ?
6631 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6632 IS_DC_GATE_GRAY(element) ?
6633 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6635 TEST_DrawLevelField(x, y);
6637 else if (IS_GATE_GRAY_ACTIVE(element) &&
6638 game.magnify_time_left == 0)
6640 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6641 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6642 IS_EM_GATE_GRAY_ACTIVE(element) ?
6643 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6644 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6645 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6646 IS_DC_GATE_GRAY_ACTIVE(element) ?
6647 EL_DC_GATE_WHITE_GRAY :
6649 TEST_DrawLevelField(x, y);
6654 static void ToggleLightSwitch(int x, int y)
6656 int element = Tile[x][y];
6658 game.light_time_left =
6659 (element == EL_LIGHT_SWITCH ?
6660 level.time_light * FRAMES_PER_SECOND : 0);
6662 RedrawAllLightSwitchesAndInvisibleElements();
6665 static void ActivateTimegateSwitch(int x, int y)
6669 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6671 SCAN_PLAYFIELD(xx, yy)
6673 int element = Tile[xx][yy];
6675 if (element == EL_TIMEGATE_CLOSED ||
6676 element == EL_TIMEGATE_CLOSING)
6678 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6679 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6683 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6685 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6686 TEST_DrawLevelField(xx, yy);
6692 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6693 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6696 static void Impact(int x, int y)
6698 boolean last_line = (y == lev_fieldy - 1);
6699 boolean object_hit = FALSE;
6700 boolean impact = (last_line || object_hit);
6701 int element = Tile[x][y];
6702 int smashed = EL_STEELWALL;
6704 if (!last_line) // check if element below was hit
6706 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6709 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6710 MovDir[x][y + 1] != MV_DOWN ||
6711 MovPos[x][y + 1] <= TILEY / 2));
6713 // do not smash moving elements that left the smashed field in time
6714 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6715 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6718 #if USE_QUICKSAND_IMPACT_BUGFIX
6719 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6721 RemoveMovingField(x, y + 1);
6722 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6723 Tile[x][y + 2] = EL_ROCK;
6724 TEST_DrawLevelField(x, y + 2);
6729 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6731 RemoveMovingField(x, y + 1);
6732 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6733 Tile[x][y + 2] = EL_ROCK;
6734 TEST_DrawLevelField(x, y + 2);
6741 smashed = MovingOrBlocked2Element(x, y + 1);
6743 impact = (last_line || object_hit);
6746 if (!last_line && smashed == EL_ACID) // element falls into acid
6748 SplashAcid(x, y + 1);
6752 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6753 // only reset graphic animation if graphic really changes after impact
6755 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6757 ResetGfxAnimation(x, y);
6758 TEST_DrawLevelField(x, y);
6761 if (impact && CAN_EXPLODE_IMPACT(element))
6766 else if (impact && element == EL_PEARL &&
6767 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6769 ResetGfxAnimation(x, y);
6771 Tile[x][y] = EL_PEARL_BREAKING;
6772 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6775 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6777 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6782 if (impact && element == EL_AMOEBA_DROP)
6784 if (object_hit && IS_PLAYER(x, y + 1))
6785 KillPlayerUnlessEnemyProtected(x, y + 1);
6786 else if (object_hit && smashed == EL_PENGUIN)
6790 Tile[x][y] = EL_AMOEBA_GROWING;
6791 Store[x][y] = EL_AMOEBA_WET;
6793 ResetRandomAnimationValue(x, y);
6798 if (object_hit) // check which object was hit
6800 if ((CAN_PASS_MAGIC_WALL(element) &&
6801 (smashed == EL_MAGIC_WALL ||
6802 smashed == EL_BD_MAGIC_WALL)) ||
6803 (CAN_PASS_DC_MAGIC_WALL(element) &&
6804 smashed == EL_DC_MAGIC_WALL))
6807 int activated_magic_wall =
6808 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6809 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6810 EL_DC_MAGIC_WALL_ACTIVE);
6812 // activate magic wall / mill
6813 SCAN_PLAYFIELD(xx, yy)
6815 if (Tile[xx][yy] == smashed)
6816 Tile[xx][yy] = activated_magic_wall;
6819 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6820 game.magic_wall_active = TRUE;
6822 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6823 SND_MAGIC_WALL_ACTIVATING :
6824 smashed == EL_BD_MAGIC_WALL ?
6825 SND_BD_MAGIC_WALL_ACTIVATING :
6826 SND_DC_MAGIC_WALL_ACTIVATING));
6829 if (IS_PLAYER(x, y + 1))
6831 if (CAN_SMASH_PLAYER(element))
6833 KillPlayerUnlessEnemyProtected(x, y + 1);
6837 else if (smashed == EL_PENGUIN)
6839 if (CAN_SMASH_PLAYER(element))
6845 else if (element == EL_BD_DIAMOND)
6847 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6853 else if (((element == EL_SP_INFOTRON ||
6854 element == EL_SP_ZONK) &&
6855 (smashed == EL_SP_SNIKSNAK ||
6856 smashed == EL_SP_ELECTRON ||
6857 smashed == EL_SP_DISK_ORANGE)) ||
6858 (element == EL_SP_INFOTRON &&
6859 smashed == EL_SP_DISK_YELLOW))
6864 else if (CAN_SMASH_EVERYTHING(element))
6866 if (IS_CLASSIC_ENEMY(smashed) ||
6867 CAN_EXPLODE_SMASHED(smashed))
6872 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6874 if (smashed == EL_LAMP ||
6875 smashed == EL_LAMP_ACTIVE)
6880 else if (smashed == EL_NUT)
6882 Tile[x][y + 1] = EL_NUT_BREAKING;
6883 PlayLevelSound(x, y, SND_NUT_BREAKING);
6884 RaiseScoreElement(EL_NUT);
6887 else if (smashed == EL_PEARL)
6889 ResetGfxAnimation(x, y);
6891 Tile[x][y + 1] = EL_PEARL_BREAKING;
6892 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6895 else if (smashed == EL_DIAMOND)
6897 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6898 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6901 else if (IS_BELT_SWITCH(smashed))
6903 ToggleBeltSwitch(x, y + 1);
6905 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6906 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6907 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6908 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6910 ToggleSwitchgateSwitch();
6912 else if (smashed == EL_LIGHT_SWITCH ||
6913 smashed == EL_LIGHT_SWITCH_ACTIVE)
6915 ToggleLightSwitch(x, y + 1);
6919 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6921 CheckElementChangeBySide(x, y + 1, smashed, element,
6922 CE_SWITCHED, CH_SIDE_TOP);
6923 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6929 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6934 // play sound of magic wall / mill
6936 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6937 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6938 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6940 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6941 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6942 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6943 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6944 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6945 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6950 // play sound of object that hits the ground
6951 if (last_line || object_hit)
6952 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6955 static void TurnRoundExt(int x, int y)
6967 { 0, 0 }, { 0, 0 }, { 0, 0 },
6972 int left, right, back;
6976 { MV_DOWN, MV_UP, MV_RIGHT },
6977 { MV_UP, MV_DOWN, MV_LEFT },
6979 { MV_LEFT, MV_RIGHT, MV_DOWN },
6983 { MV_RIGHT, MV_LEFT, MV_UP }
6986 int element = Tile[x][y];
6987 int move_pattern = element_info[element].move_pattern;
6989 int old_move_dir = MovDir[x][y];
6990 int left_dir = turn[old_move_dir].left;
6991 int right_dir = turn[old_move_dir].right;
6992 int back_dir = turn[old_move_dir].back;
6994 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6995 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6996 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6997 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6999 int left_x = x + left_dx, left_y = y + left_dy;
7000 int right_x = x + right_dx, right_y = y + right_dy;
7001 int move_x = x + move_dx, move_y = y + move_dy;
7005 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
7007 TestIfBadThingTouchesOtherBadThing(x, y);
7009 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
7010 MovDir[x][y] = right_dir;
7011 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7012 MovDir[x][y] = left_dir;
7014 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7016 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7019 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7021 TestIfBadThingTouchesOtherBadThing(x, y);
7023 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7024 MovDir[x][y] = left_dir;
7025 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7026 MovDir[x][y] = right_dir;
7028 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7030 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7033 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7035 TestIfBadThingTouchesOtherBadThing(x, y);
7037 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7038 MovDir[x][y] = left_dir;
7039 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7040 MovDir[x][y] = right_dir;
7042 if (MovDir[x][y] != old_move_dir)
7045 else if (element == EL_YAMYAM)
7047 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7048 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7050 if (can_turn_left && can_turn_right)
7051 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7052 else if (can_turn_left)
7053 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7054 else if (can_turn_right)
7055 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7057 MovDir[x][y] = back_dir;
7059 MovDelay[x][y] = 16 + 16 * RND(3);
7061 else if (element == EL_DARK_YAMYAM)
7063 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7065 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7068 if (can_turn_left && can_turn_right)
7069 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7070 else if (can_turn_left)
7071 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7072 else if (can_turn_right)
7073 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7075 MovDir[x][y] = back_dir;
7077 MovDelay[x][y] = 16 + 16 * RND(3);
7079 else if (element == EL_PACMAN)
7081 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7082 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7084 if (can_turn_left && can_turn_right)
7085 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7086 else if (can_turn_left)
7087 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7088 else if (can_turn_right)
7089 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7091 MovDir[x][y] = back_dir;
7093 MovDelay[x][y] = 6 + RND(40);
7095 else if (element == EL_PIG)
7097 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7098 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7099 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7100 boolean should_turn_left, should_turn_right, should_move_on;
7102 int rnd = RND(rnd_value);
7104 should_turn_left = (can_turn_left &&
7106 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7107 y + back_dy + left_dy)));
7108 should_turn_right = (can_turn_right &&
7110 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7111 y + back_dy + right_dy)));
7112 should_move_on = (can_move_on &&
7115 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7116 y + move_dy + left_dy) ||
7117 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7118 y + move_dy + right_dy)));
7120 if (should_turn_left || should_turn_right || should_move_on)
7122 if (should_turn_left && should_turn_right && should_move_on)
7123 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7124 rnd < 2 * rnd_value / 3 ? right_dir :
7126 else if (should_turn_left && should_turn_right)
7127 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7128 else if (should_turn_left && should_move_on)
7129 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7130 else if (should_turn_right && should_move_on)
7131 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7132 else if (should_turn_left)
7133 MovDir[x][y] = left_dir;
7134 else if (should_turn_right)
7135 MovDir[x][y] = right_dir;
7136 else if (should_move_on)
7137 MovDir[x][y] = old_move_dir;
7139 else if (can_move_on && rnd > rnd_value / 8)
7140 MovDir[x][y] = old_move_dir;
7141 else if (can_turn_left && can_turn_right)
7142 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7143 else if (can_turn_left && rnd > rnd_value / 8)
7144 MovDir[x][y] = left_dir;
7145 else if (can_turn_right && rnd > rnd_value/8)
7146 MovDir[x][y] = right_dir;
7148 MovDir[x][y] = back_dir;
7150 xx = x + move_xy[MovDir[x][y]].dx;
7151 yy = y + move_xy[MovDir[x][y]].dy;
7153 if (!IN_LEV_FIELD(xx, yy) ||
7154 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7155 MovDir[x][y] = old_move_dir;
7159 else if (element == EL_DRAGON)
7161 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7162 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7163 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7165 int rnd = RND(rnd_value);
7167 if (can_move_on && rnd > rnd_value / 8)
7168 MovDir[x][y] = old_move_dir;
7169 else if (can_turn_left && can_turn_right)
7170 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7171 else if (can_turn_left && rnd > rnd_value / 8)
7172 MovDir[x][y] = left_dir;
7173 else if (can_turn_right && rnd > rnd_value / 8)
7174 MovDir[x][y] = right_dir;
7176 MovDir[x][y] = back_dir;
7178 xx = x + move_xy[MovDir[x][y]].dx;
7179 yy = y + move_xy[MovDir[x][y]].dy;
7181 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7182 MovDir[x][y] = old_move_dir;
7186 else if (element == EL_MOLE)
7188 boolean can_move_on =
7189 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7190 IS_AMOEBOID(Tile[move_x][move_y]) ||
7191 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7194 boolean can_turn_left =
7195 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7196 IS_AMOEBOID(Tile[left_x][left_y])));
7198 boolean can_turn_right =
7199 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7200 IS_AMOEBOID(Tile[right_x][right_y])));
7202 if (can_turn_left && can_turn_right)
7203 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7204 else if (can_turn_left)
7205 MovDir[x][y] = left_dir;
7207 MovDir[x][y] = right_dir;
7210 if (MovDir[x][y] != old_move_dir)
7213 else if (element == EL_BALLOON)
7215 MovDir[x][y] = game.wind_direction;
7218 else if (element == EL_SPRING)
7220 if (MovDir[x][y] & MV_HORIZONTAL)
7222 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7223 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7225 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7226 ResetGfxAnimation(move_x, move_y);
7227 TEST_DrawLevelField(move_x, move_y);
7229 MovDir[x][y] = back_dir;
7231 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7232 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7233 MovDir[x][y] = MV_NONE;
7238 else if (element == EL_ROBOT ||
7239 element == EL_SATELLITE ||
7240 element == EL_PENGUIN ||
7241 element == EL_EMC_ANDROID)
7243 int attr_x = -1, attr_y = -1;
7245 if (game.all_players_gone)
7247 attr_x = game.exit_x;
7248 attr_y = game.exit_y;
7254 for (i = 0; i < MAX_PLAYERS; i++)
7256 struct PlayerInfo *player = &stored_player[i];
7257 int jx = player->jx, jy = player->jy;
7259 if (!player->active)
7263 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7271 if (element == EL_ROBOT &&
7272 game.robot_wheel_x >= 0 &&
7273 game.robot_wheel_y >= 0 &&
7274 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7275 game.engine_version < VERSION_IDENT(3,1,0,0)))
7277 attr_x = game.robot_wheel_x;
7278 attr_y = game.robot_wheel_y;
7281 if (element == EL_PENGUIN)
7284 struct XY *xy = xy_topdown;
7286 for (i = 0; i < NUM_DIRECTIONS; i++)
7288 int ex = x + xy[i].x;
7289 int ey = y + xy[i].y;
7291 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7292 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7293 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7294 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7303 MovDir[x][y] = MV_NONE;
7305 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7306 else if (attr_x > x)
7307 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7309 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7310 else if (attr_y > y)
7311 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7313 if (element == EL_ROBOT)
7317 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7318 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7319 Moving2Blocked(x, y, &newx, &newy);
7321 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7322 MovDelay[x][y] = 8 + 8 * !RND(3);
7324 MovDelay[x][y] = 16;
7326 else if (element == EL_PENGUIN)
7332 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7334 boolean first_horiz = RND(2);
7335 int new_move_dir = MovDir[x][y];
7338 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7339 Moving2Blocked(x, y, &newx, &newy);
7341 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7345 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7346 Moving2Blocked(x, y, &newx, &newy);
7348 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7351 MovDir[x][y] = old_move_dir;
7355 else if (element == EL_SATELLITE)
7361 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7363 boolean first_horiz = RND(2);
7364 int new_move_dir = MovDir[x][y];
7367 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7368 Moving2Blocked(x, y, &newx, &newy);
7370 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7374 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7375 Moving2Blocked(x, y, &newx, &newy);
7377 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7380 MovDir[x][y] = old_move_dir;
7384 else if (element == EL_EMC_ANDROID)
7386 static int check_pos[16] =
7388 -1, // 0 => (invalid)
7391 -1, // 3 => (invalid)
7393 0, // 5 => MV_LEFT | MV_UP
7394 2, // 6 => MV_RIGHT | MV_UP
7395 -1, // 7 => (invalid)
7397 6, // 9 => MV_LEFT | MV_DOWN
7398 4, // 10 => MV_RIGHT | MV_DOWN
7399 -1, // 11 => (invalid)
7400 -1, // 12 => (invalid)
7401 -1, // 13 => (invalid)
7402 -1, // 14 => (invalid)
7403 -1, // 15 => (invalid)
7411 { -1, -1, MV_LEFT | MV_UP },
7413 { +1, -1, MV_RIGHT | MV_UP },
7414 { +1, 0, MV_RIGHT },
7415 { +1, +1, MV_RIGHT | MV_DOWN },
7417 { -1, +1, MV_LEFT | MV_DOWN },
7420 int start_pos, check_order;
7421 boolean can_clone = FALSE;
7424 // check if there is any free field around current position
7425 for (i = 0; i < 8; i++)
7427 int newx = x + check_xy[i].dx;
7428 int newy = y + check_xy[i].dy;
7430 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7438 if (can_clone) // randomly find an element to clone
7442 start_pos = check_pos[RND(8)];
7443 check_order = (RND(2) ? -1 : +1);
7445 for (i = 0; i < 8; i++)
7447 int pos_raw = start_pos + i * check_order;
7448 int pos = (pos_raw + 8) % 8;
7449 int newx = x + check_xy[pos].dx;
7450 int newy = y + check_xy[pos].dy;
7452 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7454 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7455 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7457 Store[x][y] = Tile[newx][newy];
7466 if (can_clone) // randomly find a direction to move
7470 start_pos = check_pos[RND(8)];
7471 check_order = (RND(2) ? -1 : +1);
7473 for (i = 0; i < 8; i++)
7475 int pos_raw = start_pos + i * check_order;
7476 int pos = (pos_raw + 8) % 8;
7477 int newx = x + check_xy[pos].dx;
7478 int newy = y + check_xy[pos].dy;
7479 int new_move_dir = check_xy[pos].dir;
7481 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7483 MovDir[x][y] = new_move_dir;
7484 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7493 if (can_clone) // cloning and moving successful
7496 // cannot clone -- try to move towards player
7498 start_pos = check_pos[MovDir[x][y] & 0x0f];
7499 check_order = (RND(2) ? -1 : +1);
7501 for (i = 0; i < 3; i++)
7503 // first check start_pos, then previous/next or (next/previous) pos
7504 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7505 int pos = (pos_raw + 8) % 8;
7506 int newx = x + check_xy[pos].dx;
7507 int newy = y + check_xy[pos].dy;
7508 int new_move_dir = check_xy[pos].dir;
7510 if (IS_PLAYER(newx, newy))
7513 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7515 MovDir[x][y] = new_move_dir;
7516 MovDelay[x][y] = level.android_move_time * 8 + 1;
7523 else if (move_pattern == MV_TURNING_LEFT ||
7524 move_pattern == MV_TURNING_RIGHT ||
7525 move_pattern == MV_TURNING_LEFT_RIGHT ||
7526 move_pattern == MV_TURNING_RIGHT_LEFT ||
7527 move_pattern == MV_TURNING_RANDOM ||
7528 move_pattern == MV_ALL_DIRECTIONS)
7530 boolean can_turn_left =
7531 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7532 boolean can_turn_right =
7533 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7535 if (element_info[element].move_stepsize == 0) // "not moving"
7538 if (move_pattern == MV_TURNING_LEFT)
7539 MovDir[x][y] = left_dir;
7540 else if (move_pattern == MV_TURNING_RIGHT)
7541 MovDir[x][y] = right_dir;
7542 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7543 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7544 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7545 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7546 else if (move_pattern == MV_TURNING_RANDOM)
7547 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7548 can_turn_right && !can_turn_left ? right_dir :
7549 RND(2) ? left_dir : right_dir);
7550 else if (can_turn_left && can_turn_right)
7551 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7552 else if (can_turn_left)
7553 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7554 else if (can_turn_right)
7555 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7557 MovDir[x][y] = back_dir;
7559 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7561 else if (move_pattern == MV_HORIZONTAL ||
7562 move_pattern == MV_VERTICAL)
7564 if (move_pattern & old_move_dir)
7565 MovDir[x][y] = back_dir;
7566 else if (move_pattern == MV_HORIZONTAL)
7567 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7568 else if (move_pattern == MV_VERTICAL)
7569 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7571 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7573 else if (move_pattern & MV_ANY_DIRECTION)
7575 MovDir[x][y] = move_pattern;
7576 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7578 else if (move_pattern & MV_WIND_DIRECTION)
7580 MovDir[x][y] = game.wind_direction;
7581 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7583 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7585 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7586 MovDir[x][y] = left_dir;
7587 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7588 MovDir[x][y] = right_dir;
7590 if (MovDir[x][y] != old_move_dir)
7591 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7593 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7595 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7596 MovDir[x][y] = right_dir;
7597 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7598 MovDir[x][y] = left_dir;
7600 if (MovDir[x][y] != old_move_dir)
7601 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7603 else if (move_pattern == MV_TOWARDS_PLAYER ||
7604 move_pattern == MV_AWAY_FROM_PLAYER)
7606 int attr_x = -1, attr_y = -1;
7608 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7610 if (game.all_players_gone)
7612 attr_x = game.exit_x;
7613 attr_y = game.exit_y;
7619 for (i = 0; i < MAX_PLAYERS; i++)
7621 struct PlayerInfo *player = &stored_player[i];
7622 int jx = player->jx, jy = player->jy;
7624 if (!player->active)
7628 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7636 MovDir[x][y] = MV_NONE;
7638 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7639 else if (attr_x > x)
7640 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7642 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7643 else if (attr_y > y)
7644 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7646 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7648 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7650 boolean first_horiz = RND(2);
7651 int new_move_dir = MovDir[x][y];
7653 if (element_info[element].move_stepsize == 0) // "not moving"
7655 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7656 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7662 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7663 Moving2Blocked(x, y, &newx, &newy);
7665 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7669 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7670 Moving2Blocked(x, y, &newx, &newy);
7672 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7675 MovDir[x][y] = old_move_dir;
7678 else if (move_pattern == MV_WHEN_PUSHED ||
7679 move_pattern == MV_WHEN_DROPPED)
7681 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7682 MovDir[x][y] = MV_NONE;
7686 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7688 struct XY *test_xy = xy_topdown;
7689 static int test_dir[4] =
7696 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7697 int move_preference = -1000000; // start with very low preference
7698 int new_move_dir = MV_NONE;
7699 int start_test = RND(4);
7702 for (i = 0; i < NUM_DIRECTIONS; i++)
7704 int j = (start_test + i) % 4;
7705 int move_dir = test_dir[j];
7706 int move_dir_preference;
7708 xx = x + test_xy[j].x;
7709 yy = y + test_xy[j].y;
7711 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7712 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7714 new_move_dir = move_dir;
7719 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7722 move_dir_preference = -1 * RunnerVisit[xx][yy];
7723 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7724 move_dir_preference = PlayerVisit[xx][yy];
7726 if (move_dir_preference > move_preference)
7728 // prefer field that has not been visited for the longest time
7729 move_preference = move_dir_preference;
7730 new_move_dir = move_dir;
7732 else if (move_dir_preference == move_preference &&
7733 move_dir == old_move_dir)
7735 // prefer last direction when all directions are preferred equally
7736 move_preference = move_dir_preference;
7737 new_move_dir = move_dir;
7741 MovDir[x][y] = new_move_dir;
7742 if (old_move_dir != new_move_dir)
7743 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7747 static void TurnRound(int x, int y)
7749 int direction = MovDir[x][y];
7753 GfxDir[x][y] = MovDir[x][y];
7755 if (direction != MovDir[x][y])
7759 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7761 ResetGfxFrame(x, y);
7764 static boolean JustBeingPushed(int x, int y)
7768 for (i = 0; i < MAX_PLAYERS; i++)
7770 struct PlayerInfo *player = &stored_player[i];
7772 if (player->active && player->is_pushing && player->MovPos)
7774 int next_jx = player->jx + (player->jx - player->last_jx);
7775 int next_jy = player->jy + (player->jy - player->last_jy);
7777 if (x == next_jx && y == next_jy)
7785 static void StartMoving(int x, int y)
7787 boolean started_moving = FALSE; // some elements can fall _and_ move
7788 int element = Tile[x][y];
7793 if (MovDelay[x][y] == 0)
7794 GfxAction[x][y] = ACTION_DEFAULT;
7796 if (CAN_FALL(element) && y < lev_fieldy - 1)
7798 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7799 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7800 if (JustBeingPushed(x, y))
7803 if (element == EL_QUICKSAND_FULL)
7805 if (IS_FREE(x, y + 1))
7807 InitMovingField(x, y, MV_DOWN);
7808 started_moving = TRUE;
7810 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7811 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7812 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7813 Store[x][y] = EL_ROCK;
7815 Store[x][y] = EL_ROCK;
7818 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7820 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7822 if (!MovDelay[x][y])
7824 MovDelay[x][y] = TILEY + 1;
7826 ResetGfxAnimation(x, y);
7827 ResetGfxAnimation(x, y + 1);
7832 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7833 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7840 Tile[x][y] = EL_QUICKSAND_EMPTY;
7841 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7842 Store[x][y + 1] = Store[x][y];
7845 PlayLevelSoundAction(x, y, ACTION_FILLING);
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_EMPTYING);
7860 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7867 Tile[x][y] = EL_QUICKSAND_EMPTY;
7868 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7869 Store[x][y + 1] = Store[x][y];
7872 PlayLevelSoundAction(x, y, ACTION_FILLING);
7875 else if (element == EL_QUICKSAND_FAST_FULL)
7877 if (IS_FREE(x, y + 1))
7879 InitMovingField(x, y, MV_DOWN);
7880 started_moving = TRUE;
7882 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7883 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7884 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7885 Store[x][y] = EL_ROCK;
7887 Store[x][y] = EL_ROCK;
7890 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7892 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7894 if (!MovDelay[x][y])
7896 MovDelay[x][y] = TILEY + 1;
7898 ResetGfxAnimation(x, y);
7899 ResetGfxAnimation(x, y + 1);
7904 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7905 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7912 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7913 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7914 Store[x][y + 1] = Store[x][y];
7917 PlayLevelSoundAction(x, y, ACTION_FILLING);
7919 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7921 if (!MovDelay[x][y])
7923 MovDelay[x][y] = TILEY + 1;
7925 ResetGfxAnimation(x, y);
7926 ResetGfxAnimation(x, y + 1);
7931 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7932 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7939 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7940 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7941 Store[x][y + 1] = Store[x][y];
7944 PlayLevelSoundAction(x, y, ACTION_FILLING);
7947 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7948 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7950 InitMovingField(x, y, MV_DOWN);
7951 started_moving = TRUE;
7953 Tile[x][y] = EL_QUICKSAND_FILLING;
7954 Store[x][y] = element;
7956 PlayLevelSoundAction(x, y, ACTION_FILLING);
7958 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7959 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7961 InitMovingField(x, y, MV_DOWN);
7962 started_moving = TRUE;
7964 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7965 Store[x][y] = element;
7967 PlayLevelSoundAction(x, y, ACTION_FILLING);
7969 else if (element == EL_MAGIC_WALL_FULL)
7971 if (IS_FREE(x, y + 1))
7973 InitMovingField(x, y, MV_DOWN);
7974 started_moving = TRUE;
7976 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7977 Store[x][y] = EL_CHANGED(Store[x][y]);
7979 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7981 if (!MovDelay[x][y])
7982 MovDelay[x][y] = TILEY / 4 + 1;
7991 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7992 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7993 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7997 else if (element == EL_BD_MAGIC_WALL_FULL)
7999 if (IS_FREE(x, y + 1))
8001 InitMovingField(x, y, MV_DOWN);
8002 started_moving = TRUE;
8004 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8005 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8007 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8009 if (!MovDelay[x][y])
8010 MovDelay[x][y] = TILEY / 4 + 1;
8019 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8020 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8021 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8025 else if (element == EL_DC_MAGIC_WALL_FULL)
8027 if (IS_FREE(x, y + 1))
8029 InitMovingField(x, y, MV_DOWN);
8030 started_moving = TRUE;
8032 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8033 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8035 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8037 if (!MovDelay[x][y])
8038 MovDelay[x][y] = TILEY / 4 + 1;
8047 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8048 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8049 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8053 else if ((CAN_PASS_MAGIC_WALL(element) &&
8054 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8055 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8056 (CAN_PASS_DC_MAGIC_WALL(element) &&
8057 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8060 InitMovingField(x, y, MV_DOWN);
8061 started_moving = TRUE;
8064 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8065 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8066 EL_DC_MAGIC_WALL_FILLING);
8067 Store[x][y] = element;
8069 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8071 SplashAcid(x, y + 1);
8073 InitMovingField(x, y, MV_DOWN);
8074 started_moving = TRUE;
8076 Store[x][y] = EL_ACID;
8079 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8080 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8081 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8082 CAN_FALL(element) && WasJustFalling[x][y] &&
8083 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8085 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8086 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8087 (Tile[x][y + 1] == EL_BLOCKED)))
8089 /* this is needed for a special case not covered by calling "Impact()"
8090 from "ContinueMoving()": if an element moves to a tile directly below
8091 another element which was just falling on that tile (which was empty
8092 in the previous frame), the falling element above would just stop
8093 instead of smashing the element below (in previous version, the above
8094 element was just checked for "moving" instead of "falling", resulting
8095 in incorrect smashes caused by horizontal movement of the above
8096 element; also, the case of the player being the element to smash was
8097 simply not covered here... :-/ ) */
8099 CheckCollision[x][y] = 0;
8100 CheckImpact[x][y] = 0;
8104 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8106 if (MovDir[x][y] == MV_NONE)
8108 InitMovingField(x, y, MV_DOWN);
8109 started_moving = TRUE;
8112 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8114 if (WasJustFalling[x][y]) // prevent animation from being restarted
8115 MovDir[x][y] = MV_DOWN;
8117 InitMovingField(x, y, MV_DOWN);
8118 started_moving = TRUE;
8120 else if (element == EL_AMOEBA_DROP)
8122 Tile[x][y] = EL_AMOEBA_GROWING;
8123 Store[x][y] = EL_AMOEBA_WET;
8125 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8126 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8127 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8128 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8130 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8131 (IS_FREE(x - 1, y + 1) ||
8132 Tile[x - 1][y + 1] == EL_ACID));
8133 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8134 (IS_FREE(x + 1, y + 1) ||
8135 Tile[x + 1][y + 1] == EL_ACID));
8136 boolean can_fall_any = (can_fall_left || can_fall_right);
8137 boolean can_fall_both = (can_fall_left && can_fall_right);
8138 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8140 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8142 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8143 can_fall_right = FALSE;
8144 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8145 can_fall_left = FALSE;
8146 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8147 can_fall_right = FALSE;
8148 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8149 can_fall_left = FALSE;
8151 can_fall_any = (can_fall_left || can_fall_right);
8152 can_fall_both = FALSE;
8157 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8158 can_fall_right = FALSE; // slip down on left side
8160 can_fall_left = !(can_fall_right = RND(2));
8162 can_fall_both = FALSE;
8167 // if not determined otherwise, prefer left side for slipping down
8168 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8169 started_moving = TRUE;
8172 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8174 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8175 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8176 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8177 int belt_dir = game.belt_dir[belt_nr];
8179 if ((belt_dir == MV_LEFT && left_is_free) ||
8180 (belt_dir == MV_RIGHT && right_is_free))
8182 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8184 InitMovingField(x, y, belt_dir);
8185 started_moving = TRUE;
8187 Pushed[x][y] = TRUE;
8188 Pushed[nextx][y] = TRUE;
8190 GfxAction[x][y] = ACTION_DEFAULT;
8194 MovDir[x][y] = 0; // if element was moving, stop it
8199 // not "else if" because of elements that can fall and move (EL_SPRING)
8200 if (CAN_MOVE(element) && !started_moving)
8202 int move_pattern = element_info[element].move_pattern;
8205 Moving2Blocked(x, y, &newx, &newy);
8207 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8210 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8211 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8213 WasJustMoving[x][y] = 0;
8214 CheckCollision[x][y] = 0;
8216 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8218 if (Tile[x][y] != element) // element has changed
8222 if (!MovDelay[x][y]) // start new movement phase
8224 // all objects that can change their move direction after each step
8225 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8227 if (element != EL_YAMYAM &&
8228 element != EL_DARK_YAMYAM &&
8229 element != EL_PACMAN &&
8230 !(move_pattern & MV_ANY_DIRECTION) &&
8231 move_pattern != MV_TURNING_LEFT &&
8232 move_pattern != MV_TURNING_RIGHT &&
8233 move_pattern != MV_TURNING_LEFT_RIGHT &&
8234 move_pattern != MV_TURNING_RIGHT_LEFT &&
8235 move_pattern != MV_TURNING_RANDOM)
8239 if (MovDelay[x][y] && (element == EL_BUG ||
8240 element == EL_SPACESHIP ||
8241 element == EL_SP_SNIKSNAK ||
8242 element == EL_SP_ELECTRON ||
8243 element == EL_MOLE))
8244 TEST_DrawLevelField(x, y);
8248 if (MovDelay[x][y]) // wait some time before next movement
8252 if (element == EL_ROBOT ||
8253 element == EL_YAMYAM ||
8254 element == EL_DARK_YAMYAM)
8256 DrawLevelElementAnimationIfNeeded(x, y, element);
8257 PlayLevelSoundAction(x, y, ACTION_WAITING);
8259 else if (element == EL_SP_ELECTRON)
8260 DrawLevelElementAnimationIfNeeded(x, y, element);
8261 else if (element == EL_DRAGON)
8264 int dir = MovDir[x][y];
8265 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8266 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8267 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8268 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8269 dir == MV_UP ? IMG_FLAMES_1_UP :
8270 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8271 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8273 GfxAction[x][y] = ACTION_ATTACKING;
8275 if (IS_PLAYER(x, y))
8276 DrawPlayerField(x, y);
8278 TEST_DrawLevelField(x, y);
8280 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8282 for (i = 1; i <= 3; i++)
8284 int xx = x + i * dx;
8285 int yy = y + i * dy;
8286 int sx = SCREENX(xx);
8287 int sy = SCREENY(yy);
8288 int flame_graphic = graphic + (i - 1);
8290 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8295 int flamed = MovingOrBlocked2Element(xx, yy);
8297 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8300 RemoveMovingField(xx, yy);
8302 ChangeDelay[xx][yy] = 0;
8304 Tile[xx][yy] = EL_FLAMES;
8306 if (IN_SCR_FIELD(sx, sy))
8308 TEST_DrawLevelFieldCrumbled(xx, yy);
8309 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8314 if (Tile[xx][yy] == EL_FLAMES)
8315 Tile[xx][yy] = EL_EMPTY;
8316 TEST_DrawLevelField(xx, yy);
8321 if (MovDelay[x][y]) // element still has to wait some time
8323 PlayLevelSoundAction(x, y, ACTION_WAITING);
8329 // now make next step
8331 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8333 if (DONT_COLLIDE_WITH(element) &&
8334 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8335 !PLAYER_ENEMY_PROTECTED(newx, newy))
8337 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8342 else if (CAN_MOVE_INTO_ACID(element) &&
8343 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8344 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8345 (MovDir[x][y] == MV_DOWN ||
8346 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8348 SplashAcid(newx, newy);
8349 Store[x][y] = EL_ACID;
8351 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8353 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8354 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8355 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8356 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8359 TEST_DrawLevelField(x, y);
8361 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8362 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8363 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8365 game.friends_still_needed--;
8366 if (!game.friends_still_needed &&
8368 game.all_players_gone)
8373 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8375 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8376 TEST_DrawLevelField(newx, newy);
8378 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8380 else if (!IS_FREE(newx, newy))
8382 GfxAction[x][y] = ACTION_WAITING;
8384 if (IS_PLAYER(x, y))
8385 DrawPlayerField(x, y);
8387 TEST_DrawLevelField(x, y);
8392 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8394 if (IS_FOOD_PIG(Tile[newx][newy]))
8396 if (IS_MOVING(newx, newy))
8397 RemoveMovingField(newx, newy);
8400 Tile[newx][newy] = EL_EMPTY;
8401 TEST_DrawLevelField(newx, newy);
8404 PlayLevelSound(x, y, SND_PIG_DIGGING);
8406 else if (!IS_FREE(newx, newy))
8408 if (IS_PLAYER(x, y))
8409 DrawPlayerField(x, y);
8411 TEST_DrawLevelField(x, y);
8416 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8418 if (Store[x][y] != EL_EMPTY)
8420 boolean can_clone = FALSE;
8423 // check if element to clone is still there
8424 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8426 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8434 // cannot clone or target field not free anymore -- do not clone
8435 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8436 Store[x][y] = EL_EMPTY;
8439 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8441 if (IS_MV_DIAGONAL(MovDir[x][y]))
8443 int diagonal_move_dir = MovDir[x][y];
8444 int stored = Store[x][y];
8445 int change_delay = 8;
8448 // android is moving diagonally
8450 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8452 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8453 GfxElement[x][y] = EL_EMC_ANDROID;
8454 GfxAction[x][y] = ACTION_SHRINKING;
8455 GfxDir[x][y] = diagonal_move_dir;
8456 ChangeDelay[x][y] = change_delay;
8458 if (Store[x][y] == EL_EMPTY)
8459 Store[x][y] = GfxElementEmpty[x][y];
8461 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8464 DrawLevelGraphicAnimation(x, y, graphic);
8465 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8467 if (Tile[newx][newy] == EL_ACID)
8469 SplashAcid(newx, newy);
8474 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8476 Store[newx][newy] = EL_EMC_ANDROID;
8477 GfxElement[newx][newy] = EL_EMC_ANDROID;
8478 GfxAction[newx][newy] = ACTION_GROWING;
8479 GfxDir[newx][newy] = diagonal_move_dir;
8480 ChangeDelay[newx][newy] = change_delay;
8482 graphic = el_act_dir2img(GfxElement[newx][newy],
8483 GfxAction[newx][newy], GfxDir[newx][newy]);
8485 DrawLevelGraphicAnimation(newx, newy, graphic);
8486 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8492 Tile[newx][newy] = EL_EMPTY;
8493 TEST_DrawLevelField(newx, newy);
8495 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8498 else if (!IS_FREE(newx, newy))
8503 else if (IS_CUSTOM_ELEMENT(element) &&
8504 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8506 if (!DigFieldByCE(newx, newy, element))
8509 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8511 RunnerVisit[x][y] = FrameCounter;
8512 PlayerVisit[x][y] /= 8; // expire player visit path
8515 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8517 if (!IS_FREE(newx, newy))
8519 if (IS_PLAYER(x, y))
8520 DrawPlayerField(x, y);
8522 TEST_DrawLevelField(x, y);
8528 boolean wanna_flame = !RND(10);
8529 int dx = newx - x, dy = newy - y;
8530 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8531 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8532 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8533 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8534 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8535 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8538 IS_CLASSIC_ENEMY(element1) ||
8539 IS_CLASSIC_ENEMY(element2)) &&
8540 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8541 element1 != EL_FLAMES && element2 != EL_FLAMES)
8543 ResetGfxAnimation(x, y);
8544 GfxAction[x][y] = ACTION_ATTACKING;
8546 if (IS_PLAYER(x, y))
8547 DrawPlayerField(x, y);
8549 TEST_DrawLevelField(x, y);
8551 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8553 MovDelay[x][y] = 50;
8555 Tile[newx][newy] = EL_FLAMES;
8556 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8557 Tile[newx1][newy1] = EL_FLAMES;
8558 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8559 Tile[newx2][newy2] = EL_FLAMES;
8565 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8566 Tile[newx][newy] == EL_DIAMOND)
8568 if (IS_MOVING(newx, newy))
8569 RemoveMovingField(newx, newy);
8572 Tile[newx][newy] = EL_EMPTY;
8573 TEST_DrawLevelField(newx, newy);
8576 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8578 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8579 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8581 if (AmoebaNr[newx][newy])
8583 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8584 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8585 Tile[newx][newy] == EL_BD_AMOEBA)
8586 AmoebaCnt[AmoebaNr[newx][newy]]--;
8589 if (IS_MOVING(newx, newy))
8591 RemoveMovingField(newx, newy);
8595 Tile[newx][newy] = EL_EMPTY;
8596 TEST_DrawLevelField(newx, newy);
8599 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8601 else if ((element == EL_PACMAN || element == EL_MOLE)
8602 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8604 if (AmoebaNr[newx][newy])
8606 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8607 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8608 Tile[newx][newy] == EL_BD_AMOEBA)
8609 AmoebaCnt[AmoebaNr[newx][newy]]--;
8612 if (element == EL_MOLE)
8614 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8615 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8617 ResetGfxAnimation(x, y);
8618 GfxAction[x][y] = ACTION_DIGGING;
8619 TEST_DrawLevelField(x, y);
8621 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8623 return; // wait for shrinking amoeba
8625 else // element == EL_PACMAN
8627 Tile[newx][newy] = EL_EMPTY;
8628 TEST_DrawLevelField(newx, newy);
8629 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8632 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8633 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8634 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8636 // wait for shrinking amoeba to completely disappear
8639 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8641 // object was running against a wall
8645 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8646 DrawLevelElementAnimation(x, y, element);
8648 if (DONT_TOUCH(element))
8649 TestIfBadThingTouchesPlayer(x, y);
8654 InitMovingField(x, y, MovDir[x][y]);
8656 PlayLevelSoundAction(x, y, ACTION_MOVING);
8660 ContinueMoving(x, y);
8663 void ContinueMoving(int x, int y)
8665 int element = Tile[x][y];
8666 struct ElementInfo *ei = &element_info[element];
8667 int direction = MovDir[x][y];
8668 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8669 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8670 int newx = x + dx, newy = y + dy;
8671 int stored = Store[x][y];
8672 int stored_new = Store[newx][newy];
8673 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8674 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8675 boolean last_line = (newy == lev_fieldy - 1);
8676 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8678 if (pushed_by_player) // special case: moving object pushed by player
8680 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8682 else if (use_step_delay) // special case: moving object has step delay
8684 if (!MovDelay[x][y])
8685 MovPos[x][y] += getElementMoveStepsize(x, y);
8690 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8694 TEST_DrawLevelField(x, y);
8696 return; // element is still waiting
8699 else // normal case: generically moving object
8701 MovPos[x][y] += getElementMoveStepsize(x, y);
8704 if (ABS(MovPos[x][y]) < TILEX)
8706 TEST_DrawLevelField(x, y);
8708 return; // element is still moving
8711 // element reached destination field
8713 Tile[x][y] = EL_EMPTY;
8714 Tile[newx][newy] = element;
8715 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8717 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8719 element = Tile[newx][newy] = EL_ACID;
8721 else if (element == EL_MOLE)
8723 Tile[x][y] = EL_SAND;
8725 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8727 else if (element == EL_QUICKSAND_FILLING)
8729 element = Tile[newx][newy] = get_next_element(element);
8730 Store[newx][newy] = Store[x][y];
8732 else if (element == EL_QUICKSAND_EMPTYING)
8734 Tile[x][y] = get_next_element(element);
8735 element = Tile[newx][newy] = Store[x][y];
8737 else if (element == EL_QUICKSAND_FAST_FILLING)
8739 element = Tile[newx][newy] = get_next_element(element);
8740 Store[newx][newy] = Store[x][y];
8742 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8744 Tile[x][y] = get_next_element(element);
8745 element = Tile[newx][newy] = Store[x][y];
8747 else if (element == EL_MAGIC_WALL_FILLING)
8749 element = Tile[newx][newy] = get_next_element(element);
8750 if (!game.magic_wall_active)
8751 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8752 Store[newx][newy] = Store[x][y];
8754 else if (element == EL_MAGIC_WALL_EMPTYING)
8756 Tile[x][y] = get_next_element(element);
8757 if (!game.magic_wall_active)
8758 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8759 element = Tile[newx][newy] = Store[x][y];
8761 InitField(newx, newy, FALSE);
8763 else if (element == EL_BD_MAGIC_WALL_FILLING)
8765 element = Tile[newx][newy] = get_next_element(element);
8766 if (!game.magic_wall_active)
8767 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8768 Store[newx][newy] = Store[x][y];
8770 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8772 Tile[x][y] = get_next_element(element);
8773 if (!game.magic_wall_active)
8774 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8775 element = Tile[newx][newy] = Store[x][y];
8777 InitField(newx, newy, FALSE);
8779 else if (element == EL_DC_MAGIC_WALL_FILLING)
8781 element = Tile[newx][newy] = get_next_element(element);
8782 if (!game.magic_wall_active)
8783 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8784 Store[newx][newy] = Store[x][y];
8786 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8788 Tile[x][y] = get_next_element(element);
8789 if (!game.magic_wall_active)
8790 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8791 element = Tile[newx][newy] = Store[x][y];
8793 InitField(newx, newy, FALSE);
8795 else if (element == EL_AMOEBA_DROPPING)
8797 Tile[x][y] = get_next_element(element);
8798 element = Tile[newx][newy] = Store[x][y];
8800 else if (element == EL_SOKOBAN_OBJECT)
8803 Tile[x][y] = Back[x][y];
8805 if (Back[newx][newy])
8806 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8808 Back[x][y] = Back[newx][newy] = 0;
8811 Store[x][y] = EL_EMPTY;
8816 MovDelay[newx][newy] = 0;
8818 if (CAN_CHANGE_OR_HAS_ACTION(element))
8820 // copy element change control values to new field
8821 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8822 ChangePage[newx][newy] = ChangePage[x][y];
8823 ChangeCount[newx][newy] = ChangeCount[x][y];
8824 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8827 CustomValue[newx][newy] = CustomValue[x][y];
8829 ChangeDelay[x][y] = 0;
8830 ChangePage[x][y] = -1;
8831 ChangeCount[x][y] = 0;
8832 ChangeEvent[x][y] = -1;
8834 CustomValue[x][y] = 0;
8836 // copy animation control values to new field
8837 GfxFrame[newx][newy] = GfxFrame[x][y];
8838 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8839 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8840 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8842 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8844 // some elements can leave other elements behind after moving
8845 if (ei->move_leave_element != EL_EMPTY &&
8846 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8847 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8849 int move_leave_element = ei->move_leave_element;
8851 // this makes it possible to leave the removed element again
8852 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8853 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8855 Tile[x][y] = move_leave_element;
8857 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8858 MovDir[x][y] = direction;
8860 InitField(x, y, FALSE);
8862 if (GFX_CRUMBLED(Tile[x][y]))
8863 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8865 if (IS_PLAYER_ELEMENT(move_leave_element))
8866 RelocatePlayer(x, y, move_leave_element);
8869 // do this after checking for left-behind element
8870 ResetGfxAnimation(x, y); // reset animation values for old field
8872 if (!CAN_MOVE(element) ||
8873 (CAN_FALL(element) && direction == MV_DOWN &&
8874 (element == EL_SPRING ||
8875 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8876 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8877 GfxDir[x][y] = MovDir[newx][newy] = 0;
8879 TEST_DrawLevelField(x, y);
8880 TEST_DrawLevelField(newx, newy);
8882 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8884 // prevent pushed element from moving on in pushed direction
8885 if (pushed_by_player && CAN_MOVE(element) &&
8886 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8887 !(element_info[element].move_pattern & direction))
8888 TurnRound(newx, newy);
8890 // prevent elements on conveyor belt from moving on in last direction
8891 if (pushed_by_conveyor && CAN_FALL(element) &&
8892 direction & MV_HORIZONTAL)
8893 MovDir[newx][newy] = 0;
8895 if (!pushed_by_player)
8897 int nextx = newx + dx, nexty = newy + dy;
8898 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8900 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8902 if (CAN_FALL(element) && direction == MV_DOWN)
8903 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8905 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8906 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8908 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8909 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8912 if (DONT_TOUCH(element)) // object may be nasty to player or others
8914 TestIfBadThingTouchesPlayer(newx, newy);
8915 TestIfBadThingTouchesFriend(newx, newy);
8917 if (!IS_CUSTOM_ELEMENT(element))
8918 TestIfBadThingTouchesOtherBadThing(newx, newy);
8920 else if (element == EL_PENGUIN)
8921 TestIfFriendTouchesBadThing(newx, newy);
8923 if (DONT_GET_HIT_BY(element))
8925 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8928 // give the player one last chance (one more frame) to move away
8929 if (CAN_FALL(element) && direction == MV_DOWN &&
8930 (last_line || (!IS_FREE(x, newy + 1) &&
8931 (!IS_PLAYER(x, newy + 1) ||
8932 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8935 if (pushed_by_player && !game.use_change_when_pushing_bug)
8937 int push_side = MV_DIR_OPPOSITE(direction);
8938 struct PlayerInfo *player = PLAYERINFO(x, y);
8940 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8941 player->index_bit, push_side);
8942 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8943 player->index_bit, push_side);
8946 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8947 MovDelay[newx][newy] = 1;
8949 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8951 TestIfElementTouchesCustomElement(x, y); // empty or new element
8952 TestIfElementHitsCustomElement(newx, newy, direction);
8953 TestIfPlayerTouchesCustomElement(newx, newy);
8954 TestIfElementTouchesCustomElement(newx, newy);
8956 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8957 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8958 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8959 MV_DIR_OPPOSITE(direction));
8962 int AmoebaNeighbourNr(int ax, int ay)
8965 int element = Tile[ax][ay];
8967 struct XY *xy = xy_topdown;
8969 for (i = 0; i < NUM_DIRECTIONS; i++)
8971 int x = ax + xy[i].x;
8972 int y = ay + xy[i].y;
8974 if (!IN_LEV_FIELD(x, y))
8977 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8978 group_nr = AmoebaNr[x][y];
8984 static void AmoebaMerge(int ax, int ay)
8986 int i, x, y, xx, yy;
8987 int new_group_nr = AmoebaNr[ax][ay];
8988 struct XY *xy = xy_topdown;
8990 if (new_group_nr == 0)
8993 for (i = 0; i < NUM_DIRECTIONS; i++)
8998 if (!IN_LEV_FIELD(x, y))
9001 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9002 Tile[x][y] == EL_BD_AMOEBA ||
9003 Tile[x][y] == EL_AMOEBA_DEAD) &&
9004 AmoebaNr[x][y] != new_group_nr)
9006 int old_group_nr = AmoebaNr[x][y];
9008 if (old_group_nr == 0)
9011 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9012 AmoebaCnt[old_group_nr] = 0;
9013 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9014 AmoebaCnt2[old_group_nr] = 0;
9016 SCAN_PLAYFIELD(xx, yy)
9018 if (AmoebaNr[xx][yy] == old_group_nr)
9019 AmoebaNr[xx][yy] = new_group_nr;
9025 void AmoebaToDiamond(int ax, int ay)
9029 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9031 int group_nr = AmoebaNr[ax][ay];
9036 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9037 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9043 SCAN_PLAYFIELD(x, y)
9045 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9048 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9052 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9053 SND_AMOEBA_TURNING_TO_GEM :
9054 SND_AMOEBA_TURNING_TO_ROCK));
9059 struct XY *xy = xy_topdown;
9061 for (i = 0; i < NUM_DIRECTIONS; i++)
9066 if (!IN_LEV_FIELD(x, y))
9069 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9071 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9072 SND_AMOEBA_TURNING_TO_GEM :
9073 SND_AMOEBA_TURNING_TO_ROCK));
9080 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9083 int group_nr = AmoebaNr[ax][ay];
9084 boolean done = FALSE;
9089 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9090 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9096 SCAN_PLAYFIELD(x, y)
9098 if (AmoebaNr[x][y] == group_nr &&
9099 (Tile[x][y] == EL_AMOEBA_DEAD ||
9100 Tile[x][y] == EL_BD_AMOEBA ||
9101 Tile[x][y] == EL_AMOEBA_GROWING))
9104 Tile[x][y] = new_element;
9105 InitField(x, y, FALSE);
9106 TEST_DrawLevelField(x, y);
9112 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9113 SND_BD_AMOEBA_TURNING_TO_ROCK :
9114 SND_BD_AMOEBA_TURNING_TO_GEM));
9117 static void AmoebaGrowing(int x, int y)
9119 static DelayCounter sound_delay = { 0 };
9121 if (!MovDelay[x][y]) // start new growing cycle
9125 if (DelayReached(&sound_delay))
9127 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9128 sound_delay.value = 30;
9132 if (MovDelay[x][y]) // wait some time before growing bigger
9135 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9137 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9138 6 - MovDelay[x][y]);
9140 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9143 if (!MovDelay[x][y])
9145 Tile[x][y] = Store[x][y];
9147 TEST_DrawLevelField(x, y);
9152 static void AmoebaShrinking(int x, int y)
9154 static DelayCounter sound_delay = { 0 };
9156 if (!MovDelay[x][y]) // start new shrinking cycle
9160 if (DelayReached(&sound_delay))
9161 sound_delay.value = 30;
9164 if (MovDelay[x][y]) // wait some time before shrinking
9167 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9169 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9170 6 - MovDelay[x][y]);
9172 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9175 if (!MovDelay[x][y])
9177 Tile[x][y] = EL_EMPTY;
9178 TEST_DrawLevelField(x, y);
9180 // don't let mole enter this field in this cycle;
9181 // (give priority to objects falling to this field from above)
9187 static void AmoebaReproduce(int ax, int ay)
9190 int element = Tile[ax][ay];
9191 int graphic = el2img(element);
9192 int newax = ax, neway = ay;
9193 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9194 struct XY *xy = xy_topdown;
9196 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9198 Tile[ax][ay] = EL_AMOEBA_DEAD;
9199 TEST_DrawLevelField(ax, ay);
9203 if (IS_ANIMATED(graphic))
9204 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9206 if (!MovDelay[ax][ay]) // start making new amoeba field
9207 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9209 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9212 if (MovDelay[ax][ay])
9216 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9219 int x = ax + xy[start].x;
9220 int y = ay + xy[start].y;
9222 if (!IN_LEV_FIELD(x, y))
9225 if (IS_FREE(x, y) ||
9226 CAN_GROW_INTO(Tile[x][y]) ||
9227 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9228 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9234 if (newax == ax && neway == ay)
9237 else // normal or "filled" (BD style) amoeba
9240 boolean waiting_for_player = FALSE;
9242 for (i = 0; i < NUM_DIRECTIONS; i++)
9244 int j = (start + i) % 4;
9245 int x = ax + xy[j].x;
9246 int y = ay + xy[j].y;
9248 if (!IN_LEV_FIELD(x, y))
9251 if (IS_FREE(x, y) ||
9252 CAN_GROW_INTO(Tile[x][y]) ||
9253 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9254 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9260 else if (IS_PLAYER(x, y))
9261 waiting_for_player = TRUE;
9264 if (newax == ax && neway == ay) // amoeba cannot grow
9266 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9268 Tile[ax][ay] = EL_AMOEBA_DEAD;
9269 TEST_DrawLevelField(ax, ay);
9270 AmoebaCnt[AmoebaNr[ax][ay]]--;
9272 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9274 if (element == EL_AMOEBA_FULL)
9275 AmoebaToDiamond(ax, ay);
9276 else if (element == EL_BD_AMOEBA)
9277 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9282 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9284 // amoeba gets larger by growing in some direction
9286 int new_group_nr = AmoebaNr[ax][ay];
9289 if (new_group_nr == 0)
9291 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9293 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9299 AmoebaNr[newax][neway] = new_group_nr;
9300 AmoebaCnt[new_group_nr]++;
9301 AmoebaCnt2[new_group_nr]++;
9303 // if amoeba touches other amoeba(s) after growing, unify them
9304 AmoebaMerge(newax, neway);
9306 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9308 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9314 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9315 (neway == lev_fieldy - 1 && newax != ax))
9317 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9318 Store[newax][neway] = element;
9320 else if (neway == ay || element == EL_EMC_DRIPPER)
9322 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9324 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9328 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9329 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9330 Store[ax][ay] = EL_AMOEBA_DROP;
9331 ContinueMoving(ax, ay);
9335 TEST_DrawLevelField(newax, neway);
9338 static void Life(int ax, int ay)
9342 int element = Tile[ax][ay];
9343 int graphic = el2img(element);
9344 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9346 boolean changed = FALSE;
9348 if (IS_ANIMATED(graphic))
9349 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9354 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9355 MovDelay[ax][ay] = life_time;
9357 if (MovDelay[ax][ay]) // wait some time before next cycle
9360 if (MovDelay[ax][ay])
9364 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9366 int xx = ax+x1, yy = ay+y1;
9367 int old_element = Tile[xx][yy];
9368 int num_neighbours = 0;
9370 if (!IN_LEV_FIELD(xx, yy))
9373 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9375 int x = xx+x2, y = yy+y2;
9377 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9380 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9381 boolean is_neighbour = FALSE;
9383 if (level.use_life_bugs)
9385 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9386 (IS_FREE(x, y) && Stop[x][y]));
9389 (Last[x][y] == element || is_player_cell);
9395 boolean is_free = FALSE;
9397 if (level.use_life_bugs)
9398 is_free = (IS_FREE(xx, yy));
9400 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9402 if (xx == ax && yy == ay) // field in the middle
9404 if (num_neighbours < life_parameter[0] ||
9405 num_neighbours > life_parameter[1])
9407 Tile[xx][yy] = EL_EMPTY;
9408 if (Tile[xx][yy] != old_element)
9409 TEST_DrawLevelField(xx, yy);
9410 Stop[xx][yy] = TRUE;
9414 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9415 { // free border field
9416 if (num_neighbours >= life_parameter[2] &&
9417 num_neighbours <= life_parameter[3])
9419 Tile[xx][yy] = element;
9420 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9421 if (Tile[xx][yy] != old_element)
9422 TEST_DrawLevelField(xx, yy);
9423 Stop[xx][yy] = TRUE;
9430 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9431 SND_GAME_OF_LIFE_GROWING);
9434 static void InitRobotWheel(int x, int y)
9436 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9439 static void RunRobotWheel(int x, int y)
9441 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9444 static void StopRobotWheel(int x, int y)
9446 if (game.robot_wheel_x == x &&
9447 game.robot_wheel_y == y)
9449 game.robot_wheel_x = -1;
9450 game.robot_wheel_y = -1;
9451 game.robot_wheel_active = FALSE;
9455 static void InitTimegateWheel(int x, int y)
9457 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9460 static void RunTimegateWheel(int x, int y)
9462 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9465 static void InitMagicBallDelay(int x, int y)
9467 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9470 static void ActivateMagicBall(int bx, int by)
9474 if (level.ball_random)
9476 int pos_border = RND(8); // select one of the eight border elements
9477 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9478 int xx = pos_content % 3;
9479 int yy = pos_content / 3;
9484 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9485 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9489 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9491 int xx = x - bx + 1;
9492 int yy = y - by + 1;
9494 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9495 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9499 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9502 static void CheckExit(int x, int y)
9504 if (game.gems_still_needed > 0 ||
9505 game.sokoban_fields_still_needed > 0 ||
9506 game.sokoban_objects_still_needed > 0 ||
9507 game.lights_still_needed > 0)
9509 int element = Tile[x][y];
9510 int graphic = el2img(element);
9512 if (IS_ANIMATED(graphic))
9513 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9518 // do not re-open exit door closed after last player
9519 if (game.all_players_gone)
9522 Tile[x][y] = EL_EXIT_OPENING;
9524 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9527 static void CheckExitEM(int x, int y)
9529 if (game.gems_still_needed > 0 ||
9530 game.sokoban_fields_still_needed > 0 ||
9531 game.sokoban_objects_still_needed > 0 ||
9532 game.lights_still_needed > 0)
9534 int element = Tile[x][y];
9535 int graphic = el2img(element);
9537 if (IS_ANIMATED(graphic))
9538 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9543 // do not re-open exit door closed after last player
9544 if (game.all_players_gone)
9547 Tile[x][y] = EL_EM_EXIT_OPENING;
9549 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9552 static void CheckExitSteel(int x, int y)
9554 if (game.gems_still_needed > 0 ||
9555 game.sokoban_fields_still_needed > 0 ||
9556 game.sokoban_objects_still_needed > 0 ||
9557 game.lights_still_needed > 0)
9559 int element = Tile[x][y];
9560 int graphic = el2img(element);
9562 if (IS_ANIMATED(graphic))
9563 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9568 // do not re-open exit door closed after last player
9569 if (game.all_players_gone)
9572 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9574 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9577 static void CheckExitSteelEM(int x, int y)
9579 if (game.gems_still_needed > 0 ||
9580 game.sokoban_fields_still_needed > 0 ||
9581 game.sokoban_objects_still_needed > 0 ||
9582 game.lights_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_EM_STEEL_EXIT_OPENING;
9599 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9602 static void CheckExitSP(int x, int y)
9604 if (game.gems_still_needed > 0)
9606 int element = Tile[x][y];
9607 int graphic = el2img(element);
9609 if (IS_ANIMATED(graphic))
9610 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9615 // do not re-open exit door closed after last player
9616 if (game.all_players_gone)
9619 Tile[x][y] = EL_SP_EXIT_OPENING;
9621 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9624 static void CloseAllOpenTimegates(void)
9628 SCAN_PLAYFIELD(x, y)
9630 int element = Tile[x][y];
9632 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9634 Tile[x][y] = EL_TIMEGATE_CLOSING;
9636 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9641 static void DrawTwinkleOnField(int x, int y)
9643 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9646 if (Tile[x][y] == EL_BD_DIAMOND)
9649 if (MovDelay[x][y] == 0) // next animation frame
9650 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9652 if (MovDelay[x][y] != 0) // wait some time before next frame
9656 DrawLevelElementAnimation(x, y, Tile[x][y]);
9658 if (MovDelay[x][y] != 0)
9660 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9661 10 - MovDelay[x][y]);
9663 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9668 static void WallGrowing(int x, int y)
9672 if (!MovDelay[x][y]) // next animation frame
9673 MovDelay[x][y] = 3 * delay;
9675 if (MovDelay[x][y]) // wait some time before next frame
9679 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9681 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9682 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9684 DrawLevelGraphic(x, y, graphic, frame);
9687 if (!MovDelay[x][y])
9689 if (MovDir[x][y] == MV_LEFT)
9691 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9692 TEST_DrawLevelField(x - 1, y);
9694 else if (MovDir[x][y] == MV_RIGHT)
9696 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9697 TEST_DrawLevelField(x + 1, y);
9699 else if (MovDir[x][y] == MV_UP)
9701 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9702 TEST_DrawLevelField(x, y - 1);
9706 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9707 TEST_DrawLevelField(x, y + 1);
9710 Tile[x][y] = Store[x][y];
9712 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9713 TEST_DrawLevelField(x, y);
9718 static void CheckWallGrowing(int ax, int ay)
9720 int element = Tile[ax][ay];
9721 int graphic = el2img(element);
9722 boolean free_top = FALSE;
9723 boolean free_bottom = FALSE;
9724 boolean free_left = FALSE;
9725 boolean free_right = FALSE;
9726 boolean stop_top = FALSE;
9727 boolean stop_bottom = FALSE;
9728 boolean stop_left = FALSE;
9729 boolean stop_right = FALSE;
9730 boolean new_wall = FALSE;
9732 boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9733 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9734 element == EL_EXPANDABLE_STEELWALL_ANY);
9736 boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9737 element == EL_EXPANDABLE_WALL_ANY ||
9738 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9739 element == EL_EXPANDABLE_STEELWALL_ANY);
9741 boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9742 element == EL_EXPANDABLE_WALL_ANY ||
9743 element == EL_EXPANDABLE_WALL ||
9744 element == EL_BD_EXPANDABLE_WALL ||
9745 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9746 element == EL_EXPANDABLE_STEELWALL_ANY);
9748 boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL ||
9749 element == EL_EXPANDABLE_STEELWALL_VERTICAL);
9751 boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9752 element == EL_EXPANDABLE_WALL ||
9753 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL);
9755 int wall_growing = (is_steelwall ?
9756 EL_EXPANDABLE_STEELWALL_GROWING :
9757 EL_EXPANDABLE_WALL_GROWING);
9759 int gfx_wall_growing_up = (is_steelwall ?
9760 IMG_EXPANDABLE_STEELWALL_GROWING_UP :
9761 IMG_EXPANDABLE_WALL_GROWING_UP);
9762 int gfx_wall_growing_down = (is_steelwall ?
9763 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN :
9764 IMG_EXPANDABLE_WALL_GROWING_DOWN);
9765 int gfx_wall_growing_left = (is_steelwall ?
9766 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT :
9767 IMG_EXPANDABLE_WALL_GROWING_LEFT);
9768 int gfx_wall_growing_right = (is_steelwall ?
9769 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT :
9770 IMG_EXPANDABLE_WALL_GROWING_RIGHT);
9772 if (IS_ANIMATED(graphic))
9773 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9775 if (!MovDelay[ax][ay]) // start building new wall
9776 MovDelay[ax][ay] = 6;
9778 if (MovDelay[ax][ay]) // wait some time before building new wall
9781 if (MovDelay[ax][ay])
9785 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9787 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9789 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9791 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9798 Tile[ax][ay - 1] = wall_growing;
9799 Store[ax][ay - 1] = element;
9800 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9802 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9803 DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0);
9810 Tile[ax][ay + 1] = wall_growing;
9811 Store[ax][ay + 1] = element;
9812 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9814 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9815 DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0);
9821 if (grow_horizontal)
9825 Tile[ax - 1][ay] = wall_growing;
9826 Store[ax - 1][ay] = element;
9827 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9829 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9830 DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0);
9837 Tile[ax + 1][ay] = wall_growing;
9838 Store[ax + 1][ay] = element;
9839 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9841 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9842 DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0);
9848 if (element == EL_EXPANDABLE_WALL && (free_left || free_right))
9849 TEST_DrawLevelField(ax, ay);
9851 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9853 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9855 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9857 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9860 if (((stop_top && stop_bottom) || stop_horizontal) &&
9861 ((stop_left && stop_right) || stop_vertical))
9862 Tile[ax][ay] = EL_WALL;
9865 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9868 static void CheckForDragon(int x, int y)
9871 boolean dragon_found = FALSE;
9872 struct XY *xy = xy_topdown;
9874 for (i = 0; i < NUM_DIRECTIONS; i++)
9876 for (j = 0; j < 4; j++)
9878 int xx = x + j * xy[i].x;
9879 int yy = y + j * xy[i].y;
9881 if (IN_LEV_FIELD(xx, yy) &&
9882 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9884 if (Tile[xx][yy] == EL_DRAGON)
9885 dragon_found = TRUE;
9894 for (i = 0; i < NUM_DIRECTIONS; i++)
9896 for (j = 0; j < 3; j++)
9898 int xx = x + j * xy[i].x;
9899 int yy = y + j * xy[i].y;
9901 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9903 Tile[xx][yy] = EL_EMPTY;
9904 TEST_DrawLevelField(xx, yy);
9913 static void InitBuggyBase(int x, int y)
9915 int element = Tile[x][y];
9916 int activating_delay = FRAMES_PER_SECOND / 4;
9919 (element == EL_SP_BUGGY_BASE ?
9920 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9921 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9923 element == EL_SP_BUGGY_BASE_ACTIVE ?
9924 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9927 static void WarnBuggyBase(int x, int y)
9930 struct XY *xy = xy_topdown;
9932 for (i = 0; i < NUM_DIRECTIONS; i++)
9934 int xx = x + xy[i].x;
9935 int yy = y + xy[i].y;
9937 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9939 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9946 static void InitTrap(int x, int y)
9948 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9951 static void ActivateTrap(int x, int y)
9953 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9956 static void ChangeActiveTrap(int x, int y)
9958 int graphic = IMG_TRAP_ACTIVE;
9960 // if new animation frame was drawn, correct crumbled sand border
9961 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9962 TEST_DrawLevelFieldCrumbled(x, y);
9965 static int getSpecialActionElement(int element, int number, int base_element)
9967 return (element != EL_EMPTY ? element :
9968 number != -1 ? base_element + number - 1 :
9972 static int getModifiedActionNumber(int value_old, int operator, int operand,
9973 int value_min, int value_max)
9975 int value_new = (operator == CA_MODE_SET ? operand :
9976 operator == CA_MODE_ADD ? value_old + operand :
9977 operator == CA_MODE_SUBTRACT ? value_old - operand :
9978 operator == CA_MODE_MULTIPLY ? value_old * operand :
9979 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9980 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9983 return (value_new < value_min ? value_min :
9984 value_new > value_max ? value_max :
9988 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9990 struct ElementInfo *ei = &element_info[element];
9991 struct ElementChangeInfo *change = &ei->change_page[page];
9992 int target_element = change->target_element;
9993 int action_type = change->action_type;
9994 int action_mode = change->action_mode;
9995 int action_arg = change->action_arg;
9996 int action_element = change->action_element;
9999 if (!change->has_action)
10002 // ---------- determine action paramater values -----------------------------
10004 int level_time_value =
10005 (level.time > 0 ? TimeLeft :
10008 int action_arg_element_raw =
10009 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10010 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10011 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10012 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10013 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10014 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10015 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10017 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10019 int action_arg_direction =
10020 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10021 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10022 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10023 change->actual_trigger_side :
10024 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10025 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10028 int action_arg_number_min =
10029 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10032 int action_arg_number_max =
10033 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10034 action_type == CA_SET_LEVEL_GEMS ? 999 :
10035 action_type == CA_SET_LEVEL_TIME ? 9999 :
10036 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10037 action_type == CA_SET_CE_VALUE ? 9999 :
10038 action_type == CA_SET_CE_SCORE ? 9999 :
10041 int action_arg_number_reset =
10042 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10043 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10044 action_type == CA_SET_LEVEL_TIME ? level.time :
10045 action_type == CA_SET_LEVEL_SCORE ? 0 :
10046 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10047 action_type == CA_SET_CE_SCORE ? 0 :
10050 int action_arg_number =
10051 (action_arg <= CA_ARG_MAX ? action_arg :
10052 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10053 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10054 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10055 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10056 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10057 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10058 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10059 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10060 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10061 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10062 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10063 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10064 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10065 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10066 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10067 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10068 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10069 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10070 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10071 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10072 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10075 int action_arg_number_old =
10076 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10077 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10078 action_type == CA_SET_LEVEL_SCORE ? game.score :
10079 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10080 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10083 int action_arg_number_new =
10084 getModifiedActionNumber(action_arg_number_old,
10085 action_mode, action_arg_number,
10086 action_arg_number_min, action_arg_number_max);
10088 int trigger_player_bits =
10089 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10090 change->actual_trigger_player_bits : change->trigger_player);
10092 int action_arg_player_bits =
10093 (action_arg >= CA_ARG_PLAYER_1 &&
10094 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10095 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10096 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10099 // ---------- execute action -----------------------------------------------
10101 switch (action_type)
10108 // ---------- level actions ----------------------------------------------
10110 case CA_RESTART_LEVEL:
10112 game.restart_level = TRUE;
10117 case CA_SHOW_ENVELOPE:
10119 int element = getSpecialActionElement(action_arg_element,
10120 action_arg_number, EL_ENVELOPE_1);
10122 if (IS_ENVELOPE(element))
10123 local_player->show_envelope = element;
10128 case CA_SET_LEVEL_TIME:
10130 if (level.time > 0) // only modify limited time value
10132 TimeLeft = action_arg_number_new;
10134 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10136 DisplayGameControlValues();
10138 if (!TimeLeft && game.time_limit)
10139 for (i = 0; i < MAX_PLAYERS; i++)
10140 KillPlayer(&stored_player[i]);
10146 case CA_SET_LEVEL_SCORE:
10148 game.score = action_arg_number_new;
10150 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10152 DisplayGameControlValues();
10157 case CA_SET_LEVEL_GEMS:
10159 game.gems_still_needed = action_arg_number_new;
10161 game.snapshot.collected_item = TRUE;
10163 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10165 DisplayGameControlValues();
10170 case CA_SET_LEVEL_WIND:
10172 game.wind_direction = action_arg_direction;
10177 case CA_SET_LEVEL_RANDOM_SEED:
10179 // ensure that setting a new random seed while playing is predictable
10180 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10185 // ---------- player actions ---------------------------------------------
10187 case CA_MOVE_PLAYER:
10188 case CA_MOVE_PLAYER_NEW:
10190 // automatically move to the next field in specified direction
10191 for (i = 0; i < MAX_PLAYERS; i++)
10192 if (trigger_player_bits & (1 << i))
10193 if (action_type == CA_MOVE_PLAYER ||
10194 stored_player[i].MovPos == 0)
10195 stored_player[i].programmed_action = action_arg_direction;
10200 case CA_EXIT_PLAYER:
10202 for (i = 0; i < MAX_PLAYERS; i++)
10203 if (action_arg_player_bits & (1 << i))
10204 ExitPlayer(&stored_player[i]);
10206 if (game.players_still_needed == 0)
10212 case CA_KILL_PLAYER:
10214 for (i = 0; i < MAX_PLAYERS; i++)
10215 if (action_arg_player_bits & (1 << i))
10216 KillPlayer(&stored_player[i]);
10221 case CA_SET_PLAYER_KEYS:
10223 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10224 int element = getSpecialActionElement(action_arg_element,
10225 action_arg_number, EL_KEY_1);
10227 if (IS_KEY(element))
10229 for (i = 0; i < MAX_PLAYERS; i++)
10231 if (trigger_player_bits & (1 << i))
10233 stored_player[i].key[KEY_NR(element)] = key_state;
10235 DrawGameDoorValues();
10243 case CA_SET_PLAYER_SPEED:
10245 for (i = 0; i < MAX_PLAYERS; i++)
10247 if (trigger_player_bits & (1 << i))
10249 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10251 if (action_arg == CA_ARG_SPEED_FASTER &&
10252 stored_player[i].cannot_move)
10254 action_arg_number = STEPSIZE_VERY_SLOW;
10256 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10257 action_arg == CA_ARG_SPEED_FASTER)
10259 action_arg_number = 2;
10260 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10263 else if (action_arg == CA_ARG_NUMBER_RESET)
10265 action_arg_number = level.initial_player_stepsize[i];
10269 getModifiedActionNumber(move_stepsize,
10272 action_arg_number_min,
10273 action_arg_number_max);
10275 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10282 case CA_SET_PLAYER_SHIELD:
10284 for (i = 0; i < MAX_PLAYERS; i++)
10286 if (trigger_player_bits & (1 << i))
10288 if (action_arg == CA_ARG_SHIELD_OFF)
10290 stored_player[i].shield_normal_time_left = 0;
10291 stored_player[i].shield_deadly_time_left = 0;
10293 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10295 stored_player[i].shield_normal_time_left = 999999;
10297 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10299 stored_player[i].shield_normal_time_left = 999999;
10300 stored_player[i].shield_deadly_time_left = 999999;
10308 case CA_SET_PLAYER_GRAVITY:
10310 for (i = 0; i < MAX_PLAYERS; i++)
10312 if (trigger_player_bits & (1 << i))
10314 stored_player[i].gravity =
10315 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10316 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10317 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10318 stored_player[i].gravity);
10325 case CA_SET_PLAYER_ARTWORK:
10327 for (i = 0; i < MAX_PLAYERS; i++)
10329 if (trigger_player_bits & (1 << i))
10331 int artwork_element = action_arg_element;
10333 if (action_arg == CA_ARG_ELEMENT_RESET)
10335 (level.use_artwork_element[i] ? level.artwork_element[i] :
10336 stored_player[i].element_nr);
10338 if (stored_player[i].artwork_element != artwork_element)
10339 stored_player[i].Frame = 0;
10341 stored_player[i].artwork_element = artwork_element;
10343 SetPlayerWaiting(&stored_player[i], FALSE);
10345 // set number of special actions for bored and sleeping animation
10346 stored_player[i].num_special_action_bored =
10347 get_num_special_action(artwork_element,
10348 ACTION_BORING_1, ACTION_BORING_LAST);
10349 stored_player[i].num_special_action_sleeping =
10350 get_num_special_action(artwork_element,
10351 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10358 case CA_SET_PLAYER_INVENTORY:
10360 for (i = 0; i < MAX_PLAYERS; i++)
10362 struct PlayerInfo *player = &stored_player[i];
10365 if (trigger_player_bits & (1 << i))
10367 int inventory_element = action_arg_element;
10369 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10370 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10371 action_arg == CA_ARG_ELEMENT_ACTION)
10373 int element = inventory_element;
10374 int collect_count = element_info[element].collect_count_initial;
10376 if (!IS_CUSTOM_ELEMENT(element))
10379 if (collect_count == 0)
10380 player->inventory_infinite_element = element;
10382 for (k = 0; k < collect_count; k++)
10383 if (player->inventory_size < MAX_INVENTORY_SIZE)
10384 player->inventory_element[player->inventory_size++] =
10387 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10388 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10389 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10391 if (player->inventory_infinite_element != EL_UNDEFINED &&
10392 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10393 action_arg_element_raw))
10394 player->inventory_infinite_element = EL_UNDEFINED;
10396 for (k = 0, j = 0; j < player->inventory_size; j++)
10398 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10399 action_arg_element_raw))
10400 player->inventory_element[k++] = player->inventory_element[j];
10403 player->inventory_size = k;
10405 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10407 if (player->inventory_size > 0)
10409 for (j = 0; j < player->inventory_size - 1; j++)
10410 player->inventory_element[j] = player->inventory_element[j + 1];
10412 player->inventory_size--;
10415 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10417 if (player->inventory_size > 0)
10418 player->inventory_size--;
10420 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10422 player->inventory_infinite_element = EL_UNDEFINED;
10423 player->inventory_size = 0;
10425 else if (action_arg == CA_ARG_INVENTORY_RESET)
10427 player->inventory_infinite_element = EL_UNDEFINED;
10428 player->inventory_size = 0;
10430 if (level.use_initial_inventory[i])
10432 for (j = 0; j < level.initial_inventory_size[i]; j++)
10434 int element = level.initial_inventory_content[i][j];
10435 int collect_count = element_info[element].collect_count_initial;
10437 if (!IS_CUSTOM_ELEMENT(element))
10440 if (collect_count == 0)
10441 player->inventory_infinite_element = element;
10443 for (k = 0; k < collect_count; k++)
10444 if (player->inventory_size < MAX_INVENTORY_SIZE)
10445 player->inventory_element[player->inventory_size++] =
10456 // ---------- CE actions -------------------------------------------------
10458 case CA_SET_CE_VALUE:
10460 int last_ce_value = CustomValue[x][y];
10462 CustomValue[x][y] = action_arg_number_new;
10464 if (CustomValue[x][y] != last_ce_value)
10466 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10467 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10469 if (CustomValue[x][y] == 0)
10471 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10472 ChangeCount[x][y] = 0; // allow at least one more change
10474 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10475 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10482 case CA_SET_CE_SCORE:
10484 int last_ce_score = ei->collect_score;
10486 ei->collect_score = action_arg_number_new;
10488 if (ei->collect_score != last_ce_score)
10490 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10491 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10493 if (ei->collect_score == 0)
10497 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10498 ChangeCount[x][y] = 0; // allow at least one more change
10500 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10501 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10504 This is a very special case that seems to be a mixture between
10505 CheckElementChange() and CheckTriggeredElementChange(): while
10506 the first one only affects single elements that are triggered
10507 directly, the second one affects multiple elements in the playfield
10508 that are triggered indirectly by another element. This is a third
10509 case: Changing the CE score always affects multiple identical CEs,
10510 so every affected CE must be checked, not only the single CE for
10511 which the CE score was changed in the first place (as every instance
10512 of that CE shares the same CE score, and therefore also can change)!
10514 SCAN_PLAYFIELD(xx, yy)
10516 if (Tile[xx][yy] == element)
10517 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10518 CE_SCORE_GETS_ZERO);
10526 case CA_SET_CE_ARTWORK:
10528 int artwork_element = action_arg_element;
10529 boolean reset_frame = FALSE;
10532 if (action_arg == CA_ARG_ELEMENT_RESET)
10533 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10536 if (ei->gfx_element != artwork_element)
10537 reset_frame = TRUE;
10539 ei->gfx_element = artwork_element;
10541 SCAN_PLAYFIELD(xx, yy)
10543 if (Tile[xx][yy] == element)
10547 ResetGfxAnimation(xx, yy);
10548 ResetRandomAnimationValue(xx, yy);
10551 TEST_DrawLevelField(xx, yy);
10558 // ---------- engine actions ---------------------------------------------
10560 case CA_SET_ENGINE_SCAN_MODE:
10562 InitPlayfieldScanMode(action_arg);
10572 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10574 int old_element = Tile[x][y];
10575 int new_element = GetElementFromGroupElement(element);
10576 int previous_move_direction = MovDir[x][y];
10577 int last_ce_value = CustomValue[x][y];
10578 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10579 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10580 boolean add_player_onto_element = (new_element_is_player &&
10581 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10582 IS_WALKABLE(old_element));
10584 if (!add_player_onto_element)
10586 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10587 RemoveMovingField(x, y);
10591 Tile[x][y] = new_element;
10593 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10594 MovDir[x][y] = previous_move_direction;
10596 if (element_info[new_element].use_last_ce_value)
10597 CustomValue[x][y] = last_ce_value;
10599 InitField_WithBug1(x, y, FALSE);
10601 new_element = Tile[x][y]; // element may have changed
10603 ResetGfxAnimation(x, y);
10604 ResetRandomAnimationValue(x, y);
10606 TEST_DrawLevelField(x, y);
10608 if (GFX_CRUMBLED(new_element))
10609 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10612 // check if element under the player changes from accessible to unaccessible
10613 // (needed for special case of dropping element which then changes)
10614 // (must be checked after creating new element for walkable group elements)
10615 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10616 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10623 // "ChangeCount" not set yet to allow "entered by player" change one time
10624 if (new_element_is_player)
10625 RelocatePlayer(x, y, new_element);
10628 ChangeCount[x][y]++; // count number of changes in the same frame
10630 TestIfBadThingTouchesPlayer(x, y);
10631 TestIfPlayerTouchesCustomElement(x, y);
10632 TestIfElementTouchesCustomElement(x, y);
10635 static void CreateField(int x, int y, int element)
10637 CreateFieldExt(x, y, element, FALSE);
10640 static void CreateElementFromChange(int x, int y, int element)
10642 element = GET_VALID_RUNTIME_ELEMENT(element);
10644 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10646 int old_element = Tile[x][y];
10648 // prevent changed element from moving in same engine frame
10649 // unless both old and new element can either fall or move
10650 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10651 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10655 CreateFieldExt(x, y, element, TRUE);
10658 static boolean ChangeElement(int x, int y, int element, int page)
10660 struct ElementInfo *ei = &element_info[element];
10661 struct ElementChangeInfo *change = &ei->change_page[page];
10662 int ce_value = CustomValue[x][y];
10663 int ce_score = ei->collect_score;
10664 int target_element;
10665 int old_element = Tile[x][y];
10667 // always use default change event to prevent running into a loop
10668 if (ChangeEvent[x][y] == -1)
10669 ChangeEvent[x][y] = CE_DELAY;
10671 if (ChangeEvent[x][y] == CE_DELAY)
10673 // reset actual trigger element, trigger player and action element
10674 change->actual_trigger_element = EL_EMPTY;
10675 change->actual_trigger_player = EL_EMPTY;
10676 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10677 change->actual_trigger_side = CH_SIDE_NONE;
10678 change->actual_trigger_ce_value = 0;
10679 change->actual_trigger_ce_score = 0;
10682 // do not change elements more than a specified maximum number of changes
10683 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10686 ChangeCount[x][y]++; // count number of changes in the same frame
10688 if (change->explode)
10695 if (change->use_target_content)
10697 boolean complete_replace = TRUE;
10698 boolean can_replace[3][3];
10701 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10704 boolean is_walkable;
10705 boolean is_diggable;
10706 boolean is_collectible;
10707 boolean is_removable;
10708 boolean is_destructible;
10709 int ex = x + xx - 1;
10710 int ey = y + yy - 1;
10711 int content_element = change->target_content.e[xx][yy];
10714 can_replace[xx][yy] = TRUE;
10716 if (ex == x && ey == y) // do not check changing element itself
10719 if (content_element == EL_EMPTY_SPACE)
10721 can_replace[xx][yy] = FALSE; // do not replace border with space
10726 if (!IN_LEV_FIELD(ex, ey))
10728 can_replace[xx][yy] = FALSE;
10729 complete_replace = FALSE;
10736 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10737 e = MovingOrBlocked2Element(ex, ey);
10739 is_empty = (IS_FREE(ex, ey) ||
10740 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10742 is_walkable = (is_empty || IS_WALKABLE(e));
10743 is_diggable = (is_empty || IS_DIGGABLE(e));
10744 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10745 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10746 is_removable = (is_diggable || is_collectible);
10748 can_replace[xx][yy] =
10749 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10750 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10751 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10752 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10753 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10754 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10755 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10757 if (!can_replace[xx][yy])
10758 complete_replace = FALSE;
10761 if (!change->only_if_complete || complete_replace)
10763 boolean something_has_changed = FALSE;
10765 if (change->only_if_complete && change->use_random_replace &&
10766 RND(100) < change->random_percentage)
10769 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10771 int ex = x + xx - 1;
10772 int ey = y + yy - 1;
10773 int content_element;
10775 if (can_replace[xx][yy] && (!change->use_random_replace ||
10776 RND(100) < change->random_percentage))
10778 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10779 RemoveMovingField(ex, ey);
10781 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10783 content_element = change->target_content.e[xx][yy];
10784 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10785 ce_value, ce_score);
10787 CreateElementFromChange(ex, ey, target_element);
10789 something_has_changed = TRUE;
10791 // for symmetry reasons, freeze newly created border elements
10792 if (ex != x || ey != y)
10793 Stop[ex][ey] = TRUE; // no more moving in this frame
10797 if (something_has_changed)
10799 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10800 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10806 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10807 ce_value, ce_score);
10809 if (element == EL_DIAGONAL_GROWING ||
10810 element == EL_DIAGONAL_SHRINKING)
10812 target_element = Store[x][y];
10814 Store[x][y] = EL_EMPTY;
10817 // special case: element changes to player (and may be kept if walkable)
10818 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10819 CreateElementFromChange(x, y, EL_EMPTY);
10821 CreateElementFromChange(x, y, target_element);
10823 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10824 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10827 // this uses direct change before indirect change
10828 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10833 static void HandleElementChange(int x, int y, int page)
10835 int element = MovingOrBlocked2Element(x, y);
10836 struct ElementInfo *ei = &element_info[element];
10837 struct ElementChangeInfo *change = &ei->change_page[page];
10838 boolean handle_action_before_change = FALSE;
10841 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10842 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10844 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10845 x, y, element, element_info[element].token_name);
10846 Debug("game:playing:HandleElementChange", "This should never happen!");
10850 // this can happen with classic bombs on walkable, changing elements
10851 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10856 if (ChangeDelay[x][y] == 0) // initialize element change
10858 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10860 if (change->can_change)
10862 // !!! not clear why graphic animation should be reset at all here !!!
10863 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10864 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10867 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10869 When using an animation frame delay of 1 (this only happens with
10870 "sp_zonk.moving.left/right" in the classic graphics), the default
10871 (non-moving) animation shows wrong animation frames (while the
10872 moving animation, like "sp_zonk.moving.left/right", is correct,
10873 so this graphical bug never shows up with the classic graphics).
10874 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10875 be drawn instead of the correct frames 0,1,2,3. This is caused by
10876 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10877 an element change: First when the change delay ("ChangeDelay[][]")
10878 counter has reached zero after decrementing, then a second time in
10879 the next frame (after "GfxFrame[][]" was already incremented) when
10880 "ChangeDelay[][]" is reset to the initial delay value again.
10882 This causes frame 0 to be drawn twice, while the last frame won't
10883 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10885 As some animations may already be cleverly designed around this bug
10886 (at least the "Snake Bite" snake tail animation does this), it cannot
10887 simply be fixed here without breaking such existing animations.
10888 Unfortunately, it cannot easily be detected if a graphics set was
10889 designed "before" or "after" the bug was fixed. As a workaround,
10890 a new graphics set option "game.graphics_engine_version" was added
10891 to be able to specify the game's major release version for which the
10892 graphics set was designed, which can then be used to decide if the
10893 bugfix should be used (version 4 and above) or not (version 3 or
10894 below, or if no version was specified at all, as with old sets).
10896 (The wrong/fixed animation frames can be tested with the test level set
10897 "test_gfxframe" and level "000", which contains a specially prepared
10898 custom element at level position (x/y) == (11/9) which uses the zonk
10899 animation mentioned above. Using "game.graphics_engine_version: 4"
10900 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10901 This can also be seen from the debug output for this test element.)
10904 // when a custom element is about to change (for example by change delay),
10905 // do not reset graphic animation when the custom element is moving
10906 if (game.graphics_engine_version < 4 &&
10909 ResetGfxAnimation(x, y);
10910 ResetRandomAnimationValue(x, y);
10913 if (change->pre_change_function)
10914 change->pre_change_function(x, y);
10918 ChangeDelay[x][y]--;
10920 if (ChangeDelay[x][y] != 0) // continue element change
10922 if (change->can_change)
10924 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10926 if (IS_ANIMATED(graphic))
10927 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10929 if (change->change_function)
10930 change->change_function(x, y);
10933 else // finish element change
10935 if (ChangePage[x][y] != -1) // remember page from delayed change
10937 page = ChangePage[x][y];
10938 ChangePage[x][y] = -1;
10940 change = &ei->change_page[page];
10943 if (IS_MOVING(x, y)) // never change a running system ;-)
10945 ChangeDelay[x][y] = 1; // try change after next move step
10946 ChangePage[x][y] = page; // remember page to use for change
10951 // special case: set new level random seed before changing element
10952 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10953 handle_action_before_change = TRUE;
10955 if (change->has_action && handle_action_before_change)
10956 ExecuteCustomElementAction(x, y, element, page);
10958 if (change->can_change)
10960 if (ChangeElement(x, y, element, page))
10962 if (change->post_change_function)
10963 change->post_change_function(x, y);
10967 if (change->has_action && !handle_action_before_change)
10968 ExecuteCustomElementAction(x, y, element, page);
10972 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10973 int trigger_element,
10975 int trigger_player,
10979 boolean change_done_any = FALSE;
10980 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10983 if (!(trigger_events[trigger_element][trigger_event]))
10986 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10988 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10990 int element = EL_CUSTOM_START + i;
10991 boolean change_done = FALSE;
10994 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10995 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10998 for (p = 0; p < element_info[element].num_change_pages; p++)
11000 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11002 if (change->can_change_or_has_action &&
11003 change->has_event[trigger_event] &&
11004 change->trigger_side & trigger_side &&
11005 change->trigger_player & trigger_player &&
11006 change->trigger_page & trigger_page_bits &&
11007 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11009 change->actual_trigger_element = trigger_element;
11010 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11011 change->actual_trigger_player_bits = trigger_player;
11012 change->actual_trigger_side = trigger_side;
11013 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11014 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11016 if ((change->can_change && !change_done) || change->has_action)
11020 SCAN_PLAYFIELD(x, y)
11022 if (Tile[x][y] == element)
11024 if (change->can_change && !change_done)
11026 // if element already changed in this frame, not only prevent
11027 // another element change (checked in ChangeElement()), but
11028 // also prevent additional element actions for this element
11030 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11031 !level.use_action_after_change_bug)
11034 ChangeDelay[x][y] = 1;
11035 ChangeEvent[x][y] = trigger_event;
11037 HandleElementChange(x, y, p);
11039 else if (change->has_action)
11041 // if element already changed in this frame, not only prevent
11042 // another element change (checked in ChangeElement()), but
11043 // also prevent additional element actions for this element
11045 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11046 !level.use_action_after_change_bug)
11049 ExecuteCustomElementAction(x, y, element, p);
11050 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11055 if (change->can_change)
11057 change_done = TRUE;
11058 change_done_any = TRUE;
11065 RECURSION_LOOP_DETECTION_END();
11067 return change_done_any;
11070 static boolean CheckElementChangeExt(int x, int y,
11072 int trigger_element,
11074 int trigger_player,
11077 boolean change_done = FALSE;
11080 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11081 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11084 if (Tile[x][y] == EL_BLOCKED)
11086 Blocked2Moving(x, y, &x, &y);
11087 element = Tile[x][y];
11090 // check if element has already changed or is about to change after moving
11091 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11092 Tile[x][y] != element) ||
11094 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11095 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11096 ChangePage[x][y] != -1)))
11099 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11101 for (p = 0; p < element_info[element].num_change_pages; p++)
11103 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11105 /* check trigger element for all events where the element that is checked
11106 for changing interacts with a directly adjacent element -- this is
11107 different to element changes that affect other elements to change on the
11108 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11109 boolean check_trigger_element =
11110 (trigger_event == CE_NEXT_TO_X ||
11111 trigger_event == CE_TOUCHING_X ||
11112 trigger_event == CE_HITTING_X ||
11113 trigger_event == CE_HIT_BY_X ||
11114 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11116 if (change->can_change_or_has_action &&
11117 change->has_event[trigger_event] &&
11118 change->trigger_side & trigger_side &&
11119 change->trigger_player & trigger_player &&
11120 (!check_trigger_element ||
11121 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11123 change->actual_trigger_element = trigger_element;
11124 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11125 change->actual_trigger_player_bits = trigger_player;
11126 change->actual_trigger_side = trigger_side;
11127 change->actual_trigger_ce_value = CustomValue[x][y];
11128 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11130 // special case: trigger element not at (x,y) position for some events
11131 if (check_trigger_element)
11143 { 0, 0 }, { 0, 0 }, { 0, 0 },
11147 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11148 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11150 change->actual_trigger_ce_value = CustomValue[xx][yy];
11151 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11154 if (change->can_change && !change_done)
11156 ChangeDelay[x][y] = 1;
11157 ChangeEvent[x][y] = trigger_event;
11159 HandleElementChange(x, y, p);
11161 change_done = TRUE;
11163 else if (change->has_action)
11165 ExecuteCustomElementAction(x, y, element, p);
11166 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11171 RECURSION_LOOP_DETECTION_END();
11173 return change_done;
11176 static void PlayPlayerSound(struct PlayerInfo *player)
11178 int jx = player->jx, jy = player->jy;
11179 int sound_element = player->artwork_element;
11180 int last_action = player->last_action_waiting;
11181 int action = player->action_waiting;
11183 if (player->is_waiting)
11185 if (action != last_action)
11186 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11188 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11192 if (action != last_action)
11193 StopSound(element_info[sound_element].sound[last_action]);
11195 if (last_action == ACTION_SLEEPING)
11196 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11200 static void PlayAllPlayersSound(void)
11204 for (i = 0; i < MAX_PLAYERS; i++)
11205 if (stored_player[i].active)
11206 PlayPlayerSound(&stored_player[i]);
11209 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11211 boolean last_waiting = player->is_waiting;
11212 int move_dir = player->MovDir;
11214 player->dir_waiting = move_dir;
11215 player->last_action_waiting = player->action_waiting;
11219 if (!last_waiting) // not waiting -> waiting
11221 player->is_waiting = TRUE;
11223 player->frame_counter_bored =
11225 game.player_boring_delay_fixed +
11226 GetSimpleRandom(game.player_boring_delay_random);
11227 player->frame_counter_sleeping =
11229 game.player_sleeping_delay_fixed +
11230 GetSimpleRandom(game.player_sleeping_delay_random);
11232 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11235 if (game.player_sleeping_delay_fixed +
11236 game.player_sleeping_delay_random > 0 &&
11237 player->anim_delay_counter == 0 &&
11238 player->post_delay_counter == 0 &&
11239 FrameCounter >= player->frame_counter_sleeping)
11240 player->is_sleeping = TRUE;
11241 else if (game.player_boring_delay_fixed +
11242 game.player_boring_delay_random > 0 &&
11243 FrameCounter >= player->frame_counter_bored)
11244 player->is_bored = TRUE;
11246 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11247 player->is_bored ? ACTION_BORING :
11250 if (player->is_sleeping && player->use_murphy)
11252 // special case for sleeping Murphy when leaning against non-free tile
11254 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11255 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11256 !IS_MOVING(player->jx - 1, player->jy)))
11257 move_dir = MV_LEFT;
11258 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11259 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11260 !IS_MOVING(player->jx + 1, player->jy)))
11261 move_dir = MV_RIGHT;
11263 player->is_sleeping = FALSE;
11265 player->dir_waiting = move_dir;
11268 if (player->is_sleeping)
11270 if (player->num_special_action_sleeping > 0)
11272 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11274 int last_special_action = player->special_action_sleeping;
11275 int num_special_action = player->num_special_action_sleeping;
11276 int special_action =
11277 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11278 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11279 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11280 last_special_action + 1 : ACTION_SLEEPING);
11281 int special_graphic =
11282 el_act_dir2img(player->artwork_element, special_action, move_dir);
11284 player->anim_delay_counter =
11285 graphic_info[special_graphic].anim_delay_fixed +
11286 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11287 player->post_delay_counter =
11288 graphic_info[special_graphic].post_delay_fixed +
11289 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11291 player->special_action_sleeping = special_action;
11294 if (player->anim_delay_counter > 0)
11296 player->action_waiting = player->special_action_sleeping;
11297 player->anim_delay_counter--;
11299 else if (player->post_delay_counter > 0)
11301 player->post_delay_counter--;
11305 else if (player->is_bored)
11307 if (player->num_special_action_bored > 0)
11309 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11311 int special_action =
11312 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11313 int special_graphic =
11314 el_act_dir2img(player->artwork_element, special_action, move_dir);
11316 player->anim_delay_counter =
11317 graphic_info[special_graphic].anim_delay_fixed +
11318 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11319 player->post_delay_counter =
11320 graphic_info[special_graphic].post_delay_fixed +
11321 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11323 player->special_action_bored = special_action;
11326 if (player->anim_delay_counter > 0)
11328 player->action_waiting = player->special_action_bored;
11329 player->anim_delay_counter--;
11331 else if (player->post_delay_counter > 0)
11333 player->post_delay_counter--;
11338 else if (last_waiting) // waiting -> not waiting
11340 player->is_waiting = FALSE;
11341 player->is_bored = FALSE;
11342 player->is_sleeping = FALSE;
11344 player->frame_counter_bored = -1;
11345 player->frame_counter_sleeping = -1;
11347 player->anim_delay_counter = 0;
11348 player->post_delay_counter = 0;
11350 player->dir_waiting = player->MovDir;
11351 player->action_waiting = ACTION_DEFAULT;
11353 player->special_action_bored = ACTION_DEFAULT;
11354 player->special_action_sleeping = ACTION_DEFAULT;
11358 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11360 if ((!player->is_moving && player->was_moving) ||
11361 (player->MovPos == 0 && player->was_moving) ||
11362 (player->is_snapping && !player->was_snapping) ||
11363 (player->is_dropping && !player->was_dropping))
11365 if (!CheckSaveEngineSnapshotToList())
11368 player->was_moving = FALSE;
11369 player->was_snapping = TRUE;
11370 player->was_dropping = TRUE;
11374 if (player->is_moving)
11375 player->was_moving = TRUE;
11377 if (!player->is_snapping)
11378 player->was_snapping = FALSE;
11380 if (!player->is_dropping)
11381 player->was_dropping = FALSE;
11384 static struct MouseActionInfo mouse_action_last = { 0 };
11385 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11386 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11389 CheckSaveEngineSnapshotToList();
11391 mouse_action_last = mouse_action;
11394 static void CheckSingleStepMode(struct PlayerInfo *player)
11396 if (tape.single_step && tape.recording && !tape.pausing)
11398 // as it is called "single step mode", just return to pause mode when the
11399 // player stopped moving after one tile (or never starts moving at all)
11400 // (reverse logic needed here in case single step mode used in team mode)
11401 if (player->is_moving ||
11402 player->is_pushing ||
11403 player->is_dropping_pressed ||
11404 player->effective_mouse_action.button)
11405 game.enter_single_step_mode = FALSE;
11408 CheckSaveEngineSnapshot(player);
11411 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11413 int left = player_action & JOY_LEFT;
11414 int right = player_action & JOY_RIGHT;
11415 int up = player_action & JOY_UP;
11416 int down = player_action & JOY_DOWN;
11417 int button1 = player_action & JOY_BUTTON_1;
11418 int button2 = player_action & JOY_BUTTON_2;
11419 int dx = (left ? -1 : right ? 1 : 0);
11420 int dy = (up ? -1 : down ? 1 : 0);
11422 if (!player->active || tape.pausing)
11428 SnapField(player, dx, dy);
11432 DropElement(player);
11434 MovePlayer(player, dx, dy);
11437 CheckSingleStepMode(player);
11439 SetPlayerWaiting(player, FALSE);
11441 return player_action;
11445 // no actions for this player (no input at player's configured device)
11447 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11448 SnapField(player, 0, 0);
11449 CheckGravityMovementWhenNotMoving(player);
11451 if (player->MovPos == 0)
11452 SetPlayerWaiting(player, TRUE);
11454 if (player->MovPos == 0) // needed for tape.playing
11455 player->is_moving = FALSE;
11457 player->is_dropping = FALSE;
11458 player->is_dropping_pressed = FALSE;
11459 player->drop_pressed_delay = 0;
11461 CheckSingleStepMode(player);
11467 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11470 if (!tape.use_mouse_actions)
11473 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11474 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11475 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11478 static void SetTapeActionFromMouseAction(byte *tape_action,
11479 struct MouseActionInfo *mouse_action)
11481 if (!tape.use_mouse_actions)
11484 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11485 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11486 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11489 static void CheckLevelSolved(void)
11491 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11493 if (game_em.level_solved &&
11494 !game_em.game_over) // game won
11498 game_em.game_over = TRUE;
11500 game.all_players_gone = TRUE;
11503 if (game_em.game_over) // game lost
11504 game.all_players_gone = TRUE;
11506 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11508 if (game_sp.level_solved &&
11509 !game_sp.game_over) // game won
11513 game_sp.game_over = TRUE;
11515 game.all_players_gone = TRUE;
11518 if (game_sp.game_over) // game lost
11519 game.all_players_gone = TRUE;
11521 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11523 if (game_mm.level_solved &&
11524 !game_mm.game_over) // game won
11528 game_mm.game_over = TRUE;
11530 game.all_players_gone = TRUE;
11533 if (game_mm.game_over) // game lost
11534 game.all_players_gone = TRUE;
11538 static void CheckLevelTime_StepCounter(void)
11548 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11549 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11551 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11553 DisplayGameControlValues();
11555 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11556 for (i = 0; i < MAX_PLAYERS; i++)
11557 KillPlayer(&stored_player[i]);
11559 else if (game.no_level_time_limit && !game.all_players_gone)
11561 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11563 DisplayGameControlValues();
11567 static void CheckLevelTime(void)
11571 if (TimeFrames >= FRAMES_PER_SECOND)
11576 for (i = 0; i < MAX_PLAYERS; i++)
11578 struct PlayerInfo *player = &stored_player[i];
11580 if (SHIELD_ON(player))
11582 player->shield_normal_time_left--;
11584 if (player->shield_deadly_time_left > 0)
11585 player->shield_deadly_time_left--;
11589 if (!game.LevelSolved && !level.use_step_counter)
11597 if (TimeLeft <= 10 && game.time_limit)
11598 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11600 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11601 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11603 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11605 if (!TimeLeft && game.time_limit)
11607 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11608 game_em.lev->killed_out_of_time = TRUE;
11610 for (i = 0; i < MAX_PLAYERS; i++)
11611 KillPlayer(&stored_player[i]);
11614 else if (game.no_level_time_limit && !game.all_players_gone)
11616 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11619 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11622 if (tape.recording || tape.playing)
11623 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11626 if (tape.recording || tape.playing)
11627 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11629 UpdateAndDisplayGameControlValues();
11632 void AdvanceFrameAndPlayerCounters(int player_nr)
11636 // advance frame counters (global frame counter and time frame counter)
11640 // advance player counters (counters for move delay, move animation etc.)
11641 for (i = 0; i < MAX_PLAYERS; i++)
11643 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11644 int move_delay_value = stored_player[i].move_delay_value;
11645 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11647 if (!advance_player_counters) // not all players may be affected
11650 if (move_frames == 0) // less than one move per game frame
11652 int stepsize = TILEX / move_delay_value;
11653 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11654 int count = (stored_player[i].is_moving ?
11655 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11657 if (count % delay == 0)
11661 stored_player[i].Frame += move_frames;
11663 if (stored_player[i].MovPos != 0)
11664 stored_player[i].StepFrame += move_frames;
11666 if (stored_player[i].move_delay > 0)
11667 stored_player[i].move_delay--;
11669 // due to bugs in previous versions, counter must count up, not down
11670 if (stored_player[i].push_delay != -1)
11671 stored_player[i].push_delay++;
11673 if (stored_player[i].drop_delay > 0)
11674 stored_player[i].drop_delay--;
11676 if (stored_player[i].is_dropping_pressed)
11677 stored_player[i].drop_pressed_delay++;
11681 void AdvanceFrameCounter(void)
11686 void AdvanceGfxFrame(void)
11690 SCAN_PLAYFIELD(x, y)
11696 void StartGameActions(boolean init_network_game, boolean record_tape,
11699 unsigned int new_random_seed = InitRND(random_seed);
11702 TapeStartRecording(new_random_seed);
11704 if (setup.auto_pause_on_start && !tape.pausing)
11705 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11707 if (init_network_game)
11709 SendToServer_LevelFile();
11710 SendToServer_StartPlaying();
11718 static void GameActionsExt(void)
11721 static unsigned int game_frame_delay = 0;
11723 unsigned int game_frame_delay_value;
11724 byte *recorded_player_action;
11725 byte summarized_player_action = 0;
11726 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11729 // detect endless loops, caused by custom element programming
11730 if (recursion_loop_detected && recursion_loop_depth == 0)
11732 char *message = getStringCat3("Internal Error! Element ",
11733 EL_NAME(recursion_loop_element),
11734 " caused endless loop! Quit the game?");
11736 Warn("element '%s' caused endless loop in game engine",
11737 EL_NAME(recursion_loop_element));
11739 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11741 recursion_loop_detected = FALSE; // if game should be continued
11748 if (game.restart_level)
11749 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11751 CheckLevelSolved();
11753 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11756 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11759 if (game_status != GAME_MODE_PLAYING) // status might have changed
11762 game_frame_delay_value =
11763 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11765 if (tape.playing && tape.warp_forward && !tape.pausing)
11766 game_frame_delay_value = 0;
11768 SetVideoFrameDelay(game_frame_delay_value);
11770 // (de)activate virtual buttons depending on current game status
11771 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11773 if (game.all_players_gone) // if no players there to be controlled anymore
11774 SetOverlayActive(FALSE);
11775 else if (!tape.playing) // if game continues after tape stopped playing
11776 SetOverlayActive(TRUE);
11781 // ---------- main game synchronization point ----------
11783 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11785 Debug("game:playing:skip", "skip == %d", skip);
11788 // ---------- main game synchronization point ----------
11790 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11794 if (network_playing && !network_player_action_received)
11796 // try to get network player actions in time
11798 // last chance to get network player actions without main loop delay
11799 HandleNetworking();
11801 // game was quit by network peer
11802 if (game_status != GAME_MODE_PLAYING)
11805 // check if network player actions still missing and game still running
11806 if (!network_player_action_received && !checkGameEnded())
11807 return; // failed to get network player actions in time
11809 // do not yet reset "network_player_action_received" (for tape.pausing)
11815 // at this point we know that we really continue executing the game
11817 network_player_action_received = FALSE;
11819 // when playing tape, read previously recorded player input from tape data
11820 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11822 local_player->effective_mouse_action = local_player->mouse_action;
11824 if (recorded_player_action != NULL)
11825 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11826 recorded_player_action);
11828 // TapePlayAction() may return NULL when toggling to "pause before death"
11832 if (tape.set_centered_player)
11834 game.centered_player_nr_next = tape.centered_player_nr_next;
11835 game.set_centered_player = TRUE;
11838 for (i = 0; i < MAX_PLAYERS; i++)
11840 summarized_player_action |= stored_player[i].action;
11842 if (!network_playing && (game.team_mode || tape.playing))
11843 stored_player[i].effective_action = stored_player[i].action;
11846 if (network_playing && !checkGameEnded())
11847 SendToServer_MovePlayer(summarized_player_action);
11849 // summarize all actions at local players mapped input device position
11850 // (this allows using different input devices in single player mode)
11851 if (!network.enabled && !game.team_mode)
11852 stored_player[map_player_action[local_player->index_nr]].effective_action =
11853 summarized_player_action;
11855 // summarize all actions at centered player in local team mode
11856 if (tape.recording &&
11857 setup.team_mode && !network.enabled &&
11858 setup.input_on_focus &&
11859 game.centered_player_nr != -1)
11861 for (i = 0; i < MAX_PLAYERS; i++)
11862 stored_player[map_player_action[i]].effective_action =
11863 (i == game.centered_player_nr ? summarized_player_action : 0);
11866 if (recorded_player_action != NULL)
11867 for (i = 0; i < MAX_PLAYERS; i++)
11868 stored_player[i].effective_action = recorded_player_action[i];
11870 for (i = 0; i < MAX_PLAYERS; i++)
11872 tape_action[i] = stored_player[i].effective_action;
11874 /* (this may happen in the RND game engine if a player was not present on
11875 the playfield on level start, but appeared later from a custom element */
11876 if (setup.team_mode &&
11879 !tape.player_participates[i])
11880 tape.player_participates[i] = TRUE;
11883 SetTapeActionFromMouseAction(tape_action,
11884 &local_player->effective_mouse_action);
11886 // only record actions from input devices, but not programmed actions
11887 if (tape.recording)
11888 TapeRecordAction(tape_action);
11890 // remember if game was played (especially after tape stopped playing)
11891 if (!tape.playing && summarized_player_action)
11892 game.GamePlayed = TRUE;
11894 #if USE_NEW_PLAYER_ASSIGNMENTS
11895 // !!! also map player actions in single player mode !!!
11896 // if (game.team_mode)
11899 byte mapped_action[MAX_PLAYERS];
11901 #if DEBUG_PLAYER_ACTIONS
11902 for (i = 0; i < MAX_PLAYERS; i++)
11903 DebugContinued("", "%d, ", stored_player[i].effective_action);
11906 for (i = 0; i < MAX_PLAYERS; i++)
11907 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11909 for (i = 0; i < MAX_PLAYERS; i++)
11910 stored_player[i].effective_action = mapped_action[i];
11912 #if DEBUG_PLAYER_ACTIONS
11913 DebugContinued("", "=> ");
11914 for (i = 0; i < MAX_PLAYERS; i++)
11915 DebugContinued("", "%d, ", stored_player[i].effective_action);
11916 DebugContinued("game:playing:player", "\n");
11919 #if DEBUG_PLAYER_ACTIONS
11922 for (i = 0; i < MAX_PLAYERS; i++)
11923 DebugContinued("", "%d, ", stored_player[i].effective_action);
11924 DebugContinued("game:playing:player", "\n");
11929 for (i = 0; i < MAX_PLAYERS; i++)
11931 // allow engine snapshot in case of changed movement attempt
11932 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11933 (stored_player[i].effective_action & KEY_MOTION))
11934 game.snapshot.changed_action = TRUE;
11936 // allow engine snapshot in case of snapping/dropping attempt
11937 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11938 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11939 game.snapshot.changed_action = TRUE;
11941 game.snapshot.last_action[i] = stored_player[i].effective_action;
11944 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11946 GameActions_EM_Main();
11948 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11950 GameActions_SP_Main();
11952 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11954 GameActions_MM_Main();
11958 GameActions_RND_Main();
11961 BlitScreenToBitmap(backbuffer);
11963 CheckLevelSolved();
11966 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11968 if (global.show_frames_per_second)
11970 static unsigned int fps_counter = 0;
11971 static int fps_frames = 0;
11972 unsigned int fps_delay_ms = Counter() - fps_counter;
11976 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11978 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11981 fps_counter = Counter();
11983 // always draw FPS to screen after FPS value was updated
11984 redraw_mask |= REDRAW_FPS;
11987 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11988 if (GetDrawDeactivationMask() == REDRAW_NONE)
11989 redraw_mask |= REDRAW_FPS;
11993 static void GameActions_CheckSaveEngineSnapshot(void)
11995 if (!game.snapshot.save_snapshot)
11998 // clear flag for saving snapshot _before_ saving snapshot
11999 game.snapshot.save_snapshot = FALSE;
12001 SaveEngineSnapshotToList();
12004 void GameActions(void)
12008 GameActions_CheckSaveEngineSnapshot();
12011 void GameActions_EM_Main(void)
12013 byte effective_action[MAX_PLAYERS];
12016 for (i = 0; i < MAX_PLAYERS; i++)
12017 effective_action[i] = stored_player[i].effective_action;
12019 GameActions_EM(effective_action);
12022 void GameActions_SP_Main(void)
12024 byte effective_action[MAX_PLAYERS];
12027 for (i = 0; i < MAX_PLAYERS; i++)
12028 effective_action[i] = stored_player[i].effective_action;
12030 GameActions_SP(effective_action);
12032 for (i = 0; i < MAX_PLAYERS; i++)
12034 if (stored_player[i].force_dropping)
12035 stored_player[i].action |= KEY_BUTTON_DROP;
12037 stored_player[i].force_dropping = FALSE;
12041 void GameActions_MM_Main(void)
12045 GameActions_MM(local_player->effective_mouse_action);
12048 void GameActions_RND_Main(void)
12053 void GameActions_RND(void)
12055 static struct MouseActionInfo mouse_action_last = { 0 };
12056 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12057 int magic_wall_x = 0, magic_wall_y = 0;
12058 int i, x, y, element, graphic, last_gfx_frame;
12060 InitPlayfieldScanModeVars();
12062 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12064 SCAN_PLAYFIELD(x, y)
12066 ChangeCount[x][y] = 0;
12067 ChangeEvent[x][y] = -1;
12071 if (game.set_centered_player)
12073 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12075 // switching to "all players" only possible if all players fit to screen
12076 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12078 game.centered_player_nr_next = game.centered_player_nr;
12079 game.set_centered_player = FALSE;
12082 // do not switch focus to non-existing (or non-active) player
12083 if (game.centered_player_nr_next >= 0 &&
12084 !stored_player[game.centered_player_nr_next].active)
12086 game.centered_player_nr_next = game.centered_player_nr;
12087 game.set_centered_player = FALSE;
12091 if (game.set_centered_player &&
12092 ScreenMovPos == 0) // screen currently aligned at tile position
12096 if (game.centered_player_nr_next == -1)
12098 setScreenCenteredToAllPlayers(&sx, &sy);
12102 sx = stored_player[game.centered_player_nr_next].jx;
12103 sy = stored_player[game.centered_player_nr_next].jy;
12106 game.centered_player_nr = game.centered_player_nr_next;
12107 game.set_centered_player = FALSE;
12109 DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch);
12110 DrawGameDoorValues();
12113 // check single step mode (set flag and clear again if any player is active)
12114 game.enter_single_step_mode =
12115 (tape.single_step && tape.recording && !tape.pausing);
12117 for (i = 0; i < MAX_PLAYERS; i++)
12119 int actual_player_action = stored_player[i].effective_action;
12122 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12123 - rnd_equinox_tetrachloride 048
12124 - rnd_equinox_tetrachloride_ii 096
12125 - rnd_emanuel_schmieg 002
12126 - doctor_sloan_ww 001, 020
12128 if (stored_player[i].MovPos == 0)
12129 CheckGravityMovement(&stored_player[i]);
12132 // overwrite programmed action with tape action
12133 if (stored_player[i].programmed_action)
12134 actual_player_action = stored_player[i].programmed_action;
12136 PlayerActions(&stored_player[i], actual_player_action);
12138 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12141 // single step pause mode may already have been toggled by "ScrollPlayer()"
12142 if (game.enter_single_step_mode && !tape.pausing)
12143 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12145 ScrollScreen(NULL, SCROLL_GO_ON);
12147 /* for backwards compatibility, the following code emulates a fixed bug that
12148 occured when pushing elements (causing elements that just made their last
12149 pushing step to already (if possible) make their first falling step in the
12150 same game frame, which is bad); this code is also needed to use the famous
12151 "spring push bug" which is used in older levels and might be wanted to be
12152 used also in newer levels, but in this case the buggy pushing code is only
12153 affecting the "spring" element and no other elements */
12155 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12157 for (i = 0; i < MAX_PLAYERS; i++)
12159 struct PlayerInfo *player = &stored_player[i];
12160 int x = player->jx;
12161 int y = player->jy;
12163 if (player->active && player->is_pushing && player->is_moving &&
12165 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12166 Tile[x][y] == EL_SPRING))
12168 ContinueMoving(x, y);
12170 // continue moving after pushing (this is actually a bug)
12171 if (!IS_MOVING(x, y))
12172 Stop[x][y] = FALSE;
12177 SCAN_PLAYFIELD(x, y)
12179 Last[x][y] = Tile[x][y];
12181 ChangeCount[x][y] = 0;
12182 ChangeEvent[x][y] = -1;
12184 // this must be handled before main playfield loop
12185 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12188 if (MovDelay[x][y] <= 0)
12192 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12195 if (MovDelay[x][y] <= 0)
12197 int element = Store[x][y];
12198 int move_direction = MovDir[x][y];
12199 int player_index_bit = Store2[x][y];
12205 TEST_DrawLevelField(x, y);
12207 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12209 if (IS_ENVELOPE(element))
12210 local_player->show_envelope = element;
12215 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12217 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12219 Debug("game:playing:GameActions_RND", "This should never happen!");
12221 ChangePage[x][y] = -1;
12225 Stop[x][y] = FALSE;
12226 if (WasJustMoving[x][y] > 0)
12227 WasJustMoving[x][y]--;
12228 if (WasJustFalling[x][y] > 0)
12229 WasJustFalling[x][y]--;
12230 if (CheckCollision[x][y] > 0)
12231 CheckCollision[x][y]--;
12232 if (CheckImpact[x][y] > 0)
12233 CheckImpact[x][y]--;
12237 /* reset finished pushing action (not done in ContinueMoving() to allow
12238 continuous pushing animation for elements with zero push delay) */
12239 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12241 ResetGfxAnimation(x, y);
12242 TEST_DrawLevelField(x, y);
12246 if (IS_BLOCKED(x, y))
12250 Blocked2Moving(x, y, &oldx, &oldy);
12251 if (!IS_MOVING(oldx, oldy))
12253 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12254 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12255 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12256 Debug("game:playing:GameActions_RND", "This should never happen!");
12262 if (mouse_action.button)
12264 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12265 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12267 x = mouse_action.lx;
12268 y = mouse_action.ly;
12269 element = Tile[x][y];
12273 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12274 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12278 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12279 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12282 if (level.use_step_counter)
12284 boolean counted_click = FALSE;
12286 // element clicked that can change when clicked/pressed
12287 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12288 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12289 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12290 counted_click = TRUE;
12292 // element clicked that can trigger change when clicked/pressed
12293 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12294 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12295 counted_click = TRUE;
12297 if (new_button && counted_click)
12298 CheckLevelTime_StepCounter();
12302 SCAN_PLAYFIELD(x, y)
12304 element = Tile[x][y];
12305 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12306 last_gfx_frame = GfxFrame[x][y];
12308 if (element == EL_EMPTY)
12309 graphic = el2img(GfxElementEmpty[x][y]);
12311 ResetGfxFrame(x, y);
12313 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12314 DrawLevelGraphicAnimation(x, y, graphic);
12316 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12317 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12318 ResetRandomAnimationValue(x, y);
12320 SetRandomAnimationValue(x, y);
12322 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12324 if (IS_INACTIVE(element))
12326 if (IS_ANIMATED(graphic))
12327 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12332 // this may take place after moving, so 'element' may have changed
12333 if (IS_CHANGING(x, y) &&
12334 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12336 int page = element_info[element].event_page_nr[CE_DELAY];
12338 HandleElementChange(x, y, page);
12340 element = Tile[x][y];
12341 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12344 CheckNextToConditions(x, y);
12346 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12350 element = Tile[x][y];
12351 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12353 if (IS_ANIMATED(graphic) &&
12354 !IS_MOVING(x, y) &&
12356 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12358 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12359 TEST_DrawTwinkleOnField(x, y);
12361 else if (element == EL_ACID)
12364 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12366 else if ((element == EL_EXIT_OPEN ||
12367 element == EL_EM_EXIT_OPEN ||
12368 element == EL_SP_EXIT_OPEN ||
12369 element == EL_STEEL_EXIT_OPEN ||
12370 element == EL_EM_STEEL_EXIT_OPEN ||
12371 element == EL_SP_TERMINAL ||
12372 element == EL_SP_TERMINAL_ACTIVE ||
12373 element == EL_EXTRA_TIME ||
12374 element == EL_SHIELD_NORMAL ||
12375 element == EL_SHIELD_DEADLY) &&
12376 IS_ANIMATED(graphic))
12377 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12378 else if (IS_MOVING(x, y))
12379 ContinueMoving(x, y);
12380 else if (IS_ACTIVE_BOMB(element))
12381 CheckDynamite(x, y);
12382 else if (element == EL_AMOEBA_GROWING)
12383 AmoebaGrowing(x, y);
12384 else if (element == EL_AMOEBA_SHRINKING)
12385 AmoebaShrinking(x, y);
12387 #if !USE_NEW_AMOEBA_CODE
12388 else if (IS_AMOEBALIVE(element))
12389 AmoebaReproduce(x, y);
12392 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12394 else if (element == EL_EXIT_CLOSED)
12396 else if (element == EL_EM_EXIT_CLOSED)
12398 else if (element == EL_STEEL_EXIT_CLOSED)
12399 CheckExitSteel(x, y);
12400 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12401 CheckExitSteelEM(x, y);
12402 else if (element == EL_SP_EXIT_CLOSED)
12404 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12405 element == EL_EXPANDABLE_STEELWALL_GROWING)
12407 else if (element == EL_EXPANDABLE_WALL ||
12408 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12409 element == EL_EXPANDABLE_WALL_VERTICAL ||
12410 element == EL_EXPANDABLE_WALL_ANY ||
12411 element == EL_BD_EXPANDABLE_WALL ||
12412 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12413 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12414 element == EL_EXPANDABLE_STEELWALL_ANY)
12415 CheckWallGrowing(x, y);
12416 else if (element == EL_FLAMES)
12417 CheckForDragon(x, y);
12418 else if (element == EL_EXPLOSION)
12419 ; // drawing of correct explosion animation is handled separately
12420 else if (element == EL_ELEMENT_SNAPPING ||
12421 element == EL_DIAGONAL_SHRINKING ||
12422 element == EL_DIAGONAL_GROWING)
12424 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12426 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12428 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12429 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12431 if (IS_BELT_ACTIVE(element))
12432 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12434 if (game.magic_wall_active)
12436 int jx = local_player->jx, jy = local_player->jy;
12438 // play the element sound at the position nearest to the player
12439 if ((element == EL_MAGIC_WALL_FULL ||
12440 element == EL_MAGIC_WALL_ACTIVE ||
12441 element == EL_MAGIC_WALL_EMPTYING ||
12442 element == EL_BD_MAGIC_WALL_FULL ||
12443 element == EL_BD_MAGIC_WALL_ACTIVE ||
12444 element == EL_BD_MAGIC_WALL_EMPTYING ||
12445 element == EL_DC_MAGIC_WALL_FULL ||
12446 element == EL_DC_MAGIC_WALL_ACTIVE ||
12447 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12448 ABS(x - jx) + ABS(y - jy) <
12449 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12457 #if USE_NEW_AMOEBA_CODE
12458 // new experimental amoeba growth stuff
12459 if (!(FrameCounter % 8))
12461 static unsigned int random = 1684108901;
12463 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12465 x = RND(lev_fieldx);
12466 y = RND(lev_fieldy);
12467 element = Tile[x][y];
12469 if (!IS_PLAYER(x,y) &&
12470 (element == EL_EMPTY ||
12471 CAN_GROW_INTO(element) ||
12472 element == EL_QUICKSAND_EMPTY ||
12473 element == EL_QUICKSAND_FAST_EMPTY ||
12474 element == EL_ACID_SPLASH_LEFT ||
12475 element == EL_ACID_SPLASH_RIGHT))
12477 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12478 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12479 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12480 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12481 Tile[x][y] = EL_AMOEBA_DROP;
12484 random = random * 129 + 1;
12489 game.explosions_delayed = FALSE;
12491 SCAN_PLAYFIELD(x, y)
12493 element = Tile[x][y];
12495 if (ExplodeField[x][y])
12496 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12497 else if (element == EL_EXPLOSION)
12498 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12500 ExplodeField[x][y] = EX_TYPE_NONE;
12503 game.explosions_delayed = TRUE;
12505 if (game.magic_wall_active)
12507 if (!(game.magic_wall_time_left % 4))
12509 int element = Tile[magic_wall_x][magic_wall_y];
12511 if (element == EL_BD_MAGIC_WALL_FULL ||
12512 element == EL_BD_MAGIC_WALL_ACTIVE ||
12513 element == EL_BD_MAGIC_WALL_EMPTYING)
12514 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12515 else if (element == EL_DC_MAGIC_WALL_FULL ||
12516 element == EL_DC_MAGIC_WALL_ACTIVE ||
12517 element == EL_DC_MAGIC_WALL_EMPTYING)
12518 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12520 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12523 if (game.magic_wall_time_left > 0)
12525 game.magic_wall_time_left--;
12527 if (!game.magic_wall_time_left)
12529 SCAN_PLAYFIELD(x, y)
12531 element = Tile[x][y];
12533 if (element == EL_MAGIC_WALL_ACTIVE ||
12534 element == EL_MAGIC_WALL_FULL)
12536 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12537 TEST_DrawLevelField(x, y);
12539 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12540 element == EL_BD_MAGIC_WALL_FULL)
12542 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12543 TEST_DrawLevelField(x, y);
12545 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12546 element == EL_DC_MAGIC_WALL_FULL)
12548 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12549 TEST_DrawLevelField(x, y);
12553 game.magic_wall_active = FALSE;
12558 if (game.light_time_left > 0)
12560 game.light_time_left--;
12562 if (game.light_time_left == 0)
12563 RedrawAllLightSwitchesAndInvisibleElements();
12566 if (game.timegate_time_left > 0)
12568 game.timegate_time_left--;
12570 if (game.timegate_time_left == 0)
12571 CloseAllOpenTimegates();
12574 if (game.lenses_time_left > 0)
12576 game.lenses_time_left--;
12578 if (game.lenses_time_left == 0)
12579 RedrawAllInvisibleElementsForLenses();
12582 if (game.magnify_time_left > 0)
12584 game.magnify_time_left--;
12586 if (game.magnify_time_left == 0)
12587 RedrawAllInvisibleElementsForMagnifier();
12590 for (i = 0; i < MAX_PLAYERS; i++)
12592 struct PlayerInfo *player = &stored_player[i];
12594 if (SHIELD_ON(player))
12596 if (player->shield_deadly_time_left)
12597 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12598 else if (player->shield_normal_time_left)
12599 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12603 #if USE_DELAYED_GFX_REDRAW
12604 SCAN_PLAYFIELD(x, y)
12606 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12608 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12609 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12611 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12612 DrawLevelField(x, y);
12614 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12615 DrawLevelFieldCrumbled(x, y);
12617 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12618 DrawLevelFieldCrumbledNeighbours(x, y);
12620 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12621 DrawTwinkleOnField(x, y);
12624 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12629 PlayAllPlayersSound();
12631 for (i = 0; i < MAX_PLAYERS; i++)
12633 struct PlayerInfo *player = &stored_player[i];
12635 if (player->show_envelope != 0 && (!player->active ||
12636 player->MovPos == 0))
12638 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12640 player->show_envelope = 0;
12644 // use random number generator in every frame to make it less predictable
12645 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12648 mouse_action_last = mouse_action;
12651 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12653 int min_x = x, min_y = y, max_x = x, max_y = y;
12654 int scr_fieldx = getScreenFieldSizeX();
12655 int scr_fieldy = getScreenFieldSizeY();
12658 for (i = 0; i < MAX_PLAYERS; i++)
12660 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12662 if (!stored_player[i].active || &stored_player[i] == player)
12665 min_x = MIN(min_x, jx);
12666 min_y = MIN(min_y, jy);
12667 max_x = MAX(max_x, jx);
12668 max_y = MAX(max_y, jy);
12671 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12674 static boolean AllPlayersInVisibleScreen(void)
12678 for (i = 0; i < MAX_PLAYERS; i++)
12680 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12682 if (!stored_player[i].active)
12685 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12692 void ScrollLevel(int dx, int dy)
12694 int scroll_offset = 2 * TILEX_VAR;
12697 BlitBitmap(drawto_field, drawto_field,
12698 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12699 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12700 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12701 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12702 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12703 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12707 x = (dx == 1 ? BX1 : BX2);
12708 for (y = BY1; y <= BY2; y++)
12709 DrawScreenField(x, y);
12714 y = (dy == 1 ? BY1 : BY2);
12715 for (x = BX1; x <= BX2; x++)
12716 DrawScreenField(x, y);
12719 redraw_mask |= REDRAW_FIELD;
12722 static boolean canFallDown(struct PlayerInfo *player)
12724 int jx = player->jx, jy = player->jy;
12726 return (IN_LEV_FIELD(jx, jy + 1) &&
12727 (IS_FREE(jx, jy + 1) ||
12728 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12729 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12730 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12733 static boolean canPassField(int x, int y, int move_dir)
12735 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12736 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12737 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12738 int nextx = x + dx;
12739 int nexty = y + dy;
12740 int element = Tile[x][y];
12742 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12743 !CAN_MOVE(element) &&
12744 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12745 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12746 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12749 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12751 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12752 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12753 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12757 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12758 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12759 (IS_DIGGABLE(Tile[newx][newy]) ||
12760 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12761 canPassField(newx, newy, move_dir)));
12764 static void CheckGravityMovement(struct PlayerInfo *player)
12766 if (player->gravity && !player->programmed_action)
12768 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12769 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12770 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12771 int jx = player->jx, jy = player->jy;
12772 boolean player_is_moving_to_valid_field =
12773 (!player_is_snapping &&
12774 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12775 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12776 boolean player_can_fall_down = canFallDown(player);
12778 if (player_can_fall_down &&
12779 !player_is_moving_to_valid_field)
12780 player->programmed_action = MV_DOWN;
12784 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12786 return CheckGravityMovement(player);
12788 if (player->gravity && !player->programmed_action)
12790 int jx = player->jx, jy = player->jy;
12791 boolean field_under_player_is_free =
12792 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12793 boolean player_is_standing_on_valid_field =
12794 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12795 (IS_WALKABLE(Tile[jx][jy]) &&
12796 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12798 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12799 player->programmed_action = MV_DOWN;
12804 MovePlayerOneStep()
12805 -----------------------------------------------------------------------------
12806 dx, dy: direction (non-diagonal) to try to move the player to
12807 real_dx, real_dy: direction as read from input device (can be diagonal)
12810 boolean MovePlayerOneStep(struct PlayerInfo *player,
12811 int dx, int dy, int real_dx, int real_dy)
12813 int jx = player->jx, jy = player->jy;
12814 int new_jx = jx + dx, new_jy = jy + dy;
12816 boolean player_can_move = !player->cannot_move;
12818 if (!player->active || (!dx && !dy))
12819 return MP_NO_ACTION;
12821 player->MovDir = (dx < 0 ? MV_LEFT :
12822 dx > 0 ? MV_RIGHT :
12824 dy > 0 ? MV_DOWN : MV_NONE);
12826 if (!IN_LEV_FIELD(new_jx, new_jy))
12827 return MP_NO_ACTION;
12829 if (!player_can_move)
12831 if (player->MovPos == 0)
12833 player->is_moving = FALSE;
12834 player->is_digging = FALSE;
12835 player->is_collecting = FALSE;
12836 player->is_snapping = FALSE;
12837 player->is_pushing = FALSE;
12841 if (!network.enabled && game.centered_player_nr == -1 &&
12842 !AllPlayersInSight(player, new_jx, new_jy))
12843 return MP_NO_ACTION;
12845 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12846 if (can_move != MP_MOVING)
12849 // check if DigField() has caused relocation of the player
12850 if (player->jx != jx || player->jy != jy)
12851 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12853 StorePlayer[jx][jy] = 0;
12854 player->last_jx = jx;
12855 player->last_jy = jy;
12856 player->jx = new_jx;
12857 player->jy = new_jy;
12858 StorePlayer[new_jx][new_jy] = player->element_nr;
12860 if (player->move_delay_value_next != -1)
12862 player->move_delay_value = player->move_delay_value_next;
12863 player->move_delay_value_next = -1;
12867 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12869 player->step_counter++;
12871 PlayerVisit[jx][jy] = FrameCounter;
12873 player->is_moving = TRUE;
12876 // should better be called in MovePlayer(), but this breaks some tapes
12877 ScrollPlayer(player, SCROLL_INIT);
12883 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12885 int jx = player->jx, jy = player->jy;
12886 int old_jx = jx, old_jy = jy;
12887 int moved = MP_NO_ACTION;
12889 if (!player->active)
12894 if (player->MovPos == 0)
12896 player->is_moving = FALSE;
12897 player->is_digging = FALSE;
12898 player->is_collecting = FALSE;
12899 player->is_snapping = FALSE;
12900 player->is_pushing = FALSE;
12906 if (player->move_delay > 0)
12909 player->move_delay = -1; // set to "uninitialized" value
12911 // store if player is automatically moved to next field
12912 player->is_auto_moving = (player->programmed_action != MV_NONE);
12914 // remove the last programmed player action
12915 player->programmed_action = 0;
12917 if (player->MovPos)
12919 // should only happen if pre-1.2 tape recordings are played
12920 // this is only for backward compatibility
12922 int original_move_delay_value = player->move_delay_value;
12925 Debug("game:playing:MovePlayer",
12926 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12930 // scroll remaining steps with finest movement resolution
12931 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12933 while (player->MovPos)
12935 ScrollPlayer(player, SCROLL_GO_ON);
12936 ScrollScreen(NULL, SCROLL_GO_ON);
12938 AdvanceFrameAndPlayerCounters(player->index_nr);
12941 BackToFront_WithFrameDelay(0);
12944 player->move_delay_value = original_move_delay_value;
12947 player->is_active = FALSE;
12949 if (player->last_move_dir & MV_HORIZONTAL)
12951 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12952 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12956 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12957 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12960 if (!moved && !player->is_active)
12962 player->is_moving = FALSE;
12963 player->is_digging = FALSE;
12964 player->is_collecting = FALSE;
12965 player->is_snapping = FALSE;
12966 player->is_pushing = FALSE;
12972 if (moved & MP_MOVING && !ScreenMovPos &&
12973 (player->index_nr == game.centered_player_nr ||
12974 game.centered_player_nr == -1))
12976 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12978 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12980 // actual player has left the screen -- scroll in that direction
12981 if (jx != old_jx) // player has moved horizontally
12982 scroll_x += (jx - old_jx);
12983 else // player has moved vertically
12984 scroll_y += (jy - old_jy);
12988 int offset_raw = game.scroll_delay_value;
12990 if (jx != old_jx) // player has moved horizontally
12992 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12993 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12994 int new_scroll_x = jx - MIDPOSX + offset_x;
12996 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12997 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12998 scroll_x = new_scroll_x;
13000 // don't scroll over playfield boundaries
13001 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13003 // don't scroll more than one field at a time
13004 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13006 // don't scroll against the player's moving direction
13007 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13008 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13009 scroll_x = old_scroll_x;
13011 else // player has moved vertically
13013 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13014 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13015 int new_scroll_y = jy - MIDPOSY + offset_y;
13017 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13018 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13019 scroll_y = new_scroll_y;
13021 // don't scroll over playfield boundaries
13022 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13024 // don't scroll more than one field at a time
13025 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13027 // don't scroll against the player's moving direction
13028 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13029 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13030 scroll_y = old_scroll_y;
13034 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13036 if (!network.enabled && game.centered_player_nr == -1 &&
13037 !AllPlayersInVisibleScreen())
13039 scroll_x = old_scroll_x;
13040 scroll_y = old_scroll_y;
13044 ScrollScreen(player, SCROLL_INIT);
13045 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13050 player->StepFrame = 0;
13052 if (moved & MP_MOVING)
13054 if (old_jx != jx && old_jy == jy)
13055 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13056 else if (old_jx == jx && old_jy != jy)
13057 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13059 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13061 player->last_move_dir = player->MovDir;
13062 player->is_moving = TRUE;
13063 player->is_snapping = FALSE;
13064 player->is_switching = FALSE;
13065 player->is_dropping = FALSE;
13066 player->is_dropping_pressed = FALSE;
13067 player->drop_pressed_delay = 0;
13070 // should better be called here than above, but this breaks some tapes
13071 ScrollPlayer(player, SCROLL_INIT);
13076 CheckGravityMovementWhenNotMoving(player);
13078 player->is_moving = FALSE;
13080 /* at this point, the player is allowed to move, but cannot move right now
13081 (e.g. because of something blocking the way) -- ensure that the player
13082 is also allowed to move in the next frame (in old versions before 3.1.1,
13083 the player was forced to wait again for eight frames before next try) */
13085 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13086 player->move_delay = 0; // allow direct movement in the next frame
13089 if (player->move_delay == -1) // not yet initialized by DigField()
13090 player->move_delay = player->move_delay_value;
13092 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13094 TestIfPlayerTouchesBadThing(jx, jy);
13095 TestIfPlayerTouchesCustomElement(jx, jy);
13098 if (!player->active)
13099 RemovePlayer(player);
13104 void ScrollPlayer(struct PlayerInfo *player, int mode)
13106 int jx = player->jx, jy = player->jy;
13107 int last_jx = player->last_jx, last_jy = player->last_jy;
13108 int move_stepsize = TILEX / player->move_delay_value;
13110 if (!player->active)
13113 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13116 if (mode == SCROLL_INIT)
13118 player->actual_frame_counter.count = FrameCounter;
13119 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13121 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13122 Tile[last_jx][last_jy] == EL_EMPTY)
13124 int last_field_block_delay = 0; // start with no blocking at all
13125 int block_delay_adjustment = player->block_delay_adjustment;
13127 // if player blocks last field, add delay for exactly one move
13128 if (player->block_last_field)
13130 last_field_block_delay += player->move_delay_value;
13132 // when blocking enabled, prevent moving up despite gravity
13133 if (player->gravity && player->MovDir == MV_UP)
13134 block_delay_adjustment = -1;
13137 // add block delay adjustment (also possible when not blocking)
13138 last_field_block_delay += block_delay_adjustment;
13140 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13141 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13144 if (player->MovPos != 0) // player has not yet reached destination
13147 else if (!FrameReached(&player->actual_frame_counter))
13150 if (player->MovPos != 0)
13152 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13153 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13155 // before DrawPlayer() to draw correct player graphic for this case
13156 if (player->MovPos == 0)
13157 CheckGravityMovement(player);
13160 if (player->MovPos == 0) // player reached destination field
13162 if (player->move_delay_reset_counter > 0)
13164 player->move_delay_reset_counter--;
13166 if (player->move_delay_reset_counter == 0)
13168 // continue with normal speed after quickly moving through gate
13169 HALVE_PLAYER_SPEED(player);
13171 // be able to make the next move without delay
13172 player->move_delay = 0;
13176 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13177 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13178 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13179 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13180 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13181 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13182 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13183 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13185 ExitPlayer(player);
13187 if (game.players_still_needed == 0 &&
13188 (game.friends_still_needed == 0 ||
13189 IS_SP_ELEMENT(Tile[jx][jy])))
13193 player->last_jx = jx;
13194 player->last_jy = jy;
13196 // this breaks one level: "machine", level 000
13198 int move_direction = player->MovDir;
13199 int enter_side = MV_DIR_OPPOSITE(move_direction);
13200 int leave_side = move_direction;
13201 int old_jx = last_jx;
13202 int old_jy = last_jy;
13203 int old_element = Tile[old_jx][old_jy];
13204 int new_element = Tile[jx][jy];
13206 if (IS_CUSTOM_ELEMENT(old_element))
13207 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13209 player->index_bit, leave_side);
13211 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13212 CE_PLAYER_LEAVES_X,
13213 player->index_bit, leave_side);
13215 if (IS_CUSTOM_ELEMENT(new_element))
13216 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13217 player->index_bit, enter_side);
13219 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13220 CE_PLAYER_ENTERS_X,
13221 player->index_bit, enter_side);
13223 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13224 CE_MOVE_OF_X, move_direction);
13227 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13229 TestIfPlayerTouchesBadThing(jx, jy);
13230 TestIfPlayerTouchesCustomElement(jx, jy);
13232 /* needed because pushed element has not yet reached its destination,
13233 so it would trigger a change event at its previous field location */
13234 if (!player->is_pushing)
13235 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13237 if (level.finish_dig_collect &&
13238 (player->is_digging || player->is_collecting))
13240 int last_element = player->last_removed_element;
13241 int move_direction = player->MovDir;
13242 int enter_side = MV_DIR_OPPOSITE(move_direction);
13243 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13244 CE_PLAYER_COLLECTS_X);
13246 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13247 player->index_bit, enter_side);
13249 player->last_removed_element = EL_UNDEFINED;
13252 if (!player->active)
13253 RemovePlayer(player);
13256 if (level.use_step_counter)
13257 CheckLevelTime_StepCounter();
13259 if (tape.single_step && tape.recording && !tape.pausing &&
13260 !player->programmed_action)
13261 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13263 if (!player->programmed_action)
13264 CheckSaveEngineSnapshot(player);
13268 void ScrollScreen(struct PlayerInfo *player, int mode)
13270 static DelayCounter screen_frame_counter = { 0 };
13272 if (mode == SCROLL_INIT)
13274 // set scrolling step size according to actual player's moving speed
13275 ScrollStepSize = TILEX / player->move_delay_value;
13277 screen_frame_counter.count = FrameCounter;
13278 screen_frame_counter.value = 1;
13280 ScreenMovDir = player->MovDir;
13281 ScreenMovPos = player->MovPos;
13282 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13285 else if (!FrameReached(&screen_frame_counter))
13290 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13291 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13292 redraw_mask |= REDRAW_FIELD;
13295 ScreenMovDir = MV_NONE;
13298 void CheckNextToConditions(int x, int y)
13300 int element = Tile[x][y];
13302 if (IS_PLAYER(x, y))
13303 TestIfPlayerNextToCustomElement(x, y);
13305 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13306 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13307 TestIfElementNextToCustomElement(x, y);
13310 void TestIfPlayerNextToCustomElement(int x, int y)
13312 struct XY *xy = xy_topdown;
13313 static int trigger_sides[4][2] =
13315 // center side border side
13316 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13317 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13318 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13319 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13323 if (!IS_PLAYER(x, y))
13326 struct PlayerInfo *player = PLAYERINFO(x, y);
13328 if (player->is_moving)
13331 for (i = 0; i < NUM_DIRECTIONS; i++)
13333 int xx = x + xy[i].x;
13334 int yy = y + xy[i].y;
13335 int border_side = trigger_sides[i][1];
13336 int border_element;
13338 if (!IN_LEV_FIELD(xx, yy))
13341 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13342 continue; // center and border element not connected
13344 border_element = Tile[xx][yy];
13346 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13347 player->index_bit, border_side);
13348 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13349 CE_PLAYER_NEXT_TO_X,
13350 player->index_bit, border_side);
13352 /* use player element that is initially defined in the level playfield,
13353 not the player element that corresponds to the runtime player number
13354 (example: a level that contains EL_PLAYER_3 as the only player would
13355 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13357 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13358 CE_NEXT_TO_X, border_side);
13362 void TestIfPlayerTouchesCustomElement(int x, int y)
13364 struct XY *xy = xy_topdown;
13365 static int trigger_sides[4][2] =
13367 // center side border side
13368 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13369 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13370 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13371 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13373 static int touch_dir[4] =
13375 MV_LEFT | MV_RIGHT,
13380 int center_element = Tile[x][y]; // should always be non-moving!
13383 for (i = 0; i < NUM_DIRECTIONS; i++)
13385 int xx = x + xy[i].x;
13386 int yy = y + xy[i].y;
13387 int center_side = trigger_sides[i][0];
13388 int border_side = trigger_sides[i][1];
13389 int border_element;
13391 if (!IN_LEV_FIELD(xx, yy))
13394 if (IS_PLAYER(x, y)) // player found at center element
13396 struct PlayerInfo *player = PLAYERINFO(x, y);
13398 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13399 border_element = Tile[xx][yy]; // may be moving!
13400 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13401 border_element = Tile[xx][yy];
13402 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13403 border_element = MovingOrBlocked2Element(xx, yy);
13405 continue; // center and border element do not touch
13407 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13408 player->index_bit, border_side);
13409 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13410 CE_PLAYER_TOUCHES_X,
13411 player->index_bit, border_side);
13414 /* use player element that is initially defined in the level playfield,
13415 not the player element that corresponds to the runtime player number
13416 (example: a level that contains EL_PLAYER_3 as the only player would
13417 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13418 int player_element = PLAYERINFO(x, y)->initial_element;
13420 CheckElementChangeBySide(xx, yy, border_element, player_element,
13421 CE_TOUCHING_X, border_side);
13424 else if (IS_PLAYER(xx, yy)) // player found at border element
13426 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13428 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13430 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13431 continue; // center and border element do not touch
13434 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13435 player->index_bit, center_side);
13436 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13437 CE_PLAYER_TOUCHES_X,
13438 player->index_bit, center_side);
13441 /* use player element that is initially defined in the level playfield,
13442 not the player element that corresponds to the runtime player number
13443 (example: a level that contains EL_PLAYER_3 as the only player would
13444 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13445 int player_element = PLAYERINFO(xx, yy)->initial_element;
13447 CheckElementChangeBySide(x, y, center_element, player_element,
13448 CE_TOUCHING_X, center_side);
13456 void TestIfElementNextToCustomElement(int x, int y)
13458 struct XY *xy = xy_topdown;
13459 static int trigger_sides[4][2] =
13461 // center side border side
13462 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13463 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13464 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13465 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13467 int center_element = Tile[x][y]; // should always be non-moving!
13470 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13473 for (i = 0; i < NUM_DIRECTIONS; i++)
13475 int xx = x + xy[i].x;
13476 int yy = y + xy[i].y;
13477 int border_side = trigger_sides[i][1];
13478 int border_element;
13480 if (!IN_LEV_FIELD(xx, yy))
13483 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13484 continue; // center and border element not connected
13486 border_element = Tile[xx][yy];
13488 // check for change of center element (but change it only once)
13489 if (CheckElementChangeBySide(x, y, center_element, border_element,
13490 CE_NEXT_TO_X, border_side))
13495 void TestIfElementTouchesCustomElement(int x, int y)
13497 struct XY *xy = xy_topdown;
13498 static int trigger_sides[4][2] =
13500 // center side border side
13501 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13502 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13503 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13504 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13506 static int touch_dir[4] =
13508 MV_LEFT | MV_RIGHT,
13513 boolean change_center_element = FALSE;
13514 int center_element = Tile[x][y]; // should always be non-moving!
13515 int border_element_old[NUM_DIRECTIONS];
13518 for (i = 0; i < NUM_DIRECTIONS; i++)
13520 int xx = x + xy[i].x;
13521 int yy = y + xy[i].y;
13522 int border_element;
13524 border_element_old[i] = -1;
13526 if (!IN_LEV_FIELD(xx, yy))
13529 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13530 border_element = Tile[xx][yy]; // may be moving!
13531 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13532 border_element = Tile[xx][yy];
13533 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13534 border_element = MovingOrBlocked2Element(xx, yy);
13536 continue; // center and border element do not touch
13538 border_element_old[i] = border_element;
13541 for (i = 0; i < NUM_DIRECTIONS; i++)
13543 int xx = x + xy[i].x;
13544 int yy = y + xy[i].y;
13545 int center_side = trigger_sides[i][0];
13546 int border_element = border_element_old[i];
13548 if (border_element == -1)
13551 // check for change of border element
13552 CheckElementChangeBySide(xx, yy, border_element, center_element,
13553 CE_TOUCHING_X, center_side);
13555 // (center element cannot be player, so we dont have to check this here)
13558 for (i = 0; i < NUM_DIRECTIONS; i++)
13560 int xx = x + xy[i].x;
13561 int yy = y + xy[i].y;
13562 int border_side = trigger_sides[i][1];
13563 int border_element = border_element_old[i];
13565 if (border_element == -1)
13568 // check for change of center element (but change it only once)
13569 if (!change_center_element)
13570 change_center_element =
13571 CheckElementChangeBySide(x, y, center_element, border_element,
13572 CE_TOUCHING_X, border_side);
13574 if (IS_PLAYER(xx, yy))
13576 /* use player element that is initially defined in the level playfield,
13577 not the player element that corresponds to the runtime player number
13578 (example: a level that contains EL_PLAYER_3 as the only player would
13579 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13580 int player_element = PLAYERINFO(xx, yy)->initial_element;
13582 CheckElementChangeBySide(x, y, center_element, player_element,
13583 CE_TOUCHING_X, border_side);
13588 void TestIfElementHitsCustomElement(int x, int y, int direction)
13590 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13591 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13592 int hitx = x + dx, hity = y + dy;
13593 int hitting_element = Tile[x][y];
13594 int touched_element;
13596 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13599 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13600 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13602 if (IN_LEV_FIELD(hitx, hity))
13604 int opposite_direction = MV_DIR_OPPOSITE(direction);
13605 int hitting_side = direction;
13606 int touched_side = opposite_direction;
13607 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13608 MovDir[hitx][hity] != direction ||
13609 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13615 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13616 CE_HITTING_X, touched_side);
13618 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13619 CE_HIT_BY_X, hitting_side);
13621 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13622 CE_HIT_BY_SOMETHING, opposite_direction);
13624 if (IS_PLAYER(hitx, hity))
13626 /* use player element that is initially defined in the level playfield,
13627 not the player element that corresponds to the runtime player number
13628 (example: a level that contains EL_PLAYER_3 as the only player would
13629 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13630 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13632 CheckElementChangeBySide(x, y, hitting_element, player_element,
13633 CE_HITTING_X, touched_side);
13638 // "hitting something" is also true when hitting the playfield border
13639 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13640 CE_HITTING_SOMETHING, direction);
13643 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13645 int i, kill_x = -1, kill_y = -1;
13647 int bad_element = -1;
13648 struct XY *test_xy = xy_topdown;
13649 static int test_dir[4] =
13657 for (i = 0; i < NUM_DIRECTIONS; i++)
13659 int test_x, test_y, test_move_dir, test_element;
13661 test_x = good_x + test_xy[i].x;
13662 test_y = good_y + test_xy[i].y;
13664 if (!IN_LEV_FIELD(test_x, test_y))
13668 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13670 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13672 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13673 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13675 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13676 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13680 bad_element = test_element;
13686 if (kill_x != -1 || kill_y != -1)
13688 if (IS_PLAYER(good_x, good_y))
13690 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13692 if (player->shield_deadly_time_left > 0 &&
13693 !IS_INDESTRUCTIBLE(bad_element))
13694 Bang(kill_x, kill_y);
13695 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13696 KillPlayer(player);
13699 Bang(good_x, good_y);
13703 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13705 int i, kill_x = -1, kill_y = -1;
13706 int bad_element = Tile[bad_x][bad_y];
13707 struct XY *test_xy = xy_topdown;
13708 static int touch_dir[4] =
13710 MV_LEFT | MV_RIGHT,
13715 static int test_dir[4] =
13723 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13726 for (i = 0; i < NUM_DIRECTIONS; i++)
13728 int test_x, test_y, test_move_dir, test_element;
13730 test_x = bad_x + test_xy[i].x;
13731 test_y = bad_y + test_xy[i].y;
13733 if (!IN_LEV_FIELD(test_x, test_y))
13737 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13739 test_element = Tile[test_x][test_y];
13741 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13742 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13744 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13745 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13747 // good thing is player or penguin that does not move away
13748 if (IS_PLAYER(test_x, test_y))
13750 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13752 if (bad_element == EL_ROBOT && player->is_moving)
13753 continue; // robot does not kill player if he is moving
13755 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13757 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13758 continue; // center and border element do not touch
13766 else if (test_element == EL_PENGUIN)
13776 if (kill_x != -1 || kill_y != -1)
13778 if (IS_PLAYER(kill_x, kill_y))
13780 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13782 if (player->shield_deadly_time_left > 0 &&
13783 !IS_INDESTRUCTIBLE(bad_element))
13784 Bang(bad_x, bad_y);
13785 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13786 KillPlayer(player);
13789 Bang(kill_x, kill_y);
13793 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13795 int bad_element = Tile[bad_x][bad_y];
13796 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13797 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13798 int test_x = bad_x + dx, test_y = bad_y + dy;
13799 int test_move_dir, test_element;
13800 int kill_x = -1, kill_y = -1;
13802 if (!IN_LEV_FIELD(test_x, test_y))
13806 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13808 test_element = Tile[test_x][test_y];
13810 if (test_move_dir != bad_move_dir)
13812 // good thing can be player or penguin that does not move away
13813 if (IS_PLAYER(test_x, test_y))
13815 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13817 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13818 player as being hit when he is moving towards the bad thing, because
13819 the "get hit by" condition would be lost after the player stops) */
13820 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13821 return; // player moves away from bad thing
13826 else if (test_element == EL_PENGUIN)
13833 if (kill_x != -1 || kill_y != -1)
13835 if (IS_PLAYER(kill_x, kill_y))
13837 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13839 if (player->shield_deadly_time_left > 0 &&
13840 !IS_INDESTRUCTIBLE(bad_element))
13841 Bang(bad_x, bad_y);
13842 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13843 KillPlayer(player);
13846 Bang(kill_x, kill_y);
13850 void TestIfPlayerTouchesBadThing(int x, int y)
13852 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13855 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13857 TestIfGoodThingHitsBadThing(x, y, move_dir);
13860 void TestIfBadThingTouchesPlayer(int x, int y)
13862 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13865 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13867 TestIfBadThingHitsGoodThing(x, y, move_dir);
13870 void TestIfFriendTouchesBadThing(int x, int y)
13872 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13875 void TestIfBadThingTouchesFriend(int x, int y)
13877 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13880 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13882 int i, kill_x = bad_x, kill_y = bad_y;
13883 struct XY *xy = xy_topdown;
13885 for (i = 0; i < NUM_DIRECTIONS; i++)
13889 x = bad_x + xy[i].x;
13890 y = bad_y + xy[i].y;
13891 if (!IN_LEV_FIELD(x, y))
13894 element = Tile[x][y];
13895 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13896 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13904 if (kill_x != bad_x || kill_y != bad_y)
13905 Bang(bad_x, bad_y);
13908 void KillPlayer(struct PlayerInfo *player)
13910 int jx = player->jx, jy = player->jy;
13912 if (!player->active)
13916 Debug("game:playing:KillPlayer",
13917 "0: killed == %d, active == %d, reanimated == %d",
13918 player->killed, player->active, player->reanimated);
13921 /* the following code was introduced to prevent an infinite loop when calling
13923 -> CheckTriggeredElementChangeExt()
13924 -> ExecuteCustomElementAction()
13926 -> (infinitely repeating the above sequence of function calls)
13927 which occurs when killing the player while having a CE with the setting
13928 "kill player X when explosion of <player X>"; the solution using a new
13929 field "player->killed" was chosen for backwards compatibility, although
13930 clever use of the fields "player->active" etc. would probably also work */
13932 if (player->killed)
13936 player->killed = TRUE;
13938 // remove accessible field at the player's position
13939 Tile[jx][jy] = EL_EMPTY;
13941 // deactivate shield (else Bang()/Explode() would not work right)
13942 player->shield_normal_time_left = 0;
13943 player->shield_deadly_time_left = 0;
13946 Debug("game:playing:KillPlayer",
13947 "1: killed == %d, active == %d, reanimated == %d",
13948 player->killed, player->active, player->reanimated);
13954 Debug("game:playing:KillPlayer",
13955 "2: killed == %d, active == %d, reanimated == %d",
13956 player->killed, player->active, player->reanimated);
13959 if (player->reanimated) // killed player may have been reanimated
13960 player->killed = player->reanimated = FALSE;
13962 BuryPlayer(player);
13965 static void KillPlayerUnlessEnemyProtected(int x, int y)
13967 if (!PLAYER_ENEMY_PROTECTED(x, y))
13968 KillPlayer(PLAYERINFO(x, y));
13971 static void KillPlayerUnlessExplosionProtected(int x, int y)
13973 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13974 KillPlayer(PLAYERINFO(x, y));
13977 void BuryPlayer(struct PlayerInfo *player)
13979 int jx = player->jx, jy = player->jy;
13981 if (!player->active)
13984 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13985 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13987 RemovePlayer(player);
13989 player->buried = TRUE;
13991 if (game.all_players_gone)
13992 game.GameOver = TRUE;
13995 void RemovePlayer(struct PlayerInfo *player)
13997 int jx = player->jx, jy = player->jy;
13998 int i, found = FALSE;
14000 player->present = FALSE;
14001 player->active = FALSE;
14003 // required for some CE actions (even if the player is not active anymore)
14004 player->MovPos = 0;
14006 if (!ExplodeField[jx][jy])
14007 StorePlayer[jx][jy] = 0;
14009 if (player->is_moving)
14010 TEST_DrawLevelField(player->last_jx, player->last_jy);
14012 for (i = 0; i < MAX_PLAYERS; i++)
14013 if (stored_player[i].active)
14018 game.all_players_gone = TRUE;
14019 game.GameOver = TRUE;
14022 game.exit_x = game.robot_wheel_x = jx;
14023 game.exit_y = game.robot_wheel_y = jy;
14026 void ExitPlayer(struct PlayerInfo *player)
14028 DrawPlayer(player); // needed here only to cleanup last field
14029 RemovePlayer(player);
14031 if (game.players_still_needed > 0)
14032 game.players_still_needed--;
14035 static void SetFieldForSnapping(int x, int y, int element, int direction,
14036 int player_index_bit)
14038 struct ElementInfo *ei = &element_info[element];
14039 int direction_bit = MV_DIR_TO_BIT(direction);
14040 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14041 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14042 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14044 Tile[x][y] = EL_ELEMENT_SNAPPING;
14045 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14046 MovDir[x][y] = direction;
14047 Store[x][y] = element;
14048 Store2[x][y] = player_index_bit;
14050 ResetGfxAnimation(x, y);
14052 GfxElement[x][y] = element;
14053 GfxAction[x][y] = action;
14054 GfxDir[x][y] = direction;
14055 GfxFrame[x][y] = -1;
14058 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14059 int player_index_bit)
14061 TestIfElementTouchesCustomElement(x, y); // for empty space
14063 if (level.finish_dig_collect)
14065 int dig_side = MV_DIR_OPPOSITE(direction);
14066 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14067 CE_PLAYER_COLLECTS_X);
14069 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14070 player_index_bit, dig_side);
14071 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14072 player_index_bit, dig_side);
14077 =============================================================================
14078 checkDiagonalPushing()
14079 -----------------------------------------------------------------------------
14080 check if diagonal input device direction results in pushing of object
14081 (by checking if the alternative direction is walkable, diggable, ...)
14082 =============================================================================
14085 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14086 int x, int y, int real_dx, int real_dy)
14088 int jx, jy, dx, dy, xx, yy;
14090 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14093 // diagonal direction: check alternative direction
14098 xx = jx + (dx == 0 ? real_dx : 0);
14099 yy = jy + (dy == 0 ? real_dy : 0);
14101 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14105 =============================================================================
14107 -----------------------------------------------------------------------------
14108 x, y: field next to player (non-diagonal) to try to dig to
14109 real_dx, real_dy: direction as read from input device (can be diagonal)
14110 =============================================================================
14113 static int DigField(struct PlayerInfo *player,
14114 int oldx, int oldy, int x, int y,
14115 int real_dx, int real_dy, int mode)
14117 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14118 boolean player_was_pushing = player->is_pushing;
14119 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14120 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14121 int jx = oldx, jy = oldy;
14122 int dx = x - jx, dy = y - jy;
14123 int nextx = x + dx, nexty = y + dy;
14124 int move_direction = (dx == -1 ? MV_LEFT :
14125 dx == +1 ? MV_RIGHT :
14127 dy == +1 ? MV_DOWN : MV_NONE);
14128 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14129 int dig_side = MV_DIR_OPPOSITE(move_direction);
14130 int old_element = Tile[jx][jy];
14131 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14134 if (is_player) // function can also be called by EL_PENGUIN
14136 if (player->MovPos == 0)
14138 player->is_digging = FALSE;
14139 player->is_collecting = FALSE;
14142 if (player->MovPos == 0) // last pushing move finished
14143 player->is_pushing = FALSE;
14145 if (mode == DF_NO_PUSH) // player just stopped pushing
14147 player->is_switching = FALSE;
14148 player->push_delay = -1;
14150 return MP_NO_ACTION;
14153 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14154 old_element = Back[jx][jy];
14156 // in case of element dropped at player position, check background
14157 else if (Back[jx][jy] != EL_EMPTY &&
14158 game.engine_version >= VERSION_IDENT(2,2,0,0))
14159 old_element = Back[jx][jy];
14161 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14162 return MP_NO_ACTION; // field has no opening in this direction
14164 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14165 return MP_NO_ACTION; // field has no opening in this direction
14167 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14171 Tile[jx][jy] = player->artwork_element;
14172 InitMovingField(jx, jy, MV_DOWN);
14173 Store[jx][jy] = EL_ACID;
14174 ContinueMoving(jx, jy);
14175 BuryPlayer(player);
14177 return MP_DONT_RUN_INTO;
14180 if (player_can_move && DONT_RUN_INTO(element))
14182 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14184 return MP_DONT_RUN_INTO;
14187 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14188 return MP_NO_ACTION;
14190 collect_count = element_info[element].collect_count_initial;
14192 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14193 return MP_NO_ACTION;
14195 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14196 player_can_move = player_can_move_or_snap;
14198 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14199 game.engine_version >= VERSION_IDENT(2,2,0,0))
14201 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14202 player->index_bit, dig_side);
14203 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14204 player->index_bit, dig_side);
14206 if (element == EL_DC_LANDMINE)
14209 if (Tile[x][y] != element) // field changed by snapping
14212 return MP_NO_ACTION;
14215 if (player->gravity && is_player && !player->is_auto_moving &&
14216 canFallDown(player) && move_direction != MV_DOWN &&
14217 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14218 return MP_NO_ACTION; // player cannot walk here due to gravity
14220 if (player_can_move &&
14221 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14223 int sound_element = SND_ELEMENT(element);
14224 int sound_action = ACTION_WALKING;
14226 if (IS_RND_GATE(element))
14228 if (!player->key[RND_GATE_NR(element)])
14229 return MP_NO_ACTION;
14231 else if (IS_RND_GATE_GRAY(element))
14233 if (!player->key[RND_GATE_GRAY_NR(element)])
14234 return MP_NO_ACTION;
14236 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14238 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14239 return MP_NO_ACTION;
14241 else if (element == EL_EXIT_OPEN ||
14242 element == EL_EM_EXIT_OPEN ||
14243 element == EL_EM_EXIT_OPENING ||
14244 element == EL_STEEL_EXIT_OPEN ||
14245 element == EL_EM_STEEL_EXIT_OPEN ||
14246 element == EL_EM_STEEL_EXIT_OPENING ||
14247 element == EL_SP_EXIT_OPEN ||
14248 element == EL_SP_EXIT_OPENING)
14250 sound_action = ACTION_PASSING; // player is passing exit
14252 else if (element == EL_EMPTY)
14254 sound_action = ACTION_MOVING; // nothing to walk on
14257 // play sound from background or player, whatever is available
14258 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14259 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14261 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14263 else if (player_can_move &&
14264 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14266 if (!ACCESS_FROM(element, opposite_direction))
14267 return MP_NO_ACTION; // field not accessible from this direction
14269 if (CAN_MOVE(element)) // only fixed elements can be passed!
14270 return MP_NO_ACTION;
14272 if (IS_EM_GATE(element))
14274 if (!player->key[EM_GATE_NR(element)])
14275 return MP_NO_ACTION;
14277 else if (IS_EM_GATE_GRAY(element))
14279 if (!player->key[EM_GATE_GRAY_NR(element)])
14280 return MP_NO_ACTION;
14282 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14284 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14285 return MP_NO_ACTION;
14287 else if (IS_EMC_GATE(element))
14289 if (!player->key[EMC_GATE_NR(element)])
14290 return MP_NO_ACTION;
14292 else if (IS_EMC_GATE_GRAY(element))
14294 if (!player->key[EMC_GATE_GRAY_NR(element)])
14295 return MP_NO_ACTION;
14297 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14299 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14300 return MP_NO_ACTION;
14302 else if (element == EL_DC_GATE_WHITE ||
14303 element == EL_DC_GATE_WHITE_GRAY ||
14304 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14306 if (player->num_white_keys == 0)
14307 return MP_NO_ACTION;
14309 player->num_white_keys--;
14311 else if (IS_SP_PORT(element))
14313 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14314 element == EL_SP_GRAVITY_PORT_RIGHT ||
14315 element == EL_SP_GRAVITY_PORT_UP ||
14316 element == EL_SP_GRAVITY_PORT_DOWN)
14317 player->gravity = !player->gravity;
14318 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14319 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14320 element == EL_SP_GRAVITY_ON_PORT_UP ||
14321 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14322 player->gravity = TRUE;
14323 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14324 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14325 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14326 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14327 player->gravity = FALSE;
14330 // automatically move to the next field with double speed
14331 player->programmed_action = move_direction;
14333 if (player->move_delay_reset_counter == 0)
14335 player->move_delay_reset_counter = 2; // two double speed steps
14337 DOUBLE_PLAYER_SPEED(player);
14340 PlayLevelSoundAction(x, y, ACTION_PASSING);
14342 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14346 if (mode != DF_SNAP)
14348 GfxElement[x][y] = GFX_ELEMENT(element);
14349 player->is_digging = TRUE;
14352 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14354 // use old behaviour for old levels (digging)
14355 if (!level.finish_dig_collect)
14357 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14358 player->index_bit, dig_side);
14360 // if digging triggered player relocation, finish digging tile
14361 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14362 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14365 if (mode == DF_SNAP)
14367 if (level.block_snap_field)
14368 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14370 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14372 // use old behaviour for old levels (snapping)
14373 if (!level.finish_dig_collect)
14374 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14375 player->index_bit, dig_side);
14378 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14382 if (is_player && mode != DF_SNAP)
14384 GfxElement[x][y] = element;
14385 player->is_collecting = TRUE;
14388 if (element == EL_SPEED_PILL)
14390 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14392 else if (element == EL_EXTRA_TIME && level.time > 0)
14394 TimeLeft += level.extra_time;
14396 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14398 DisplayGameControlValues();
14400 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14402 int shield_time = (element == EL_SHIELD_DEADLY ?
14403 level.shield_deadly_time :
14404 level.shield_normal_time);
14406 player->shield_normal_time_left += shield_time;
14407 if (element == EL_SHIELD_DEADLY)
14408 player->shield_deadly_time_left += shield_time;
14410 else if (element == EL_DYNAMITE ||
14411 element == EL_EM_DYNAMITE ||
14412 element == EL_SP_DISK_RED)
14414 if (player->inventory_size < MAX_INVENTORY_SIZE)
14415 player->inventory_element[player->inventory_size++] = element;
14417 DrawGameDoorValues();
14419 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14421 player->dynabomb_count++;
14422 player->dynabombs_left++;
14424 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14426 player->dynabomb_size++;
14428 else if (element == EL_DYNABOMB_INCREASE_POWER)
14430 player->dynabomb_xl = TRUE;
14432 else if (IS_KEY(element))
14434 player->key[KEY_NR(element)] = TRUE;
14436 DrawGameDoorValues();
14438 else if (element == EL_DC_KEY_WHITE)
14440 player->num_white_keys++;
14442 // display white keys?
14443 // DrawGameDoorValues();
14445 else if (IS_ENVELOPE(element))
14447 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14449 if (!wait_for_snapping)
14450 player->show_envelope = element;
14452 else if (element == EL_EMC_LENSES)
14454 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14456 RedrawAllInvisibleElementsForLenses();
14458 else if (element == EL_EMC_MAGNIFIER)
14460 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14462 RedrawAllInvisibleElementsForMagnifier();
14464 else if (IS_DROPPABLE(element) ||
14465 IS_THROWABLE(element)) // can be collected and dropped
14469 if (collect_count == 0)
14470 player->inventory_infinite_element = element;
14472 for (i = 0; i < collect_count; i++)
14473 if (player->inventory_size < MAX_INVENTORY_SIZE)
14474 player->inventory_element[player->inventory_size++] = element;
14476 DrawGameDoorValues();
14478 else if (collect_count > 0)
14480 game.gems_still_needed -= collect_count;
14481 if (game.gems_still_needed < 0)
14482 game.gems_still_needed = 0;
14484 game.snapshot.collected_item = TRUE;
14486 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14488 DisplayGameControlValues();
14491 RaiseScoreElement(element);
14492 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14494 // use old behaviour for old levels (collecting)
14495 if (!level.finish_dig_collect && is_player)
14497 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14498 player->index_bit, dig_side);
14500 // if collecting triggered player relocation, finish collecting tile
14501 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14502 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14505 if (mode == DF_SNAP)
14507 if (level.block_snap_field)
14508 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14510 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14512 // use old behaviour for old levels (snapping)
14513 if (!level.finish_dig_collect)
14514 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14515 player->index_bit, dig_side);
14518 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14520 if (mode == DF_SNAP && element != EL_BD_ROCK)
14521 return MP_NO_ACTION;
14523 if (CAN_FALL(element) && dy)
14524 return MP_NO_ACTION;
14526 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14527 !(element == EL_SPRING && level.use_spring_bug))
14528 return MP_NO_ACTION;
14530 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14531 ((move_direction & MV_VERTICAL &&
14532 ((element_info[element].move_pattern & MV_LEFT &&
14533 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14534 (element_info[element].move_pattern & MV_RIGHT &&
14535 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14536 (move_direction & MV_HORIZONTAL &&
14537 ((element_info[element].move_pattern & MV_UP &&
14538 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14539 (element_info[element].move_pattern & MV_DOWN &&
14540 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14541 return MP_NO_ACTION;
14543 // do not push elements already moving away faster than player
14544 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14545 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14546 return MP_NO_ACTION;
14548 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14550 if (player->push_delay_value == -1 || !player_was_pushing)
14551 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14553 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14555 if (player->push_delay_value == -1)
14556 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14558 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14560 if (!player->is_pushing)
14561 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14564 player->is_pushing = TRUE;
14565 player->is_active = TRUE;
14567 if (!(IN_LEV_FIELD(nextx, nexty) &&
14568 (IS_FREE(nextx, nexty) ||
14569 (IS_SB_ELEMENT(element) &&
14570 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14571 (IS_CUSTOM_ELEMENT(element) &&
14572 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14573 return MP_NO_ACTION;
14575 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14576 return MP_NO_ACTION;
14578 if (player->push_delay == -1) // new pushing; restart delay
14579 player->push_delay = 0;
14581 if (player->push_delay < player->push_delay_value &&
14582 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14583 element != EL_SPRING && element != EL_BALLOON)
14585 // make sure that there is no move delay before next try to push
14586 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14587 player->move_delay = 0;
14589 return MP_NO_ACTION;
14592 if (IS_CUSTOM_ELEMENT(element) &&
14593 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14595 if (!DigFieldByCE(nextx, nexty, element))
14596 return MP_NO_ACTION;
14599 if (IS_SB_ELEMENT(element))
14601 boolean sokoban_task_solved = FALSE;
14603 if (element == EL_SOKOBAN_FIELD_FULL)
14605 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14607 IncrementSokobanFieldsNeeded();
14608 IncrementSokobanObjectsNeeded();
14611 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14613 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14615 DecrementSokobanFieldsNeeded();
14616 DecrementSokobanObjectsNeeded();
14618 // sokoban object was pushed from empty field to sokoban field
14619 if (Back[x][y] == EL_EMPTY)
14620 sokoban_task_solved = TRUE;
14623 Tile[x][y] = EL_SOKOBAN_OBJECT;
14625 if (Back[x][y] == Back[nextx][nexty])
14626 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14627 else if (Back[x][y] != 0)
14628 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14631 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14634 if (sokoban_task_solved &&
14635 game.sokoban_fields_still_needed == 0 &&
14636 game.sokoban_objects_still_needed == 0 &&
14637 level.auto_exit_sokoban)
14639 game.players_still_needed = 0;
14643 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14647 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14649 InitMovingField(x, y, move_direction);
14650 GfxAction[x][y] = ACTION_PUSHING;
14652 if (mode == DF_SNAP)
14653 ContinueMoving(x, y);
14655 MovPos[x][y] = (dx != 0 ? dx : dy);
14657 Pushed[x][y] = TRUE;
14658 Pushed[nextx][nexty] = TRUE;
14660 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14661 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14663 player->push_delay_value = -1; // get new value later
14665 // check for element change _after_ element has been pushed
14666 if (game.use_change_when_pushing_bug)
14668 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14669 player->index_bit, dig_side);
14670 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14671 player->index_bit, dig_side);
14674 else if (IS_SWITCHABLE(element))
14676 if (PLAYER_SWITCHING(player, x, y))
14678 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14679 player->index_bit, dig_side);
14684 player->is_switching = TRUE;
14685 player->switch_x = x;
14686 player->switch_y = y;
14688 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14690 if (element == EL_ROBOT_WHEEL)
14692 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14694 game.robot_wheel_x = x;
14695 game.robot_wheel_y = y;
14696 game.robot_wheel_active = TRUE;
14698 TEST_DrawLevelField(x, y);
14700 else if (element == EL_SP_TERMINAL)
14704 SCAN_PLAYFIELD(xx, yy)
14706 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14710 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14712 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14714 ResetGfxAnimation(xx, yy);
14715 TEST_DrawLevelField(xx, yy);
14719 else if (IS_BELT_SWITCH(element))
14721 ToggleBeltSwitch(x, y);
14723 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14724 element == EL_SWITCHGATE_SWITCH_DOWN ||
14725 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14726 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14728 ToggleSwitchgateSwitch();
14730 else if (element == EL_LIGHT_SWITCH ||
14731 element == EL_LIGHT_SWITCH_ACTIVE)
14733 ToggleLightSwitch(x, y);
14735 else if (element == EL_TIMEGATE_SWITCH ||
14736 element == EL_DC_TIMEGATE_SWITCH)
14738 ActivateTimegateSwitch(x, y);
14740 else if (element == EL_BALLOON_SWITCH_LEFT ||
14741 element == EL_BALLOON_SWITCH_RIGHT ||
14742 element == EL_BALLOON_SWITCH_UP ||
14743 element == EL_BALLOON_SWITCH_DOWN ||
14744 element == EL_BALLOON_SWITCH_NONE ||
14745 element == EL_BALLOON_SWITCH_ANY)
14747 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14748 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14749 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14750 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14751 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14754 else if (element == EL_LAMP)
14756 Tile[x][y] = EL_LAMP_ACTIVE;
14757 game.lights_still_needed--;
14759 ResetGfxAnimation(x, y);
14760 TEST_DrawLevelField(x, y);
14762 else if (element == EL_TIME_ORB_FULL)
14764 Tile[x][y] = EL_TIME_ORB_EMPTY;
14766 if (level.time > 0 || level.use_time_orb_bug)
14768 TimeLeft += level.time_orb_time;
14769 game.no_level_time_limit = FALSE;
14771 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14773 DisplayGameControlValues();
14776 ResetGfxAnimation(x, y);
14777 TEST_DrawLevelField(x, y);
14779 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14780 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14784 game.ball_active = !game.ball_active;
14786 SCAN_PLAYFIELD(xx, yy)
14788 int e = Tile[xx][yy];
14790 if (game.ball_active)
14792 if (e == EL_EMC_MAGIC_BALL)
14793 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14794 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14795 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14799 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14800 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14801 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14802 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14807 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14808 player->index_bit, dig_side);
14810 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14811 player->index_bit, dig_side);
14813 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14814 player->index_bit, dig_side);
14820 if (!PLAYER_SWITCHING(player, x, y))
14822 player->is_switching = TRUE;
14823 player->switch_x = x;
14824 player->switch_y = y;
14826 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14827 player->index_bit, dig_side);
14828 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14829 player->index_bit, dig_side);
14831 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14832 player->index_bit, dig_side);
14833 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14834 player->index_bit, dig_side);
14837 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14838 player->index_bit, dig_side);
14839 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14840 player->index_bit, dig_side);
14842 return MP_NO_ACTION;
14845 player->push_delay = -1;
14847 if (is_player) // function can also be called by EL_PENGUIN
14849 if (Tile[x][y] != element) // really digged/collected something
14851 player->is_collecting = !player->is_digging;
14852 player->is_active = TRUE;
14854 player->last_removed_element = element;
14861 static boolean DigFieldByCE(int x, int y, int digging_element)
14863 int element = Tile[x][y];
14865 if (!IS_FREE(x, y))
14867 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14868 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14871 // no element can dig solid indestructible elements
14872 if (IS_INDESTRUCTIBLE(element) &&
14873 !IS_DIGGABLE(element) &&
14874 !IS_COLLECTIBLE(element))
14877 if (AmoebaNr[x][y] &&
14878 (element == EL_AMOEBA_FULL ||
14879 element == EL_BD_AMOEBA ||
14880 element == EL_AMOEBA_GROWING))
14882 AmoebaCnt[AmoebaNr[x][y]]--;
14883 AmoebaCnt2[AmoebaNr[x][y]]--;
14886 if (IS_MOVING(x, y))
14887 RemoveMovingField(x, y);
14891 TEST_DrawLevelField(x, y);
14894 // if digged element was about to explode, prevent the explosion
14895 ExplodeField[x][y] = EX_TYPE_NONE;
14897 PlayLevelSoundAction(x, y, action);
14900 Store[x][y] = EL_EMPTY;
14902 // this makes it possible to leave the removed element again
14903 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14904 Store[x][y] = element;
14909 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14911 int jx = player->jx, jy = player->jy;
14912 int x = jx + dx, y = jy + dy;
14913 int snap_direction = (dx == -1 ? MV_LEFT :
14914 dx == +1 ? MV_RIGHT :
14916 dy == +1 ? MV_DOWN : MV_NONE);
14917 boolean can_continue_snapping = (level.continuous_snapping &&
14918 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14920 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14923 if (!player->active || !IN_LEV_FIELD(x, y))
14931 if (player->MovPos == 0)
14932 player->is_pushing = FALSE;
14934 player->is_snapping = FALSE;
14936 if (player->MovPos == 0)
14938 player->is_moving = FALSE;
14939 player->is_digging = FALSE;
14940 player->is_collecting = FALSE;
14946 // prevent snapping with already pressed snap key when not allowed
14947 if (player->is_snapping && !can_continue_snapping)
14950 player->MovDir = snap_direction;
14952 if (player->MovPos == 0)
14954 player->is_moving = FALSE;
14955 player->is_digging = FALSE;
14956 player->is_collecting = FALSE;
14959 player->is_dropping = FALSE;
14960 player->is_dropping_pressed = FALSE;
14961 player->drop_pressed_delay = 0;
14963 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14966 player->is_snapping = TRUE;
14967 player->is_active = TRUE;
14969 if (player->MovPos == 0)
14971 player->is_moving = FALSE;
14972 player->is_digging = FALSE;
14973 player->is_collecting = FALSE;
14976 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14977 TEST_DrawLevelField(player->last_jx, player->last_jy);
14979 TEST_DrawLevelField(x, y);
14984 static boolean DropElement(struct PlayerInfo *player)
14986 int old_element, new_element;
14987 int dropx = player->jx, dropy = player->jy;
14988 int drop_direction = player->MovDir;
14989 int drop_side = drop_direction;
14990 int drop_element = get_next_dropped_element(player);
14992 /* do not drop an element on top of another element; when holding drop key
14993 pressed without moving, dropped element must move away before the next
14994 element can be dropped (this is especially important if the next element
14995 is dynamite, which can be placed on background for historical reasons) */
14996 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14999 if (IS_THROWABLE(drop_element))
15001 dropx += GET_DX_FROM_DIR(drop_direction);
15002 dropy += GET_DY_FROM_DIR(drop_direction);
15004 if (!IN_LEV_FIELD(dropx, dropy))
15008 old_element = Tile[dropx][dropy]; // old element at dropping position
15009 new_element = drop_element; // default: no change when dropping
15011 // check if player is active, not moving and ready to drop
15012 if (!player->active || player->MovPos || player->drop_delay > 0)
15015 // check if player has anything that can be dropped
15016 if (new_element == EL_UNDEFINED)
15019 // only set if player has anything that can be dropped
15020 player->is_dropping_pressed = TRUE;
15022 // check if drop key was pressed long enough for EM style dynamite
15023 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15026 // check if anything can be dropped at the current position
15027 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15030 // collected custom elements can only be dropped on empty fields
15031 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15034 if (old_element != EL_EMPTY)
15035 Back[dropx][dropy] = old_element; // store old element on this field
15037 ResetGfxAnimation(dropx, dropy);
15038 ResetRandomAnimationValue(dropx, dropy);
15040 if (player->inventory_size > 0 ||
15041 player->inventory_infinite_element != EL_UNDEFINED)
15043 if (player->inventory_size > 0)
15045 player->inventory_size--;
15047 DrawGameDoorValues();
15049 if (new_element == EL_DYNAMITE)
15050 new_element = EL_DYNAMITE_ACTIVE;
15051 else if (new_element == EL_EM_DYNAMITE)
15052 new_element = EL_EM_DYNAMITE_ACTIVE;
15053 else if (new_element == EL_SP_DISK_RED)
15054 new_element = EL_SP_DISK_RED_ACTIVE;
15057 Tile[dropx][dropy] = new_element;
15059 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15060 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15061 el2img(Tile[dropx][dropy]), 0);
15063 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15065 // needed if previous element just changed to "empty" in the last frame
15066 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15068 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15069 player->index_bit, drop_side);
15070 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15072 player->index_bit, drop_side);
15074 TestIfElementTouchesCustomElement(dropx, dropy);
15076 else // player is dropping a dyna bomb
15078 player->dynabombs_left--;
15080 Tile[dropx][dropy] = new_element;
15082 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15083 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15084 el2img(Tile[dropx][dropy]), 0);
15086 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15089 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15090 InitField_WithBug1(dropx, dropy, FALSE);
15092 new_element = Tile[dropx][dropy]; // element might have changed
15094 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15095 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15097 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15098 MovDir[dropx][dropy] = drop_direction;
15100 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15102 // do not cause impact style collision by dropping elements that can fall
15103 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15106 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15107 player->is_dropping = TRUE;
15109 player->drop_pressed_delay = 0;
15110 player->is_dropping_pressed = FALSE;
15112 player->drop_x = dropx;
15113 player->drop_y = dropy;
15118 // ----------------------------------------------------------------------------
15119 // game sound playing functions
15120 // ----------------------------------------------------------------------------
15122 static int *loop_sound_frame = NULL;
15123 static int *loop_sound_volume = NULL;
15125 void InitPlayLevelSound(void)
15127 int num_sounds = getSoundListSize();
15129 checked_free(loop_sound_frame);
15130 checked_free(loop_sound_volume);
15132 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15133 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15136 static void PlayLevelSound(int x, int y, int nr)
15138 int sx = SCREENX(x), sy = SCREENY(y);
15139 int volume, stereo_position;
15140 int max_distance = 8;
15141 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15143 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15144 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15147 if (!IN_LEV_FIELD(x, y) ||
15148 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15149 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15152 volume = SOUND_MAX_VOLUME;
15154 if (!IN_SCR_FIELD(sx, sy))
15156 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15157 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15159 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15162 stereo_position = (SOUND_MAX_LEFT +
15163 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15164 (SCR_FIELDX + 2 * max_distance));
15166 if (IS_LOOP_SOUND(nr))
15168 /* This assures that quieter loop sounds do not overwrite louder ones,
15169 while restarting sound volume comparison with each new game frame. */
15171 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15174 loop_sound_volume[nr] = volume;
15175 loop_sound_frame[nr] = FrameCounter;
15178 PlaySoundExt(nr, volume, stereo_position, type);
15181 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15183 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15184 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15185 y < LEVELY(BY1) ? LEVELY(BY1) :
15186 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15190 static void PlayLevelSoundAction(int x, int y, int action)
15192 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15195 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15197 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15199 if (sound_effect != SND_UNDEFINED)
15200 PlayLevelSound(x, y, sound_effect);
15203 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15206 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15208 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15209 PlayLevelSound(x, y, sound_effect);
15212 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15214 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15216 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15217 PlayLevelSound(x, y, sound_effect);
15220 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15222 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15224 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15225 StopSound(sound_effect);
15228 static int getLevelMusicNr(void)
15230 if (levelset.music[level_nr] != MUS_UNDEFINED)
15231 return levelset.music[level_nr]; // from config file
15233 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15236 static void FadeLevelSounds(void)
15241 static void FadeLevelMusic(void)
15243 int music_nr = getLevelMusicNr();
15244 char *curr_music = getCurrentlyPlayingMusicFilename();
15245 char *next_music = getMusicInfoEntryFilename(music_nr);
15247 if (!strEqual(curr_music, next_music))
15251 void FadeLevelSoundsAndMusic(void)
15257 static void PlayLevelMusic(void)
15259 int music_nr = getLevelMusicNr();
15260 char *curr_music = getCurrentlyPlayingMusicFilename();
15261 char *next_music = getMusicInfoEntryFilename(music_nr);
15263 if (!strEqual(curr_music, next_music))
15264 PlayMusicLoop(music_nr);
15267 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15269 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15271 int x = xx - offset;
15272 int y = yy - offset;
15277 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15281 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15285 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15289 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15293 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15297 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15301 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15304 case SOUND_android_clone:
15305 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15308 case SOUND_android_move:
15309 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15313 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15317 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15321 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15324 case SOUND_eater_eat:
15325 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15329 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15332 case SOUND_collect:
15333 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15336 case SOUND_diamond:
15337 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15341 // !!! CHECK THIS !!!
15343 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15345 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15349 case SOUND_wonderfall:
15350 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15354 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15358 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15362 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15366 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15370 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15374 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15378 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15382 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15385 case SOUND_exit_open:
15386 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15389 case SOUND_exit_leave:
15390 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15393 case SOUND_dynamite:
15394 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15398 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15402 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15406 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15410 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15414 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15418 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15422 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15427 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15429 int element = map_element_SP_to_RND(element_sp);
15430 int action = map_action_SP_to_RND(action_sp);
15431 int offset = (setup.sp_show_border_elements ? 0 : 1);
15432 int x = xx - offset;
15433 int y = yy - offset;
15435 PlayLevelSoundElementAction(x, y, element, action);
15438 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15440 int element = map_element_MM_to_RND(element_mm);
15441 int action = map_action_MM_to_RND(action_mm);
15443 int x = xx - offset;
15444 int y = yy - offset;
15446 if (!IS_MM_ELEMENT(element))
15447 element = EL_MM_DEFAULT;
15449 PlayLevelSoundElementAction(x, y, element, action);
15452 void PlaySound_MM(int sound_mm)
15454 int sound = map_sound_MM_to_RND(sound_mm);
15456 if (sound == SND_UNDEFINED)
15462 void PlaySoundLoop_MM(int sound_mm)
15464 int sound = map_sound_MM_to_RND(sound_mm);
15466 if (sound == SND_UNDEFINED)
15469 PlaySoundLoop(sound);
15472 void StopSound_MM(int sound_mm)
15474 int sound = map_sound_MM_to_RND(sound_mm);
15476 if (sound == SND_UNDEFINED)
15482 void RaiseScore(int value)
15484 game.score += value;
15486 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15488 DisplayGameControlValues();
15491 void RaiseScoreElement(int element)
15496 case EL_BD_DIAMOND:
15497 case EL_EMERALD_YELLOW:
15498 case EL_EMERALD_RED:
15499 case EL_EMERALD_PURPLE:
15500 case EL_SP_INFOTRON:
15501 RaiseScore(level.score[SC_EMERALD]);
15504 RaiseScore(level.score[SC_DIAMOND]);
15507 RaiseScore(level.score[SC_CRYSTAL]);
15510 RaiseScore(level.score[SC_PEARL]);
15513 case EL_BD_BUTTERFLY:
15514 case EL_SP_ELECTRON:
15515 RaiseScore(level.score[SC_BUG]);
15518 case EL_BD_FIREFLY:
15519 case EL_SP_SNIKSNAK:
15520 RaiseScore(level.score[SC_SPACESHIP]);
15523 case EL_DARK_YAMYAM:
15524 RaiseScore(level.score[SC_YAMYAM]);
15527 RaiseScore(level.score[SC_ROBOT]);
15530 RaiseScore(level.score[SC_PACMAN]);
15533 RaiseScore(level.score[SC_NUT]);
15536 case EL_EM_DYNAMITE:
15537 case EL_SP_DISK_RED:
15538 case EL_DYNABOMB_INCREASE_NUMBER:
15539 case EL_DYNABOMB_INCREASE_SIZE:
15540 case EL_DYNABOMB_INCREASE_POWER:
15541 RaiseScore(level.score[SC_DYNAMITE]);
15543 case EL_SHIELD_NORMAL:
15544 case EL_SHIELD_DEADLY:
15545 RaiseScore(level.score[SC_SHIELD]);
15547 case EL_EXTRA_TIME:
15548 RaiseScore(level.extra_time_score);
15562 case EL_DC_KEY_WHITE:
15563 RaiseScore(level.score[SC_KEY]);
15566 RaiseScore(element_info[element].collect_score);
15571 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15573 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15577 // prevent short reactivation of overlay buttons while closing door
15578 SetOverlayActive(FALSE);
15579 UnmapGameButtons();
15581 // door may still be open due to skipped or envelope style request
15582 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15585 if (network.enabled)
15586 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15590 FadeSkipNextFadeIn();
15592 SetGameStatus(GAME_MODE_MAIN);
15597 else // continue playing the game
15599 if (tape.playing && tape.deactivate_display)
15600 TapeDeactivateDisplayOff(TRUE);
15602 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15604 if (tape.playing && tape.deactivate_display)
15605 TapeDeactivateDisplayOn();
15609 void RequestQuitGame(boolean escape_key_pressed)
15611 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15612 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15613 level_editor_test_game);
15614 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15615 quick_quit || score_info_tape_play);
15617 RequestQuitGameExt(skip_request, quick_quit,
15618 "Do you really want to quit the game?");
15621 void RequestRestartGame(char *message)
15623 game.restart_game_message = NULL;
15625 boolean has_started_game = hasStartedNetworkGame();
15626 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15628 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15630 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15634 // needed in case of envelope request to close game panel
15635 CloseDoor(DOOR_CLOSE_1);
15637 SetGameStatus(GAME_MODE_MAIN);
15643 void CheckGameOver(void)
15645 static boolean last_game_over = FALSE;
15646 static int game_over_delay = 0;
15647 int game_over_delay_value = 50;
15648 boolean game_over = checkGameFailed();
15650 // do not handle game over if request dialog is already active
15651 if (game.request_active)
15654 // do not ask to play again if game was never actually played
15655 if (!game.GamePlayed)
15660 last_game_over = FALSE;
15661 game_over_delay = game_over_delay_value;
15666 if (game_over_delay > 0)
15673 if (last_game_over != game_over)
15674 game.restart_game_message = (hasStartedNetworkGame() ?
15675 "Game over! Play it again?" :
15678 last_game_over = game_over;
15681 boolean checkGameSolved(void)
15683 // set for all game engines if level was solved
15684 return game.LevelSolved_GameEnd;
15687 boolean checkGameFailed(void)
15689 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15690 return (game_em.game_over && !game_em.level_solved);
15691 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15692 return (game_sp.game_over && !game_sp.level_solved);
15693 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15694 return (game_mm.game_over && !game_mm.level_solved);
15695 else // GAME_ENGINE_TYPE_RND
15696 return (game.GameOver && !game.LevelSolved);
15699 boolean checkGameEnded(void)
15701 return (checkGameSolved() || checkGameFailed());
15705 // ----------------------------------------------------------------------------
15706 // random generator functions
15707 // ----------------------------------------------------------------------------
15709 unsigned int InitEngineRandom_RND(int seed)
15711 game.num_random_calls = 0;
15713 return InitEngineRandom(seed);
15716 unsigned int RND(int max)
15720 game.num_random_calls++;
15722 return GetEngineRandom(max);
15729 // ----------------------------------------------------------------------------
15730 // game engine snapshot handling functions
15731 // ----------------------------------------------------------------------------
15733 struct EngineSnapshotInfo
15735 // runtime values for custom element collect score
15736 int collect_score[NUM_CUSTOM_ELEMENTS];
15738 // runtime values for group element choice position
15739 int choice_pos[NUM_GROUP_ELEMENTS];
15741 // runtime values for belt position animations
15742 int belt_graphic[4][NUM_BELT_PARTS];
15743 int belt_anim_mode[4][NUM_BELT_PARTS];
15746 static struct EngineSnapshotInfo engine_snapshot_rnd;
15747 static char *snapshot_level_identifier = NULL;
15748 static int snapshot_level_nr = -1;
15750 static void SaveEngineSnapshotValues_RND(void)
15752 static int belt_base_active_element[4] =
15754 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15755 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15756 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15757 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15761 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15763 int element = EL_CUSTOM_START + i;
15765 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15768 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15770 int element = EL_GROUP_START + i;
15772 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15775 for (i = 0; i < 4; i++)
15777 for (j = 0; j < NUM_BELT_PARTS; j++)
15779 int element = belt_base_active_element[i] + j;
15780 int graphic = el2img(element);
15781 int anim_mode = graphic_info[graphic].anim_mode;
15783 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15784 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15789 static void LoadEngineSnapshotValues_RND(void)
15791 unsigned int num_random_calls = game.num_random_calls;
15794 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15796 int element = EL_CUSTOM_START + i;
15798 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15801 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15803 int element = EL_GROUP_START + i;
15805 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15808 for (i = 0; i < 4; i++)
15810 for (j = 0; j < NUM_BELT_PARTS; j++)
15812 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15813 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15815 graphic_info[graphic].anim_mode = anim_mode;
15819 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15821 InitRND(tape.random_seed);
15822 for (i = 0; i < num_random_calls; i++)
15826 if (game.num_random_calls != num_random_calls)
15828 Error("number of random calls out of sync");
15829 Error("number of random calls should be %d", num_random_calls);
15830 Error("number of random calls is %d", game.num_random_calls);
15832 Fail("this should not happen -- please debug");
15836 void FreeEngineSnapshotSingle(void)
15838 FreeSnapshotSingle();
15840 setString(&snapshot_level_identifier, NULL);
15841 snapshot_level_nr = -1;
15844 void FreeEngineSnapshotList(void)
15846 FreeSnapshotList();
15849 static ListNode *SaveEngineSnapshotBuffers(void)
15851 ListNode *buffers = NULL;
15853 // copy some special values to a structure better suited for the snapshot
15855 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15856 SaveEngineSnapshotValues_RND();
15857 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15858 SaveEngineSnapshotValues_EM();
15859 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15860 SaveEngineSnapshotValues_SP(&buffers);
15861 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15862 SaveEngineSnapshotValues_MM();
15864 // save values stored in special snapshot structure
15866 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15867 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15868 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15869 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15870 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15871 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15872 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15873 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15875 // save further RND engine values
15877 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15878 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15879 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15881 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15882 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15883 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15884 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15885 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15887 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15888 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15889 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15891 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15893 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15894 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15896 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15897 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15898 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15899 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15900 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15901 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15902 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15903 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15904 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15905 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15906 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15907 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15908 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15909 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15910 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15911 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15912 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15913 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15915 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15916 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15918 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15919 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15920 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15922 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15923 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15925 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15927 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15928 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15929 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15930 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15933 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15936 ListNode *node = engine_snapshot_list_rnd;
15939 while (node != NULL)
15941 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15946 Debug("game:playing:SaveEngineSnapshotBuffers",
15947 "size of engine snapshot: %d bytes", num_bytes);
15953 void SaveEngineSnapshotSingle(void)
15955 ListNode *buffers = SaveEngineSnapshotBuffers();
15957 // finally save all snapshot buffers to single snapshot
15958 SaveSnapshotSingle(buffers);
15960 // save level identification information
15961 setString(&snapshot_level_identifier, leveldir_current->identifier);
15962 snapshot_level_nr = level_nr;
15965 boolean CheckSaveEngineSnapshotToList(void)
15967 boolean save_snapshot =
15968 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15969 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15970 game.snapshot.changed_action) ||
15971 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15972 game.snapshot.collected_item));
15974 game.snapshot.changed_action = FALSE;
15975 game.snapshot.collected_item = FALSE;
15976 game.snapshot.save_snapshot = save_snapshot;
15978 return save_snapshot;
15981 void SaveEngineSnapshotToList(void)
15983 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15987 ListNode *buffers = SaveEngineSnapshotBuffers();
15989 // finally save all snapshot buffers to snapshot list
15990 SaveSnapshotToList(buffers);
15993 void SaveEngineSnapshotToListInitial(void)
15995 FreeEngineSnapshotList();
15997 SaveEngineSnapshotToList();
16000 static void LoadEngineSnapshotValues(void)
16002 // restore special values from snapshot structure
16004 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16005 LoadEngineSnapshotValues_RND();
16006 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16007 LoadEngineSnapshotValues_EM();
16008 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16009 LoadEngineSnapshotValues_SP();
16010 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16011 LoadEngineSnapshotValues_MM();
16014 void LoadEngineSnapshotSingle(void)
16016 LoadSnapshotSingle();
16018 LoadEngineSnapshotValues();
16021 static void LoadEngineSnapshot_Undo(int steps)
16023 LoadSnapshotFromList_Older(steps);
16025 LoadEngineSnapshotValues();
16028 static void LoadEngineSnapshot_Redo(int steps)
16030 LoadSnapshotFromList_Newer(steps);
16032 LoadEngineSnapshotValues();
16035 boolean CheckEngineSnapshotSingle(void)
16037 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16038 snapshot_level_nr == level_nr);
16041 boolean CheckEngineSnapshotList(void)
16043 return CheckSnapshotList();
16047 // ---------- new game button stuff -------------------------------------------
16054 boolean *setup_value;
16055 boolean allowed_on_tape;
16056 boolean is_touch_button;
16058 } gamebutton_info[NUM_GAME_BUTTONS] =
16061 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16062 GAME_CTRL_ID_STOP, NULL,
16063 TRUE, FALSE, "stop game"
16066 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16067 GAME_CTRL_ID_PAUSE, NULL,
16068 TRUE, FALSE, "pause game"
16071 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16072 GAME_CTRL_ID_PLAY, NULL,
16073 TRUE, FALSE, "play game"
16076 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16077 GAME_CTRL_ID_UNDO, NULL,
16078 TRUE, FALSE, "undo step"
16081 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16082 GAME_CTRL_ID_REDO, NULL,
16083 TRUE, FALSE, "redo step"
16086 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16087 GAME_CTRL_ID_SAVE, NULL,
16088 TRUE, FALSE, "save game"
16091 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16092 GAME_CTRL_ID_PAUSE2, NULL,
16093 TRUE, FALSE, "pause game"
16096 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16097 GAME_CTRL_ID_LOAD, NULL,
16098 TRUE, FALSE, "load game"
16101 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16102 GAME_CTRL_ID_PANEL_STOP, NULL,
16103 FALSE, FALSE, "stop game"
16106 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16107 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16108 FALSE, FALSE, "pause game"
16111 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16112 GAME_CTRL_ID_PANEL_PLAY, NULL,
16113 FALSE, FALSE, "play game"
16116 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16117 GAME_CTRL_ID_TOUCH_STOP, NULL,
16118 FALSE, TRUE, "stop game"
16121 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16122 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16123 FALSE, TRUE, "pause game"
16126 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16127 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16128 TRUE, FALSE, "background music on/off"
16131 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16132 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16133 TRUE, FALSE, "sound loops on/off"
16136 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16137 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16138 TRUE, FALSE, "normal sounds on/off"
16141 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16142 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16143 FALSE, FALSE, "background music on/off"
16146 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16147 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16148 FALSE, FALSE, "sound loops on/off"
16151 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16152 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16153 FALSE, FALSE, "normal sounds on/off"
16157 void CreateGameButtons(void)
16161 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16163 int graphic = gamebutton_info[i].graphic;
16164 struct GraphicInfo *gfx = &graphic_info[graphic];
16165 struct XY *pos = gamebutton_info[i].pos;
16166 struct GadgetInfo *gi;
16169 unsigned int event_mask;
16170 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16171 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16172 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16173 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16174 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16175 int gd_x = gfx->src_x;
16176 int gd_y = gfx->src_y;
16177 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16178 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16179 int gd_xa = gfx->src_x + gfx->active_xoffset;
16180 int gd_ya = gfx->src_y + gfx->active_yoffset;
16181 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16182 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16183 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16184 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16187 // do not use touch buttons if overlay touch buttons are disabled
16188 if (is_touch_button && !setup.touch.overlay_buttons)
16191 if (gfx->bitmap == NULL)
16193 game_gadget[id] = NULL;
16198 if (id == GAME_CTRL_ID_STOP ||
16199 id == GAME_CTRL_ID_PANEL_STOP ||
16200 id == GAME_CTRL_ID_TOUCH_STOP ||
16201 id == GAME_CTRL_ID_PLAY ||
16202 id == GAME_CTRL_ID_PANEL_PLAY ||
16203 id == GAME_CTRL_ID_SAVE ||
16204 id == GAME_CTRL_ID_LOAD)
16206 button_type = GD_TYPE_NORMAL_BUTTON;
16208 event_mask = GD_EVENT_RELEASED;
16210 else if (id == GAME_CTRL_ID_UNDO ||
16211 id == GAME_CTRL_ID_REDO)
16213 button_type = GD_TYPE_NORMAL_BUTTON;
16215 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16219 button_type = GD_TYPE_CHECK_BUTTON;
16220 checked = (gamebutton_info[i].setup_value != NULL ?
16221 *gamebutton_info[i].setup_value : FALSE);
16222 event_mask = GD_EVENT_PRESSED;
16225 gi = CreateGadget(GDI_CUSTOM_ID, id,
16226 GDI_IMAGE_ID, graphic,
16227 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16230 GDI_WIDTH, gfx->width,
16231 GDI_HEIGHT, gfx->height,
16232 GDI_TYPE, button_type,
16233 GDI_STATE, GD_BUTTON_UNPRESSED,
16234 GDI_CHECKED, checked,
16235 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16236 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16237 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16238 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16239 GDI_DIRECT_DRAW, FALSE,
16240 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16241 GDI_EVENT_MASK, event_mask,
16242 GDI_CALLBACK_ACTION, HandleGameButtons,
16246 Fail("cannot create gadget");
16248 game_gadget[id] = gi;
16252 void FreeGameButtons(void)
16256 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16257 FreeGadget(game_gadget[i]);
16260 static void UnmapGameButtonsAtSamePosition(int id)
16264 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16266 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16267 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16268 UnmapGadget(game_gadget[i]);
16271 static void UnmapGameButtonsAtSamePosition_All(void)
16273 if (setup.show_load_save_buttons)
16275 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16276 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16277 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16279 else if (setup.show_undo_redo_buttons)
16281 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16282 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16283 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16287 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16288 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16289 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16291 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16292 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16293 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16297 void MapLoadSaveButtons(void)
16299 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16300 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16302 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16303 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16306 void MapUndoRedoButtons(void)
16308 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16309 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16311 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16312 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16315 void ModifyPauseButtons(void)
16319 GAME_CTRL_ID_PAUSE,
16320 GAME_CTRL_ID_PAUSE2,
16321 GAME_CTRL_ID_PANEL_PAUSE,
16322 GAME_CTRL_ID_TOUCH_PAUSE,
16327 for (i = 0; ids[i] > -1; i++)
16328 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16331 static void MapGameButtonsExt(boolean on_tape)
16335 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16337 if ((i == GAME_CTRL_ID_UNDO ||
16338 i == GAME_CTRL_ID_REDO) &&
16339 game_status != GAME_MODE_PLAYING)
16342 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16343 MapGadget(game_gadget[i]);
16346 UnmapGameButtonsAtSamePosition_All();
16348 RedrawGameButtons();
16351 static void UnmapGameButtonsExt(boolean on_tape)
16355 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16356 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16357 UnmapGadget(game_gadget[i]);
16360 static void RedrawGameButtonsExt(boolean on_tape)
16364 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16365 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16366 RedrawGadget(game_gadget[i]);
16369 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16374 gi->checked = state;
16377 static void RedrawSoundButtonGadget(int id)
16379 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16380 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16381 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16382 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16383 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16384 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16387 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16388 RedrawGadget(game_gadget[id2]);
16391 void MapGameButtons(void)
16393 MapGameButtonsExt(FALSE);
16396 void UnmapGameButtons(void)
16398 UnmapGameButtonsExt(FALSE);
16401 void RedrawGameButtons(void)
16403 RedrawGameButtonsExt(FALSE);
16406 void MapGameButtonsOnTape(void)
16408 MapGameButtonsExt(TRUE);
16411 void UnmapGameButtonsOnTape(void)
16413 UnmapGameButtonsExt(TRUE);
16416 void RedrawGameButtonsOnTape(void)
16418 RedrawGameButtonsExt(TRUE);
16421 static void GameUndoRedoExt(void)
16423 ClearPlayerAction();
16425 tape.pausing = TRUE;
16428 UpdateAndDisplayGameControlValues();
16430 DrawCompleteVideoDisplay();
16431 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16432 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16433 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16435 ModifyPauseButtons();
16440 static void GameUndo(int steps)
16442 if (!CheckEngineSnapshotList())
16445 int tape_property_bits = tape.property_bits;
16447 LoadEngineSnapshot_Undo(steps);
16449 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16454 static void GameRedo(int steps)
16456 if (!CheckEngineSnapshotList())
16459 int tape_property_bits = tape.property_bits;
16461 LoadEngineSnapshot_Redo(steps);
16463 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16468 static void HandleGameButtonsExt(int id, int button)
16470 static boolean game_undo_executed = FALSE;
16471 int steps = BUTTON_STEPSIZE(button);
16472 boolean handle_game_buttons =
16473 (game_status == GAME_MODE_PLAYING ||
16474 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16476 if (!handle_game_buttons)
16481 case GAME_CTRL_ID_STOP:
16482 case GAME_CTRL_ID_PANEL_STOP:
16483 case GAME_CTRL_ID_TOUCH_STOP:
16488 case GAME_CTRL_ID_PAUSE:
16489 case GAME_CTRL_ID_PAUSE2:
16490 case GAME_CTRL_ID_PANEL_PAUSE:
16491 case GAME_CTRL_ID_TOUCH_PAUSE:
16492 if (network.enabled && game_status == GAME_MODE_PLAYING)
16495 SendToServer_ContinuePlaying();
16497 SendToServer_PausePlaying();
16500 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16502 game_undo_executed = FALSE;
16506 case GAME_CTRL_ID_PLAY:
16507 case GAME_CTRL_ID_PANEL_PLAY:
16508 if (game_status == GAME_MODE_MAIN)
16510 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16512 else if (tape.pausing)
16514 if (network.enabled)
16515 SendToServer_ContinuePlaying();
16517 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16521 case GAME_CTRL_ID_UNDO:
16522 // Important: When using "save snapshot when collecting an item" mode,
16523 // load last (current) snapshot for first "undo" after pressing "pause"
16524 // (else the last-but-one snapshot would be loaded, because the snapshot
16525 // pointer already points to the last snapshot when pressing "pause",
16526 // which is fine for "every step/move" mode, but not for "every collect")
16527 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16528 !game_undo_executed)
16531 game_undo_executed = TRUE;
16536 case GAME_CTRL_ID_REDO:
16540 case GAME_CTRL_ID_SAVE:
16544 case GAME_CTRL_ID_LOAD:
16548 case SOUND_CTRL_ID_MUSIC:
16549 case SOUND_CTRL_ID_PANEL_MUSIC:
16550 if (setup.sound_music)
16552 setup.sound_music = FALSE;
16556 else if (audio.music_available)
16558 setup.sound = setup.sound_music = TRUE;
16560 SetAudioMode(setup.sound);
16562 if (game_status == GAME_MODE_PLAYING)
16566 RedrawSoundButtonGadget(id);
16570 case SOUND_CTRL_ID_LOOPS:
16571 case SOUND_CTRL_ID_PANEL_LOOPS:
16572 if (setup.sound_loops)
16573 setup.sound_loops = FALSE;
16574 else if (audio.loops_available)
16576 setup.sound = setup.sound_loops = TRUE;
16578 SetAudioMode(setup.sound);
16581 RedrawSoundButtonGadget(id);
16585 case SOUND_CTRL_ID_SIMPLE:
16586 case SOUND_CTRL_ID_PANEL_SIMPLE:
16587 if (setup.sound_simple)
16588 setup.sound_simple = FALSE;
16589 else if (audio.sound_available)
16591 setup.sound = setup.sound_simple = TRUE;
16593 SetAudioMode(setup.sound);
16596 RedrawSoundButtonGadget(id);
16605 static void HandleGameButtons(struct GadgetInfo *gi)
16607 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16610 void HandleSoundButtonKeys(Key key)
16612 if (key == setup.shortcut.sound_simple)
16613 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16614 else if (key == setup.shortcut.sound_loops)
16615 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16616 else if (key == setup.shortcut.sound_music)
16617 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);