1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2031 else if (IS_EMPTY_ELEMENT(element))
2033 GfxElementEmpty[x][y] = element;
2034 Tile[x][y] = EL_EMPTY;
2036 if (element_info[element].use_gfx_element)
2037 game.use_masked_elements = TRUE;
2044 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2047 static void InitField_WithBug1(int x, int y, boolean init_game)
2049 InitField(x, y, init_game);
2051 // not needed to call InitMovDir() -- already done by InitField()!
2052 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2053 CAN_MOVE(Tile[x][y]))
2057 static void InitField_WithBug2(int x, int y, boolean init_game)
2059 int old_element = Tile[x][y];
2061 InitField(x, y, init_game);
2063 // not needed to call InitMovDir() -- already done by InitField()!
2064 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2065 CAN_MOVE(old_element) &&
2066 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2069 /* this case is in fact a combination of not less than three bugs:
2070 first, it calls InitMovDir() for elements that can move, although this is
2071 already done by InitField(); then, it checks the element that was at this
2072 field _before_ the call to InitField() (which can change it); lastly, it
2073 was not called for "mole with direction" elements, which were treated as
2074 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2078 static int get_key_element_from_nr(int key_nr)
2080 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2081 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2082 EL_EM_KEY_1 : EL_KEY_1);
2084 return key_base_element + key_nr;
2087 static int get_next_dropped_element(struct PlayerInfo *player)
2089 return (player->inventory_size > 0 ?
2090 player->inventory_element[player->inventory_size - 1] :
2091 player->inventory_infinite_element != EL_UNDEFINED ?
2092 player->inventory_infinite_element :
2093 player->dynabombs_left > 0 ?
2094 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2098 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2100 // pos >= 0: get element from bottom of the stack;
2101 // pos < 0: get element from top of the stack
2105 int min_inventory_size = -pos;
2106 int inventory_pos = player->inventory_size - min_inventory_size;
2107 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2109 return (player->inventory_size >= min_inventory_size ?
2110 player->inventory_element[inventory_pos] :
2111 player->inventory_infinite_element != EL_UNDEFINED ?
2112 player->inventory_infinite_element :
2113 player->dynabombs_left >= min_dynabombs_left ?
2114 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 int min_dynabombs_left = pos + 1;
2120 int min_inventory_size = pos + 1 - player->dynabombs_left;
2121 int inventory_pos = pos - player->dynabombs_left;
2123 return (player->inventory_infinite_element != EL_UNDEFINED ?
2124 player->inventory_infinite_element :
2125 player->dynabombs_left >= min_dynabombs_left ?
2126 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 player->inventory_size >= min_inventory_size ?
2128 player->inventory_element[inventory_pos] :
2133 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2135 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2136 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2139 if (gpo1->sort_priority != gpo2->sort_priority)
2140 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2142 compare_result = gpo1->nr - gpo2->nr;
2144 return compare_result;
2147 int getPlayerInventorySize(int player_nr)
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2150 return game_em.ply[player_nr]->dynamite;
2151 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2152 return game_sp.red_disk_count;
2154 return stored_player[player_nr].inventory_size;
2157 static void InitGameControlValues(void)
2161 for (i = 0; game_panel_controls[i].nr != -1; i++)
2163 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2164 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2165 struct TextPosInfo *pos = gpc->pos;
2167 int type = gpc->type;
2171 Error("'game_panel_controls' structure corrupted at %d", i);
2173 Fail("this should not happen -- please debug");
2176 // force update of game controls after initialization
2177 gpc->value = gpc->last_value = -1;
2178 gpc->frame = gpc->last_frame = -1;
2179 gpc->gfx_frame = -1;
2181 // determine panel value width for later calculation of alignment
2182 if (type == TYPE_INTEGER || type == TYPE_STRING)
2184 pos->width = pos->size * getFontWidth(pos->font);
2185 pos->height = getFontHeight(pos->font);
2187 else if (type == TYPE_ELEMENT)
2189 pos->width = pos->size;
2190 pos->height = pos->size;
2193 // fill structure for game panel draw order
2195 gpo->sort_priority = pos->sort_priority;
2198 // sort game panel controls according to sort_priority and control number
2199 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2200 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2203 static void UpdatePlayfieldElementCount(void)
2205 boolean use_element_count = FALSE;
2208 // first check if it is needed at all to calculate playfield element count
2209 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2210 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2211 use_element_count = TRUE;
2213 if (!use_element_count)
2216 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2217 element_info[i].element_count = 0;
2219 SCAN_PLAYFIELD(x, y)
2221 element_info[Tile[x][y]].element_count++;
2224 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2225 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2226 if (IS_IN_GROUP(j, i))
2227 element_info[EL_GROUP_START + i].element_count +=
2228 element_info[j].element_count;
2231 static void UpdateGameControlValues(void)
2234 int time = (game.LevelSolved ?
2235 game.LevelSolved_CountingTime :
2236 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2239 game_sp.time_played :
2240 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 game_mm.energy_left :
2242 game.no_time_limit ? TimePlayed : TimeLeft);
2243 int score = (game.LevelSolved ?
2244 game.LevelSolved_CountingScore :
2245 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 game_em.lev->score :
2247 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2249 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2253 game_em.lev->gems_needed :
2254 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2255 game_sp.infotrons_still_needed :
2256 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2257 game_mm.kettles_still_needed :
2258 game.gems_still_needed);
2259 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2260 game_em.lev->gems_needed > 0 :
2261 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2262 game_sp.infotrons_still_needed > 0 :
2263 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2264 game_mm.kettles_still_needed > 0 ||
2265 game_mm.lights_still_needed > 0 :
2266 game.gems_still_needed > 0 ||
2267 game.sokoban_fields_still_needed > 0 ||
2268 game.sokoban_objects_still_needed > 0 ||
2269 game.lights_still_needed > 0);
2270 int health = (game.LevelSolved ?
2271 game.LevelSolved_CountingHealth :
2272 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2273 MM_HEALTH(game_mm.laser_overload_value) :
2275 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2277 UpdatePlayfieldElementCount();
2279 // update game panel control values
2281 // used instead of "level_nr" (for network games)
2282 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2283 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2285 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2286 for (i = 0; i < MAX_NUM_KEYS; i++)
2287 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2288 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2289 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2291 if (game.centered_player_nr == -1)
2293 for (i = 0; i < MAX_PLAYERS; i++)
2295 // only one player in Supaplex game engine
2296 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2299 for (k = 0; k < MAX_NUM_KEYS; k++)
2301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2303 if (game_em.ply[i]->keys & (1 << k))
2304 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2305 get_key_element_from_nr(k);
2307 else if (stored_player[i].key[k])
2308 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2309 get_key_element_from_nr(k);
2312 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2313 getPlayerInventorySize(i);
2315 if (stored_player[i].num_white_keys > 0)
2316 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2319 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2320 stored_player[i].num_white_keys;
2325 int player_nr = game.centered_player_nr;
2327 for (k = 0; k < MAX_NUM_KEYS; k++)
2329 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2331 if (game_em.ply[player_nr]->keys & (1 << k))
2332 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2333 get_key_element_from_nr(k);
2335 else if (stored_player[player_nr].key[k])
2336 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2337 get_key_element_from_nr(k);
2340 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2341 getPlayerInventorySize(player_nr);
2343 if (stored_player[player_nr].num_white_keys > 0)
2344 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2346 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2347 stored_player[player_nr].num_white_keys;
2350 // re-arrange keys on game panel, if needed or if defined by style settings
2351 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2353 int nr = GAME_PANEL_KEY_1 + i;
2354 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2355 struct TextPosInfo *pos = gpc->pos;
2357 // skip check if key is not in the player's inventory
2358 if (gpc->value == EL_EMPTY)
2361 // check if keys should be arranged on panel from left to right
2362 if (pos->style == STYLE_LEFTMOST_POSITION)
2364 // check previous key positions (left from current key)
2365 for (k = 0; k < i; k++)
2367 int nr_new = GAME_PANEL_KEY_1 + k;
2369 if (game_panel_controls[nr_new].value == EL_EMPTY)
2371 game_panel_controls[nr_new].value = gpc->value;
2372 gpc->value = EL_EMPTY;
2379 // check if "undefined" keys can be placed at some other position
2380 if (pos->x == -1 && pos->y == -1)
2382 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2384 // 1st try: display key at the same position as normal or EM keys
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2391 // 2nd try: display key at the next free position in the key panel
2392 for (k = 0; k < STD_NUM_KEYS; k++)
2394 nr_new = GAME_PANEL_KEY_1 + k;
2396 if (game_panel_controls[nr_new].value == EL_EMPTY)
2398 game_panel_controls[nr_new].value = gpc->value;
2407 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2409 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2410 get_inventory_element_from_pos(local_player, i);
2411 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2412 get_inventory_element_from_pos(local_player, -i - 1);
2415 game_panel_controls[GAME_PANEL_SCORE].value = score;
2416 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2418 game_panel_controls[GAME_PANEL_TIME].value = time;
2420 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2421 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2422 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2424 if (level.time == 0)
2425 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2427 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2429 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2430 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2432 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2434 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2435 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2437 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2438 local_player->shield_normal_time_left;
2439 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2440 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2442 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2443 local_player->shield_deadly_time_left;
2445 game_panel_controls[GAME_PANEL_EXIT].value =
2446 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2448 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2449 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2450 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2451 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2452 EL_EMC_MAGIC_BALL_SWITCH);
2454 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2455 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2456 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2457 game.light_time_left;
2459 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2460 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2461 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2462 game.timegate_time_left;
2464 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2465 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2467 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2468 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2469 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2470 game.lenses_time_left;
2472 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2473 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2474 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2475 game.magnify_time_left;
2477 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2478 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2479 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2480 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2481 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2482 EL_BALLOON_SWITCH_NONE);
2484 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2485 local_player->dynabomb_count;
2486 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2487 local_player->dynabomb_size;
2488 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2489 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2491 game_panel_controls[GAME_PANEL_PENGUINS].value =
2492 game.friends_still_needed;
2494 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2495 game.sokoban_objects_still_needed;
2496 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2497 game.sokoban_fields_still_needed;
2499 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2500 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2502 for (i = 0; i < NUM_BELTS; i++)
2504 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2505 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2506 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2507 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2508 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2511 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2512 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2513 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2514 game.magic_wall_time_left;
2516 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2517 local_player->gravity;
2519 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2520 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2522 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2523 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2524 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2525 game.panel.element[i].id : EL_UNDEFINED);
2527 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2528 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2529 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2530 element_info[game.panel.element_count[i].id].element_count : 0);
2532 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2533 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2534 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2535 element_info[game.panel.ce_score[i].id].collect_score : 0);
2537 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2538 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2539 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2540 element_info[game.panel.ce_score_element[i].id].collect_score :
2543 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2544 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2545 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2547 // update game panel control frames
2549 for (i = 0; game_panel_controls[i].nr != -1; i++)
2551 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2553 if (gpc->type == TYPE_ELEMENT)
2555 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2557 int last_anim_random_frame = gfx.anim_random_frame;
2558 int element = gpc->value;
2559 int graphic = el2panelimg(element);
2560 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2561 sync_random_frame : INIT_GFX_RANDOM());
2563 if (gpc->value != gpc->last_value)
2566 gpc->gfx_random = init_gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2573 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2574 gpc->gfx_random = init_gfx_random;
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = gpc->gfx_random;
2580 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2581 gpc->gfx_frame = element_info[element].collect_score;
2583 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2585 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2586 gfx.anim_random_frame = last_anim_random_frame;
2589 else if (gpc->type == TYPE_GRAPHIC)
2591 if (gpc->graphic != IMG_UNDEFINED)
2593 int last_anim_random_frame = gfx.anim_random_frame;
2594 int graphic = gpc->graphic;
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2596 sync_random_frame : INIT_GFX_RANDOM());
2598 if (gpc->value != gpc->last_value)
2601 gpc->gfx_random = init_gfx_random;
2607 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2608 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2609 gpc->gfx_random = init_gfx_random;
2612 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2613 gfx.anim_random_frame = gpc->gfx_random;
2615 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2617 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2618 gfx.anim_random_frame = last_anim_random_frame;
2624 static void DisplayGameControlValues(void)
2626 boolean redraw_panel = FALSE;
2629 for (i = 0; game_panel_controls[i].nr != -1; i++)
2631 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2633 if (PANEL_DEACTIVATED(gpc->pos))
2636 if (gpc->value == gpc->last_value &&
2637 gpc->frame == gpc->last_frame)
2640 redraw_panel = TRUE;
2646 // copy default game door content to main double buffer
2648 // !!! CHECK AGAIN !!!
2649 SetPanelBackground();
2650 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2651 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2653 // redraw game control buttons
2654 RedrawGameButtons();
2656 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2658 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2660 int nr = game_panel_order[i].nr;
2661 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2662 struct TextPosInfo *pos = gpc->pos;
2663 int type = gpc->type;
2664 int value = gpc->value;
2665 int frame = gpc->frame;
2666 int size = pos->size;
2667 int font = pos->font;
2668 boolean draw_masked = pos->draw_masked;
2669 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2671 if (PANEL_DEACTIVATED(pos))
2674 if (pos->class == get_hash_from_key("extra_panel_items") &&
2675 !setup.prefer_extra_panel_items)
2678 gpc->last_value = value;
2679 gpc->last_frame = frame;
2681 if (type == TYPE_INTEGER)
2683 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2684 nr == GAME_PANEL_TIME)
2686 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2688 if (use_dynamic_size) // use dynamic number of digits
2690 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2691 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2692 int size2 = size1 + 1;
2693 int font1 = pos->font;
2694 int font2 = pos->font_alt;
2696 size = (value < value_change ? size1 : size2);
2697 font = (value < value_change ? font1 : font2);
2701 // correct text size if "digits" is zero or less
2703 size = strlen(int2str(value, size));
2705 // dynamically correct text alignment
2706 pos->width = size * getFontWidth(font);
2708 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2709 int2str(value, size), font, mask_mode);
2711 else if (type == TYPE_ELEMENT)
2713 int element, graphic;
2717 int dst_x = PANEL_XPOS(pos);
2718 int dst_y = PANEL_YPOS(pos);
2720 if (value != EL_UNDEFINED && value != EL_EMPTY)
2723 graphic = el2panelimg(value);
2726 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2727 element, EL_NAME(element), size);
2730 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2733 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2736 width = graphic_info[graphic].width * size / TILESIZE;
2737 height = graphic_info[graphic].height * size / TILESIZE;
2740 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2743 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2747 else if (type == TYPE_GRAPHIC)
2749 int graphic = gpc->graphic;
2750 int graphic_active = gpc->graphic_active;
2754 int dst_x = PANEL_XPOS(pos);
2755 int dst_y = PANEL_YPOS(pos);
2756 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2757 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2759 if (graphic != IMG_UNDEFINED && !skip)
2761 if (pos->style == STYLE_REVERSE)
2762 value = 100 - value;
2764 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2766 if (pos->direction & MV_HORIZONTAL)
2768 width = graphic_info[graphic_active].width * value / 100;
2769 height = graphic_info[graphic_active].height;
2771 if (pos->direction == MV_LEFT)
2773 src_x += graphic_info[graphic_active].width - width;
2774 dst_x += graphic_info[graphic_active].width - width;
2779 width = graphic_info[graphic_active].width;
2780 height = graphic_info[graphic_active].height * value / 100;
2782 if (pos->direction == MV_UP)
2784 src_y += graphic_info[graphic_active].height - height;
2785 dst_y += graphic_info[graphic_active].height - height;
2790 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2793 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2796 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2798 if (pos->direction & MV_HORIZONTAL)
2800 if (pos->direction == MV_RIGHT)
2807 dst_x = PANEL_XPOS(pos);
2810 width = graphic_info[graphic].width - width;
2814 if (pos->direction == MV_DOWN)
2821 dst_y = PANEL_YPOS(pos);
2824 height = graphic_info[graphic].height - height;
2828 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2831 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2835 else if (type == TYPE_STRING)
2837 boolean active = (value != 0);
2838 char *state_normal = "off";
2839 char *state_active = "on";
2840 char *state = (active ? state_active : state_normal);
2841 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2842 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2843 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2844 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2846 if (nr == GAME_PANEL_GRAVITY_STATE)
2848 int font1 = pos->font; // (used for normal state)
2849 int font2 = pos->font_alt; // (used for active state)
2851 font = (active ? font2 : font1);
2860 // don't truncate output if "chars" is zero or less
2863 // dynamically correct text alignment
2864 pos->width = size * getFontWidth(font);
2867 s_cut = getStringCopyN(s, size);
2869 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2870 s_cut, font, mask_mode);
2876 redraw_mask |= REDRAW_DOOR_1;
2879 SetGameStatus(GAME_MODE_PLAYING);
2882 void UpdateAndDisplayGameControlValues(void)
2884 if (tape.deactivate_display)
2887 UpdateGameControlValues();
2888 DisplayGameControlValues();
2891 void UpdateGameDoorValues(void)
2893 UpdateGameControlValues();
2896 void DrawGameDoorValues(void)
2898 DisplayGameControlValues();
2902 // ============================================================================
2904 // ----------------------------------------------------------------------------
2905 // initialize game engine due to level / tape version number
2906 // ============================================================================
2908 static void InitGameEngine(void)
2910 int i, j, k, l, x, y;
2912 // set game engine from tape file when re-playing, else from level file
2913 game.engine_version = (tape.playing ? tape.engine_version :
2914 level.game_version);
2916 // set single or multi-player game mode (needed for re-playing tapes)
2917 game.team_mode = setup.team_mode;
2921 int num_players = 0;
2923 for (i = 0; i < MAX_PLAYERS; i++)
2924 if (tape.player_participates[i])
2927 // multi-player tapes contain input data for more than one player
2928 game.team_mode = (num_players > 1);
2932 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2933 level.game_version);
2934 Debug("game:init:level", " tape.file_version == %06d",
2936 Debug("game:init:level", " tape.game_version == %06d",
2938 Debug("game:init:level", " tape.engine_version == %06d",
2939 tape.engine_version);
2940 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2941 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2944 // --------------------------------------------------------------------------
2945 // set flags for bugs and changes according to active game engine version
2946 // --------------------------------------------------------------------------
2950 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2952 Bug was introduced in version:
2955 Bug was fixed in version:
2959 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2960 but the property "can fall" was missing, which caused some levels to be
2961 unsolvable. This was fixed in version 4.2.0.0.
2963 Affected levels/tapes:
2964 An example for a tape that was fixed by this bugfix is tape 029 from the
2965 level set "rnd_sam_bateman".
2966 The wrong behaviour will still be used for all levels or tapes that were
2967 created/recorded with it. An example for this is tape 023 from the level
2968 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2971 boolean use_amoeba_dropping_cannot_fall_bug =
2972 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2973 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2975 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2976 tape.game_version < VERSION_IDENT(4,2,0,0)));
2979 Summary of bugfix/change:
2980 Fixed move speed of elements entering or leaving magic wall.
2982 Fixed/changed in version:
2986 Before 2.0.1, move speed of elements entering or leaving magic wall was
2987 twice as fast as it is now.
2988 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2990 Affected levels/tapes:
2991 The first condition is generally needed for all levels/tapes before version
2992 2.0.1, which might use the old behaviour before it was changed; known tapes
2993 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2994 The second condition is an exception from the above case and is needed for
2995 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2996 above, but before it was known that this change would break tapes like the
2997 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2998 although the engine version while recording maybe was before 2.0.1. There
2999 are a lot of tapes that are affected by this exception, like tape 006 from
3000 the level set "rnd_conor_mancone".
3003 boolean use_old_move_stepsize_for_magic_wall =
3004 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3006 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3007 tape.game_version < VERSION_IDENT(4,2,0,0)));
3010 Summary of bugfix/change:
3011 Fixed handling for custom elements that change when pushed by the player.
3013 Fixed/changed in version:
3017 Before 3.1.0, custom elements that "change when pushing" changed directly
3018 after the player started pushing them (until then handled in "DigField()").
3019 Since 3.1.0, these custom elements are not changed until the "pushing"
3020 move of the element is finished (now handled in "ContinueMoving()").
3022 Affected levels/tapes:
3023 The first condition is generally needed for all levels/tapes before version
3024 3.1.0, which might use the old behaviour before it was changed; known tapes
3025 that are affected are some tapes from the level set "Walpurgis Gardens" by
3027 The second condition is an exception from the above case and is needed for
3028 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3029 above (including some development versions of 3.1.0), but before it was
3030 known that this change would break tapes like the above and was fixed in
3031 3.1.1, so that the changed behaviour was active although the engine version
3032 while recording maybe was before 3.1.0. There is at least one tape that is
3033 affected by this exception, which is the tape for the one-level set "Bug
3034 Machine" by Juergen Bonhagen.
3037 game.use_change_when_pushing_bug =
3038 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3040 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3041 tape.game_version < VERSION_IDENT(3,1,1,0)));
3044 Summary of bugfix/change:
3045 Fixed handling for blocking the field the player leaves when moving.
3047 Fixed/changed in version:
3051 Before 3.1.1, when "block last field when moving" was enabled, the field
3052 the player is leaving when moving was blocked for the time of the move,
3053 and was directly unblocked afterwards. This resulted in the last field
3054 being blocked for exactly one less than the number of frames of one player
3055 move. Additionally, even when blocking was disabled, the last field was
3056 blocked for exactly one frame.
3057 Since 3.1.1, due to changes in player movement handling, the last field
3058 is not blocked at all when blocking is disabled. When blocking is enabled,
3059 the last field is blocked for exactly the number of frames of one player
3060 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3061 last field is blocked for exactly one more than the number of frames of
3064 Affected levels/tapes:
3065 (!!! yet to be determined -- probably many !!!)
3068 game.use_block_last_field_bug =
3069 (game.engine_version < VERSION_IDENT(3,1,1,0));
3071 /* various special flags and settings for native Emerald Mine game engine */
3073 game_em.use_single_button =
3074 (game.engine_version > VERSION_IDENT(4,0,0,2));
3076 game_em.use_snap_key_bug =
3077 (game.engine_version < VERSION_IDENT(4,0,1,0));
3079 game_em.use_random_bug =
3080 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3082 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3084 game_em.use_old_explosions = use_old_em_engine;
3085 game_em.use_old_android = use_old_em_engine;
3086 game_em.use_old_push_elements = use_old_em_engine;
3087 game_em.use_old_push_into_acid = use_old_em_engine;
3089 game_em.use_wrap_around = !use_old_em_engine;
3091 // --------------------------------------------------------------------------
3093 // set maximal allowed number of custom element changes per game frame
3094 game.max_num_changes_per_frame = 1;
3096 // default scan direction: scan playfield from top/left to bottom/right
3097 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3099 // dynamically adjust element properties according to game engine version
3100 InitElementPropertiesEngine(game.engine_version);
3102 // ---------- initialize special element properties -------------------------
3104 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3105 if (use_amoeba_dropping_cannot_fall_bug)
3106 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3108 // ---------- initialize player's initial move delay ------------------------
3110 // dynamically adjust player properties according to level information
3111 for (i = 0; i < MAX_PLAYERS; i++)
3112 game.initial_move_delay_value[i] =
3113 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3115 // dynamically adjust player properties according to game engine version
3116 for (i = 0; i < MAX_PLAYERS; i++)
3117 game.initial_move_delay[i] =
3118 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3119 game.initial_move_delay_value[i] : 0);
3121 // ---------- initialize player's initial push delay ------------------------
3123 // dynamically adjust player properties according to game engine version
3124 game.initial_push_delay_value =
3125 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3127 // ---------- initialize changing elements ----------------------------------
3129 // initialize changing elements information
3130 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3132 struct ElementInfo *ei = &element_info[i];
3134 // this pointer might have been changed in the level editor
3135 ei->change = &ei->change_page[0];
3137 if (!IS_CUSTOM_ELEMENT(i))
3139 ei->change->target_element = EL_EMPTY_SPACE;
3140 ei->change->delay_fixed = 0;
3141 ei->change->delay_random = 0;
3142 ei->change->delay_frames = 1;
3145 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3147 ei->has_change_event[j] = FALSE;
3149 ei->event_page_nr[j] = 0;
3150 ei->event_page[j] = &ei->change_page[0];
3154 // add changing elements from pre-defined list
3155 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3157 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3158 struct ElementInfo *ei = &element_info[ch_delay->element];
3160 ei->change->target_element = ch_delay->target_element;
3161 ei->change->delay_fixed = ch_delay->change_delay;
3163 ei->change->pre_change_function = ch_delay->pre_change_function;
3164 ei->change->change_function = ch_delay->change_function;
3165 ei->change->post_change_function = ch_delay->post_change_function;
3167 ei->change->can_change = TRUE;
3168 ei->change->can_change_or_has_action = TRUE;
3170 ei->has_change_event[CE_DELAY] = TRUE;
3172 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3173 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3176 // ---------- initialize internal run-time variables ------------------------
3178 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3180 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3182 for (j = 0; j < ei->num_change_pages; j++)
3184 ei->change_page[j].can_change_or_has_action =
3185 (ei->change_page[j].can_change |
3186 ei->change_page[j].has_action);
3190 // add change events from custom element configuration
3191 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3193 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3195 for (j = 0; j < ei->num_change_pages; j++)
3197 if (!ei->change_page[j].can_change_or_has_action)
3200 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3202 // only add event page for the first page found with this event
3203 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3205 ei->has_change_event[k] = TRUE;
3207 ei->event_page_nr[k] = j;
3208 ei->event_page[k] = &ei->change_page[j];
3214 // ---------- initialize reference elements in change conditions ------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 int element = EL_CUSTOM_START + i;
3219 struct ElementInfo *ei = &element_info[element];
3221 for (j = 0; j < ei->num_change_pages; j++)
3223 int trigger_element = ei->change_page[j].initial_trigger_element;
3225 if (trigger_element >= EL_PREV_CE_8 &&
3226 trigger_element <= EL_NEXT_CE_8)
3227 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3229 ei->change_page[j].trigger_element = trigger_element;
3233 // ---------- initialize run-time trigger player and element ----------------
3235 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3237 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3239 for (j = 0; j < ei->num_change_pages; j++)
3241 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3242 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3243 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3244 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3245 ei->change_page[j].actual_trigger_ce_value = 0;
3246 ei->change_page[j].actual_trigger_ce_score = 0;
3250 // ---------- initialize trigger events -------------------------------------
3252 // initialize trigger events information
3253 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3254 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3255 trigger_events[i][j] = FALSE;
3257 // add trigger events from element change event properties
3258 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 struct ElementInfo *ei = &element_info[i];
3262 for (j = 0; j < ei->num_change_pages; j++)
3264 if (!ei->change_page[j].can_change_or_has_action)
3267 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3269 int trigger_element = ei->change_page[j].trigger_element;
3271 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3273 if (ei->change_page[j].has_event[k])
3275 if (IS_GROUP_ELEMENT(trigger_element))
3277 struct ElementGroupInfo *group =
3278 element_info[trigger_element].group;
3280 for (l = 0; l < group->num_elements_resolved; l++)
3281 trigger_events[group->element_resolved[l]][k] = TRUE;
3283 else if (trigger_element == EL_ANY_ELEMENT)
3284 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3285 trigger_events[l][k] = TRUE;
3287 trigger_events[trigger_element][k] = TRUE;
3294 // ---------- initialize push delay -----------------------------------------
3296 // initialize push delay values to default
3297 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3299 if (!IS_CUSTOM_ELEMENT(i))
3301 // set default push delay values (corrected since version 3.0.7-1)
3302 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3304 element_info[i].push_delay_fixed = 2;
3305 element_info[i].push_delay_random = 8;
3309 element_info[i].push_delay_fixed = 8;
3310 element_info[i].push_delay_random = 8;
3315 // set push delay value for certain elements from pre-defined list
3316 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3318 int e = push_delay_list[i].element;
3320 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3321 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3324 // set push delay value for Supaplex elements for newer engine versions
3325 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3327 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3329 if (IS_SP_ELEMENT(i))
3331 // set SP push delay to just enough to push under a falling zonk
3332 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3334 element_info[i].push_delay_fixed = delay;
3335 element_info[i].push_delay_random = 0;
3340 // ---------- initialize move stepsize --------------------------------------
3342 // initialize move stepsize values to default
3343 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3344 if (!IS_CUSTOM_ELEMENT(i))
3345 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3347 // set move stepsize value for certain elements from pre-defined list
3348 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3350 int e = move_stepsize_list[i].element;
3352 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3354 // set move stepsize value for certain elements for older engine versions
3355 if (use_old_move_stepsize_for_magic_wall)
3357 if (e == EL_MAGIC_WALL_FILLING ||
3358 e == EL_MAGIC_WALL_EMPTYING ||
3359 e == EL_BD_MAGIC_WALL_FILLING ||
3360 e == EL_BD_MAGIC_WALL_EMPTYING)
3361 element_info[e].move_stepsize *= 2;
3365 // ---------- initialize collect score --------------------------------------
3367 // initialize collect score values for custom elements from initial value
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (IS_CUSTOM_ELEMENT(i))
3370 element_info[i].collect_score = element_info[i].collect_score_initial;
3372 // ---------- initialize collect count --------------------------------------
3374 // initialize collect count values for non-custom elements
3375 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3376 if (!IS_CUSTOM_ELEMENT(i))
3377 element_info[i].collect_count_initial = 0;
3379 // add collect count values for all elements from pre-defined list
3380 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3381 element_info[collect_count_list[i].element].collect_count_initial =
3382 collect_count_list[i].count;
3384 // ---------- initialize access direction -----------------------------------
3386 // initialize access direction values to default (access from every side)
3387 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (!IS_CUSTOM_ELEMENT(i))
3389 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3391 // set access direction value for certain elements from pre-defined list
3392 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3393 element_info[access_direction_list[i].element].access_direction =
3394 access_direction_list[i].direction;
3396 // ---------- initialize explosion content ----------------------------------
3397 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3399 if (IS_CUSTOM_ELEMENT(i))
3402 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3404 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3406 element_info[i].content.e[x][y] =
3407 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3408 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3409 i == EL_PLAYER_3 ? EL_EMERALD :
3410 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3411 i == EL_MOLE ? EL_EMERALD_RED :
3412 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3413 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3414 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3415 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3416 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3417 i == EL_WALL_EMERALD ? EL_EMERALD :
3418 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3419 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3420 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3421 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3422 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3423 i == EL_WALL_PEARL ? EL_PEARL :
3424 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3429 // ---------- initialize recursion detection --------------------------------
3430 recursion_loop_depth = 0;
3431 recursion_loop_detected = FALSE;
3432 recursion_loop_element = EL_UNDEFINED;
3434 // ---------- initialize graphics engine ------------------------------------
3435 game.scroll_delay_value =
3436 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3437 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3438 !setup.forced_scroll_delay ? 0 :
3439 setup.scroll_delay ? setup.scroll_delay_value : 0);
3440 game.scroll_delay_value =
3441 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3443 // ---------- initialize game engine snapshots ------------------------------
3444 for (i = 0; i < MAX_PLAYERS; i++)
3445 game.snapshot.last_action[i] = 0;
3446 game.snapshot.changed_action = FALSE;
3447 game.snapshot.collected_item = FALSE;
3448 game.snapshot.mode =
3449 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3450 SNAPSHOT_MODE_EVERY_STEP :
3451 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3452 SNAPSHOT_MODE_EVERY_MOVE :
3453 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3454 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3455 game.snapshot.save_snapshot = FALSE;
3457 // ---------- initialize level time for Supaplex engine ---------------------
3458 // Supaplex levels with time limit currently unsupported -- should be added
3459 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3462 // ---------- initialize flags for handling game actions --------------------
3464 // set flags for game actions to default values
3465 game.use_key_actions = TRUE;
3466 game.use_mouse_actions = FALSE;
3468 // when using Mirror Magic game engine, handle mouse events only
3469 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3471 game.use_key_actions = FALSE;
3472 game.use_mouse_actions = TRUE;
3475 // check for custom elements with mouse click events
3476 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3478 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3480 int element = EL_CUSTOM_START + i;
3482 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3483 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3484 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3485 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3486 game.use_mouse_actions = TRUE;
3491 static int get_num_special_action(int element, int action_first,
3494 int num_special_action = 0;
3497 for (i = action_first; i <= action_last; i++)
3499 boolean found = FALSE;
3501 for (j = 0; j < NUM_DIRECTIONS; j++)
3502 if (el_act_dir2img(element, i, j) !=
3503 el_act_dir2img(element, ACTION_DEFAULT, j))
3507 num_special_action++;
3512 return num_special_action;
3516 // ============================================================================
3518 // ----------------------------------------------------------------------------
3519 // initialize and start new game
3520 // ============================================================================
3522 #if DEBUG_INIT_PLAYER
3523 static void DebugPrintPlayerStatus(char *message)
3530 Debug("game:init:player", "%s:", message);
3532 for (i = 0; i < MAX_PLAYERS; i++)
3534 struct PlayerInfo *player = &stored_player[i];
3536 Debug("game:init:player",
3537 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3541 player->connected_locally,
3542 player->connected_network,
3544 (local_player == player ? " (local player)" : ""));
3551 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3552 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3553 int fade_mask = REDRAW_FIELD;
3555 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3556 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3557 int initial_move_dir = MV_DOWN;
3560 // required here to update video display before fading (FIX THIS)
3561 DrawMaskedBorder(REDRAW_DOOR_2);
3563 if (!game.restart_level)
3564 CloseDoor(DOOR_CLOSE_1);
3566 SetGameStatus(GAME_MODE_PLAYING);
3568 if (level_editor_test_game)
3569 FadeSkipNextFadeOut();
3571 FadeSetEnterScreen();
3574 fade_mask = REDRAW_ALL;
3576 FadeLevelSoundsAndMusic();
3578 ExpireSoundLoops(TRUE);
3582 if (level_editor_test_game)
3583 FadeSkipNextFadeIn();
3585 // needed if different viewport properties defined for playing
3586 ChangeViewportPropertiesIfNeeded();
3590 DrawCompleteVideoDisplay();
3592 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3595 InitGameControlValues();
3599 // initialize tape actions from game when recording tape
3600 tape.use_key_actions = game.use_key_actions;
3601 tape.use_mouse_actions = game.use_mouse_actions;
3603 // initialize visible playfield size when recording tape (for team mode)
3604 tape.scr_fieldx = SCR_FIELDX;
3605 tape.scr_fieldy = SCR_FIELDY;
3608 // don't play tapes over network
3609 network_playing = (network.enabled && !tape.playing);
3611 for (i = 0; i < MAX_PLAYERS; i++)
3613 struct PlayerInfo *player = &stored_player[i];
3615 player->index_nr = i;
3616 player->index_bit = (1 << i);
3617 player->element_nr = EL_PLAYER_1 + i;
3619 player->present = FALSE;
3620 player->active = FALSE;
3621 player->mapped = FALSE;
3623 player->killed = FALSE;
3624 player->reanimated = FALSE;
3625 player->buried = FALSE;
3628 player->effective_action = 0;
3629 player->programmed_action = 0;
3630 player->snap_action = 0;
3632 player->mouse_action.lx = 0;
3633 player->mouse_action.ly = 0;
3634 player->mouse_action.button = 0;
3635 player->mouse_action.button_hint = 0;
3637 player->effective_mouse_action.lx = 0;
3638 player->effective_mouse_action.ly = 0;
3639 player->effective_mouse_action.button = 0;
3640 player->effective_mouse_action.button_hint = 0;
3642 for (j = 0; j < MAX_NUM_KEYS; j++)
3643 player->key[j] = FALSE;
3645 player->num_white_keys = 0;
3647 player->dynabomb_count = 0;
3648 player->dynabomb_size = 1;
3649 player->dynabombs_left = 0;
3650 player->dynabomb_xl = FALSE;
3652 player->MovDir = initial_move_dir;
3655 player->GfxDir = initial_move_dir;
3656 player->GfxAction = ACTION_DEFAULT;
3658 player->StepFrame = 0;
3660 player->initial_element = player->element_nr;
3661 player->artwork_element =
3662 (level.use_artwork_element[i] ? level.artwork_element[i] :
3663 player->element_nr);
3664 player->use_murphy = FALSE;
3666 player->block_last_field = FALSE; // initialized in InitPlayerField()
3667 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3669 player->gravity = level.initial_player_gravity[i];
3671 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3673 player->actual_frame_counter = 0;
3675 player->step_counter = 0;
3677 player->last_move_dir = initial_move_dir;
3679 player->is_active = FALSE;
3681 player->is_waiting = FALSE;
3682 player->is_moving = FALSE;
3683 player->is_auto_moving = FALSE;
3684 player->is_digging = FALSE;
3685 player->is_snapping = FALSE;
3686 player->is_collecting = FALSE;
3687 player->is_pushing = FALSE;
3688 player->is_switching = FALSE;
3689 player->is_dropping = FALSE;
3690 player->is_dropping_pressed = FALSE;
3692 player->is_bored = FALSE;
3693 player->is_sleeping = FALSE;
3695 player->was_waiting = TRUE;
3696 player->was_moving = FALSE;
3697 player->was_snapping = FALSE;
3698 player->was_dropping = FALSE;
3700 player->force_dropping = FALSE;
3702 player->frame_counter_bored = -1;
3703 player->frame_counter_sleeping = -1;
3705 player->anim_delay_counter = 0;
3706 player->post_delay_counter = 0;
3708 player->dir_waiting = initial_move_dir;
3709 player->action_waiting = ACTION_DEFAULT;
3710 player->last_action_waiting = ACTION_DEFAULT;
3711 player->special_action_bored = ACTION_DEFAULT;
3712 player->special_action_sleeping = ACTION_DEFAULT;
3714 player->switch_x = -1;
3715 player->switch_y = -1;
3717 player->drop_x = -1;
3718 player->drop_y = -1;
3720 player->show_envelope = 0;
3722 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3724 player->push_delay = -1; // initialized when pushing starts
3725 player->push_delay_value = game.initial_push_delay_value;
3727 player->drop_delay = 0;
3728 player->drop_pressed_delay = 0;
3730 player->last_jx = -1;
3731 player->last_jy = -1;
3735 player->shield_normal_time_left = 0;
3736 player->shield_deadly_time_left = 0;
3738 player->last_removed_element = EL_UNDEFINED;
3740 player->inventory_infinite_element = EL_UNDEFINED;
3741 player->inventory_size = 0;
3743 if (level.use_initial_inventory[i])
3745 for (j = 0; j < level.initial_inventory_size[i]; j++)
3747 int element = level.initial_inventory_content[i][j];
3748 int collect_count = element_info[element].collect_count_initial;
3751 if (!IS_CUSTOM_ELEMENT(element))
3754 if (collect_count == 0)
3755 player->inventory_infinite_element = element;
3757 for (k = 0; k < collect_count; k++)
3758 if (player->inventory_size < MAX_INVENTORY_SIZE)
3759 player->inventory_element[player->inventory_size++] = element;
3763 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3764 SnapField(player, 0, 0);
3766 map_player_action[i] = i;
3769 network_player_action_received = FALSE;
3771 // initial null action
3772 if (network_playing)
3773 SendToServer_MovePlayer(MV_NONE);
3778 TimeLeft = level.time;
3781 ScreenMovDir = MV_NONE;
3785 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3787 game.robot_wheel_x = -1;
3788 game.robot_wheel_y = -1;
3793 game.all_players_gone = FALSE;
3795 game.LevelSolved = FALSE;
3796 game.GameOver = FALSE;
3798 game.GamePlayed = !tape.playing;
3800 game.LevelSolved_GameWon = FALSE;
3801 game.LevelSolved_GameEnd = FALSE;
3802 game.LevelSolved_SaveTape = FALSE;
3803 game.LevelSolved_SaveScore = FALSE;
3805 game.LevelSolved_CountingTime = 0;
3806 game.LevelSolved_CountingScore = 0;
3807 game.LevelSolved_CountingHealth = 0;
3809 game.panel.active = TRUE;
3811 game.no_time_limit = (level.time == 0);
3813 game.yamyam_content_nr = 0;
3814 game.robot_wheel_active = FALSE;
3815 game.magic_wall_active = FALSE;
3816 game.magic_wall_time_left = 0;
3817 game.light_time_left = 0;
3818 game.timegate_time_left = 0;
3819 game.switchgate_pos = 0;
3820 game.wind_direction = level.wind_direction_initial;
3822 game.time_final = 0;
3823 game.score_time_final = 0;
3826 game.score_final = 0;
3828 game.health = MAX_HEALTH;
3829 game.health_final = MAX_HEALTH;
3831 game.gems_still_needed = level.gems_needed;
3832 game.sokoban_fields_still_needed = 0;
3833 game.sokoban_objects_still_needed = 0;
3834 game.lights_still_needed = 0;
3835 game.players_still_needed = 0;
3836 game.friends_still_needed = 0;
3838 game.lenses_time_left = 0;
3839 game.magnify_time_left = 0;
3841 game.ball_active = level.ball_active_initial;
3842 game.ball_content_nr = 0;
3844 game.explosions_delayed = TRUE;
3846 game.envelope_active = FALSE;
3848 for (i = 0; i < NUM_BELTS; i++)
3850 game.belt_dir[i] = MV_NONE;
3851 game.belt_dir_nr[i] = 3; // not moving, next moving left
3854 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3855 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3857 #if DEBUG_INIT_PLAYER
3858 DebugPrintPlayerStatus("Player status at level initialization");
3861 SCAN_PLAYFIELD(x, y)
3863 Tile[x][y] = Last[x][y] = level.field[x][y];
3864 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3865 ChangeDelay[x][y] = 0;
3866 ChangePage[x][y] = -1;
3867 CustomValue[x][y] = 0; // initialized in InitField()
3868 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3870 WasJustMoving[x][y] = 0;
3871 WasJustFalling[x][y] = 0;
3872 CheckCollision[x][y] = 0;
3873 CheckImpact[x][y] = 0;
3875 Pushed[x][y] = FALSE;
3877 ChangeCount[x][y] = 0;
3878 ChangeEvent[x][y] = -1;
3880 ExplodePhase[x][y] = 0;
3881 ExplodeDelay[x][y] = 0;
3882 ExplodeField[x][y] = EX_TYPE_NONE;
3884 RunnerVisit[x][y] = 0;
3885 PlayerVisit[x][y] = 0;
3888 GfxRandom[x][y] = INIT_GFX_RANDOM();
3889 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3890 GfxElement[x][y] = EL_UNDEFINED;
3891 GfxElementEmpty[x][y] = EL_EMPTY;
3892 GfxAction[x][y] = ACTION_DEFAULT;
3893 GfxDir[x][y] = MV_NONE;
3894 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3897 SCAN_PLAYFIELD(x, y)
3899 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3901 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3904 InitField(x, y, TRUE);
3906 ResetGfxAnimation(x, y);
3911 for (i = 0; i < MAX_PLAYERS; i++)
3913 struct PlayerInfo *player = &stored_player[i];
3915 // set number of special actions for bored and sleeping animation
3916 player->num_special_action_bored =
3917 get_num_special_action(player->artwork_element,
3918 ACTION_BORING_1, ACTION_BORING_LAST);
3919 player->num_special_action_sleeping =
3920 get_num_special_action(player->artwork_element,
3921 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3924 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3925 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3927 // initialize type of slippery elements
3928 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3930 if (!IS_CUSTOM_ELEMENT(i))
3932 // default: elements slip down either to the left or right randomly
3933 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3935 // SP style elements prefer to slip down on the left side
3936 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3937 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3939 // BD style elements prefer to slip down on the left side
3940 if (game.emulation == EMU_BOULDERDASH)
3941 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3945 // initialize explosion and ignition delay
3946 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3948 if (!IS_CUSTOM_ELEMENT(i))
3951 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3952 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3953 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3954 int last_phase = (num_phase + 1) * delay;
3955 int half_phase = (num_phase / 2) * delay;
3957 element_info[i].explosion_delay = last_phase - 1;
3958 element_info[i].ignition_delay = half_phase;
3960 if (i == EL_BLACK_ORB)
3961 element_info[i].ignition_delay = 1;
3965 // correct non-moving belts to start moving left
3966 for (i = 0; i < NUM_BELTS; i++)
3967 if (game.belt_dir[i] == MV_NONE)
3968 game.belt_dir_nr[i] = 3; // not moving, next moving left
3970 #if USE_NEW_PLAYER_ASSIGNMENTS
3971 // use preferred player also in local single-player mode
3972 if (!network.enabled && !game.team_mode)
3974 int new_index_nr = setup.network_player_nr;
3976 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3978 for (i = 0; i < MAX_PLAYERS; i++)
3979 stored_player[i].connected_locally = FALSE;
3981 stored_player[new_index_nr].connected_locally = TRUE;
3985 for (i = 0; i < MAX_PLAYERS; i++)
3987 stored_player[i].connected = FALSE;
3989 // in network game mode, the local player might not be the first player
3990 if (stored_player[i].connected_locally)
3991 local_player = &stored_player[i];
3994 if (!network.enabled)
3995 local_player->connected = TRUE;
3999 for (i = 0; i < MAX_PLAYERS; i++)
4000 stored_player[i].connected = tape.player_participates[i];
4002 else if (network.enabled)
4004 // add team mode players connected over the network (needed for correct
4005 // assignment of player figures from level to locally playing players)
4007 for (i = 0; i < MAX_PLAYERS; i++)
4008 if (stored_player[i].connected_network)
4009 stored_player[i].connected = TRUE;
4011 else if (game.team_mode)
4013 // try to guess locally connected team mode players (needed for correct
4014 // assignment of player figures from level to locally playing players)
4016 for (i = 0; i < MAX_PLAYERS; i++)
4017 if (setup.input[i].use_joystick ||
4018 setup.input[i].key.left != KSYM_UNDEFINED)
4019 stored_player[i].connected = TRUE;
4022 #if DEBUG_INIT_PLAYER
4023 DebugPrintPlayerStatus("Player status after level initialization");
4026 #if DEBUG_INIT_PLAYER
4027 Debug("game:init:player", "Reassigning players ...");
4030 // check if any connected player was not found in playfield
4031 for (i = 0; i < MAX_PLAYERS; i++)
4033 struct PlayerInfo *player = &stored_player[i];
4035 if (player->connected && !player->present)
4037 struct PlayerInfo *field_player = NULL;
4039 #if DEBUG_INIT_PLAYER
4040 Debug("game:init:player",
4041 "- looking for field player for player %d ...", i + 1);
4044 // assign first free player found that is present in the playfield
4046 // first try: look for unmapped playfield player that is not connected
4047 for (j = 0; j < MAX_PLAYERS; j++)
4048 if (field_player == NULL &&
4049 stored_player[j].present &&
4050 !stored_player[j].mapped &&
4051 !stored_player[j].connected)
4052 field_player = &stored_player[j];
4054 // second try: look for *any* unmapped playfield player
4055 for (j = 0; j < MAX_PLAYERS; j++)
4056 if (field_player == NULL &&
4057 stored_player[j].present &&
4058 !stored_player[j].mapped)
4059 field_player = &stored_player[j];
4061 if (field_player != NULL)
4063 int jx = field_player->jx, jy = field_player->jy;
4065 #if DEBUG_INIT_PLAYER
4066 Debug("game:init:player", "- found player %d",
4067 field_player->index_nr + 1);
4070 player->present = FALSE;
4071 player->active = FALSE;
4073 field_player->present = TRUE;
4074 field_player->active = TRUE;
4077 player->initial_element = field_player->initial_element;
4078 player->artwork_element = field_player->artwork_element;
4080 player->block_last_field = field_player->block_last_field;
4081 player->block_delay_adjustment = field_player->block_delay_adjustment;
4084 StorePlayer[jx][jy] = field_player->element_nr;
4086 field_player->jx = field_player->last_jx = jx;
4087 field_player->jy = field_player->last_jy = jy;
4089 if (local_player == player)
4090 local_player = field_player;
4092 map_player_action[field_player->index_nr] = i;
4094 field_player->mapped = TRUE;
4096 #if DEBUG_INIT_PLAYER
4097 Debug("game:init:player", "- map_player_action[%d] == %d",
4098 field_player->index_nr + 1, i + 1);
4103 if (player->connected && player->present)
4104 player->mapped = TRUE;
4107 #if DEBUG_INIT_PLAYER
4108 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4113 // check if any connected player was not found in playfield
4114 for (i = 0; i < MAX_PLAYERS; i++)
4116 struct PlayerInfo *player = &stored_player[i];
4118 if (player->connected && !player->present)
4120 for (j = 0; j < MAX_PLAYERS; j++)
4122 struct PlayerInfo *field_player = &stored_player[j];
4123 int jx = field_player->jx, jy = field_player->jy;
4125 // assign first free player found that is present in the playfield
4126 if (field_player->present && !field_player->connected)
4128 player->present = TRUE;
4129 player->active = TRUE;
4131 field_player->present = FALSE;
4132 field_player->active = FALSE;
4134 player->initial_element = field_player->initial_element;
4135 player->artwork_element = field_player->artwork_element;
4137 player->block_last_field = field_player->block_last_field;
4138 player->block_delay_adjustment = field_player->block_delay_adjustment;
4140 StorePlayer[jx][jy] = player->element_nr;
4142 player->jx = player->last_jx = jx;
4143 player->jy = player->last_jy = jy;
4153 Debug("game:init:player", "local_player->present == %d",
4154 local_player->present);
4157 // set focus to local player for network games, else to all players
4158 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4159 game.centered_player_nr_next = game.centered_player_nr;
4160 game.set_centered_player = FALSE;
4161 game.set_centered_player_wrap = FALSE;
4163 if (network_playing && tape.recording)
4165 // store client dependent player focus when recording network games
4166 tape.centered_player_nr_next = game.centered_player_nr_next;
4167 tape.set_centered_player = TRUE;
4172 // when playing a tape, eliminate all players who do not participate
4174 #if USE_NEW_PLAYER_ASSIGNMENTS
4176 if (!game.team_mode)
4178 for (i = 0; i < MAX_PLAYERS; i++)
4180 if (stored_player[i].active &&
4181 !tape.player_participates[map_player_action[i]])
4183 struct PlayerInfo *player = &stored_player[i];
4184 int jx = player->jx, jy = player->jy;
4186 #if DEBUG_INIT_PLAYER
4187 Debug("game:init:player", "Removing player %d at (%d, %d)",
4191 player->active = FALSE;
4192 StorePlayer[jx][jy] = 0;
4193 Tile[jx][jy] = EL_EMPTY;
4200 for (i = 0; i < MAX_PLAYERS; i++)
4202 if (stored_player[i].active &&
4203 !tape.player_participates[i])
4205 struct PlayerInfo *player = &stored_player[i];
4206 int jx = player->jx, jy = player->jy;
4208 player->active = FALSE;
4209 StorePlayer[jx][jy] = 0;
4210 Tile[jx][jy] = EL_EMPTY;
4215 else if (!network.enabled && !game.team_mode) // && !tape.playing
4217 // when in single player mode, eliminate all but the local player
4219 for (i = 0; i < MAX_PLAYERS; i++)
4221 struct PlayerInfo *player = &stored_player[i];
4223 if (player->active && player != local_player)
4225 int jx = player->jx, jy = player->jy;
4227 player->active = FALSE;
4228 player->present = FALSE;
4230 StorePlayer[jx][jy] = 0;
4231 Tile[jx][jy] = EL_EMPTY;
4236 for (i = 0; i < MAX_PLAYERS; i++)
4237 if (stored_player[i].active)
4238 game.players_still_needed++;
4240 if (level.solved_by_one_player)
4241 game.players_still_needed = 1;
4243 // when recording the game, store which players take part in the game
4246 #if USE_NEW_PLAYER_ASSIGNMENTS
4247 for (i = 0; i < MAX_PLAYERS; i++)
4248 if (stored_player[i].connected)
4249 tape.player_participates[i] = TRUE;
4251 for (i = 0; i < MAX_PLAYERS; i++)
4252 if (stored_player[i].active)
4253 tape.player_participates[i] = TRUE;
4257 #if DEBUG_INIT_PLAYER
4258 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4261 if (BorderElement == EL_EMPTY)
4264 SBX_Right = lev_fieldx - SCR_FIELDX;
4266 SBY_Lower = lev_fieldy - SCR_FIELDY;
4271 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4273 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4276 if (full_lev_fieldx <= SCR_FIELDX)
4277 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4278 if (full_lev_fieldy <= SCR_FIELDY)
4279 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4281 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4283 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4286 // if local player not found, look for custom element that might create
4287 // the player (make some assumptions about the right custom element)
4288 if (!local_player->present)
4290 int start_x = 0, start_y = 0;
4291 int found_rating = 0;
4292 int found_element = EL_UNDEFINED;
4293 int player_nr = local_player->index_nr;
4295 SCAN_PLAYFIELD(x, y)
4297 int element = Tile[x][y];
4302 if (level.use_start_element[player_nr] &&
4303 level.start_element[player_nr] == element &&
4310 found_element = element;
4313 if (!IS_CUSTOM_ELEMENT(element))
4316 if (CAN_CHANGE(element))
4318 for (i = 0; i < element_info[element].num_change_pages; i++)
4320 // check for player created from custom element as single target
4321 content = element_info[element].change_page[i].target_element;
4322 is_player = IS_PLAYER_ELEMENT(content);
4324 if (is_player && (found_rating < 3 ||
4325 (found_rating == 3 && element < found_element)))
4331 found_element = element;
4336 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4338 // check for player created from custom element as explosion content
4339 content = element_info[element].content.e[xx][yy];
4340 is_player = IS_PLAYER_ELEMENT(content);
4342 if (is_player && (found_rating < 2 ||
4343 (found_rating == 2 && element < found_element)))
4345 start_x = x + xx - 1;
4346 start_y = y + yy - 1;
4349 found_element = element;
4352 if (!CAN_CHANGE(element))
4355 for (i = 0; i < element_info[element].num_change_pages; i++)
4357 // check for player created from custom element as extended target
4359 element_info[element].change_page[i].target_content.e[xx][yy];
4361 is_player = IS_PLAYER_ELEMENT(content);
4363 if (is_player && (found_rating < 1 ||
4364 (found_rating == 1 && element < found_element)))
4366 start_x = x + xx - 1;
4367 start_y = y + yy - 1;
4370 found_element = element;
4376 scroll_x = SCROLL_POSITION_X(start_x);
4377 scroll_y = SCROLL_POSITION_Y(start_y);
4381 scroll_x = SCROLL_POSITION_X(local_player->jx);
4382 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4385 // !!! FIX THIS (START) !!!
4386 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4388 InitGameEngine_EM();
4390 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4392 InitGameEngine_SP();
4394 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4396 InitGameEngine_MM();
4400 DrawLevel(REDRAW_FIELD);
4403 // after drawing the level, correct some elements
4404 if (game.timegate_time_left == 0)
4405 CloseAllOpenTimegates();
4408 // blit playfield from scroll buffer to normal back buffer for fading in
4409 BlitScreenToBitmap(backbuffer);
4410 // !!! FIX THIS (END) !!!
4412 DrawMaskedBorder(fade_mask);
4417 // full screen redraw is required at this point in the following cases:
4418 // - special editor door undrawn when game was started from level editor
4419 // - drawing area (playfield) was changed and has to be removed completely
4420 redraw_mask = REDRAW_ALL;
4424 if (!game.restart_level)
4426 // copy default game door content to main double buffer
4428 // !!! CHECK AGAIN !!!
4429 SetPanelBackground();
4430 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4431 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4434 SetPanelBackground();
4435 SetDrawBackgroundMask(REDRAW_DOOR_1);
4437 UpdateAndDisplayGameControlValues();
4439 if (!game.restart_level)
4445 CreateGameButtons();
4450 // copy actual game door content to door double buffer for OpenDoor()
4451 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4453 OpenDoor(DOOR_OPEN_ALL);
4455 KeyboardAutoRepeatOffUnlessAutoplay();
4457 #if DEBUG_INIT_PLAYER
4458 DebugPrintPlayerStatus("Player status (final)");
4467 if (!game.restart_level && !tape.playing)
4469 LevelStats_incPlayed(level_nr);
4471 SaveLevelSetup_SeriesInfo();
4474 game.restart_level = FALSE;
4475 game.restart_game_message = NULL;
4477 game.request_active = FALSE;
4478 game.request_active_or_moving = FALSE;
4480 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4481 InitGameActions_MM();
4483 SaveEngineSnapshotToListInitial();
4485 if (!game.restart_level)
4487 PlaySound(SND_GAME_STARTING);
4489 if (setup.sound_music)
4493 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4496 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4497 int actual_player_x, int actual_player_y)
4499 // this is used for non-R'n'D game engines to update certain engine values
4501 // needed to determine if sounds are played within the visible screen area
4502 scroll_x = actual_scroll_x;
4503 scroll_y = actual_scroll_y;
4505 // needed to get player position for "follow finger" playing input method
4506 local_player->jx = actual_player_x;
4507 local_player->jy = actual_player_y;
4510 void InitMovDir(int x, int y)
4512 int i, element = Tile[x][y];
4513 static int xy[4][2] =
4520 static int direction[3][4] =
4522 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4523 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4524 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4533 Tile[x][y] = EL_BUG;
4534 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4537 case EL_SPACESHIP_RIGHT:
4538 case EL_SPACESHIP_UP:
4539 case EL_SPACESHIP_LEFT:
4540 case EL_SPACESHIP_DOWN:
4541 Tile[x][y] = EL_SPACESHIP;
4542 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4545 case EL_BD_BUTTERFLY_RIGHT:
4546 case EL_BD_BUTTERFLY_UP:
4547 case EL_BD_BUTTERFLY_LEFT:
4548 case EL_BD_BUTTERFLY_DOWN:
4549 Tile[x][y] = EL_BD_BUTTERFLY;
4550 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4553 case EL_BD_FIREFLY_RIGHT:
4554 case EL_BD_FIREFLY_UP:
4555 case EL_BD_FIREFLY_LEFT:
4556 case EL_BD_FIREFLY_DOWN:
4557 Tile[x][y] = EL_BD_FIREFLY;
4558 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4561 case EL_PACMAN_RIGHT:
4563 case EL_PACMAN_LEFT:
4564 case EL_PACMAN_DOWN:
4565 Tile[x][y] = EL_PACMAN;
4566 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4569 case EL_YAMYAM_LEFT:
4570 case EL_YAMYAM_RIGHT:
4572 case EL_YAMYAM_DOWN:
4573 Tile[x][y] = EL_YAMYAM;
4574 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4577 case EL_SP_SNIKSNAK:
4578 MovDir[x][y] = MV_UP;
4581 case EL_SP_ELECTRON:
4582 MovDir[x][y] = MV_LEFT;
4589 Tile[x][y] = EL_MOLE;
4590 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4593 case EL_SPRING_LEFT:
4594 case EL_SPRING_RIGHT:
4595 Tile[x][y] = EL_SPRING;
4596 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4600 if (IS_CUSTOM_ELEMENT(element))
4602 struct ElementInfo *ei = &element_info[element];
4603 int move_direction_initial = ei->move_direction_initial;
4604 int move_pattern = ei->move_pattern;
4606 if (move_direction_initial == MV_START_PREVIOUS)
4608 if (MovDir[x][y] != MV_NONE)
4611 move_direction_initial = MV_START_AUTOMATIC;
4614 if (move_direction_initial == MV_START_RANDOM)
4615 MovDir[x][y] = 1 << RND(4);
4616 else if (move_direction_initial & MV_ANY_DIRECTION)
4617 MovDir[x][y] = move_direction_initial;
4618 else if (move_pattern == MV_ALL_DIRECTIONS ||
4619 move_pattern == MV_TURNING_LEFT ||
4620 move_pattern == MV_TURNING_RIGHT ||
4621 move_pattern == MV_TURNING_LEFT_RIGHT ||
4622 move_pattern == MV_TURNING_RIGHT_LEFT ||
4623 move_pattern == MV_TURNING_RANDOM)
4624 MovDir[x][y] = 1 << RND(4);
4625 else if (move_pattern == MV_HORIZONTAL)
4626 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4627 else if (move_pattern == MV_VERTICAL)
4628 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4629 else if (move_pattern & MV_ANY_DIRECTION)
4630 MovDir[x][y] = element_info[element].move_pattern;
4631 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4632 move_pattern == MV_ALONG_RIGHT_SIDE)
4634 // use random direction as default start direction
4635 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4636 MovDir[x][y] = 1 << RND(4);
4638 for (i = 0; i < NUM_DIRECTIONS; i++)
4640 int x1 = x + xy[i][0];
4641 int y1 = y + xy[i][1];
4643 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4645 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4646 MovDir[x][y] = direction[0][i];
4648 MovDir[x][y] = direction[1][i];
4657 MovDir[x][y] = 1 << RND(4);
4659 if (element != EL_BUG &&
4660 element != EL_SPACESHIP &&
4661 element != EL_BD_BUTTERFLY &&
4662 element != EL_BD_FIREFLY)
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 (element == EL_BUG || element == EL_BD_BUTTERFLY)
4674 MovDir[x][y] = direction[0][i];
4677 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4678 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4680 MovDir[x][y] = direction[1][i];
4689 GfxDir[x][y] = MovDir[x][y];
4692 void InitAmoebaNr(int x, int y)
4695 int group_nr = AmoebaNeighbourNr(x, y);
4699 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4701 if (AmoebaCnt[i] == 0)
4709 AmoebaNr[x][y] = group_nr;
4710 AmoebaCnt[group_nr]++;
4711 AmoebaCnt2[group_nr]++;
4714 static void LevelSolved_SetFinalGameValues(void)
4716 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4717 game.score_time_final = (level.use_step_counter ? TimePlayed :
4718 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4720 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4721 game_em.lev->score :
4722 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4726 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4727 MM_HEALTH(game_mm.laser_overload_value) :
4730 game.LevelSolved_CountingTime = game.time_final;
4731 game.LevelSolved_CountingScore = game.score_final;
4732 game.LevelSolved_CountingHealth = game.health_final;
4735 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4737 game.LevelSolved_CountingTime = time;
4738 game.LevelSolved_CountingScore = score;
4739 game.LevelSolved_CountingHealth = health;
4741 game_panel_controls[GAME_PANEL_TIME].value = time;
4742 game_panel_controls[GAME_PANEL_SCORE].value = score;
4743 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4745 DisplayGameControlValues();
4748 static void LevelSolved(void)
4750 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4751 game.players_still_needed > 0)
4754 game.LevelSolved = TRUE;
4755 game.GameOver = TRUE;
4757 // needed here to display correct panel values while player walks into exit
4758 LevelSolved_SetFinalGameValues();
4763 static int time_count_steps;
4764 static int time, time_final;
4765 static float score, score_final; // needed for time score < 10 for 10 seconds
4766 static int health, health_final;
4767 static int game_over_delay_1 = 0;
4768 static int game_over_delay_2 = 0;
4769 static int game_over_delay_3 = 0;
4770 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4771 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4773 if (!game.LevelSolved_GameWon)
4777 // do not start end game actions before the player stops moving (to exit)
4778 if (local_player->active && local_player->MovPos)
4781 // calculate final game values after player finished walking into exit
4782 LevelSolved_SetFinalGameValues();
4784 game.LevelSolved_GameWon = TRUE;
4785 game.LevelSolved_SaveTape = tape.recording;
4786 game.LevelSolved_SaveScore = !tape.playing;
4790 LevelStats_incSolved(level_nr);
4792 SaveLevelSetup_SeriesInfo();
4795 if (tape.auto_play) // tape might already be stopped here
4796 tape.auto_play_level_solved = TRUE;
4800 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4801 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4802 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4804 time = time_final = game.time_final;
4805 score = score_final = game.score_final;
4806 health = health_final = game.health_final;
4808 // update game panel values before (delayed) counting of score (if any)
4809 LevelSolved_DisplayFinalGameValues(time, score, health);
4811 // if level has time score defined, calculate new final game values
4814 int time_final_max = 999;
4815 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4816 int time_frames = 0;
4817 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4818 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4823 time_frames = time_frames_left;
4825 else if (game.no_time_limit && TimePlayed < time_final_max)
4827 time_final = time_final_max;
4828 time_frames = time_frames_final_max - time_frames_played;
4831 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4833 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4835 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4838 score_final += health * time_score;
4841 game.score_final = score_final;
4842 game.health_final = health_final;
4845 // if not counting score after game, immediately update game panel values
4846 if (level_editor_test_game || !setup.count_score_after_game)
4849 score = score_final;
4851 LevelSolved_DisplayFinalGameValues(time, score, health);
4854 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4856 // check if last player has left the level
4857 if (game.exit_x >= 0 &&
4860 int x = game.exit_x;
4861 int y = game.exit_y;
4862 int element = Tile[x][y];
4864 // close exit door after last player
4865 if ((game.all_players_gone &&
4866 (element == EL_EXIT_OPEN ||
4867 element == EL_SP_EXIT_OPEN ||
4868 element == EL_STEEL_EXIT_OPEN)) ||
4869 element == EL_EM_EXIT_OPEN ||
4870 element == EL_EM_STEEL_EXIT_OPEN)
4874 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4875 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4876 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4877 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4878 EL_EM_STEEL_EXIT_CLOSING);
4880 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4883 // player disappears
4884 DrawLevelField(x, y);
4887 for (i = 0; i < MAX_PLAYERS; i++)
4889 struct PlayerInfo *player = &stored_player[i];
4891 if (player->present)
4893 RemovePlayer(player);
4895 // player disappears
4896 DrawLevelField(player->jx, player->jy);
4901 PlaySound(SND_GAME_WINNING);
4904 if (setup.count_score_after_game)
4906 if (time != time_final)
4908 if (game_over_delay_1 > 0)
4910 game_over_delay_1--;
4915 int time_to_go = ABS(time_final - time);
4916 int time_count_dir = (time < time_final ? +1 : -1);
4918 if (time_to_go < time_count_steps)
4919 time_count_steps = 1;
4921 time += time_count_steps * time_count_dir;
4922 score += time_count_steps * time_score;
4924 // set final score to correct rounding differences after counting score
4925 if (time == time_final)
4926 score = score_final;
4928 LevelSolved_DisplayFinalGameValues(time, score, health);
4930 if (time == time_final)
4931 StopSound(SND_GAME_LEVELTIME_BONUS);
4932 else if (setup.sound_loops)
4933 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4935 PlaySound(SND_GAME_LEVELTIME_BONUS);
4940 if (health != health_final)
4942 if (game_over_delay_2 > 0)
4944 game_over_delay_2--;
4949 int health_count_dir = (health < health_final ? +1 : -1);
4951 health += health_count_dir;
4952 score += time_score;
4954 LevelSolved_DisplayFinalGameValues(time, score, health);
4956 if (health == health_final)
4957 StopSound(SND_GAME_LEVELTIME_BONUS);
4958 else if (setup.sound_loops)
4959 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4961 PlaySound(SND_GAME_LEVELTIME_BONUS);
4967 game.panel.active = FALSE;
4969 if (game_over_delay_3 > 0)
4971 game_over_delay_3--;
4981 // used instead of "level_nr" (needed for network games)
4982 int last_level_nr = levelset.level_nr;
4983 boolean tape_saved = FALSE;
4985 game.LevelSolved_GameEnd = TRUE;
4987 if (game.LevelSolved_SaveTape)
4989 // make sure that request dialog to save tape does not open door again
4990 if (!global.use_envelope_request)
4991 CloseDoor(DOOR_CLOSE_1);
4994 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4996 // set unique basename for score tape (also saved in high score table)
4997 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5000 // if no tape is to be saved, close both doors simultaneously
5001 CloseDoor(DOOR_CLOSE_ALL);
5003 if (level_editor_test_game)
5005 SetGameStatus(GAME_MODE_MAIN);
5012 if (!game.LevelSolved_SaveScore)
5014 SetGameStatus(GAME_MODE_MAIN);
5021 if (level_nr == leveldir_current->handicap_level)
5023 leveldir_current->handicap_level++;
5025 SaveLevelSetup_SeriesInfo();
5028 // save score and score tape before potentially erasing tape below
5029 NewHighScore(last_level_nr, tape_saved);
5031 if (setup.increment_levels &&
5032 level_nr < leveldir_current->last_level &&
5035 level_nr++; // advance to next level
5036 TapeErase(); // start with empty tape
5038 if (setup.auto_play_next_level)
5040 LoadLevel(level_nr);
5042 SaveLevelSetup_SeriesInfo();
5046 if (scores.last_added >= 0 && setup.show_scores_after_game)
5048 SetGameStatus(GAME_MODE_SCORES);
5050 DrawHallOfFame(last_level_nr);
5052 else if (setup.auto_play_next_level && setup.increment_levels &&
5053 last_level_nr < leveldir_current->last_level &&
5056 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5060 SetGameStatus(GAME_MODE_MAIN);
5066 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5067 boolean one_score_entry_per_name)
5071 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5074 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5076 struct ScoreEntry *entry = &list->entry[i];
5077 boolean score_is_better = (new_entry->score > entry->score);
5078 boolean score_is_equal = (new_entry->score == entry->score);
5079 boolean time_is_better = (new_entry->time < entry->time);
5080 boolean time_is_equal = (new_entry->time == entry->time);
5081 boolean better_by_score = (score_is_better ||
5082 (score_is_equal && time_is_better));
5083 boolean better_by_time = (time_is_better ||
5084 (time_is_equal && score_is_better));
5085 boolean is_better = (level.rate_time_over_score ? better_by_time :
5087 boolean entry_is_empty = (entry->score == 0 &&
5090 // prevent adding server score entries if also existing in local score file
5091 // (special case: historic score entries have an empty tape basename entry)
5092 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5093 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5096 if (is_better || entry_is_empty)
5098 // player has made it to the hall of fame
5100 if (i < MAX_SCORE_ENTRIES - 1)
5102 int m = MAX_SCORE_ENTRIES - 1;
5105 if (one_score_entry_per_name)
5107 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5108 if (strEqual(list->entry[l].name, new_entry->name))
5111 if (m == i) // player's new highscore overwrites his old one
5115 for (l = m; l > i; l--)
5116 list->entry[l] = list->entry[l - 1];
5121 *entry = *new_entry;
5125 else if (one_score_entry_per_name &&
5126 strEqual(entry->name, new_entry->name))
5128 // player already in high score list with better score or time
5137 void NewHighScore(int level_nr, boolean tape_saved)
5139 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5140 boolean one_per_name = FALSE;
5142 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5143 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5145 new_entry.score = game.score_final;
5146 new_entry.time = game.score_time_final;
5148 LoadScore(level_nr);
5150 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5152 if (scores.last_added < 0)
5155 SaveScore(level_nr);
5157 // store last added local score entry (before merging server scores)
5158 scores.last_added_local = scores.last_added;
5160 if (!game.LevelSolved_SaveTape)
5163 SaveScoreTape(level_nr);
5165 if (setup.ask_for_using_api_server)
5167 setup.use_api_server =
5168 Request("Upload your score and tape to the high score server?", REQ_ASK);
5170 if (!setup.use_api_server)
5171 Request("Not using high score server! Use setup menu to enable again!",
5174 runtime.use_api_server = setup.use_api_server;
5176 // after asking for using API server once, do not ask again
5177 setup.ask_for_using_api_server = FALSE;
5179 SaveSetup_ServerSetup();
5182 SaveServerScore(level_nr, tape_saved);
5185 void MergeServerScore(void)
5187 struct ScoreEntry last_added_entry;
5188 boolean one_per_name = FALSE;
5191 if (scores.last_added >= 0)
5192 last_added_entry = scores.entry[scores.last_added];
5194 for (i = 0; i < server_scores.num_entries; i++)
5196 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5198 if (pos >= 0 && pos <= scores.last_added)
5199 scores.last_added++;
5202 if (scores.last_added >= MAX_SCORE_ENTRIES)
5204 scores.last_added = MAX_SCORE_ENTRIES - 1;
5205 scores.force_last_added = TRUE;
5207 scores.entry[scores.last_added] = last_added_entry;
5211 static int getElementMoveStepsizeExt(int x, int y, int direction)
5213 int element = Tile[x][y];
5214 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5215 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5216 int horiz_move = (dx != 0);
5217 int sign = (horiz_move ? dx : dy);
5218 int step = sign * element_info[element].move_stepsize;
5220 // special values for move stepsize for spring and things on conveyor belt
5223 if (CAN_FALL(element) &&
5224 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5225 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5226 else if (element == EL_SPRING)
5227 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5233 static int getElementMoveStepsize(int x, int y)
5235 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5238 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5240 if (player->GfxAction != action || player->GfxDir != dir)
5242 player->GfxAction = action;
5243 player->GfxDir = dir;
5245 player->StepFrame = 0;
5249 static void ResetGfxFrame(int x, int y)
5251 // profiling showed that "autotest" spends 10~20% of its time in this function
5252 if (DrawingDeactivatedField())
5255 int element = Tile[x][y];
5256 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5258 if (graphic_info[graphic].anim_global_sync)
5259 GfxFrame[x][y] = FrameCounter;
5260 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5261 GfxFrame[x][y] = CustomValue[x][y];
5262 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5263 GfxFrame[x][y] = element_info[element].collect_score;
5264 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5265 GfxFrame[x][y] = ChangeDelay[x][y];
5268 static void ResetGfxAnimation(int x, int y)
5270 GfxAction[x][y] = ACTION_DEFAULT;
5271 GfxDir[x][y] = MovDir[x][y];
5274 ResetGfxFrame(x, y);
5277 static void ResetRandomAnimationValue(int x, int y)
5279 GfxRandom[x][y] = INIT_GFX_RANDOM();
5282 static void InitMovingField(int x, int y, int direction)
5284 int element = Tile[x][y];
5285 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5286 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5289 boolean is_moving_before, is_moving_after;
5291 // check if element was/is moving or being moved before/after mode change
5292 is_moving_before = (WasJustMoving[x][y] != 0);
5293 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5295 // reset animation only for moving elements which change direction of moving
5296 // or which just started or stopped moving
5297 // (else CEs with property "can move" / "not moving" are reset each frame)
5298 if (is_moving_before != is_moving_after ||
5299 direction != MovDir[x][y])
5300 ResetGfxAnimation(x, y);
5302 MovDir[x][y] = direction;
5303 GfxDir[x][y] = direction;
5305 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5306 direction == MV_DOWN && CAN_FALL(element) ?
5307 ACTION_FALLING : ACTION_MOVING);
5309 // this is needed for CEs with property "can move" / "not moving"
5311 if (is_moving_after)
5313 if (Tile[newx][newy] == EL_EMPTY)
5314 Tile[newx][newy] = EL_BLOCKED;
5316 MovDir[newx][newy] = MovDir[x][y];
5318 CustomValue[newx][newy] = CustomValue[x][y];
5320 GfxFrame[newx][newy] = GfxFrame[x][y];
5321 GfxRandom[newx][newy] = GfxRandom[x][y];
5322 GfxAction[newx][newy] = GfxAction[x][y];
5323 GfxDir[newx][newy] = GfxDir[x][y];
5327 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5329 int direction = MovDir[x][y];
5330 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5331 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5337 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5339 int oldx = x, oldy = y;
5340 int direction = MovDir[x][y];
5342 if (direction == MV_LEFT)
5344 else if (direction == MV_RIGHT)
5346 else if (direction == MV_UP)
5348 else if (direction == MV_DOWN)
5351 *comes_from_x = oldx;
5352 *comes_from_y = oldy;
5355 static int MovingOrBlocked2Element(int x, int y)
5357 int element = Tile[x][y];
5359 if (element == EL_BLOCKED)
5363 Blocked2Moving(x, y, &oldx, &oldy);
5364 return Tile[oldx][oldy];
5370 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5372 // like MovingOrBlocked2Element(), but if element is moving
5373 // and (x,y) is the field the moving element is just leaving,
5374 // return EL_BLOCKED instead of the element value
5375 int element = Tile[x][y];
5377 if (IS_MOVING(x, y))
5379 if (element == EL_BLOCKED)
5383 Blocked2Moving(x, y, &oldx, &oldy);
5384 return Tile[oldx][oldy];
5393 static void RemoveField(int x, int y)
5395 Tile[x][y] = EL_EMPTY;
5401 CustomValue[x][y] = 0;
5404 ChangeDelay[x][y] = 0;
5405 ChangePage[x][y] = -1;
5406 Pushed[x][y] = FALSE;
5408 GfxElement[x][y] = EL_UNDEFINED;
5409 GfxAction[x][y] = ACTION_DEFAULT;
5410 GfxDir[x][y] = MV_NONE;
5413 static void RemoveMovingField(int x, int y)
5415 int oldx = x, oldy = y, newx = x, newy = y;
5416 int element = Tile[x][y];
5417 int next_element = EL_UNDEFINED;
5419 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5422 if (IS_MOVING(x, y))
5424 Moving2Blocked(x, y, &newx, &newy);
5426 if (Tile[newx][newy] != EL_BLOCKED)
5428 // element is moving, but target field is not free (blocked), but
5429 // already occupied by something different (example: acid pool);
5430 // in this case, only remove the moving field, but not the target
5432 RemoveField(oldx, oldy);
5434 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5436 TEST_DrawLevelField(oldx, oldy);
5441 else if (element == EL_BLOCKED)
5443 Blocked2Moving(x, y, &oldx, &oldy);
5444 if (!IS_MOVING(oldx, oldy))
5448 if (element == EL_BLOCKED &&
5449 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5450 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5451 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5452 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5453 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5454 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5455 next_element = get_next_element(Tile[oldx][oldy]);
5457 RemoveField(oldx, oldy);
5458 RemoveField(newx, newy);
5460 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5462 if (next_element != EL_UNDEFINED)
5463 Tile[oldx][oldy] = next_element;
5465 TEST_DrawLevelField(oldx, oldy);
5466 TEST_DrawLevelField(newx, newy);
5469 void DrawDynamite(int x, int y)
5471 int sx = SCREENX(x), sy = SCREENY(y);
5472 int graphic = el2img(Tile[x][y]);
5475 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5478 if (IS_WALKABLE_INSIDE(Back[x][y]))
5482 DrawLevelElement(x, y, Back[x][y]);
5483 else if (Store[x][y])
5484 DrawLevelElement(x, y, Store[x][y]);
5485 else if (game.use_masked_elements)
5486 DrawLevelElement(x, y, EL_EMPTY);
5488 frame = getGraphicAnimationFrameXY(graphic, x, y);
5490 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5491 DrawGraphicThruMask(sx, sy, graphic, frame);
5493 DrawGraphic(sx, sy, graphic, frame);
5496 static void CheckDynamite(int x, int y)
5498 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5502 if (MovDelay[x][y] != 0)
5505 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5511 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5516 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5518 boolean num_checked_players = 0;
5521 for (i = 0; i < MAX_PLAYERS; i++)
5523 if (stored_player[i].active)
5525 int sx = stored_player[i].jx;
5526 int sy = stored_player[i].jy;
5528 if (num_checked_players == 0)
5535 *sx1 = MIN(*sx1, sx);
5536 *sy1 = MIN(*sy1, sy);
5537 *sx2 = MAX(*sx2, sx);
5538 *sy2 = MAX(*sy2, sy);
5541 num_checked_players++;
5546 static boolean checkIfAllPlayersFitToScreen_RND(void)
5548 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5550 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5552 return (sx2 - sx1 < SCR_FIELDX &&
5553 sy2 - sy1 < SCR_FIELDY);
5556 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5558 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5560 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5562 *sx = (sx1 + sx2) / 2;
5563 *sy = (sy1 + sy2) / 2;
5566 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5567 boolean center_screen, boolean quick_relocation)
5569 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5570 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5571 boolean no_delay = (tape.warp_forward);
5572 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5573 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5574 int new_scroll_x, new_scroll_y;
5576 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5578 // case 1: quick relocation inside visible screen (without scrolling)
5585 if (!level.shifted_relocation || center_screen)
5587 // relocation _with_ centering of screen
5589 new_scroll_x = SCROLL_POSITION_X(x);
5590 new_scroll_y = SCROLL_POSITION_Y(y);
5594 // relocation _without_ centering of screen
5596 int center_scroll_x = SCROLL_POSITION_X(old_x);
5597 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5598 int offset_x = x + (scroll_x - center_scroll_x);
5599 int offset_y = y + (scroll_y - center_scroll_y);
5601 // for new screen position, apply previous offset to center position
5602 new_scroll_x = SCROLL_POSITION_X(offset_x);
5603 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5606 if (quick_relocation)
5608 // case 2: quick relocation (redraw without visible scrolling)
5610 scroll_x = new_scroll_x;
5611 scroll_y = new_scroll_y;
5618 // case 3: visible relocation (with scrolling to new position)
5620 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5622 SetVideoFrameDelay(wait_delay_value);
5624 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5626 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5627 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5629 if (dx == 0 && dy == 0) // no scrolling needed at all
5635 // set values for horizontal/vertical screen scrolling (half tile size)
5636 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5637 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5638 int pos_x = dx * TILEX / 2;
5639 int pos_y = dy * TILEY / 2;
5640 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5641 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5643 ScrollLevel(dx, dy);
5646 // scroll in two steps of half tile size to make things smoother
5647 BlitScreenToBitmapExt_RND(window, fx, fy);
5649 // scroll second step to align at full tile size
5650 BlitScreenToBitmap(window);
5656 SetVideoFrameDelay(frame_delay_value_old);
5659 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5661 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5662 int player_nr = GET_PLAYER_NR(el_player);
5663 struct PlayerInfo *player = &stored_player[player_nr];
5664 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5665 boolean no_delay = (tape.warp_forward);
5666 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5667 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5668 int old_jx = player->jx;
5669 int old_jy = player->jy;
5670 int old_element = Tile[old_jx][old_jy];
5671 int element = Tile[jx][jy];
5672 boolean player_relocated = (old_jx != jx || old_jy != jy);
5674 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5675 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5676 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5677 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5678 int leave_side_horiz = move_dir_horiz;
5679 int leave_side_vert = move_dir_vert;
5680 int enter_side = enter_side_horiz | enter_side_vert;
5681 int leave_side = leave_side_horiz | leave_side_vert;
5683 if (player->buried) // do not reanimate dead player
5686 if (!player_relocated) // no need to relocate the player
5689 if (IS_PLAYER(jx, jy)) // player already placed at new position
5691 RemoveField(jx, jy); // temporarily remove newly placed player
5692 DrawLevelField(jx, jy);
5695 if (player->present)
5697 while (player->MovPos)
5699 ScrollPlayer(player, SCROLL_GO_ON);
5700 ScrollScreen(NULL, SCROLL_GO_ON);
5702 AdvanceFrameAndPlayerCounters(player->index_nr);
5706 BackToFront_WithFrameDelay(wait_delay_value);
5709 DrawPlayer(player); // needed here only to cleanup last field
5710 DrawLevelField(player->jx, player->jy); // remove player graphic
5712 player->is_moving = FALSE;
5715 if (IS_CUSTOM_ELEMENT(old_element))
5716 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5718 player->index_bit, leave_side);
5720 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5722 player->index_bit, leave_side);
5724 Tile[jx][jy] = el_player;
5725 InitPlayerField(jx, jy, el_player, TRUE);
5727 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5728 possible that the relocation target field did not contain a player element,
5729 but a walkable element, to which the new player was relocated -- in this
5730 case, restore that (already initialized!) element on the player field */
5731 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5733 Tile[jx][jy] = element; // restore previously existing element
5736 // only visually relocate centered player
5737 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5738 FALSE, level.instant_relocation);
5740 TestIfPlayerTouchesBadThing(jx, jy);
5741 TestIfPlayerTouchesCustomElement(jx, jy);
5743 if (IS_CUSTOM_ELEMENT(element))
5744 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5745 player->index_bit, enter_side);
5747 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5748 player->index_bit, enter_side);
5750 if (player->is_switching)
5752 /* ensure that relocation while still switching an element does not cause
5753 a new element to be treated as also switched directly after relocation
5754 (this is important for teleporter switches that teleport the player to
5755 a place where another teleporter switch is in the same direction, which
5756 would then incorrectly be treated as immediately switched before the
5757 direction key that caused the switch was released) */
5759 player->switch_x += jx - old_jx;
5760 player->switch_y += jy - old_jy;
5764 static void Explode(int ex, int ey, int phase, int mode)
5770 // !!! eliminate this variable !!!
5771 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5773 if (game.explosions_delayed)
5775 ExplodeField[ex][ey] = mode;
5779 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5781 int center_element = Tile[ex][ey];
5782 int artwork_element, explosion_element; // set these values later
5784 // remove things displayed in background while burning dynamite
5785 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5788 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5790 // put moving element to center field (and let it explode there)
5791 center_element = MovingOrBlocked2Element(ex, ey);
5792 RemoveMovingField(ex, ey);
5793 Tile[ex][ey] = center_element;
5796 // now "center_element" is finally determined -- set related values now
5797 artwork_element = center_element; // for custom player artwork
5798 explosion_element = center_element; // for custom player artwork
5800 if (IS_PLAYER(ex, ey))
5802 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5804 artwork_element = stored_player[player_nr].artwork_element;
5806 if (level.use_explosion_element[player_nr])
5808 explosion_element = level.explosion_element[player_nr];
5809 artwork_element = explosion_element;
5813 if (mode == EX_TYPE_NORMAL ||
5814 mode == EX_TYPE_CENTER ||
5815 mode == EX_TYPE_CROSS)
5816 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5818 last_phase = element_info[explosion_element].explosion_delay + 1;
5820 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5822 int xx = x - ex + 1;
5823 int yy = y - ey + 1;
5826 if (!IN_LEV_FIELD(x, y) ||
5827 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5828 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5831 element = Tile[x][y];
5833 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5835 element = MovingOrBlocked2Element(x, y);
5837 if (!IS_EXPLOSION_PROOF(element))
5838 RemoveMovingField(x, y);
5841 // indestructible elements can only explode in center (but not flames)
5842 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5843 mode == EX_TYPE_BORDER)) ||
5844 element == EL_FLAMES)
5847 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5848 behaviour, for example when touching a yamyam that explodes to rocks
5849 with active deadly shield, a rock is created under the player !!! */
5850 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5852 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5853 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5854 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5856 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5859 if (IS_ACTIVE_BOMB(element))
5861 // re-activate things under the bomb like gate or penguin
5862 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5869 // save walkable background elements while explosion on same tile
5870 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5871 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5872 Back[x][y] = element;
5874 // ignite explodable elements reached by other explosion
5875 if (element == EL_EXPLOSION)
5876 element = Store2[x][y];
5878 if (AmoebaNr[x][y] &&
5879 (element == EL_AMOEBA_FULL ||
5880 element == EL_BD_AMOEBA ||
5881 element == EL_AMOEBA_GROWING))
5883 AmoebaCnt[AmoebaNr[x][y]]--;
5884 AmoebaCnt2[AmoebaNr[x][y]]--;
5889 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5891 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5893 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5895 if (PLAYERINFO(ex, ey)->use_murphy)
5896 Store[x][y] = EL_EMPTY;
5899 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5900 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5901 else if (IS_PLAYER_ELEMENT(center_element))
5902 Store[x][y] = EL_EMPTY;
5903 else if (center_element == EL_YAMYAM)
5904 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5905 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5906 Store[x][y] = element_info[center_element].content.e[xx][yy];
5908 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5909 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5910 // otherwise) -- FIX THIS !!!
5911 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5912 Store[x][y] = element_info[element].content.e[1][1];
5914 else if (!CAN_EXPLODE(element))
5915 Store[x][y] = element_info[element].content.e[1][1];
5918 Store[x][y] = EL_EMPTY;
5920 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5921 center_element == EL_AMOEBA_TO_DIAMOND)
5922 Store2[x][y] = element;
5924 Tile[x][y] = EL_EXPLOSION;
5925 GfxElement[x][y] = artwork_element;
5927 ExplodePhase[x][y] = 1;
5928 ExplodeDelay[x][y] = last_phase;
5933 if (center_element == EL_YAMYAM)
5934 game.yamyam_content_nr =
5935 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5947 GfxFrame[x][y] = 0; // restart explosion animation
5949 last_phase = ExplodeDelay[x][y];
5951 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5953 // this can happen if the player leaves an explosion just in time
5954 if (GfxElement[x][y] == EL_UNDEFINED)
5955 GfxElement[x][y] = EL_EMPTY;
5957 border_element = Store2[x][y];
5958 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5959 border_element = StorePlayer[x][y];
5961 if (phase == element_info[border_element].ignition_delay ||
5962 phase == last_phase)
5964 boolean border_explosion = FALSE;
5966 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5967 !PLAYER_EXPLOSION_PROTECTED(x, y))
5969 KillPlayerUnlessExplosionProtected(x, y);
5970 border_explosion = TRUE;
5972 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5974 Tile[x][y] = Store2[x][y];
5977 border_explosion = TRUE;
5979 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5981 AmoebaToDiamond(x, y);
5983 border_explosion = TRUE;
5986 // if an element just explodes due to another explosion (chain-reaction),
5987 // do not immediately end the new explosion when it was the last frame of
5988 // the explosion (as it would be done in the following "if"-statement!)
5989 if (border_explosion && phase == last_phase)
5993 if (phase == last_phase)
5997 element = Tile[x][y] = Store[x][y];
5998 Store[x][y] = Store2[x][y] = 0;
5999 GfxElement[x][y] = EL_UNDEFINED;
6001 // player can escape from explosions and might therefore be still alive
6002 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6003 element <= EL_PLAYER_IS_EXPLODING_4)
6005 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6006 int explosion_element = EL_PLAYER_1 + player_nr;
6007 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6008 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6010 if (level.use_explosion_element[player_nr])
6011 explosion_element = level.explosion_element[player_nr];
6013 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6014 element_info[explosion_element].content.e[xx][yy]);
6017 // restore probably existing indestructible background element
6018 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6019 element = Tile[x][y] = Back[x][y];
6022 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6023 GfxDir[x][y] = MV_NONE;
6024 ChangeDelay[x][y] = 0;
6025 ChangePage[x][y] = -1;
6027 CustomValue[x][y] = 0;
6029 InitField_WithBug2(x, y, FALSE);
6031 TEST_DrawLevelField(x, y);
6033 TestIfElementTouchesCustomElement(x, y);
6035 if (GFX_CRUMBLED(element))
6036 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6038 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6039 StorePlayer[x][y] = 0;
6041 if (IS_PLAYER_ELEMENT(element))
6042 RelocatePlayer(x, y, element);
6044 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6046 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6047 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6050 TEST_DrawLevelFieldCrumbled(x, y);
6052 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6054 DrawLevelElement(x, y, Back[x][y]);
6055 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6057 else if (IS_WALKABLE_UNDER(Back[x][y]))
6059 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6060 DrawLevelElementThruMask(x, y, Back[x][y]);
6062 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6063 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6067 static void DynaExplode(int ex, int ey)
6070 int dynabomb_element = Tile[ex][ey];
6071 int dynabomb_size = 1;
6072 boolean dynabomb_xl = FALSE;
6073 struct PlayerInfo *player;
6074 static int xy[4][2] =
6082 if (IS_ACTIVE_BOMB(dynabomb_element))
6084 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6085 dynabomb_size = player->dynabomb_size;
6086 dynabomb_xl = player->dynabomb_xl;
6087 player->dynabombs_left++;
6090 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6092 for (i = 0; i < NUM_DIRECTIONS; i++)
6094 for (j = 1; j <= dynabomb_size; j++)
6096 int x = ex + j * xy[i][0];
6097 int y = ey + j * xy[i][1];
6100 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6103 element = Tile[x][y];
6105 // do not restart explosions of fields with active bombs
6106 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6109 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6111 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6112 !IS_DIGGABLE(element) && !dynabomb_xl)
6118 void Bang(int x, int y)
6120 int element = MovingOrBlocked2Element(x, y);
6121 int explosion_type = EX_TYPE_NORMAL;
6123 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6125 struct PlayerInfo *player = PLAYERINFO(x, y);
6127 element = Tile[x][y] = player->initial_element;
6129 if (level.use_explosion_element[player->index_nr])
6131 int explosion_element = level.explosion_element[player->index_nr];
6133 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6134 explosion_type = EX_TYPE_CROSS;
6135 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6136 explosion_type = EX_TYPE_CENTER;
6144 case EL_BD_BUTTERFLY:
6147 case EL_DARK_YAMYAM:
6151 RaiseScoreElement(element);
6154 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6155 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6156 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6157 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6158 case EL_DYNABOMB_INCREASE_NUMBER:
6159 case EL_DYNABOMB_INCREASE_SIZE:
6160 case EL_DYNABOMB_INCREASE_POWER:
6161 explosion_type = EX_TYPE_DYNA;
6164 case EL_DC_LANDMINE:
6165 explosion_type = EX_TYPE_CENTER;
6170 case EL_LAMP_ACTIVE:
6171 case EL_AMOEBA_TO_DIAMOND:
6172 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6173 explosion_type = EX_TYPE_CENTER;
6177 if (element_info[element].explosion_type == EXPLODES_CROSS)
6178 explosion_type = EX_TYPE_CROSS;
6179 else if (element_info[element].explosion_type == EXPLODES_1X1)
6180 explosion_type = EX_TYPE_CENTER;
6184 if (explosion_type == EX_TYPE_DYNA)
6187 Explode(x, y, EX_PHASE_START, explosion_type);
6189 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6192 static void SplashAcid(int x, int y)
6194 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6195 (!IN_LEV_FIELD(x - 1, y - 2) ||
6196 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6197 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6199 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6200 (!IN_LEV_FIELD(x + 1, y - 2) ||
6201 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6202 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6204 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6207 static void InitBeltMovement(void)
6209 static int belt_base_element[4] =
6211 EL_CONVEYOR_BELT_1_LEFT,
6212 EL_CONVEYOR_BELT_2_LEFT,
6213 EL_CONVEYOR_BELT_3_LEFT,
6214 EL_CONVEYOR_BELT_4_LEFT
6216 static int belt_base_active_element[4] =
6218 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6219 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6220 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6221 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6226 // set frame order for belt animation graphic according to belt direction
6227 for (i = 0; i < NUM_BELTS; i++)
6231 for (j = 0; j < NUM_BELT_PARTS; j++)
6233 int element = belt_base_active_element[belt_nr] + j;
6234 int graphic_1 = el2img(element);
6235 int graphic_2 = el2panelimg(element);
6237 if (game.belt_dir[i] == MV_LEFT)
6239 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6240 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6244 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6245 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6250 SCAN_PLAYFIELD(x, y)
6252 int element = Tile[x][y];
6254 for (i = 0; i < NUM_BELTS; i++)
6256 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6258 int e_belt_nr = getBeltNrFromBeltElement(element);
6261 if (e_belt_nr == belt_nr)
6263 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6265 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6272 static void ToggleBeltSwitch(int x, int y)
6274 static int belt_base_element[4] =
6276 EL_CONVEYOR_BELT_1_LEFT,
6277 EL_CONVEYOR_BELT_2_LEFT,
6278 EL_CONVEYOR_BELT_3_LEFT,
6279 EL_CONVEYOR_BELT_4_LEFT
6281 static int belt_base_active_element[4] =
6283 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6284 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6285 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6286 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6288 static int belt_base_switch_element[4] =
6290 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6291 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6292 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6293 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6295 static int belt_move_dir[4] =
6303 int element = Tile[x][y];
6304 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6305 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6306 int belt_dir = belt_move_dir[belt_dir_nr];
6309 if (!IS_BELT_SWITCH(element))
6312 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6313 game.belt_dir[belt_nr] = belt_dir;
6315 if (belt_dir_nr == 3)
6318 // set frame order for belt animation graphic according to belt direction
6319 for (i = 0; i < NUM_BELT_PARTS; i++)
6321 int element = belt_base_active_element[belt_nr] + i;
6322 int graphic_1 = el2img(element);
6323 int graphic_2 = el2panelimg(element);
6325 if (belt_dir == MV_LEFT)
6327 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6328 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6332 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6333 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6337 SCAN_PLAYFIELD(xx, yy)
6339 int element = Tile[xx][yy];
6341 if (IS_BELT_SWITCH(element))
6343 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6345 if (e_belt_nr == belt_nr)
6347 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6348 TEST_DrawLevelField(xx, yy);
6351 else if (IS_BELT(element) && belt_dir != MV_NONE)
6353 int e_belt_nr = getBeltNrFromBeltElement(element);
6355 if (e_belt_nr == belt_nr)
6357 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6359 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6360 TEST_DrawLevelField(xx, yy);
6363 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6365 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6367 if (e_belt_nr == belt_nr)
6369 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6371 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6372 TEST_DrawLevelField(xx, yy);
6378 static void ToggleSwitchgateSwitch(int x, int y)
6382 game.switchgate_pos = !game.switchgate_pos;
6384 SCAN_PLAYFIELD(xx, yy)
6386 int element = Tile[xx][yy];
6388 if (element == EL_SWITCHGATE_SWITCH_UP)
6390 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6391 TEST_DrawLevelField(xx, yy);
6393 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6395 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6396 TEST_DrawLevelField(xx, yy);
6398 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6400 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6401 TEST_DrawLevelField(xx, yy);
6403 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6405 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6406 TEST_DrawLevelField(xx, yy);
6408 else if (element == EL_SWITCHGATE_OPEN ||
6409 element == EL_SWITCHGATE_OPENING)
6411 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6413 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6415 else if (element == EL_SWITCHGATE_CLOSED ||
6416 element == EL_SWITCHGATE_CLOSING)
6418 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6420 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6425 static int getInvisibleActiveFromInvisibleElement(int element)
6427 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6428 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6429 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6433 static int getInvisibleFromInvisibleActiveElement(int element)
6435 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6436 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6437 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6441 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6445 SCAN_PLAYFIELD(x, y)
6447 int element = Tile[x][y];
6449 if (element == EL_LIGHT_SWITCH &&
6450 game.light_time_left > 0)
6452 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6453 TEST_DrawLevelField(x, y);
6455 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6456 game.light_time_left == 0)
6458 Tile[x][y] = EL_LIGHT_SWITCH;
6459 TEST_DrawLevelField(x, y);
6461 else if (element == EL_EMC_DRIPPER &&
6462 game.light_time_left > 0)
6464 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6465 TEST_DrawLevelField(x, y);
6467 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6468 game.light_time_left == 0)
6470 Tile[x][y] = EL_EMC_DRIPPER;
6471 TEST_DrawLevelField(x, y);
6473 else if (element == EL_INVISIBLE_STEELWALL ||
6474 element == EL_INVISIBLE_WALL ||
6475 element == EL_INVISIBLE_SAND)
6477 if (game.light_time_left > 0)
6478 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6480 TEST_DrawLevelField(x, y);
6482 // uncrumble neighbour fields, if needed
6483 if (element == EL_INVISIBLE_SAND)
6484 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6486 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6487 element == EL_INVISIBLE_WALL_ACTIVE ||
6488 element == EL_INVISIBLE_SAND_ACTIVE)
6490 if (game.light_time_left == 0)
6491 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6493 TEST_DrawLevelField(x, y);
6495 // re-crumble neighbour fields, if needed
6496 if (element == EL_INVISIBLE_SAND)
6497 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6502 static void RedrawAllInvisibleElementsForLenses(void)
6506 SCAN_PLAYFIELD(x, y)
6508 int element = Tile[x][y];
6510 if (element == EL_EMC_DRIPPER &&
6511 game.lenses_time_left > 0)
6513 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6514 TEST_DrawLevelField(x, y);
6516 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6517 game.lenses_time_left == 0)
6519 Tile[x][y] = EL_EMC_DRIPPER;
6520 TEST_DrawLevelField(x, y);
6522 else if (element == EL_INVISIBLE_STEELWALL ||
6523 element == EL_INVISIBLE_WALL ||
6524 element == EL_INVISIBLE_SAND)
6526 if (game.lenses_time_left > 0)
6527 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6529 TEST_DrawLevelField(x, y);
6531 // uncrumble neighbour fields, if needed
6532 if (element == EL_INVISIBLE_SAND)
6533 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6535 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6536 element == EL_INVISIBLE_WALL_ACTIVE ||
6537 element == EL_INVISIBLE_SAND_ACTIVE)
6539 if (game.lenses_time_left == 0)
6540 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6542 TEST_DrawLevelField(x, y);
6544 // re-crumble neighbour fields, if needed
6545 if (element == EL_INVISIBLE_SAND)
6546 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6551 static void RedrawAllInvisibleElementsForMagnifier(void)
6555 SCAN_PLAYFIELD(x, y)
6557 int element = Tile[x][y];
6559 if (element == EL_EMC_FAKE_GRASS &&
6560 game.magnify_time_left > 0)
6562 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6563 TEST_DrawLevelField(x, y);
6565 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6566 game.magnify_time_left == 0)
6568 Tile[x][y] = EL_EMC_FAKE_GRASS;
6569 TEST_DrawLevelField(x, y);
6571 else if (IS_GATE_GRAY(element) &&
6572 game.magnify_time_left > 0)
6574 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6575 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6576 IS_EM_GATE_GRAY(element) ?
6577 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6578 IS_EMC_GATE_GRAY(element) ?
6579 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6580 IS_DC_GATE_GRAY(element) ?
6581 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6583 TEST_DrawLevelField(x, y);
6585 else if (IS_GATE_GRAY_ACTIVE(element) &&
6586 game.magnify_time_left == 0)
6588 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6589 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6590 IS_EM_GATE_GRAY_ACTIVE(element) ?
6591 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6592 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6593 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6594 IS_DC_GATE_GRAY_ACTIVE(element) ?
6595 EL_DC_GATE_WHITE_GRAY :
6597 TEST_DrawLevelField(x, y);
6602 static void ToggleLightSwitch(int x, int y)
6604 int element = Tile[x][y];
6606 game.light_time_left =
6607 (element == EL_LIGHT_SWITCH ?
6608 level.time_light * FRAMES_PER_SECOND : 0);
6610 RedrawAllLightSwitchesAndInvisibleElements();
6613 static void ActivateTimegateSwitch(int x, int y)
6617 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6619 SCAN_PLAYFIELD(xx, yy)
6621 int element = Tile[xx][yy];
6623 if (element == EL_TIMEGATE_CLOSED ||
6624 element == EL_TIMEGATE_CLOSING)
6626 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6627 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6631 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6633 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6634 TEST_DrawLevelField(xx, yy);
6640 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6641 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6644 static void Impact(int x, int y)
6646 boolean last_line = (y == lev_fieldy - 1);
6647 boolean object_hit = FALSE;
6648 boolean impact = (last_line || object_hit);
6649 int element = Tile[x][y];
6650 int smashed = EL_STEELWALL;
6652 if (!last_line) // check if element below was hit
6654 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6657 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6658 MovDir[x][y + 1] != MV_DOWN ||
6659 MovPos[x][y + 1] <= TILEY / 2));
6661 // do not smash moving elements that left the smashed field in time
6662 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6663 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6666 #if USE_QUICKSAND_IMPACT_BUGFIX
6667 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6669 RemoveMovingField(x, y + 1);
6670 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6671 Tile[x][y + 2] = EL_ROCK;
6672 TEST_DrawLevelField(x, y + 2);
6677 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6679 RemoveMovingField(x, y + 1);
6680 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6681 Tile[x][y + 2] = EL_ROCK;
6682 TEST_DrawLevelField(x, y + 2);
6689 smashed = MovingOrBlocked2Element(x, y + 1);
6691 impact = (last_line || object_hit);
6694 if (!last_line && smashed == EL_ACID) // element falls into acid
6696 SplashAcid(x, y + 1);
6700 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6701 // only reset graphic animation if graphic really changes after impact
6703 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6705 ResetGfxAnimation(x, y);
6706 TEST_DrawLevelField(x, y);
6709 if (impact && CAN_EXPLODE_IMPACT(element))
6714 else if (impact && element == EL_PEARL &&
6715 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6717 ResetGfxAnimation(x, y);
6719 Tile[x][y] = EL_PEARL_BREAKING;
6720 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6723 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6725 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6730 if (impact && element == EL_AMOEBA_DROP)
6732 if (object_hit && IS_PLAYER(x, y + 1))
6733 KillPlayerUnlessEnemyProtected(x, y + 1);
6734 else if (object_hit && smashed == EL_PENGUIN)
6738 Tile[x][y] = EL_AMOEBA_GROWING;
6739 Store[x][y] = EL_AMOEBA_WET;
6741 ResetRandomAnimationValue(x, y);
6746 if (object_hit) // check which object was hit
6748 if ((CAN_PASS_MAGIC_WALL(element) &&
6749 (smashed == EL_MAGIC_WALL ||
6750 smashed == EL_BD_MAGIC_WALL)) ||
6751 (CAN_PASS_DC_MAGIC_WALL(element) &&
6752 smashed == EL_DC_MAGIC_WALL))
6755 int activated_magic_wall =
6756 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6757 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6758 EL_DC_MAGIC_WALL_ACTIVE);
6760 // activate magic wall / mill
6761 SCAN_PLAYFIELD(xx, yy)
6763 if (Tile[xx][yy] == smashed)
6764 Tile[xx][yy] = activated_magic_wall;
6767 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6768 game.magic_wall_active = TRUE;
6770 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6771 SND_MAGIC_WALL_ACTIVATING :
6772 smashed == EL_BD_MAGIC_WALL ?
6773 SND_BD_MAGIC_WALL_ACTIVATING :
6774 SND_DC_MAGIC_WALL_ACTIVATING));
6777 if (IS_PLAYER(x, y + 1))
6779 if (CAN_SMASH_PLAYER(element))
6781 KillPlayerUnlessEnemyProtected(x, y + 1);
6785 else if (smashed == EL_PENGUIN)
6787 if (CAN_SMASH_PLAYER(element))
6793 else if (element == EL_BD_DIAMOND)
6795 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6801 else if (((element == EL_SP_INFOTRON ||
6802 element == EL_SP_ZONK) &&
6803 (smashed == EL_SP_SNIKSNAK ||
6804 smashed == EL_SP_ELECTRON ||
6805 smashed == EL_SP_DISK_ORANGE)) ||
6806 (element == EL_SP_INFOTRON &&
6807 smashed == EL_SP_DISK_YELLOW))
6812 else if (CAN_SMASH_EVERYTHING(element))
6814 if (IS_CLASSIC_ENEMY(smashed) ||
6815 CAN_EXPLODE_SMASHED(smashed))
6820 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6822 if (smashed == EL_LAMP ||
6823 smashed == EL_LAMP_ACTIVE)
6828 else if (smashed == EL_NUT)
6830 Tile[x][y + 1] = EL_NUT_BREAKING;
6831 PlayLevelSound(x, y, SND_NUT_BREAKING);
6832 RaiseScoreElement(EL_NUT);
6835 else if (smashed == EL_PEARL)
6837 ResetGfxAnimation(x, y);
6839 Tile[x][y + 1] = EL_PEARL_BREAKING;
6840 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6843 else if (smashed == EL_DIAMOND)
6845 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6846 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6849 else if (IS_BELT_SWITCH(smashed))
6851 ToggleBeltSwitch(x, y + 1);
6853 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6854 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6855 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6856 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6858 ToggleSwitchgateSwitch(x, y + 1);
6860 else if (smashed == EL_LIGHT_SWITCH ||
6861 smashed == EL_LIGHT_SWITCH_ACTIVE)
6863 ToggleLightSwitch(x, y + 1);
6867 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6869 CheckElementChangeBySide(x, y + 1, smashed, element,
6870 CE_SWITCHED, CH_SIDE_TOP);
6871 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6877 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6882 // play sound of magic wall / mill
6884 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6885 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6886 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6888 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6889 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6890 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6891 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6892 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6893 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6898 // play sound of object that hits the ground
6899 if (last_line || object_hit)
6900 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6903 static void TurnRoundExt(int x, int y)
6915 { 0, 0 }, { 0, 0 }, { 0, 0 },
6920 int left, right, back;
6924 { MV_DOWN, MV_UP, MV_RIGHT },
6925 { MV_UP, MV_DOWN, MV_LEFT },
6927 { MV_LEFT, MV_RIGHT, MV_DOWN },
6931 { MV_RIGHT, MV_LEFT, MV_UP }
6934 int element = Tile[x][y];
6935 int move_pattern = element_info[element].move_pattern;
6937 int old_move_dir = MovDir[x][y];
6938 int left_dir = turn[old_move_dir].left;
6939 int right_dir = turn[old_move_dir].right;
6940 int back_dir = turn[old_move_dir].back;
6942 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6943 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6944 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6945 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6947 int left_x = x + left_dx, left_y = y + left_dy;
6948 int right_x = x + right_dx, right_y = y + right_dy;
6949 int move_x = x + move_dx, move_y = y + move_dy;
6953 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6955 TestIfBadThingTouchesOtherBadThing(x, y);
6957 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6958 MovDir[x][y] = right_dir;
6959 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6960 MovDir[x][y] = left_dir;
6962 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6964 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6967 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6969 TestIfBadThingTouchesOtherBadThing(x, y);
6971 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6972 MovDir[x][y] = left_dir;
6973 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6974 MovDir[x][y] = right_dir;
6976 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6978 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6981 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6983 TestIfBadThingTouchesOtherBadThing(x, y);
6985 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6986 MovDir[x][y] = left_dir;
6987 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6988 MovDir[x][y] = right_dir;
6990 if (MovDir[x][y] != old_move_dir)
6993 else if (element == EL_YAMYAM)
6995 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6996 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6998 if (can_turn_left && can_turn_right)
6999 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7000 else if (can_turn_left)
7001 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7002 else if (can_turn_right)
7003 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7005 MovDir[x][y] = back_dir;
7007 MovDelay[x][y] = 16 + 16 * RND(3);
7009 else if (element == EL_DARK_YAMYAM)
7011 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7013 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7016 if (can_turn_left && can_turn_right)
7017 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7018 else if (can_turn_left)
7019 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7020 else if (can_turn_right)
7021 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7023 MovDir[x][y] = back_dir;
7025 MovDelay[x][y] = 16 + 16 * RND(3);
7027 else if (element == EL_PACMAN)
7029 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7030 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7032 if (can_turn_left && can_turn_right)
7033 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7034 else if (can_turn_left)
7035 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7036 else if (can_turn_right)
7037 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7039 MovDir[x][y] = back_dir;
7041 MovDelay[x][y] = 6 + RND(40);
7043 else if (element == EL_PIG)
7045 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7046 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7047 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7048 boolean should_turn_left, should_turn_right, should_move_on;
7050 int rnd = RND(rnd_value);
7052 should_turn_left = (can_turn_left &&
7054 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7055 y + back_dy + left_dy)));
7056 should_turn_right = (can_turn_right &&
7058 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7059 y + back_dy + right_dy)));
7060 should_move_on = (can_move_on &&
7063 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7064 y + move_dy + left_dy) ||
7065 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7066 y + move_dy + right_dy)));
7068 if (should_turn_left || should_turn_right || should_move_on)
7070 if (should_turn_left && should_turn_right && should_move_on)
7071 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7072 rnd < 2 * rnd_value / 3 ? right_dir :
7074 else if (should_turn_left && should_turn_right)
7075 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7076 else if (should_turn_left && should_move_on)
7077 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7078 else if (should_turn_right && should_move_on)
7079 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7080 else if (should_turn_left)
7081 MovDir[x][y] = left_dir;
7082 else if (should_turn_right)
7083 MovDir[x][y] = right_dir;
7084 else if (should_move_on)
7085 MovDir[x][y] = old_move_dir;
7087 else if (can_move_on && rnd > rnd_value / 8)
7088 MovDir[x][y] = old_move_dir;
7089 else if (can_turn_left && can_turn_right)
7090 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7091 else if (can_turn_left && rnd > rnd_value / 8)
7092 MovDir[x][y] = left_dir;
7093 else if (can_turn_right && rnd > rnd_value/8)
7094 MovDir[x][y] = right_dir;
7096 MovDir[x][y] = back_dir;
7098 xx = x + move_xy[MovDir[x][y]].dx;
7099 yy = y + move_xy[MovDir[x][y]].dy;
7101 if (!IN_LEV_FIELD(xx, yy) ||
7102 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7103 MovDir[x][y] = old_move_dir;
7107 else if (element == EL_DRAGON)
7109 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7110 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7111 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7113 int rnd = RND(rnd_value);
7115 if (can_move_on && rnd > rnd_value / 8)
7116 MovDir[x][y] = old_move_dir;
7117 else if (can_turn_left && can_turn_right)
7118 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7119 else if (can_turn_left && rnd > rnd_value / 8)
7120 MovDir[x][y] = left_dir;
7121 else if (can_turn_right && rnd > rnd_value / 8)
7122 MovDir[x][y] = right_dir;
7124 MovDir[x][y] = back_dir;
7126 xx = x + move_xy[MovDir[x][y]].dx;
7127 yy = y + move_xy[MovDir[x][y]].dy;
7129 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7130 MovDir[x][y] = old_move_dir;
7134 else if (element == EL_MOLE)
7136 boolean can_move_on =
7137 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7138 IS_AMOEBOID(Tile[move_x][move_y]) ||
7139 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7142 boolean can_turn_left =
7143 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7144 IS_AMOEBOID(Tile[left_x][left_y])));
7146 boolean can_turn_right =
7147 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7148 IS_AMOEBOID(Tile[right_x][right_y])));
7150 if (can_turn_left && can_turn_right)
7151 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7152 else if (can_turn_left)
7153 MovDir[x][y] = left_dir;
7155 MovDir[x][y] = right_dir;
7158 if (MovDir[x][y] != old_move_dir)
7161 else if (element == EL_BALLOON)
7163 MovDir[x][y] = game.wind_direction;
7166 else if (element == EL_SPRING)
7168 if (MovDir[x][y] & MV_HORIZONTAL)
7170 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7171 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7173 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7174 ResetGfxAnimation(move_x, move_y);
7175 TEST_DrawLevelField(move_x, move_y);
7177 MovDir[x][y] = back_dir;
7179 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7180 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7181 MovDir[x][y] = MV_NONE;
7186 else if (element == EL_ROBOT ||
7187 element == EL_SATELLITE ||
7188 element == EL_PENGUIN ||
7189 element == EL_EMC_ANDROID)
7191 int attr_x = -1, attr_y = -1;
7193 if (game.all_players_gone)
7195 attr_x = game.exit_x;
7196 attr_y = game.exit_y;
7202 for (i = 0; i < MAX_PLAYERS; i++)
7204 struct PlayerInfo *player = &stored_player[i];
7205 int jx = player->jx, jy = player->jy;
7207 if (!player->active)
7211 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7219 if (element == EL_ROBOT &&
7220 game.robot_wheel_x >= 0 &&
7221 game.robot_wheel_y >= 0 &&
7222 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7223 game.engine_version < VERSION_IDENT(3,1,0,0)))
7225 attr_x = game.robot_wheel_x;
7226 attr_y = game.robot_wheel_y;
7229 if (element == EL_PENGUIN)
7232 static int xy[4][2] =
7240 for (i = 0; i < NUM_DIRECTIONS; i++)
7242 int ex = x + xy[i][0];
7243 int ey = y + xy[i][1];
7245 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7246 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7247 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7248 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7257 MovDir[x][y] = MV_NONE;
7259 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7260 else if (attr_x > x)
7261 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7263 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7264 else if (attr_y > y)
7265 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7267 if (element == EL_ROBOT)
7271 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7272 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7273 Moving2Blocked(x, y, &newx, &newy);
7275 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7276 MovDelay[x][y] = 8 + 8 * !RND(3);
7278 MovDelay[x][y] = 16;
7280 else if (element == EL_PENGUIN)
7286 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7288 boolean first_horiz = RND(2);
7289 int new_move_dir = MovDir[x][y];
7292 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7293 Moving2Blocked(x, y, &newx, &newy);
7295 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7299 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7300 Moving2Blocked(x, y, &newx, &newy);
7302 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7305 MovDir[x][y] = old_move_dir;
7309 else if (element == EL_SATELLITE)
7315 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7317 boolean first_horiz = RND(2);
7318 int new_move_dir = MovDir[x][y];
7321 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7322 Moving2Blocked(x, y, &newx, &newy);
7324 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7328 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7329 Moving2Blocked(x, y, &newx, &newy);
7331 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7334 MovDir[x][y] = old_move_dir;
7338 else if (element == EL_EMC_ANDROID)
7340 static int check_pos[16] =
7342 -1, // 0 => (invalid)
7345 -1, // 3 => (invalid)
7347 0, // 5 => MV_LEFT | MV_UP
7348 2, // 6 => MV_RIGHT | MV_UP
7349 -1, // 7 => (invalid)
7351 6, // 9 => MV_LEFT | MV_DOWN
7352 4, // 10 => MV_RIGHT | MV_DOWN
7353 -1, // 11 => (invalid)
7354 -1, // 12 => (invalid)
7355 -1, // 13 => (invalid)
7356 -1, // 14 => (invalid)
7357 -1, // 15 => (invalid)
7365 { -1, -1, MV_LEFT | MV_UP },
7367 { +1, -1, MV_RIGHT | MV_UP },
7368 { +1, 0, MV_RIGHT },
7369 { +1, +1, MV_RIGHT | MV_DOWN },
7371 { -1, +1, MV_LEFT | MV_DOWN },
7374 int start_pos, check_order;
7375 boolean can_clone = FALSE;
7378 // check if there is any free field around current position
7379 for (i = 0; i < 8; i++)
7381 int newx = x + check_xy[i].dx;
7382 int newy = y + check_xy[i].dy;
7384 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7392 if (can_clone) // randomly find an element to clone
7396 start_pos = check_pos[RND(8)];
7397 check_order = (RND(2) ? -1 : +1);
7399 for (i = 0; i < 8; i++)
7401 int pos_raw = start_pos + i * check_order;
7402 int pos = (pos_raw + 8) % 8;
7403 int newx = x + check_xy[pos].dx;
7404 int newy = y + check_xy[pos].dy;
7406 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7408 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7409 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7411 Store[x][y] = Tile[newx][newy];
7420 if (can_clone) // randomly find a direction to move
7424 start_pos = check_pos[RND(8)];
7425 check_order = (RND(2) ? -1 : +1);
7427 for (i = 0; i < 8; i++)
7429 int pos_raw = start_pos + i * check_order;
7430 int pos = (pos_raw + 8) % 8;
7431 int newx = x + check_xy[pos].dx;
7432 int newy = y + check_xy[pos].dy;
7433 int new_move_dir = check_xy[pos].dir;
7435 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7437 MovDir[x][y] = new_move_dir;
7438 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7447 if (can_clone) // cloning and moving successful
7450 // cannot clone -- try to move towards player
7452 start_pos = check_pos[MovDir[x][y] & 0x0f];
7453 check_order = (RND(2) ? -1 : +1);
7455 for (i = 0; i < 3; i++)
7457 // first check start_pos, then previous/next or (next/previous) pos
7458 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7459 int pos = (pos_raw + 8) % 8;
7460 int newx = x + check_xy[pos].dx;
7461 int newy = y + check_xy[pos].dy;
7462 int new_move_dir = check_xy[pos].dir;
7464 if (IS_PLAYER(newx, newy))
7467 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7469 MovDir[x][y] = new_move_dir;
7470 MovDelay[x][y] = level.android_move_time * 8 + 1;
7477 else if (move_pattern == MV_TURNING_LEFT ||
7478 move_pattern == MV_TURNING_RIGHT ||
7479 move_pattern == MV_TURNING_LEFT_RIGHT ||
7480 move_pattern == MV_TURNING_RIGHT_LEFT ||
7481 move_pattern == MV_TURNING_RANDOM ||
7482 move_pattern == MV_ALL_DIRECTIONS)
7484 boolean can_turn_left =
7485 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7486 boolean can_turn_right =
7487 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7489 if (element_info[element].move_stepsize == 0) // "not moving"
7492 if (move_pattern == MV_TURNING_LEFT)
7493 MovDir[x][y] = left_dir;
7494 else if (move_pattern == MV_TURNING_RIGHT)
7495 MovDir[x][y] = right_dir;
7496 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7497 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7498 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7499 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7500 else if (move_pattern == MV_TURNING_RANDOM)
7501 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7502 can_turn_right && !can_turn_left ? right_dir :
7503 RND(2) ? left_dir : right_dir);
7504 else if (can_turn_left && can_turn_right)
7505 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7506 else if (can_turn_left)
7507 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7508 else if (can_turn_right)
7509 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7511 MovDir[x][y] = back_dir;
7513 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7515 else if (move_pattern == MV_HORIZONTAL ||
7516 move_pattern == MV_VERTICAL)
7518 if (move_pattern & old_move_dir)
7519 MovDir[x][y] = back_dir;
7520 else if (move_pattern == MV_HORIZONTAL)
7521 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7522 else if (move_pattern == MV_VERTICAL)
7523 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7525 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7527 else if (move_pattern & MV_ANY_DIRECTION)
7529 MovDir[x][y] = move_pattern;
7530 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7532 else if (move_pattern & MV_WIND_DIRECTION)
7534 MovDir[x][y] = game.wind_direction;
7535 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7537 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7539 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7540 MovDir[x][y] = left_dir;
7541 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7542 MovDir[x][y] = right_dir;
7544 if (MovDir[x][y] != old_move_dir)
7545 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7547 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7549 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7550 MovDir[x][y] = right_dir;
7551 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7552 MovDir[x][y] = left_dir;
7554 if (MovDir[x][y] != old_move_dir)
7555 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7557 else if (move_pattern == MV_TOWARDS_PLAYER ||
7558 move_pattern == MV_AWAY_FROM_PLAYER)
7560 int attr_x = -1, attr_y = -1;
7562 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7564 if (game.all_players_gone)
7566 attr_x = game.exit_x;
7567 attr_y = game.exit_y;
7573 for (i = 0; i < MAX_PLAYERS; i++)
7575 struct PlayerInfo *player = &stored_player[i];
7576 int jx = player->jx, jy = player->jy;
7578 if (!player->active)
7582 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7590 MovDir[x][y] = MV_NONE;
7592 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7593 else if (attr_x > x)
7594 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7596 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7597 else if (attr_y > y)
7598 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7600 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7602 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7604 boolean first_horiz = RND(2);
7605 int new_move_dir = MovDir[x][y];
7607 if (element_info[element].move_stepsize == 0) // "not moving"
7609 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7610 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7616 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7617 Moving2Blocked(x, y, &newx, &newy);
7619 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7623 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7624 Moving2Blocked(x, y, &newx, &newy);
7626 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7629 MovDir[x][y] = old_move_dir;
7632 else if (move_pattern == MV_WHEN_PUSHED ||
7633 move_pattern == MV_WHEN_DROPPED)
7635 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7636 MovDir[x][y] = MV_NONE;
7640 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7642 static int test_xy[7][2] =
7652 static int test_dir[7] =
7662 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7663 int move_preference = -1000000; // start with very low preference
7664 int new_move_dir = MV_NONE;
7665 int start_test = RND(4);
7668 for (i = 0; i < NUM_DIRECTIONS; i++)
7670 int move_dir = test_dir[start_test + i];
7671 int move_dir_preference;
7673 xx = x + test_xy[start_test + i][0];
7674 yy = y + test_xy[start_test + i][1];
7676 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7677 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7679 new_move_dir = move_dir;
7684 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7687 move_dir_preference = -1 * RunnerVisit[xx][yy];
7688 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7689 move_dir_preference = PlayerVisit[xx][yy];
7691 if (move_dir_preference > move_preference)
7693 // prefer field that has not been visited for the longest time
7694 move_preference = move_dir_preference;
7695 new_move_dir = move_dir;
7697 else if (move_dir_preference == move_preference &&
7698 move_dir == old_move_dir)
7700 // prefer last direction when all directions are preferred equally
7701 move_preference = move_dir_preference;
7702 new_move_dir = move_dir;
7706 MovDir[x][y] = new_move_dir;
7707 if (old_move_dir != new_move_dir)
7708 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7712 static void TurnRound(int x, int y)
7714 int direction = MovDir[x][y];
7718 GfxDir[x][y] = MovDir[x][y];
7720 if (direction != MovDir[x][y])
7724 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7726 ResetGfxFrame(x, y);
7729 static boolean JustBeingPushed(int x, int y)
7733 for (i = 0; i < MAX_PLAYERS; i++)
7735 struct PlayerInfo *player = &stored_player[i];
7737 if (player->active && player->is_pushing && player->MovPos)
7739 int next_jx = player->jx + (player->jx - player->last_jx);
7740 int next_jy = player->jy + (player->jy - player->last_jy);
7742 if (x == next_jx && y == next_jy)
7750 static void StartMoving(int x, int y)
7752 boolean started_moving = FALSE; // some elements can fall _and_ move
7753 int element = Tile[x][y];
7758 if (MovDelay[x][y] == 0)
7759 GfxAction[x][y] = ACTION_DEFAULT;
7761 if (CAN_FALL(element) && y < lev_fieldy - 1)
7763 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7764 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7765 if (JustBeingPushed(x, y))
7768 if (element == EL_QUICKSAND_FULL)
7770 if (IS_FREE(x, y + 1))
7772 InitMovingField(x, y, MV_DOWN);
7773 started_moving = TRUE;
7775 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7776 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7777 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7778 Store[x][y] = EL_ROCK;
7780 Store[x][y] = EL_ROCK;
7783 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7785 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7787 if (!MovDelay[x][y])
7789 MovDelay[x][y] = TILEY + 1;
7791 ResetGfxAnimation(x, y);
7792 ResetGfxAnimation(x, y + 1);
7797 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7798 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7805 Tile[x][y] = EL_QUICKSAND_EMPTY;
7806 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7807 Store[x][y + 1] = Store[x][y];
7810 PlayLevelSoundAction(x, y, ACTION_FILLING);
7812 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7814 if (!MovDelay[x][y])
7816 MovDelay[x][y] = TILEY + 1;
7818 ResetGfxAnimation(x, y);
7819 ResetGfxAnimation(x, y + 1);
7824 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7825 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7832 Tile[x][y] = EL_QUICKSAND_EMPTY;
7833 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7834 Store[x][y + 1] = Store[x][y];
7837 PlayLevelSoundAction(x, y, ACTION_FILLING);
7840 else if (element == EL_QUICKSAND_FAST_FULL)
7842 if (IS_FREE(x, y + 1))
7844 InitMovingField(x, y, MV_DOWN);
7845 started_moving = TRUE;
7847 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7848 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7849 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7850 Store[x][y] = EL_ROCK;
7852 Store[x][y] = EL_ROCK;
7855 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7857 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7859 if (!MovDelay[x][y])
7861 MovDelay[x][y] = TILEY + 1;
7863 ResetGfxAnimation(x, y);
7864 ResetGfxAnimation(x, y + 1);
7869 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7870 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7877 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7878 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7879 Store[x][y + 1] = Store[x][y];
7882 PlayLevelSoundAction(x, y, ACTION_FILLING);
7884 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7886 if (!MovDelay[x][y])
7888 MovDelay[x][y] = TILEY + 1;
7890 ResetGfxAnimation(x, y);
7891 ResetGfxAnimation(x, y + 1);
7896 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7897 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7904 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7905 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7906 Store[x][y + 1] = Store[x][y];
7909 PlayLevelSoundAction(x, y, ACTION_FILLING);
7912 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7913 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7915 InitMovingField(x, y, MV_DOWN);
7916 started_moving = TRUE;
7918 Tile[x][y] = EL_QUICKSAND_FILLING;
7919 Store[x][y] = element;
7921 PlayLevelSoundAction(x, y, ACTION_FILLING);
7923 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7924 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7926 InitMovingField(x, y, MV_DOWN);
7927 started_moving = TRUE;
7929 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7930 Store[x][y] = element;
7932 PlayLevelSoundAction(x, y, ACTION_FILLING);
7934 else if (element == EL_MAGIC_WALL_FULL)
7936 if (IS_FREE(x, y + 1))
7938 InitMovingField(x, y, MV_DOWN);
7939 started_moving = TRUE;
7941 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7942 Store[x][y] = EL_CHANGED(Store[x][y]);
7944 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7946 if (!MovDelay[x][y])
7947 MovDelay[x][y] = TILEY / 4 + 1;
7956 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7957 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7958 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7962 else if (element == EL_BD_MAGIC_WALL_FULL)
7964 if (IS_FREE(x, y + 1))
7966 InitMovingField(x, y, MV_DOWN);
7967 started_moving = TRUE;
7969 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7970 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7972 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7974 if (!MovDelay[x][y])
7975 MovDelay[x][y] = TILEY / 4 + 1;
7984 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7985 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7986 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7990 else if (element == EL_DC_MAGIC_WALL_FULL)
7992 if (IS_FREE(x, y + 1))
7994 InitMovingField(x, y, MV_DOWN);
7995 started_moving = TRUE;
7997 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7998 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8000 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8002 if (!MovDelay[x][y])
8003 MovDelay[x][y] = TILEY / 4 + 1;
8012 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8013 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8014 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8018 else if ((CAN_PASS_MAGIC_WALL(element) &&
8019 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8020 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8021 (CAN_PASS_DC_MAGIC_WALL(element) &&
8022 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8025 InitMovingField(x, y, MV_DOWN);
8026 started_moving = TRUE;
8029 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8030 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8031 EL_DC_MAGIC_WALL_FILLING);
8032 Store[x][y] = element;
8034 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8036 SplashAcid(x, y + 1);
8038 InitMovingField(x, y, MV_DOWN);
8039 started_moving = TRUE;
8041 Store[x][y] = EL_ACID;
8044 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8045 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8046 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8047 CAN_FALL(element) && WasJustFalling[x][y] &&
8048 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8050 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8051 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8052 (Tile[x][y + 1] == EL_BLOCKED)))
8054 /* this is needed for a special case not covered by calling "Impact()"
8055 from "ContinueMoving()": if an element moves to a tile directly below
8056 another element which was just falling on that tile (which was empty
8057 in the previous frame), the falling element above would just stop
8058 instead of smashing the element below (in previous version, the above
8059 element was just checked for "moving" instead of "falling", resulting
8060 in incorrect smashes caused by horizontal movement of the above
8061 element; also, the case of the player being the element to smash was
8062 simply not covered here... :-/ ) */
8064 CheckCollision[x][y] = 0;
8065 CheckImpact[x][y] = 0;
8069 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8071 if (MovDir[x][y] == MV_NONE)
8073 InitMovingField(x, y, MV_DOWN);
8074 started_moving = TRUE;
8077 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8079 if (WasJustFalling[x][y]) // prevent animation from being restarted
8080 MovDir[x][y] = MV_DOWN;
8082 InitMovingField(x, y, MV_DOWN);
8083 started_moving = TRUE;
8085 else if (element == EL_AMOEBA_DROP)
8087 Tile[x][y] = EL_AMOEBA_GROWING;
8088 Store[x][y] = EL_AMOEBA_WET;
8090 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8091 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8092 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8093 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8095 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8096 (IS_FREE(x - 1, y + 1) ||
8097 Tile[x - 1][y + 1] == EL_ACID));
8098 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8099 (IS_FREE(x + 1, y + 1) ||
8100 Tile[x + 1][y + 1] == EL_ACID));
8101 boolean can_fall_any = (can_fall_left || can_fall_right);
8102 boolean can_fall_both = (can_fall_left && can_fall_right);
8103 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8105 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8107 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8108 can_fall_right = FALSE;
8109 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8110 can_fall_left = FALSE;
8111 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8112 can_fall_right = FALSE;
8113 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8114 can_fall_left = FALSE;
8116 can_fall_any = (can_fall_left || can_fall_right);
8117 can_fall_both = FALSE;
8122 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8123 can_fall_right = FALSE; // slip down on left side
8125 can_fall_left = !(can_fall_right = RND(2));
8127 can_fall_both = FALSE;
8132 // if not determined otherwise, prefer left side for slipping down
8133 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8134 started_moving = TRUE;
8137 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8139 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8140 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8141 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8142 int belt_dir = game.belt_dir[belt_nr];
8144 if ((belt_dir == MV_LEFT && left_is_free) ||
8145 (belt_dir == MV_RIGHT && right_is_free))
8147 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8149 InitMovingField(x, y, belt_dir);
8150 started_moving = TRUE;
8152 Pushed[x][y] = TRUE;
8153 Pushed[nextx][y] = TRUE;
8155 GfxAction[x][y] = ACTION_DEFAULT;
8159 MovDir[x][y] = 0; // if element was moving, stop it
8164 // not "else if" because of elements that can fall and move (EL_SPRING)
8165 if (CAN_MOVE(element) && !started_moving)
8167 int move_pattern = element_info[element].move_pattern;
8170 Moving2Blocked(x, y, &newx, &newy);
8172 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8175 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8176 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8178 WasJustMoving[x][y] = 0;
8179 CheckCollision[x][y] = 0;
8181 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8183 if (Tile[x][y] != element) // element has changed
8187 if (!MovDelay[x][y]) // start new movement phase
8189 // all objects that can change their move direction after each step
8190 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8192 if (element != EL_YAMYAM &&
8193 element != EL_DARK_YAMYAM &&
8194 element != EL_PACMAN &&
8195 !(move_pattern & MV_ANY_DIRECTION) &&
8196 move_pattern != MV_TURNING_LEFT &&
8197 move_pattern != MV_TURNING_RIGHT &&
8198 move_pattern != MV_TURNING_LEFT_RIGHT &&
8199 move_pattern != MV_TURNING_RIGHT_LEFT &&
8200 move_pattern != MV_TURNING_RANDOM)
8204 if (MovDelay[x][y] && (element == EL_BUG ||
8205 element == EL_SPACESHIP ||
8206 element == EL_SP_SNIKSNAK ||
8207 element == EL_SP_ELECTRON ||
8208 element == EL_MOLE))
8209 TEST_DrawLevelField(x, y);
8213 if (MovDelay[x][y]) // wait some time before next movement
8217 if (element == EL_ROBOT ||
8218 element == EL_YAMYAM ||
8219 element == EL_DARK_YAMYAM)
8221 DrawLevelElementAnimationIfNeeded(x, y, element);
8222 PlayLevelSoundAction(x, y, ACTION_WAITING);
8224 else if (element == EL_SP_ELECTRON)
8225 DrawLevelElementAnimationIfNeeded(x, y, element);
8226 else if (element == EL_DRAGON)
8229 int dir = MovDir[x][y];
8230 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8231 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8232 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8233 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8234 dir == MV_UP ? IMG_FLAMES_1_UP :
8235 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8236 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8238 GfxAction[x][y] = ACTION_ATTACKING;
8240 if (IS_PLAYER(x, y))
8241 DrawPlayerField(x, y);
8243 TEST_DrawLevelField(x, y);
8245 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8247 for (i = 1; i <= 3; i++)
8249 int xx = x + i * dx;
8250 int yy = y + i * dy;
8251 int sx = SCREENX(xx);
8252 int sy = SCREENY(yy);
8253 int flame_graphic = graphic + (i - 1);
8255 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8260 int flamed = MovingOrBlocked2Element(xx, yy);
8262 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8265 RemoveMovingField(xx, yy);
8267 ChangeDelay[xx][yy] = 0;
8269 Tile[xx][yy] = EL_FLAMES;
8271 if (IN_SCR_FIELD(sx, sy))
8273 TEST_DrawLevelFieldCrumbled(xx, yy);
8274 DrawGraphic(sx, sy, flame_graphic, frame);
8279 if (Tile[xx][yy] == EL_FLAMES)
8280 Tile[xx][yy] = EL_EMPTY;
8281 TEST_DrawLevelField(xx, yy);
8286 if (MovDelay[x][y]) // element still has to wait some time
8288 PlayLevelSoundAction(x, y, ACTION_WAITING);
8294 // now make next step
8296 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8298 if (DONT_COLLIDE_WITH(element) &&
8299 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8300 !PLAYER_ENEMY_PROTECTED(newx, newy))
8302 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8307 else if (CAN_MOVE_INTO_ACID(element) &&
8308 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8309 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8310 (MovDir[x][y] == MV_DOWN ||
8311 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8313 SplashAcid(newx, newy);
8314 Store[x][y] = EL_ACID;
8316 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8318 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8319 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8320 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8321 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8324 TEST_DrawLevelField(x, y);
8326 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8327 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8328 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8330 game.friends_still_needed--;
8331 if (!game.friends_still_needed &&
8333 game.all_players_gone)
8338 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8340 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8341 TEST_DrawLevelField(newx, newy);
8343 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8345 else if (!IS_FREE(newx, newy))
8347 GfxAction[x][y] = ACTION_WAITING;
8349 if (IS_PLAYER(x, y))
8350 DrawPlayerField(x, y);
8352 TEST_DrawLevelField(x, y);
8357 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8359 if (IS_FOOD_PIG(Tile[newx][newy]))
8361 if (IS_MOVING(newx, newy))
8362 RemoveMovingField(newx, newy);
8365 Tile[newx][newy] = EL_EMPTY;
8366 TEST_DrawLevelField(newx, newy);
8369 PlayLevelSound(x, y, SND_PIG_DIGGING);
8371 else if (!IS_FREE(newx, newy))
8373 if (IS_PLAYER(x, y))
8374 DrawPlayerField(x, y);
8376 TEST_DrawLevelField(x, y);
8381 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8383 if (Store[x][y] != EL_EMPTY)
8385 boolean can_clone = FALSE;
8388 // check if element to clone is still there
8389 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8391 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8399 // cannot clone or target field not free anymore -- do not clone
8400 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8401 Store[x][y] = EL_EMPTY;
8404 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8406 if (IS_MV_DIAGONAL(MovDir[x][y]))
8408 int diagonal_move_dir = MovDir[x][y];
8409 int stored = Store[x][y];
8410 int change_delay = 8;
8413 // android is moving diagonally
8415 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8417 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8418 GfxElement[x][y] = EL_EMC_ANDROID;
8419 GfxAction[x][y] = ACTION_SHRINKING;
8420 GfxDir[x][y] = diagonal_move_dir;
8421 ChangeDelay[x][y] = change_delay;
8423 if (Store[x][y] == EL_EMPTY)
8424 Store[x][y] = GfxElementEmpty[x][y];
8426 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8429 DrawLevelGraphicAnimation(x, y, graphic);
8430 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8432 if (Tile[newx][newy] == EL_ACID)
8434 SplashAcid(newx, newy);
8439 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8441 Store[newx][newy] = EL_EMC_ANDROID;
8442 GfxElement[newx][newy] = EL_EMC_ANDROID;
8443 GfxAction[newx][newy] = ACTION_GROWING;
8444 GfxDir[newx][newy] = diagonal_move_dir;
8445 ChangeDelay[newx][newy] = change_delay;
8447 graphic = el_act_dir2img(GfxElement[newx][newy],
8448 GfxAction[newx][newy], GfxDir[newx][newy]);
8450 DrawLevelGraphicAnimation(newx, newy, graphic);
8451 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8457 Tile[newx][newy] = EL_EMPTY;
8458 TEST_DrawLevelField(newx, newy);
8460 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8463 else if (!IS_FREE(newx, newy))
8468 else if (IS_CUSTOM_ELEMENT(element) &&
8469 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8471 if (!DigFieldByCE(newx, newy, element))
8474 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8476 RunnerVisit[x][y] = FrameCounter;
8477 PlayerVisit[x][y] /= 8; // expire player visit path
8480 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8482 if (!IS_FREE(newx, newy))
8484 if (IS_PLAYER(x, y))
8485 DrawPlayerField(x, y);
8487 TEST_DrawLevelField(x, y);
8493 boolean wanna_flame = !RND(10);
8494 int dx = newx - x, dy = newy - y;
8495 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8496 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8497 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8498 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8499 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8500 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8503 IS_CLASSIC_ENEMY(element1) ||
8504 IS_CLASSIC_ENEMY(element2)) &&
8505 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8506 element1 != EL_FLAMES && element2 != EL_FLAMES)
8508 ResetGfxAnimation(x, y);
8509 GfxAction[x][y] = ACTION_ATTACKING;
8511 if (IS_PLAYER(x, y))
8512 DrawPlayerField(x, y);
8514 TEST_DrawLevelField(x, y);
8516 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8518 MovDelay[x][y] = 50;
8520 Tile[newx][newy] = EL_FLAMES;
8521 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8522 Tile[newx1][newy1] = EL_FLAMES;
8523 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8524 Tile[newx2][newy2] = EL_FLAMES;
8530 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8531 Tile[newx][newy] == EL_DIAMOND)
8533 if (IS_MOVING(newx, newy))
8534 RemoveMovingField(newx, newy);
8537 Tile[newx][newy] = EL_EMPTY;
8538 TEST_DrawLevelField(newx, newy);
8541 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8543 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8544 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8546 if (AmoebaNr[newx][newy])
8548 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8549 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8550 Tile[newx][newy] == EL_BD_AMOEBA)
8551 AmoebaCnt[AmoebaNr[newx][newy]]--;
8554 if (IS_MOVING(newx, newy))
8556 RemoveMovingField(newx, newy);
8560 Tile[newx][newy] = EL_EMPTY;
8561 TEST_DrawLevelField(newx, newy);
8564 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8566 else if ((element == EL_PACMAN || element == EL_MOLE)
8567 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8569 if (AmoebaNr[newx][newy])
8571 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8572 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8573 Tile[newx][newy] == EL_BD_AMOEBA)
8574 AmoebaCnt[AmoebaNr[newx][newy]]--;
8577 if (element == EL_MOLE)
8579 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8580 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8582 ResetGfxAnimation(x, y);
8583 GfxAction[x][y] = ACTION_DIGGING;
8584 TEST_DrawLevelField(x, y);
8586 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8588 return; // wait for shrinking amoeba
8590 else // element == EL_PACMAN
8592 Tile[newx][newy] = EL_EMPTY;
8593 TEST_DrawLevelField(newx, newy);
8594 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8597 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8598 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8599 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8601 // wait for shrinking amoeba to completely disappear
8604 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8606 // object was running against a wall
8610 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8611 DrawLevelElementAnimation(x, y, element);
8613 if (DONT_TOUCH(element))
8614 TestIfBadThingTouchesPlayer(x, y);
8619 InitMovingField(x, y, MovDir[x][y]);
8621 PlayLevelSoundAction(x, y, ACTION_MOVING);
8625 ContinueMoving(x, y);
8628 void ContinueMoving(int x, int y)
8630 int element = Tile[x][y];
8631 struct ElementInfo *ei = &element_info[element];
8632 int direction = MovDir[x][y];
8633 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8634 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8635 int newx = x + dx, newy = y + dy;
8636 int stored = Store[x][y];
8637 int stored_new = Store[newx][newy];
8638 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8639 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8640 boolean last_line = (newy == lev_fieldy - 1);
8641 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8643 if (pushed_by_player) // special case: moving object pushed by player
8645 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8647 else if (use_step_delay) // special case: moving object has step delay
8649 if (!MovDelay[x][y])
8650 MovPos[x][y] += getElementMoveStepsize(x, y);
8655 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8659 TEST_DrawLevelField(x, y);
8661 return; // element is still waiting
8664 else // normal case: generically moving object
8666 MovPos[x][y] += getElementMoveStepsize(x, y);
8669 if (ABS(MovPos[x][y]) < TILEX)
8671 TEST_DrawLevelField(x, y);
8673 return; // element is still moving
8676 // element reached destination field
8678 Tile[x][y] = EL_EMPTY;
8679 Tile[newx][newy] = element;
8680 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8682 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8684 element = Tile[newx][newy] = EL_ACID;
8686 else if (element == EL_MOLE)
8688 Tile[x][y] = EL_SAND;
8690 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8692 else if (element == EL_QUICKSAND_FILLING)
8694 element = Tile[newx][newy] = get_next_element(element);
8695 Store[newx][newy] = Store[x][y];
8697 else if (element == EL_QUICKSAND_EMPTYING)
8699 Tile[x][y] = get_next_element(element);
8700 element = Tile[newx][newy] = Store[x][y];
8702 else if (element == EL_QUICKSAND_FAST_FILLING)
8704 element = Tile[newx][newy] = get_next_element(element);
8705 Store[newx][newy] = Store[x][y];
8707 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8709 Tile[x][y] = get_next_element(element);
8710 element = Tile[newx][newy] = Store[x][y];
8712 else if (element == EL_MAGIC_WALL_FILLING)
8714 element = Tile[newx][newy] = get_next_element(element);
8715 if (!game.magic_wall_active)
8716 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8717 Store[newx][newy] = Store[x][y];
8719 else if (element == EL_MAGIC_WALL_EMPTYING)
8721 Tile[x][y] = get_next_element(element);
8722 if (!game.magic_wall_active)
8723 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8724 element = Tile[newx][newy] = Store[x][y];
8726 InitField(newx, newy, FALSE);
8728 else if (element == EL_BD_MAGIC_WALL_FILLING)
8730 element = Tile[newx][newy] = get_next_element(element);
8731 if (!game.magic_wall_active)
8732 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8733 Store[newx][newy] = Store[x][y];
8735 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8737 Tile[x][y] = get_next_element(element);
8738 if (!game.magic_wall_active)
8739 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8740 element = Tile[newx][newy] = Store[x][y];
8742 InitField(newx, newy, FALSE);
8744 else if (element == EL_DC_MAGIC_WALL_FILLING)
8746 element = Tile[newx][newy] = get_next_element(element);
8747 if (!game.magic_wall_active)
8748 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8749 Store[newx][newy] = Store[x][y];
8751 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8753 Tile[x][y] = get_next_element(element);
8754 if (!game.magic_wall_active)
8755 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8756 element = Tile[newx][newy] = Store[x][y];
8758 InitField(newx, newy, FALSE);
8760 else if (element == EL_AMOEBA_DROPPING)
8762 Tile[x][y] = get_next_element(element);
8763 element = Tile[newx][newy] = Store[x][y];
8765 else if (element == EL_SOKOBAN_OBJECT)
8768 Tile[x][y] = Back[x][y];
8770 if (Back[newx][newy])
8771 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8773 Back[x][y] = Back[newx][newy] = 0;
8776 Store[x][y] = EL_EMPTY;
8781 MovDelay[newx][newy] = 0;
8783 if (CAN_CHANGE_OR_HAS_ACTION(element))
8785 // copy element change control values to new field
8786 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8787 ChangePage[newx][newy] = ChangePage[x][y];
8788 ChangeCount[newx][newy] = ChangeCount[x][y];
8789 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8792 CustomValue[newx][newy] = CustomValue[x][y];
8794 ChangeDelay[x][y] = 0;
8795 ChangePage[x][y] = -1;
8796 ChangeCount[x][y] = 0;
8797 ChangeEvent[x][y] = -1;
8799 CustomValue[x][y] = 0;
8801 // copy animation control values to new field
8802 GfxFrame[newx][newy] = GfxFrame[x][y];
8803 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8804 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8805 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8807 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8809 // some elements can leave other elements behind after moving
8810 if (ei->move_leave_element != EL_EMPTY &&
8811 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8812 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8814 int move_leave_element = ei->move_leave_element;
8816 // this makes it possible to leave the removed element again
8817 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8818 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8820 Tile[x][y] = move_leave_element;
8822 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8823 MovDir[x][y] = direction;
8825 InitField(x, y, FALSE);
8827 if (GFX_CRUMBLED(Tile[x][y]))
8828 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8830 if (IS_PLAYER_ELEMENT(move_leave_element))
8831 RelocatePlayer(x, y, move_leave_element);
8834 // do this after checking for left-behind element
8835 ResetGfxAnimation(x, y); // reset animation values for old field
8837 if (!CAN_MOVE(element) ||
8838 (CAN_FALL(element) && direction == MV_DOWN &&
8839 (element == EL_SPRING ||
8840 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8841 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8842 GfxDir[x][y] = MovDir[newx][newy] = 0;
8844 TEST_DrawLevelField(x, y);
8845 TEST_DrawLevelField(newx, newy);
8847 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8849 // prevent pushed element from moving on in pushed direction
8850 if (pushed_by_player && CAN_MOVE(element) &&
8851 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8852 !(element_info[element].move_pattern & direction))
8853 TurnRound(newx, newy);
8855 // prevent elements on conveyor belt from moving on in last direction
8856 if (pushed_by_conveyor && CAN_FALL(element) &&
8857 direction & MV_HORIZONTAL)
8858 MovDir[newx][newy] = 0;
8860 if (!pushed_by_player)
8862 int nextx = newx + dx, nexty = newy + dy;
8863 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8865 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8867 if (CAN_FALL(element) && direction == MV_DOWN)
8868 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8870 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8871 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8873 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8874 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8877 if (DONT_TOUCH(element)) // object may be nasty to player or others
8879 TestIfBadThingTouchesPlayer(newx, newy);
8880 TestIfBadThingTouchesFriend(newx, newy);
8882 if (!IS_CUSTOM_ELEMENT(element))
8883 TestIfBadThingTouchesOtherBadThing(newx, newy);
8885 else if (element == EL_PENGUIN)
8886 TestIfFriendTouchesBadThing(newx, newy);
8888 if (DONT_GET_HIT_BY(element))
8890 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8893 // give the player one last chance (one more frame) to move away
8894 if (CAN_FALL(element) && direction == MV_DOWN &&
8895 (last_line || (!IS_FREE(x, newy + 1) &&
8896 (!IS_PLAYER(x, newy + 1) ||
8897 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8900 if (pushed_by_player && !game.use_change_when_pushing_bug)
8902 int push_side = MV_DIR_OPPOSITE(direction);
8903 struct PlayerInfo *player = PLAYERINFO(x, y);
8905 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8906 player->index_bit, push_side);
8907 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8908 player->index_bit, push_side);
8911 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8912 MovDelay[newx][newy] = 1;
8914 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8916 TestIfElementTouchesCustomElement(x, y); // empty or new element
8917 TestIfElementHitsCustomElement(newx, newy, direction);
8918 TestIfPlayerTouchesCustomElement(newx, newy);
8919 TestIfElementTouchesCustomElement(newx, newy);
8921 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8922 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8923 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8924 MV_DIR_OPPOSITE(direction));
8927 int AmoebaNeighbourNr(int ax, int ay)
8930 int element = Tile[ax][ay];
8932 static int xy[4][2] =
8940 for (i = 0; i < NUM_DIRECTIONS; i++)
8942 int x = ax + xy[i][0];
8943 int y = ay + xy[i][1];
8945 if (!IN_LEV_FIELD(x, y))
8948 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8949 group_nr = AmoebaNr[x][y];
8955 static void AmoebaMerge(int ax, int ay)
8957 int i, x, y, xx, yy;
8958 int new_group_nr = AmoebaNr[ax][ay];
8959 static int xy[4][2] =
8967 if (new_group_nr == 0)
8970 for (i = 0; i < NUM_DIRECTIONS; i++)
8975 if (!IN_LEV_FIELD(x, y))
8978 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8979 Tile[x][y] == EL_BD_AMOEBA ||
8980 Tile[x][y] == EL_AMOEBA_DEAD) &&
8981 AmoebaNr[x][y] != new_group_nr)
8983 int old_group_nr = AmoebaNr[x][y];
8985 if (old_group_nr == 0)
8988 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8989 AmoebaCnt[old_group_nr] = 0;
8990 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8991 AmoebaCnt2[old_group_nr] = 0;
8993 SCAN_PLAYFIELD(xx, yy)
8995 if (AmoebaNr[xx][yy] == old_group_nr)
8996 AmoebaNr[xx][yy] = new_group_nr;
9002 void AmoebaToDiamond(int ax, int ay)
9006 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9008 int group_nr = AmoebaNr[ax][ay];
9013 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9014 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9020 SCAN_PLAYFIELD(x, y)
9022 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9025 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9029 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9030 SND_AMOEBA_TURNING_TO_GEM :
9031 SND_AMOEBA_TURNING_TO_ROCK));
9036 static int xy[4][2] =
9044 for (i = 0; i < NUM_DIRECTIONS; i++)
9049 if (!IN_LEV_FIELD(x, y))
9052 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9054 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9055 SND_AMOEBA_TURNING_TO_GEM :
9056 SND_AMOEBA_TURNING_TO_ROCK));
9063 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9066 int group_nr = AmoebaNr[ax][ay];
9067 boolean done = FALSE;
9072 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9073 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9079 SCAN_PLAYFIELD(x, y)
9081 if (AmoebaNr[x][y] == group_nr &&
9082 (Tile[x][y] == EL_AMOEBA_DEAD ||
9083 Tile[x][y] == EL_BD_AMOEBA ||
9084 Tile[x][y] == EL_AMOEBA_GROWING))
9087 Tile[x][y] = new_element;
9088 InitField(x, y, FALSE);
9089 TEST_DrawLevelField(x, y);
9095 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9096 SND_BD_AMOEBA_TURNING_TO_ROCK :
9097 SND_BD_AMOEBA_TURNING_TO_GEM));
9100 static void AmoebaGrowing(int x, int y)
9102 static unsigned int sound_delay = 0;
9103 static unsigned int sound_delay_value = 0;
9105 if (!MovDelay[x][y]) // start new growing cycle
9109 if (DelayReached(&sound_delay, sound_delay_value))
9111 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9112 sound_delay_value = 30;
9116 if (MovDelay[x][y]) // wait some time before growing bigger
9119 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9121 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9122 6 - MovDelay[x][y]);
9124 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9127 if (!MovDelay[x][y])
9129 Tile[x][y] = Store[x][y];
9131 TEST_DrawLevelField(x, y);
9136 static void AmoebaShrinking(int x, int y)
9138 static unsigned int sound_delay = 0;
9139 static unsigned int sound_delay_value = 0;
9141 if (!MovDelay[x][y]) // start new shrinking cycle
9145 if (DelayReached(&sound_delay, sound_delay_value))
9146 sound_delay_value = 30;
9149 if (MovDelay[x][y]) // wait some time before shrinking
9152 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9154 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9155 6 - MovDelay[x][y]);
9157 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9160 if (!MovDelay[x][y])
9162 Tile[x][y] = EL_EMPTY;
9163 TEST_DrawLevelField(x, y);
9165 // don't let mole enter this field in this cycle;
9166 // (give priority to objects falling to this field from above)
9172 static void AmoebaReproduce(int ax, int ay)
9175 int element = Tile[ax][ay];
9176 int graphic = el2img(element);
9177 int newax = ax, neway = ay;
9178 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9179 static int xy[4][2] =
9187 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9189 Tile[ax][ay] = EL_AMOEBA_DEAD;
9190 TEST_DrawLevelField(ax, ay);
9194 if (IS_ANIMATED(graphic))
9195 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9197 if (!MovDelay[ax][ay]) // start making new amoeba field
9198 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9200 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9203 if (MovDelay[ax][ay])
9207 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9210 int x = ax + xy[start][0];
9211 int y = ay + xy[start][1];
9213 if (!IN_LEV_FIELD(x, y))
9216 if (IS_FREE(x, y) ||
9217 CAN_GROW_INTO(Tile[x][y]) ||
9218 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9219 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9225 if (newax == ax && neway == ay)
9228 else // normal or "filled" (BD style) amoeba
9231 boolean waiting_for_player = FALSE;
9233 for (i = 0; i < NUM_DIRECTIONS; i++)
9235 int j = (start + i) % 4;
9236 int x = ax + xy[j][0];
9237 int y = ay + xy[j][1];
9239 if (!IN_LEV_FIELD(x, y))
9242 if (IS_FREE(x, y) ||
9243 CAN_GROW_INTO(Tile[x][y]) ||
9244 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9245 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9251 else if (IS_PLAYER(x, y))
9252 waiting_for_player = TRUE;
9255 if (newax == ax && neway == ay) // amoeba cannot grow
9257 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9259 Tile[ax][ay] = EL_AMOEBA_DEAD;
9260 TEST_DrawLevelField(ax, ay);
9261 AmoebaCnt[AmoebaNr[ax][ay]]--;
9263 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9265 if (element == EL_AMOEBA_FULL)
9266 AmoebaToDiamond(ax, ay);
9267 else if (element == EL_BD_AMOEBA)
9268 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9273 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9275 // amoeba gets larger by growing in some direction
9277 int new_group_nr = AmoebaNr[ax][ay];
9280 if (new_group_nr == 0)
9282 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9284 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9290 AmoebaNr[newax][neway] = new_group_nr;
9291 AmoebaCnt[new_group_nr]++;
9292 AmoebaCnt2[new_group_nr]++;
9294 // if amoeba touches other amoeba(s) after growing, unify them
9295 AmoebaMerge(newax, neway);
9297 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9299 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9305 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9306 (neway == lev_fieldy - 1 && newax != ax))
9308 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9309 Store[newax][neway] = element;
9311 else if (neway == ay || element == EL_EMC_DRIPPER)
9313 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9315 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9319 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9320 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9321 Store[ax][ay] = EL_AMOEBA_DROP;
9322 ContinueMoving(ax, ay);
9326 TEST_DrawLevelField(newax, neway);
9329 static void Life(int ax, int ay)
9333 int element = Tile[ax][ay];
9334 int graphic = el2img(element);
9335 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9337 boolean changed = FALSE;
9339 if (IS_ANIMATED(graphic))
9340 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9345 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9346 MovDelay[ax][ay] = life_time;
9348 if (MovDelay[ax][ay]) // wait some time before next cycle
9351 if (MovDelay[ax][ay])
9355 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9357 int xx = ax+x1, yy = ay+y1;
9358 int old_element = Tile[xx][yy];
9359 int num_neighbours = 0;
9361 if (!IN_LEV_FIELD(xx, yy))
9364 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9366 int x = xx+x2, y = yy+y2;
9368 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9371 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9372 boolean is_neighbour = FALSE;
9374 if (level.use_life_bugs)
9376 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9377 (IS_FREE(x, y) && Stop[x][y]));
9380 (Last[x][y] == element || is_player_cell);
9386 boolean is_free = FALSE;
9388 if (level.use_life_bugs)
9389 is_free = (IS_FREE(xx, yy));
9391 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9393 if (xx == ax && yy == ay) // field in the middle
9395 if (num_neighbours < life_parameter[0] ||
9396 num_neighbours > life_parameter[1])
9398 Tile[xx][yy] = EL_EMPTY;
9399 if (Tile[xx][yy] != old_element)
9400 TEST_DrawLevelField(xx, yy);
9401 Stop[xx][yy] = TRUE;
9405 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9406 { // free border field
9407 if (num_neighbours >= life_parameter[2] &&
9408 num_neighbours <= life_parameter[3])
9410 Tile[xx][yy] = element;
9411 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9412 if (Tile[xx][yy] != old_element)
9413 TEST_DrawLevelField(xx, yy);
9414 Stop[xx][yy] = TRUE;
9421 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9422 SND_GAME_OF_LIFE_GROWING);
9425 static void InitRobotWheel(int x, int y)
9427 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9430 static void RunRobotWheel(int x, int y)
9432 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9435 static void StopRobotWheel(int x, int y)
9437 if (game.robot_wheel_x == x &&
9438 game.robot_wheel_y == y)
9440 game.robot_wheel_x = -1;
9441 game.robot_wheel_y = -1;
9442 game.robot_wheel_active = FALSE;
9446 static void InitTimegateWheel(int x, int y)
9448 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9451 static void RunTimegateWheel(int x, int y)
9453 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9456 static void InitMagicBallDelay(int x, int y)
9458 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9461 static void ActivateMagicBall(int bx, int by)
9465 if (level.ball_random)
9467 int pos_border = RND(8); // select one of the eight border elements
9468 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9469 int xx = pos_content % 3;
9470 int yy = pos_content / 3;
9475 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9476 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9480 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9482 int xx = x - bx + 1;
9483 int yy = y - by + 1;
9485 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9486 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9490 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9493 static void CheckExit(int x, int y)
9495 if (game.gems_still_needed > 0 ||
9496 game.sokoban_fields_still_needed > 0 ||
9497 game.sokoban_objects_still_needed > 0 ||
9498 game.lights_still_needed > 0)
9500 int element = Tile[x][y];
9501 int graphic = el2img(element);
9503 if (IS_ANIMATED(graphic))
9504 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9509 // do not re-open exit door closed after last player
9510 if (game.all_players_gone)
9513 Tile[x][y] = EL_EXIT_OPENING;
9515 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9518 static void CheckExitEM(int x, int y)
9520 if (game.gems_still_needed > 0 ||
9521 game.sokoban_fields_still_needed > 0 ||
9522 game.sokoban_objects_still_needed > 0 ||
9523 game.lights_still_needed > 0)
9525 int element = Tile[x][y];
9526 int graphic = el2img(element);
9528 if (IS_ANIMATED(graphic))
9529 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9534 // do not re-open exit door closed after last player
9535 if (game.all_players_gone)
9538 Tile[x][y] = EL_EM_EXIT_OPENING;
9540 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9543 static void CheckExitSteel(int x, int y)
9545 if (game.gems_still_needed > 0 ||
9546 game.sokoban_fields_still_needed > 0 ||
9547 game.sokoban_objects_still_needed > 0 ||
9548 game.lights_still_needed > 0)
9550 int element = Tile[x][y];
9551 int graphic = el2img(element);
9553 if (IS_ANIMATED(graphic))
9554 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9559 // do not re-open exit door closed after last player
9560 if (game.all_players_gone)
9563 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9565 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9568 static void CheckExitSteelEM(int x, int y)
9570 if (game.gems_still_needed > 0 ||
9571 game.sokoban_fields_still_needed > 0 ||
9572 game.sokoban_objects_still_needed > 0 ||
9573 game.lights_still_needed > 0)
9575 int element = Tile[x][y];
9576 int graphic = el2img(element);
9578 if (IS_ANIMATED(graphic))
9579 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9584 // do not re-open exit door closed after last player
9585 if (game.all_players_gone)
9588 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9590 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9593 static void CheckExitSP(int x, int y)
9595 if (game.gems_still_needed > 0)
9597 int element = Tile[x][y];
9598 int graphic = el2img(element);
9600 if (IS_ANIMATED(graphic))
9601 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9606 // do not re-open exit door closed after last player
9607 if (game.all_players_gone)
9610 Tile[x][y] = EL_SP_EXIT_OPENING;
9612 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9615 static void CloseAllOpenTimegates(void)
9619 SCAN_PLAYFIELD(x, y)
9621 int element = Tile[x][y];
9623 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9625 Tile[x][y] = EL_TIMEGATE_CLOSING;
9627 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9632 static void DrawTwinkleOnField(int x, int y)
9634 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9637 if (Tile[x][y] == EL_BD_DIAMOND)
9640 if (MovDelay[x][y] == 0) // next animation frame
9641 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9643 if (MovDelay[x][y] != 0) // wait some time before next frame
9647 DrawLevelElementAnimation(x, y, Tile[x][y]);
9649 if (MovDelay[x][y] != 0)
9651 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9652 10 - MovDelay[x][y]);
9654 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9659 static void MauerWaechst(int x, int y)
9663 if (!MovDelay[x][y]) // next animation frame
9664 MovDelay[x][y] = 3 * delay;
9666 if (MovDelay[x][y]) // wait some time before next frame
9670 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9672 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9673 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9675 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9678 if (!MovDelay[x][y])
9680 if (MovDir[x][y] == MV_LEFT)
9682 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9683 TEST_DrawLevelField(x - 1, y);
9685 else if (MovDir[x][y] == MV_RIGHT)
9687 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9688 TEST_DrawLevelField(x + 1, y);
9690 else if (MovDir[x][y] == MV_UP)
9692 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9693 TEST_DrawLevelField(x, y - 1);
9697 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9698 TEST_DrawLevelField(x, y + 1);
9701 Tile[x][y] = Store[x][y];
9703 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9704 TEST_DrawLevelField(x, y);
9709 static void MauerAbleger(int ax, int ay)
9711 int element = Tile[ax][ay];
9712 int graphic = el2img(element);
9713 boolean oben_frei = FALSE, unten_frei = FALSE;
9714 boolean links_frei = FALSE, rechts_frei = FALSE;
9715 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9716 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9717 boolean new_wall = FALSE;
9719 if (IS_ANIMATED(graphic))
9720 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9722 if (!MovDelay[ax][ay]) // start building new wall
9723 MovDelay[ax][ay] = 6;
9725 if (MovDelay[ax][ay]) // wait some time before building new wall
9728 if (MovDelay[ax][ay])
9732 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9734 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9736 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9738 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9741 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9742 element == EL_EXPANDABLE_WALL_ANY)
9746 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9747 Store[ax][ay-1] = element;
9748 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9749 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9750 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9751 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9756 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9757 Store[ax][ay+1] = element;
9758 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9759 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9760 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9761 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9766 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9767 element == EL_EXPANDABLE_WALL_ANY ||
9768 element == EL_EXPANDABLE_WALL ||
9769 element == EL_BD_EXPANDABLE_WALL)
9773 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9774 Store[ax-1][ay] = element;
9775 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9776 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9777 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9778 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9784 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9785 Store[ax+1][ay] = element;
9786 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9787 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9788 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9789 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9794 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9795 TEST_DrawLevelField(ax, ay);
9797 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9799 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9800 unten_massiv = TRUE;
9801 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9802 links_massiv = TRUE;
9803 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9804 rechts_massiv = TRUE;
9806 if (((oben_massiv && unten_massiv) ||
9807 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9808 element == EL_EXPANDABLE_WALL) &&
9809 ((links_massiv && rechts_massiv) ||
9810 element == EL_EXPANDABLE_WALL_VERTICAL))
9811 Tile[ax][ay] = EL_WALL;
9814 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9817 static void MauerAblegerStahl(int ax, int ay)
9819 int element = Tile[ax][ay];
9820 int graphic = el2img(element);
9821 boolean oben_frei = FALSE, unten_frei = FALSE;
9822 boolean links_frei = FALSE, rechts_frei = FALSE;
9823 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9824 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9825 boolean new_wall = FALSE;
9827 if (IS_ANIMATED(graphic))
9828 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9830 if (!MovDelay[ax][ay]) // start building new wall
9831 MovDelay[ax][ay] = 6;
9833 if (MovDelay[ax][ay]) // wait some time before building new wall
9836 if (MovDelay[ax][ay])
9840 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9842 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9844 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9846 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9849 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9850 element == EL_EXPANDABLE_STEELWALL_ANY)
9854 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9855 Store[ax][ay-1] = element;
9856 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9857 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9858 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9859 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9864 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9865 Store[ax][ay+1] = element;
9866 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9867 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9868 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9869 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9874 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9875 element == EL_EXPANDABLE_STEELWALL_ANY)
9879 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9880 Store[ax-1][ay] = element;
9881 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9882 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9883 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9884 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9890 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9891 Store[ax+1][ay] = element;
9892 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9893 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9894 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9895 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9900 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9902 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9903 unten_massiv = TRUE;
9904 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9905 links_massiv = TRUE;
9906 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9907 rechts_massiv = TRUE;
9909 if (((oben_massiv && unten_massiv) ||
9910 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9911 ((links_massiv && rechts_massiv) ||
9912 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9913 Tile[ax][ay] = EL_STEELWALL;
9916 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9919 static void CheckForDragon(int x, int y)
9922 boolean dragon_found = FALSE;
9923 static int xy[4][2] =
9931 for (i = 0; i < NUM_DIRECTIONS; i++)
9933 for (j = 0; j < 4; j++)
9935 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9937 if (IN_LEV_FIELD(xx, yy) &&
9938 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9940 if (Tile[xx][yy] == EL_DRAGON)
9941 dragon_found = TRUE;
9950 for (i = 0; i < NUM_DIRECTIONS; i++)
9952 for (j = 0; j < 3; j++)
9954 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9956 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9958 Tile[xx][yy] = EL_EMPTY;
9959 TEST_DrawLevelField(xx, yy);
9968 static void InitBuggyBase(int x, int y)
9970 int element = Tile[x][y];
9971 int activating_delay = FRAMES_PER_SECOND / 4;
9974 (element == EL_SP_BUGGY_BASE ?
9975 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9976 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9978 element == EL_SP_BUGGY_BASE_ACTIVE ?
9979 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9982 static void WarnBuggyBase(int x, int y)
9985 static int xy[4][2] =
9993 for (i = 0; i < NUM_DIRECTIONS; i++)
9995 int xx = x + xy[i][0];
9996 int yy = y + xy[i][1];
9998 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10000 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10007 static void InitTrap(int x, int y)
10009 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10012 static void ActivateTrap(int x, int y)
10014 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10017 static void ChangeActiveTrap(int x, int y)
10019 int graphic = IMG_TRAP_ACTIVE;
10021 // if new animation frame was drawn, correct crumbled sand border
10022 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10023 TEST_DrawLevelFieldCrumbled(x, y);
10026 static int getSpecialActionElement(int element, int number, int base_element)
10028 return (element != EL_EMPTY ? element :
10029 number != -1 ? base_element + number - 1 :
10033 static int getModifiedActionNumber(int value_old, int operator, int operand,
10034 int value_min, int value_max)
10036 int value_new = (operator == CA_MODE_SET ? operand :
10037 operator == CA_MODE_ADD ? value_old + operand :
10038 operator == CA_MODE_SUBTRACT ? value_old - operand :
10039 operator == CA_MODE_MULTIPLY ? value_old * operand :
10040 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10041 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10044 return (value_new < value_min ? value_min :
10045 value_new > value_max ? value_max :
10049 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10051 struct ElementInfo *ei = &element_info[element];
10052 struct ElementChangeInfo *change = &ei->change_page[page];
10053 int target_element = change->target_element;
10054 int action_type = change->action_type;
10055 int action_mode = change->action_mode;
10056 int action_arg = change->action_arg;
10057 int action_element = change->action_element;
10060 if (!change->has_action)
10063 // ---------- determine action paramater values -----------------------------
10065 int level_time_value =
10066 (level.time > 0 ? TimeLeft :
10069 int action_arg_element_raw =
10070 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10071 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10072 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10073 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10074 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10075 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10076 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10078 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10080 int action_arg_direction =
10081 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10082 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10083 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10084 change->actual_trigger_side :
10085 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10086 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10089 int action_arg_number_min =
10090 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10093 int action_arg_number_max =
10094 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10095 action_type == CA_SET_LEVEL_GEMS ? 999 :
10096 action_type == CA_SET_LEVEL_TIME ? 9999 :
10097 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10098 action_type == CA_SET_CE_VALUE ? 9999 :
10099 action_type == CA_SET_CE_SCORE ? 9999 :
10102 int action_arg_number_reset =
10103 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10104 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10105 action_type == CA_SET_LEVEL_TIME ? level.time :
10106 action_type == CA_SET_LEVEL_SCORE ? 0 :
10107 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10108 action_type == CA_SET_CE_SCORE ? 0 :
10111 int action_arg_number =
10112 (action_arg <= CA_ARG_MAX ? action_arg :
10113 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10114 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10115 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10116 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10117 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10118 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10119 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10120 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10121 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10122 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10123 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10124 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10125 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10126 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10127 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10128 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10129 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10130 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10131 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10132 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10133 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10136 int action_arg_number_old =
10137 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10138 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10139 action_type == CA_SET_LEVEL_SCORE ? game.score :
10140 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10141 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10144 int action_arg_number_new =
10145 getModifiedActionNumber(action_arg_number_old,
10146 action_mode, action_arg_number,
10147 action_arg_number_min, action_arg_number_max);
10149 int trigger_player_bits =
10150 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10151 change->actual_trigger_player_bits : change->trigger_player);
10153 int action_arg_player_bits =
10154 (action_arg >= CA_ARG_PLAYER_1 &&
10155 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10156 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10157 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10160 // ---------- execute action -----------------------------------------------
10162 switch (action_type)
10169 // ---------- level actions ----------------------------------------------
10171 case CA_RESTART_LEVEL:
10173 game.restart_level = TRUE;
10178 case CA_SHOW_ENVELOPE:
10180 int element = getSpecialActionElement(action_arg_element,
10181 action_arg_number, EL_ENVELOPE_1);
10183 if (IS_ENVELOPE(element))
10184 local_player->show_envelope = element;
10189 case CA_SET_LEVEL_TIME:
10191 if (level.time > 0) // only modify limited time value
10193 TimeLeft = action_arg_number_new;
10195 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10197 DisplayGameControlValues();
10199 if (!TimeLeft && setup.time_limit)
10200 for (i = 0; i < MAX_PLAYERS; i++)
10201 KillPlayer(&stored_player[i]);
10207 case CA_SET_LEVEL_SCORE:
10209 game.score = action_arg_number_new;
10211 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10213 DisplayGameControlValues();
10218 case CA_SET_LEVEL_GEMS:
10220 game.gems_still_needed = action_arg_number_new;
10222 game.snapshot.collected_item = TRUE;
10224 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10226 DisplayGameControlValues();
10231 case CA_SET_LEVEL_WIND:
10233 game.wind_direction = action_arg_direction;
10238 case CA_SET_LEVEL_RANDOM_SEED:
10240 // ensure that setting a new random seed while playing is predictable
10241 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10246 // ---------- player actions ---------------------------------------------
10248 case CA_MOVE_PLAYER:
10249 case CA_MOVE_PLAYER_NEW:
10251 // automatically move to the next field in specified direction
10252 for (i = 0; i < MAX_PLAYERS; i++)
10253 if (trigger_player_bits & (1 << i))
10254 if (action_type == CA_MOVE_PLAYER ||
10255 stored_player[i].MovPos == 0)
10256 stored_player[i].programmed_action = action_arg_direction;
10261 case CA_EXIT_PLAYER:
10263 for (i = 0; i < MAX_PLAYERS; i++)
10264 if (action_arg_player_bits & (1 << i))
10265 ExitPlayer(&stored_player[i]);
10267 if (game.players_still_needed == 0)
10273 case CA_KILL_PLAYER:
10275 for (i = 0; i < MAX_PLAYERS; i++)
10276 if (action_arg_player_bits & (1 << i))
10277 KillPlayer(&stored_player[i]);
10282 case CA_SET_PLAYER_KEYS:
10284 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10285 int element = getSpecialActionElement(action_arg_element,
10286 action_arg_number, EL_KEY_1);
10288 if (IS_KEY(element))
10290 for (i = 0; i < MAX_PLAYERS; i++)
10292 if (trigger_player_bits & (1 << i))
10294 stored_player[i].key[KEY_NR(element)] = key_state;
10296 DrawGameDoorValues();
10304 case CA_SET_PLAYER_SPEED:
10306 for (i = 0; i < MAX_PLAYERS; i++)
10308 if (trigger_player_bits & (1 << i))
10310 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10312 if (action_arg == CA_ARG_SPEED_FASTER &&
10313 stored_player[i].cannot_move)
10315 action_arg_number = STEPSIZE_VERY_SLOW;
10317 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10318 action_arg == CA_ARG_SPEED_FASTER)
10320 action_arg_number = 2;
10321 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10324 else if (action_arg == CA_ARG_NUMBER_RESET)
10326 action_arg_number = level.initial_player_stepsize[i];
10330 getModifiedActionNumber(move_stepsize,
10333 action_arg_number_min,
10334 action_arg_number_max);
10336 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10343 case CA_SET_PLAYER_SHIELD:
10345 for (i = 0; i < MAX_PLAYERS; i++)
10347 if (trigger_player_bits & (1 << i))
10349 if (action_arg == CA_ARG_SHIELD_OFF)
10351 stored_player[i].shield_normal_time_left = 0;
10352 stored_player[i].shield_deadly_time_left = 0;
10354 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10356 stored_player[i].shield_normal_time_left = 999999;
10358 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10360 stored_player[i].shield_normal_time_left = 999999;
10361 stored_player[i].shield_deadly_time_left = 999999;
10369 case CA_SET_PLAYER_GRAVITY:
10371 for (i = 0; i < MAX_PLAYERS; i++)
10373 if (trigger_player_bits & (1 << i))
10375 stored_player[i].gravity =
10376 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10377 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10378 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10379 stored_player[i].gravity);
10386 case CA_SET_PLAYER_ARTWORK:
10388 for (i = 0; i < MAX_PLAYERS; i++)
10390 if (trigger_player_bits & (1 << i))
10392 int artwork_element = action_arg_element;
10394 if (action_arg == CA_ARG_ELEMENT_RESET)
10396 (level.use_artwork_element[i] ? level.artwork_element[i] :
10397 stored_player[i].element_nr);
10399 if (stored_player[i].artwork_element != artwork_element)
10400 stored_player[i].Frame = 0;
10402 stored_player[i].artwork_element = artwork_element;
10404 SetPlayerWaiting(&stored_player[i], FALSE);
10406 // set number of special actions for bored and sleeping animation
10407 stored_player[i].num_special_action_bored =
10408 get_num_special_action(artwork_element,
10409 ACTION_BORING_1, ACTION_BORING_LAST);
10410 stored_player[i].num_special_action_sleeping =
10411 get_num_special_action(artwork_element,
10412 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10419 case CA_SET_PLAYER_INVENTORY:
10421 for (i = 0; i < MAX_PLAYERS; i++)
10423 struct PlayerInfo *player = &stored_player[i];
10426 if (trigger_player_bits & (1 << i))
10428 int inventory_element = action_arg_element;
10430 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10431 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10432 action_arg == CA_ARG_ELEMENT_ACTION)
10434 int element = inventory_element;
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++] =
10448 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10449 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10450 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10452 if (player->inventory_infinite_element != EL_UNDEFINED &&
10453 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10454 action_arg_element_raw))
10455 player->inventory_infinite_element = EL_UNDEFINED;
10457 for (k = 0, j = 0; j < player->inventory_size; j++)
10459 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10460 action_arg_element_raw))
10461 player->inventory_element[k++] = player->inventory_element[j];
10464 player->inventory_size = k;
10466 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10468 if (player->inventory_size > 0)
10470 for (j = 0; j < player->inventory_size - 1; j++)
10471 player->inventory_element[j] = player->inventory_element[j + 1];
10473 player->inventory_size--;
10476 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10478 if (player->inventory_size > 0)
10479 player->inventory_size--;
10481 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10483 player->inventory_infinite_element = EL_UNDEFINED;
10484 player->inventory_size = 0;
10486 else if (action_arg == CA_ARG_INVENTORY_RESET)
10488 player->inventory_infinite_element = EL_UNDEFINED;
10489 player->inventory_size = 0;
10491 if (level.use_initial_inventory[i])
10493 for (j = 0; j < level.initial_inventory_size[i]; j++)
10495 int element = level.initial_inventory_content[i][j];
10496 int collect_count = element_info[element].collect_count_initial;
10498 if (!IS_CUSTOM_ELEMENT(element))
10501 if (collect_count == 0)
10502 player->inventory_infinite_element = element;
10504 for (k = 0; k < collect_count; k++)
10505 if (player->inventory_size < MAX_INVENTORY_SIZE)
10506 player->inventory_element[player->inventory_size++] =
10517 // ---------- CE actions -------------------------------------------------
10519 case CA_SET_CE_VALUE:
10521 int last_ce_value = CustomValue[x][y];
10523 CustomValue[x][y] = action_arg_number_new;
10525 if (CustomValue[x][y] != last_ce_value)
10527 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10528 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10530 if (CustomValue[x][y] == 0)
10532 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10533 ChangeCount[x][y] = 0; // allow at least one more change
10535 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10536 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10543 case CA_SET_CE_SCORE:
10545 int last_ce_score = ei->collect_score;
10547 ei->collect_score = action_arg_number_new;
10549 if (ei->collect_score != last_ce_score)
10551 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10552 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10554 if (ei->collect_score == 0)
10558 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10559 ChangeCount[x][y] = 0; // allow at least one more change
10561 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10562 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10565 This is a very special case that seems to be a mixture between
10566 CheckElementChange() and CheckTriggeredElementChange(): while
10567 the first one only affects single elements that are triggered
10568 directly, the second one affects multiple elements in the playfield
10569 that are triggered indirectly by another element. This is a third
10570 case: Changing the CE score always affects multiple identical CEs,
10571 so every affected CE must be checked, not only the single CE for
10572 which the CE score was changed in the first place (as every instance
10573 of that CE shares the same CE score, and therefore also can change)!
10575 SCAN_PLAYFIELD(xx, yy)
10577 if (Tile[xx][yy] == element)
10578 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10579 CE_SCORE_GETS_ZERO);
10587 case CA_SET_CE_ARTWORK:
10589 int artwork_element = action_arg_element;
10590 boolean reset_frame = FALSE;
10593 if (action_arg == CA_ARG_ELEMENT_RESET)
10594 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10597 if (ei->gfx_element != artwork_element)
10598 reset_frame = TRUE;
10600 ei->gfx_element = artwork_element;
10602 SCAN_PLAYFIELD(xx, yy)
10604 if (Tile[xx][yy] == element)
10608 ResetGfxAnimation(xx, yy);
10609 ResetRandomAnimationValue(xx, yy);
10612 TEST_DrawLevelField(xx, yy);
10619 // ---------- engine actions ---------------------------------------------
10621 case CA_SET_ENGINE_SCAN_MODE:
10623 InitPlayfieldScanMode(action_arg);
10633 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10635 int old_element = Tile[x][y];
10636 int new_element = GetElementFromGroupElement(element);
10637 int previous_move_direction = MovDir[x][y];
10638 int last_ce_value = CustomValue[x][y];
10639 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10640 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10641 boolean add_player_onto_element = (new_element_is_player &&
10642 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10643 IS_WALKABLE(old_element));
10645 if (!add_player_onto_element)
10647 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10648 RemoveMovingField(x, y);
10652 Tile[x][y] = new_element;
10654 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10655 MovDir[x][y] = previous_move_direction;
10657 if (element_info[new_element].use_last_ce_value)
10658 CustomValue[x][y] = last_ce_value;
10660 InitField_WithBug1(x, y, FALSE);
10662 new_element = Tile[x][y]; // element may have changed
10664 ResetGfxAnimation(x, y);
10665 ResetRandomAnimationValue(x, y);
10667 TEST_DrawLevelField(x, y);
10669 if (GFX_CRUMBLED(new_element))
10670 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10673 // check if element under the player changes from accessible to unaccessible
10674 // (needed for special case of dropping element which then changes)
10675 // (must be checked after creating new element for walkable group elements)
10676 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10677 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10684 // "ChangeCount" not set yet to allow "entered by player" change one time
10685 if (new_element_is_player)
10686 RelocatePlayer(x, y, new_element);
10689 ChangeCount[x][y]++; // count number of changes in the same frame
10691 TestIfBadThingTouchesPlayer(x, y);
10692 TestIfPlayerTouchesCustomElement(x, y);
10693 TestIfElementTouchesCustomElement(x, y);
10696 static void CreateField(int x, int y, int element)
10698 CreateFieldExt(x, y, element, FALSE);
10701 static void CreateElementFromChange(int x, int y, int element)
10703 element = GET_VALID_RUNTIME_ELEMENT(element);
10705 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10707 int old_element = Tile[x][y];
10709 // prevent changed element from moving in same engine frame
10710 // unless both old and new element can either fall or move
10711 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10712 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10716 CreateFieldExt(x, y, element, TRUE);
10719 static boolean ChangeElement(int x, int y, int element, int page)
10721 struct ElementInfo *ei = &element_info[element];
10722 struct ElementChangeInfo *change = &ei->change_page[page];
10723 int ce_value = CustomValue[x][y];
10724 int ce_score = ei->collect_score;
10725 int target_element;
10726 int old_element = Tile[x][y];
10728 // always use default change event to prevent running into a loop
10729 if (ChangeEvent[x][y] == -1)
10730 ChangeEvent[x][y] = CE_DELAY;
10732 if (ChangeEvent[x][y] == CE_DELAY)
10734 // reset actual trigger element, trigger player and action element
10735 change->actual_trigger_element = EL_EMPTY;
10736 change->actual_trigger_player = EL_EMPTY;
10737 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10738 change->actual_trigger_side = CH_SIDE_NONE;
10739 change->actual_trigger_ce_value = 0;
10740 change->actual_trigger_ce_score = 0;
10743 // do not change elements more than a specified maximum number of changes
10744 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10747 ChangeCount[x][y]++; // count number of changes in the same frame
10749 if (change->explode)
10756 if (change->use_target_content)
10758 boolean complete_replace = TRUE;
10759 boolean can_replace[3][3];
10762 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10765 boolean is_walkable;
10766 boolean is_diggable;
10767 boolean is_collectible;
10768 boolean is_removable;
10769 boolean is_destructible;
10770 int ex = x + xx - 1;
10771 int ey = y + yy - 1;
10772 int content_element = change->target_content.e[xx][yy];
10775 can_replace[xx][yy] = TRUE;
10777 if (ex == x && ey == y) // do not check changing element itself
10780 if (content_element == EL_EMPTY_SPACE)
10782 can_replace[xx][yy] = FALSE; // do not replace border with space
10787 if (!IN_LEV_FIELD(ex, ey))
10789 can_replace[xx][yy] = FALSE;
10790 complete_replace = FALSE;
10797 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10798 e = MovingOrBlocked2Element(ex, ey);
10800 is_empty = (IS_FREE(ex, ey) ||
10801 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10803 is_walkable = (is_empty || IS_WALKABLE(e));
10804 is_diggable = (is_empty || IS_DIGGABLE(e));
10805 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10806 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10807 is_removable = (is_diggable || is_collectible);
10809 can_replace[xx][yy] =
10810 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10811 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10812 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10813 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10814 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10815 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10816 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10818 if (!can_replace[xx][yy])
10819 complete_replace = FALSE;
10822 if (!change->only_if_complete || complete_replace)
10824 boolean something_has_changed = FALSE;
10826 if (change->only_if_complete && change->use_random_replace &&
10827 RND(100) < change->random_percentage)
10830 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10832 int ex = x + xx - 1;
10833 int ey = y + yy - 1;
10834 int content_element;
10836 if (can_replace[xx][yy] && (!change->use_random_replace ||
10837 RND(100) < change->random_percentage))
10839 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10840 RemoveMovingField(ex, ey);
10842 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10844 content_element = change->target_content.e[xx][yy];
10845 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10846 ce_value, ce_score);
10848 CreateElementFromChange(ex, ey, target_element);
10850 something_has_changed = TRUE;
10852 // for symmetry reasons, freeze newly created border elements
10853 if (ex != x || ey != y)
10854 Stop[ex][ey] = TRUE; // no more moving in this frame
10858 if (something_has_changed)
10860 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10861 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10867 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10868 ce_value, ce_score);
10870 if (element == EL_DIAGONAL_GROWING ||
10871 element == EL_DIAGONAL_SHRINKING)
10873 target_element = Store[x][y];
10875 Store[x][y] = EL_EMPTY;
10878 // special case: element changes to player (and may be kept if walkable)
10879 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10880 CreateElementFromChange(x, y, EL_EMPTY);
10882 CreateElementFromChange(x, y, target_element);
10884 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10885 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10888 // this uses direct change before indirect change
10889 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10894 static void HandleElementChange(int x, int y, int page)
10896 int element = MovingOrBlocked2Element(x, y);
10897 struct ElementInfo *ei = &element_info[element];
10898 struct ElementChangeInfo *change = &ei->change_page[page];
10899 boolean handle_action_before_change = FALSE;
10902 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10903 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10905 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10906 x, y, element, element_info[element].token_name);
10907 Debug("game:playing:HandleElementChange", "This should never happen!");
10911 // this can happen with classic bombs on walkable, changing elements
10912 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10917 if (ChangeDelay[x][y] == 0) // initialize element change
10919 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10921 if (change->can_change)
10923 // !!! not clear why graphic animation should be reset at all here !!!
10924 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10925 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10928 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10930 When using an animation frame delay of 1 (this only happens with
10931 "sp_zonk.moving.left/right" in the classic graphics), the default
10932 (non-moving) animation shows wrong animation frames (while the
10933 moving animation, like "sp_zonk.moving.left/right", is correct,
10934 so this graphical bug never shows up with the classic graphics).
10935 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10936 be drawn instead of the correct frames 0,1,2,3. This is caused by
10937 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10938 an element change: First when the change delay ("ChangeDelay[][]")
10939 counter has reached zero after decrementing, then a second time in
10940 the next frame (after "GfxFrame[][]" was already incremented) when
10941 "ChangeDelay[][]" is reset to the initial delay value again.
10943 This causes frame 0 to be drawn twice, while the last frame won't
10944 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10946 As some animations may already be cleverly designed around this bug
10947 (at least the "Snake Bite" snake tail animation does this), it cannot
10948 simply be fixed here without breaking such existing animations.
10949 Unfortunately, it cannot easily be detected if a graphics set was
10950 designed "before" or "after" the bug was fixed. As a workaround,
10951 a new graphics set option "game.graphics_engine_version" was added
10952 to be able to specify the game's major release version for which the
10953 graphics set was designed, which can then be used to decide if the
10954 bugfix should be used (version 4 and above) or not (version 3 or
10955 below, or if no version was specified at all, as with old sets).
10957 (The wrong/fixed animation frames can be tested with the test level set
10958 "test_gfxframe" and level "000", which contains a specially prepared
10959 custom element at level position (x/y) == (11/9) which uses the zonk
10960 animation mentioned above. Using "game.graphics_engine_version: 4"
10961 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10962 This can also be seen from the debug output for this test element.)
10965 // when a custom element is about to change (for example by change delay),
10966 // do not reset graphic animation when the custom element is moving
10967 if (game.graphics_engine_version < 4 &&
10970 ResetGfxAnimation(x, y);
10971 ResetRandomAnimationValue(x, y);
10974 if (change->pre_change_function)
10975 change->pre_change_function(x, y);
10979 ChangeDelay[x][y]--;
10981 if (ChangeDelay[x][y] != 0) // continue element change
10983 if (change->can_change)
10985 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10987 if (IS_ANIMATED(graphic))
10988 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10990 if (change->change_function)
10991 change->change_function(x, y);
10994 else // finish element change
10996 if (ChangePage[x][y] != -1) // remember page from delayed change
10998 page = ChangePage[x][y];
10999 ChangePage[x][y] = -1;
11001 change = &ei->change_page[page];
11004 if (IS_MOVING(x, y)) // never change a running system ;-)
11006 ChangeDelay[x][y] = 1; // try change after next move step
11007 ChangePage[x][y] = page; // remember page to use for change
11012 // special case: set new level random seed before changing element
11013 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11014 handle_action_before_change = TRUE;
11016 if (change->has_action && handle_action_before_change)
11017 ExecuteCustomElementAction(x, y, element, page);
11019 if (change->can_change)
11021 if (ChangeElement(x, y, element, page))
11023 if (change->post_change_function)
11024 change->post_change_function(x, y);
11028 if (change->has_action && !handle_action_before_change)
11029 ExecuteCustomElementAction(x, y, element, page);
11033 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11034 int trigger_element,
11036 int trigger_player,
11040 boolean change_done_any = FALSE;
11041 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11044 if (!(trigger_events[trigger_element][trigger_event]))
11047 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11049 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11051 int element = EL_CUSTOM_START + i;
11052 boolean change_done = FALSE;
11055 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11056 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11059 for (p = 0; p < element_info[element].num_change_pages; p++)
11061 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11063 if (change->can_change_or_has_action &&
11064 change->has_event[trigger_event] &&
11065 change->trigger_side & trigger_side &&
11066 change->trigger_player & trigger_player &&
11067 change->trigger_page & trigger_page_bits &&
11068 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11070 change->actual_trigger_element = trigger_element;
11071 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11072 change->actual_trigger_player_bits = trigger_player;
11073 change->actual_trigger_side = trigger_side;
11074 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11075 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11077 if ((change->can_change && !change_done) || change->has_action)
11081 SCAN_PLAYFIELD(x, y)
11083 if (Tile[x][y] == element)
11085 if (change->can_change && !change_done)
11087 // if element already changed in this frame, not only prevent
11088 // another element change (checked in ChangeElement()), but
11089 // also prevent additional element actions for this element
11091 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11092 !level.use_action_after_change_bug)
11095 ChangeDelay[x][y] = 1;
11096 ChangeEvent[x][y] = trigger_event;
11098 HandleElementChange(x, y, p);
11100 else if (change->has_action)
11102 // if element already changed in this frame, not only prevent
11103 // another element change (checked in ChangeElement()), but
11104 // also prevent additional element actions for this element
11106 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11107 !level.use_action_after_change_bug)
11110 ExecuteCustomElementAction(x, y, element, p);
11111 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11116 if (change->can_change)
11118 change_done = TRUE;
11119 change_done_any = TRUE;
11126 RECURSION_LOOP_DETECTION_END();
11128 return change_done_any;
11131 static boolean CheckElementChangeExt(int x, int y,
11133 int trigger_element,
11135 int trigger_player,
11138 boolean change_done = FALSE;
11141 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11142 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11145 if (Tile[x][y] == EL_BLOCKED)
11147 Blocked2Moving(x, y, &x, &y);
11148 element = Tile[x][y];
11151 // check if element has already changed or is about to change after moving
11152 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11153 Tile[x][y] != element) ||
11155 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11156 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11157 ChangePage[x][y] != -1)))
11160 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11162 for (p = 0; p < element_info[element].num_change_pages; p++)
11164 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11166 /* check trigger element for all events where the element that is checked
11167 for changing interacts with a directly adjacent element -- this is
11168 different to element changes that affect other elements to change on the
11169 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11170 boolean check_trigger_element =
11171 (trigger_event == CE_NEXT_TO_X ||
11172 trigger_event == CE_TOUCHING_X ||
11173 trigger_event == CE_HITTING_X ||
11174 trigger_event == CE_HIT_BY_X ||
11175 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11177 if (change->can_change_or_has_action &&
11178 change->has_event[trigger_event] &&
11179 change->trigger_side & trigger_side &&
11180 change->trigger_player & trigger_player &&
11181 (!check_trigger_element ||
11182 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11184 change->actual_trigger_element = trigger_element;
11185 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11186 change->actual_trigger_player_bits = trigger_player;
11187 change->actual_trigger_side = trigger_side;
11188 change->actual_trigger_ce_value = CustomValue[x][y];
11189 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11191 // special case: trigger element not at (x,y) position for some events
11192 if (check_trigger_element)
11204 { 0, 0 }, { 0, 0 }, { 0, 0 },
11208 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11209 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11211 change->actual_trigger_ce_value = CustomValue[xx][yy];
11212 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11215 if (change->can_change && !change_done)
11217 ChangeDelay[x][y] = 1;
11218 ChangeEvent[x][y] = trigger_event;
11220 HandleElementChange(x, y, p);
11222 change_done = TRUE;
11224 else if (change->has_action)
11226 ExecuteCustomElementAction(x, y, element, p);
11227 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11232 RECURSION_LOOP_DETECTION_END();
11234 return change_done;
11237 static void PlayPlayerSound(struct PlayerInfo *player)
11239 int jx = player->jx, jy = player->jy;
11240 int sound_element = player->artwork_element;
11241 int last_action = player->last_action_waiting;
11242 int action = player->action_waiting;
11244 if (player->is_waiting)
11246 if (action != last_action)
11247 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11249 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11253 if (action != last_action)
11254 StopSound(element_info[sound_element].sound[last_action]);
11256 if (last_action == ACTION_SLEEPING)
11257 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11261 static void PlayAllPlayersSound(void)
11265 for (i = 0; i < MAX_PLAYERS; i++)
11266 if (stored_player[i].active)
11267 PlayPlayerSound(&stored_player[i]);
11270 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11272 boolean last_waiting = player->is_waiting;
11273 int move_dir = player->MovDir;
11275 player->dir_waiting = move_dir;
11276 player->last_action_waiting = player->action_waiting;
11280 if (!last_waiting) // not waiting -> waiting
11282 player->is_waiting = TRUE;
11284 player->frame_counter_bored =
11286 game.player_boring_delay_fixed +
11287 GetSimpleRandom(game.player_boring_delay_random);
11288 player->frame_counter_sleeping =
11290 game.player_sleeping_delay_fixed +
11291 GetSimpleRandom(game.player_sleeping_delay_random);
11293 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11296 if (game.player_sleeping_delay_fixed +
11297 game.player_sleeping_delay_random > 0 &&
11298 player->anim_delay_counter == 0 &&
11299 player->post_delay_counter == 0 &&
11300 FrameCounter >= player->frame_counter_sleeping)
11301 player->is_sleeping = TRUE;
11302 else if (game.player_boring_delay_fixed +
11303 game.player_boring_delay_random > 0 &&
11304 FrameCounter >= player->frame_counter_bored)
11305 player->is_bored = TRUE;
11307 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11308 player->is_bored ? ACTION_BORING :
11311 if (player->is_sleeping && player->use_murphy)
11313 // special case for sleeping Murphy when leaning against non-free tile
11315 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11316 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11317 !IS_MOVING(player->jx - 1, player->jy)))
11318 move_dir = MV_LEFT;
11319 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11320 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11321 !IS_MOVING(player->jx + 1, player->jy)))
11322 move_dir = MV_RIGHT;
11324 player->is_sleeping = FALSE;
11326 player->dir_waiting = move_dir;
11329 if (player->is_sleeping)
11331 if (player->num_special_action_sleeping > 0)
11333 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11335 int last_special_action = player->special_action_sleeping;
11336 int num_special_action = player->num_special_action_sleeping;
11337 int special_action =
11338 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11339 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11340 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11341 last_special_action + 1 : ACTION_SLEEPING);
11342 int special_graphic =
11343 el_act_dir2img(player->artwork_element, special_action, move_dir);
11345 player->anim_delay_counter =
11346 graphic_info[special_graphic].anim_delay_fixed +
11347 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11348 player->post_delay_counter =
11349 graphic_info[special_graphic].post_delay_fixed +
11350 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11352 player->special_action_sleeping = special_action;
11355 if (player->anim_delay_counter > 0)
11357 player->action_waiting = player->special_action_sleeping;
11358 player->anim_delay_counter--;
11360 else if (player->post_delay_counter > 0)
11362 player->post_delay_counter--;
11366 else if (player->is_bored)
11368 if (player->num_special_action_bored > 0)
11370 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11372 int special_action =
11373 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11374 int special_graphic =
11375 el_act_dir2img(player->artwork_element, special_action, move_dir);
11377 player->anim_delay_counter =
11378 graphic_info[special_graphic].anim_delay_fixed +
11379 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11380 player->post_delay_counter =
11381 graphic_info[special_graphic].post_delay_fixed +
11382 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11384 player->special_action_bored = special_action;
11387 if (player->anim_delay_counter > 0)
11389 player->action_waiting = player->special_action_bored;
11390 player->anim_delay_counter--;
11392 else if (player->post_delay_counter > 0)
11394 player->post_delay_counter--;
11399 else if (last_waiting) // waiting -> not waiting
11401 player->is_waiting = FALSE;
11402 player->is_bored = FALSE;
11403 player->is_sleeping = FALSE;
11405 player->frame_counter_bored = -1;
11406 player->frame_counter_sleeping = -1;
11408 player->anim_delay_counter = 0;
11409 player->post_delay_counter = 0;
11411 player->dir_waiting = player->MovDir;
11412 player->action_waiting = ACTION_DEFAULT;
11414 player->special_action_bored = ACTION_DEFAULT;
11415 player->special_action_sleeping = ACTION_DEFAULT;
11419 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11421 if ((!player->is_moving && player->was_moving) ||
11422 (player->MovPos == 0 && player->was_moving) ||
11423 (player->is_snapping && !player->was_snapping) ||
11424 (player->is_dropping && !player->was_dropping))
11426 if (!CheckSaveEngineSnapshotToList())
11429 player->was_moving = FALSE;
11430 player->was_snapping = TRUE;
11431 player->was_dropping = TRUE;
11435 if (player->is_moving)
11436 player->was_moving = TRUE;
11438 if (!player->is_snapping)
11439 player->was_snapping = FALSE;
11441 if (!player->is_dropping)
11442 player->was_dropping = FALSE;
11445 static struct MouseActionInfo mouse_action_last = { 0 };
11446 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11447 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11450 CheckSaveEngineSnapshotToList();
11452 mouse_action_last = mouse_action;
11455 static void CheckSingleStepMode(struct PlayerInfo *player)
11457 if (tape.single_step && tape.recording && !tape.pausing)
11459 // as it is called "single step mode", just return to pause mode when the
11460 // player stopped moving after one tile (or never starts moving at all)
11461 // (reverse logic needed here in case single step mode used in team mode)
11462 if (player->is_moving ||
11463 player->is_pushing ||
11464 player->is_dropping_pressed ||
11465 player->effective_mouse_action.button)
11466 game.enter_single_step_mode = FALSE;
11469 CheckSaveEngineSnapshot(player);
11472 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11474 int left = player_action & JOY_LEFT;
11475 int right = player_action & JOY_RIGHT;
11476 int up = player_action & JOY_UP;
11477 int down = player_action & JOY_DOWN;
11478 int button1 = player_action & JOY_BUTTON_1;
11479 int button2 = player_action & JOY_BUTTON_2;
11480 int dx = (left ? -1 : right ? 1 : 0);
11481 int dy = (up ? -1 : down ? 1 : 0);
11483 if (!player->active || tape.pausing)
11489 SnapField(player, dx, dy);
11493 DropElement(player);
11495 MovePlayer(player, dx, dy);
11498 CheckSingleStepMode(player);
11500 SetPlayerWaiting(player, FALSE);
11502 return player_action;
11506 // no actions for this player (no input at player's configured device)
11508 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11509 SnapField(player, 0, 0);
11510 CheckGravityMovementWhenNotMoving(player);
11512 if (player->MovPos == 0)
11513 SetPlayerWaiting(player, TRUE);
11515 if (player->MovPos == 0) // needed for tape.playing
11516 player->is_moving = FALSE;
11518 player->is_dropping = FALSE;
11519 player->is_dropping_pressed = FALSE;
11520 player->drop_pressed_delay = 0;
11522 CheckSingleStepMode(player);
11528 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11531 if (!tape.use_mouse_actions)
11534 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11535 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11536 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11539 static void SetTapeActionFromMouseAction(byte *tape_action,
11540 struct MouseActionInfo *mouse_action)
11542 if (!tape.use_mouse_actions)
11545 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11546 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11547 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11550 static void CheckLevelSolved(void)
11552 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11554 if (game_em.level_solved &&
11555 !game_em.game_over) // game won
11559 game_em.game_over = TRUE;
11561 game.all_players_gone = TRUE;
11564 if (game_em.game_over) // game lost
11565 game.all_players_gone = TRUE;
11567 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11569 if (game_sp.level_solved &&
11570 !game_sp.game_over) // game won
11574 game_sp.game_over = TRUE;
11576 game.all_players_gone = TRUE;
11579 if (game_sp.game_over) // game lost
11580 game.all_players_gone = TRUE;
11582 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11584 if (game_mm.level_solved &&
11585 !game_mm.game_over) // game won
11589 game_mm.game_over = TRUE;
11591 game.all_players_gone = TRUE;
11594 if (game_mm.game_over) // game lost
11595 game.all_players_gone = TRUE;
11599 static void CheckLevelTime(void)
11603 if (TimeFrames >= FRAMES_PER_SECOND)
11608 for (i = 0; i < MAX_PLAYERS; i++)
11610 struct PlayerInfo *player = &stored_player[i];
11612 if (SHIELD_ON(player))
11614 player->shield_normal_time_left--;
11616 if (player->shield_deadly_time_left > 0)
11617 player->shield_deadly_time_left--;
11621 if (!game.LevelSolved && !level.use_step_counter)
11629 if (TimeLeft <= 10 && setup.time_limit)
11630 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11632 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11633 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11635 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11637 if (!TimeLeft && setup.time_limit)
11639 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11640 game_em.lev->killed_out_of_time = TRUE;
11642 for (i = 0; i < MAX_PLAYERS; i++)
11643 KillPlayer(&stored_player[i]);
11646 else if (game.no_time_limit && !game.all_players_gone)
11648 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11651 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11654 if (tape.recording || tape.playing)
11655 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11658 if (tape.recording || tape.playing)
11659 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11661 UpdateAndDisplayGameControlValues();
11664 void AdvanceFrameAndPlayerCounters(int player_nr)
11668 // advance frame counters (global frame counter and time frame counter)
11672 // advance player counters (counters for move delay, move animation etc.)
11673 for (i = 0; i < MAX_PLAYERS; i++)
11675 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11676 int move_delay_value = stored_player[i].move_delay_value;
11677 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11679 if (!advance_player_counters) // not all players may be affected
11682 if (move_frames == 0) // less than one move per game frame
11684 int stepsize = TILEX / move_delay_value;
11685 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11686 int count = (stored_player[i].is_moving ?
11687 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11689 if (count % delay == 0)
11693 stored_player[i].Frame += move_frames;
11695 if (stored_player[i].MovPos != 0)
11696 stored_player[i].StepFrame += move_frames;
11698 if (stored_player[i].move_delay > 0)
11699 stored_player[i].move_delay--;
11701 // due to bugs in previous versions, counter must count up, not down
11702 if (stored_player[i].push_delay != -1)
11703 stored_player[i].push_delay++;
11705 if (stored_player[i].drop_delay > 0)
11706 stored_player[i].drop_delay--;
11708 if (stored_player[i].is_dropping_pressed)
11709 stored_player[i].drop_pressed_delay++;
11713 void StartGameActions(boolean init_network_game, boolean record_tape,
11716 unsigned int new_random_seed = InitRND(random_seed);
11719 TapeStartRecording(new_random_seed);
11721 if (init_network_game)
11723 SendToServer_LevelFile();
11724 SendToServer_StartPlaying();
11732 static void GameActionsExt(void)
11735 static unsigned int game_frame_delay = 0;
11737 unsigned int game_frame_delay_value;
11738 byte *recorded_player_action;
11739 byte summarized_player_action = 0;
11740 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11743 // detect endless loops, caused by custom element programming
11744 if (recursion_loop_detected && recursion_loop_depth == 0)
11746 char *message = getStringCat3("Internal Error! Element ",
11747 EL_NAME(recursion_loop_element),
11748 " caused endless loop! Quit the game?");
11750 Warn("element '%s' caused endless loop in game engine",
11751 EL_NAME(recursion_loop_element));
11753 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11755 recursion_loop_detected = FALSE; // if game should be continued
11762 if (game.restart_level)
11763 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11765 CheckLevelSolved();
11767 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11770 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11773 if (game_status != GAME_MODE_PLAYING) // status might have changed
11776 game_frame_delay_value =
11777 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11779 if (tape.playing && tape.warp_forward && !tape.pausing)
11780 game_frame_delay_value = 0;
11782 SetVideoFrameDelay(game_frame_delay_value);
11784 // (de)activate virtual buttons depending on current game status
11785 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11787 if (game.all_players_gone) // if no players there to be controlled anymore
11788 SetOverlayActive(FALSE);
11789 else if (!tape.playing) // if game continues after tape stopped playing
11790 SetOverlayActive(TRUE);
11795 // ---------- main game synchronization point ----------
11797 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11799 Debug("game:playing:skip", "skip == %d", skip);
11802 // ---------- main game synchronization point ----------
11804 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11808 if (network_playing && !network_player_action_received)
11810 // try to get network player actions in time
11812 // last chance to get network player actions without main loop delay
11813 HandleNetworking();
11815 // game was quit by network peer
11816 if (game_status != GAME_MODE_PLAYING)
11819 // check if network player actions still missing and game still running
11820 if (!network_player_action_received && !checkGameEnded())
11821 return; // failed to get network player actions in time
11823 // do not yet reset "network_player_action_received" (for tape.pausing)
11829 // at this point we know that we really continue executing the game
11831 network_player_action_received = FALSE;
11833 // when playing tape, read previously recorded player input from tape data
11834 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11836 local_player->effective_mouse_action = local_player->mouse_action;
11838 if (recorded_player_action != NULL)
11839 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11840 recorded_player_action);
11842 // TapePlayAction() may return NULL when toggling to "pause before death"
11846 if (tape.set_centered_player)
11848 game.centered_player_nr_next = tape.centered_player_nr_next;
11849 game.set_centered_player = TRUE;
11852 for (i = 0; i < MAX_PLAYERS; i++)
11854 summarized_player_action |= stored_player[i].action;
11856 if (!network_playing && (game.team_mode || tape.playing))
11857 stored_player[i].effective_action = stored_player[i].action;
11860 if (network_playing && !checkGameEnded())
11861 SendToServer_MovePlayer(summarized_player_action);
11863 // summarize all actions at local players mapped input device position
11864 // (this allows using different input devices in single player mode)
11865 if (!network.enabled && !game.team_mode)
11866 stored_player[map_player_action[local_player->index_nr]].effective_action =
11867 summarized_player_action;
11869 // summarize all actions at centered player in local team mode
11870 if (tape.recording &&
11871 setup.team_mode && !network.enabled &&
11872 setup.input_on_focus &&
11873 game.centered_player_nr != -1)
11875 for (i = 0; i < MAX_PLAYERS; i++)
11876 stored_player[map_player_action[i]].effective_action =
11877 (i == game.centered_player_nr ? summarized_player_action : 0);
11880 if (recorded_player_action != NULL)
11881 for (i = 0; i < MAX_PLAYERS; i++)
11882 stored_player[i].effective_action = recorded_player_action[i];
11884 for (i = 0; i < MAX_PLAYERS; i++)
11886 tape_action[i] = stored_player[i].effective_action;
11888 /* (this may happen in the RND game engine if a player was not present on
11889 the playfield on level start, but appeared later from a custom element */
11890 if (setup.team_mode &&
11893 !tape.player_participates[i])
11894 tape.player_participates[i] = TRUE;
11897 SetTapeActionFromMouseAction(tape_action,
11898 &local_player->effective_mouse_action);
11900 // only record actions from input devices, but not programmed actions
11901 if (tape.recording)
11902 TapeRecordAction(tape_action);
11904 // remember if game was played (especially after tape stopped playing)
11905 if (!tape.playing && summarized_player_action)
11906 game.GamePlayed = TRUE;
11908 #if USE_NEW_PLAYER_ASSIGNMENTS
11909 // !!! also map player actions in single player mode !!!
11910 // if (game.team_mode)
11913 byte mapped_action[MAX_PLAYERS];
11915 #if DEBUG_PLAYER_ACTIONS
11916 for (i = 0; i < MAX_PLAYERS; i++)
11917 DebugContinued("", "%d, ", stored_player[i].effective_action);
11920 for (i = 0; i < MAX_PLAYERS; i++)
11921 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11923 for (i = 0; i < MAX_PLAYERS; i++)
11924 stored_player[i].effective_action = mapped_action[i];
11926 #if DEBUG_PLAYER_ACTIONS
11927 DebugContinued("", "=> ");
11928 for (i = 0; i < MAX_PLAYERS; i++)
11929 DebugContinued("", "%d, ", stored_player[i].effective_action);
11930 DebugContinued("game:playing:player", "\n");
11933 #if DEBUG_PLAYER_ACTIONS
11936 for (i = 0; i < MAX_PLAYERS; i++)
11937 DebugContinued("", "%d, ", stored_player[i].effective_action);
11938 DebugContinued("game:playing:player", "\n");
11943 for (i = 0; i < MAX_PLAYERS; i++)
11945 // allow engine snapshot in case of changed movement attempt
11946 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11947 (stored_player[i].effective_action & KEY_MOTION))
11948 game.snapshot.changed_action = TRUE;
11950 // allow engine snapshot in case of snapping/dropping attempt
11951 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11952 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11953 game.snapshot.changed_action = TRUE;
11955 game.snapshot.last_action[i] = stored_player[i].effective_action;
11958 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11960 GameActions_EM_Main();
11962 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11964 GameActions_SP_Main();
11966 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11968 GameActions_MM_Main();
11972 GameActions_RND_Main();
11975 BlitScreenToBitmap(backbuffer);
11977 CheckLevelSolved();
11980 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11982 if (global.show_frames_per_second)
11984 static unsigned int fps_counter = 0;
11985 static int fps_frames = 0;
11986 unsigned int fps_delay_ms = Counter() - fps_counter;
11990 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11992 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11995 fps_counter = Counter();
11997 // always draw FPS to screen after FPS value was updated
11998 redraw_mask |= REDRAW_FPS;
12001 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12002 if (GetDrawDeactivationMask() == REDRAW_NONE)
12003 redraw_mask |= REDRAW_FPS;
12007 static void GameActions_CheckSaveEngineSnapshot(void)
12009 if (!game.snapshot.save_snapshot)
12012 // clear flag for saving snapshot _before_ saving snapshot
12013 game.snapshot.save_snapshot = FALSE;
12015 SaveEngineSnapshotToList();
12018 void GameActions(void)
12022 GameActions_CheckSaveEngineSnapshot();
12025 void GameActions_EM_Main(void)
12027 byte effective_action[MAX_PLAYERS];
12028 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12031 for (i = 0; i < MAX_PLAYERS; i++)
12032 effective_action[i] = stored_player[i].effective_action;
12034 GameActions_EM(effective_action, warp_mode);
12037 void GameActions_SP_Main(void)
12039 byte effective_action[MAX_PLAYERS];
12040 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12043 for (i = 0; i < MAX_PLAYERS; i++)
12044 effective_action[i] = stored_player[i].effective_action;
12046 GameActions_SP(effective_action, warp_mode);
12048 for (i = 0; i < MAX_PLAYERS; i++)
12050 if (stored_player[i].force_dropping)
12051 stored_player[i].action |= KEY_BUTTON_DROP;
12053 stored_player[i].force_dropping = FALSE;
12057 void GameActions_MM_Main(void)
12059 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12061 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12064 void GameActions_RND_Main(void)
12069 void GameActions_RND(void)
12071 static struct MouseActionInfo mouse_action_last = { 0 };
12072 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12073 int magic_wall_x = 0, magic_wall_y = 0;
12074 int i, x, y, element, graphic, last_gfx_frame;
12076 InitPlayfieldScanModeVars();
12078 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12080 SCAN_PLAYFIELD(x, y)
12082 ChangeCount[x][y] = 0;
12083 ChangeEvent[x][y] = -1;
12087 if (game.set_centered_player)
12089 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12091 // switching to "all players" only possible if all players fit to screen
12092 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12094 game.centered_player_nr_next = game.centered_player_nr;
12095 game.set_centered_player = FALSE;
12098 // do not switch focus to non-existing (or non-active) player
12099 if (game.centered_player_nr_next >= 0 &&
12100 !stored_player[game.centered_player_nr_next].active)
12102 game.centered_player_nr_next = game.centered_player_nr;
12103 game.set_centered_player = FALSE;
12107 if (game.set_centered_player &&
12108 ScreenMovPos == 0) // screen currently aligned at tile position
12112 if (game.centered_player_nr_next == -1)
12114 setScreenCenteredToAllPlayers(&sx, &sy);
12118 sx = stored_player[game.centered_player_nr_next].jx;
12119 sy = stored_player[game.centered_player_nr_next].jy;
12122 game.centered_player_nr = game.centered_player_nr_next;
12123 game.set_centered_player = FALSE;
12125 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12126 DrawGameDoorValues();
12129 // check single step mode (set flag and clear again if any player is active)
12130 game.enter_single_step_mode =
12131 (tape.single_step && tape.recording && !tape.pausing);
12133 for (i = 0; i < MAX_PLAYERS; i++)
12135 int actual_player_action = stored_player[i].effective_action;
12138 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12139 - rnd_equinox_tetrachloride 048
12140 - rnd_equinox_tetrachloride_ii 096
12141 - rnd_emanuel_schmieg 002
12142 - doctor_sloan_ww 001, 020
12144 if (stored_player[i].MovPos == 0)
12145 CheckGravityMovement(&stored_player[i]);
12148 // overwrite programmed action with tape action
12149 if (stored_player[i].programmed_action)
12150 actual_player_action = stored_player[i].programmed_action;
12152 PlayerActions(&stored_player[i], actual_player_action);
12154 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12157 // single step pause mode may already have been toggled by "ScrollPlayer()"
12158 if (game.enter_single_step_mode && !tape.pausing)
12159 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12161 ScrollScreen(NULL, SCROLL_GO_ON);
12163 /* for backwards compatibility, the following code emulates a fixed bug that
12164 occured when pushing elements (causing elements that just made their last
12165 pushing step to already (if possible) make their first falling step in the
12166 same game frame, which is bad); this code is also needed to use the famous
12167 "spring push bug" which is used in older levels and might be wanted to be
12168 used also in newer levels, but in this case the buggy pushing code is only
12169 affecting the "spring" element and no other elements */
12171 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12173 for (i = 0; i < MAX_PLAYERS; i++)
12175 struct PlayerInfo *player = &stored_player[i];
12176 int x = player->jx;
12177 int y = player->jy;
12179 if (player->active && player->is_pushing && player->is_moving &&
12181 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12182 Tile[x][y] == EL_SPRING))
12184 ContinueMoving(x, y);
12186 // continue moving after pushing (this is actually a bug)
12187 if (!IS_MOVING(x, y))
12188 Stop[x][y] = FALSE;
12193 SCAN_PLAYFIELD(x, y)
12195 Last[x][y] = Tile[x][y];
12197 ChangeCount[x][y] = 0;
12198 ChangeEvent[x][y] = -1;
12200 // this must be handled before main playfield loop
12201 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12204 if (MovDelay[x][y] <= 0)
12208 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12211 if (MovDelay[x][y] <= 0)
12213 int element = Store[x][y];
12214 int move_direction = MovDir[x][y];
12215 int player_index_bit = Store2[x][y];
12221 TEST_DrawLevelField(x, y);
12223 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12225 if (IS_ENVELOPE(element))
12226 local_player->show_envelope = element;
12231 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12233 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12235 Debug("game:playing:GameActions_RND", "This should never happen!");
12237 ChangePage[x][y] = -1;
12241 Stop[x][y] = FALSE;
12242 if (WasJustMoving[x][y] > 0)
12243 WasJustMoving[x][y]--;
12244 if (WasJustFalling[x][y] > 0)
12245 WasJustFalling[x][y]--;
12246 if (CheckCollision[x][y] > 0)
12247 CheckCollision[x][y]--;
12248 if (CheckImpact[x][y] > 0)
12249 CheckImpact[x][y]--;
12253 /* reset finished pushing action (not done in ContinueMoving() to allow
12254 continuous pushing animation for elements with zero push delay) */
12255 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12257 ResetGfxAnimation(x, y);
12258 TEST_DrawLevelField(x, y);
12262 if (IS_BLOCKED(x, y))
12266 Blocked2Moving(x, y, &oldx, &oldy);
12267 if (!IS_MOVING(oldx, oldy))
12269 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12270 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12271 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12272 Debug("game:playing:GameActions_RND", "This should never happen!");
12278 if (mouse_action.button)
12280 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12281 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12283 x = mouse_action.lx;
12284 y = mouse_action.ly;
12285 element = Tile[x][y];
12289 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12290 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12294 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12295 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12299 SCAN_PLAYFIELD(x, y)
12301 element = Tile[x][y];
12302 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12303 last_gfx_frame = GfxFrame[x][y];
12305 if (element == EL_EMPTY)
12306 graphic = el2img(GfxElementEmpty[x][y]);
12308 ResetGfxFrame(x, y);
12310 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12311 DrawLevelGraphicAnimation(x, y, graphic);
12313 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12314 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12315 ResetRandomAnimationValue(x, y);
12317 SetRandomAnimationValue(x, y);
12319 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12321 if (IS_INACTIVE(element))
12323 if (IS_ANIMATED(graphic))
12324 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12329 // this may take place after moving, so 'element' may have changed
12330 if (IS_CHANGING(x, y) &&
12331 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12333 int page = element_info[element].event_page_nr[CE_DELAY];
12335 HandleElementChange(x, y, page);
12337 element = Tile[x][y];
12338 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12341 CheckNextToConditions(x, y);
12343 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12347 element = Tile[x][y];
12348 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12350 if (IS_ANIMATED(graphic) &&
12351 !IS_MOVING(x, y) &&
12353 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12355 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12356 TEST_DrawTwinkleOnField(x, y);
12358 else if (element == EL_ACID)
12361 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12363 else if ((element == EL_EXIT_OPEN ||
12364 element == EL_EM_EXIT_OPEN ||
12365 element == EL_SP_EXIT_OPEN ||
12366 element == EL_STEEL_EXIT_OPEN ||
12367 element == EL_EM_STEEL_EXIT_OPEN ||
12368 element == EL_SP_TERMINAL ||
12369 element == EL_SP_TERMINAL_ACTIVE ||
12370 element == EL_EXTRA_TIME ||
12371 element == EL_SHIELD_NORMAL ||
12372 element == EL_SHIELD_DEADLY) &&
12373 IS_ANIMATED(graphic))
12374 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12375 else if (IS_MOVING(x, y))
12376 ContinueMoving(x, y);
12377 else if (IS_ACTIVE_BOMB(element))
12378 CheckDynamite(x, y);
12379 else if (element == EL_AMOEBA_GROWING)
12380 AmoebaGrowing(x, y);
12381 else if (element == EL_AMOEBA_SHRINKING)
12382 AmoebaShrinking(x, y);
12384 #if !USE_NEW_AMOEBA_CODE
12385 else if (IS_AMOEBALIVE(element))
12386 AmoebaReproduce(x, y);
12389 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12391 else if (element == EL_EXIT_CLOSED)
12393 else if (element == EL_EM_EXIT_CLOSED)
12395 else if (element == EL_STEEL_EXIT_CLOSED)
12396 CheckExitSteel(x, y);
12397 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12398 CheckExitSteelEM(x, y);
12399 else if (element == EL_SP_EXIT_CLOSED)
12401 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12402 element == EL_EXPANDABLE_STEELWALL_GROWING)
12403 MauerWaechst(x, y);
12404 else if (element == EL_EXPANDABLE_WALL ||
12405 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12406 element == EL_EXPANDABLE_WALL_VERTICAL ||
12407 element == EL_EXPANDABLE_WALL_ANY ||
12408 element == EL_BD_EXPANDABLE_WALL)
12409 MauerAbleger(x, y);
12410 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12411 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12412 element == EL_EXPANDABLE_STEELWALL_ANY)
12413 MauerAblegerStahl(x, y);
12414 else if (element == EL_FLAMES)
12415 CheckForDragon(x, y);
12416 else if (element == EL_EXPLOSION)
12417 ; // drawing of correct explosion animation is handled separately
12418 else if (element == EL_ELEMENT_SNAPPING ||
12419 element == EL_DIAGONAL_SHRINKING ||
12420 element == EL_DIAGONAL_GROWING)
12422 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12424 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12426 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12427 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12429 if (IS_BELT_ACTIVE(element))
12430 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12432 if (game.magic_wall_active)
12434 int jx = local_player->jx, jy = local_player->jy;
12436 // play the element sound at the position nearest to the player
12437 if ((element == EL_MAGIC_WALL_FULL ||
12438 element == EL_MAGIC_WALL_ACTIVE ||
12439 element == EL_MAGIC_WALL_EMPTYING ||
12440 element == EL_BD_MAGIC_WALL_FULL ||
12441 element == EL_BD_MAGIC_WALL_ACTIVE ||
12442 element == EL_BD_MAGIC_WALL_EMPTYING ||
12443 element == EL_DC_MAGIC_WALL_FULL ||
12444 element == EL_DC_MAGIC_WALL_ACTIVE ||
12445 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12446 ABS(x - jx) + ABS(y - jy) <
12447 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12455 #if USE_NEW_AMOEBA_CODE
12456 // new experimental amoeba growth stuff
12457 if (!(FrameCounter % 8))
12459 static unsigned int random = 1684108901;
12461 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12463 x = RND(lev_fieldx);
12464 y = RND(lev_fieldy);
12465 element = Tile[x][y];
12467 if (!IS_PLAYER(x,y) &&
12468 (element == EL_EMPTY ||
12469 CAN_GROW_INTO(element) ||
12470 element == EL_QUICKSAND_EMPTY ||
12471 element == EL_QUICKSAND_FAST_EMPTY ||
12472 element == EL_ACID_SPLASH_LEFT ||
12473 element == EL_ACID_SPLASH_RIGHT))
12475 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12476 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12477 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12478 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12479 Tile[x][y] = EL_AMOEBA_DROP;
12482 random = random * 129 + 1;
12487 game.explosions_delayed = FALSE;
12489 SCAN_PLAYFIELD(x, y)
12491 element = Tile[x][y];
12493 if (ExplodeField[x][y])
12494 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12495 else if (element == EL_EXPLOSION)
12496 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12498 ExplodeField[x][y] = EX_TYPE_NONE;
12501 game.explosions_delayed = TRUE;
12503 if (game.magic_wall_active)
12505 if (!(game.magic_wall_time_left % 4))
12507 int element = Tile[magic_wall_x][magic_wall_y];
12509 if (element == EL_BD_MAGIC_WALL_FULL ||
12510 element == EL_BD_MAGIC_WALL_ACTIVE ||
12511 element == EL_BD_MAGIC_WALL_EMPTYING)
12512 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12513 else if (element == EL_DC_MAGIC_WALL_FULL ||
12514 element == EL_DC_MAGIC_WALL_ACTIVE ||
12515 element == EL_DC_MAGIC_WALL_EMPTYING)
12516 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12518 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12521 if (game.magic_wall_time_left > 0)
12523 game.magic_wall_time_left--;
12525 if (!game.magic_wall_time_left)
12527 SCAN_PLAYFIELD(x, y)
12529 element = Tile[x][y];
12531 if (element == EL_MAGIC_WALL_ACTIVE ||
12532 element == EL_MAGIC_WALL_FULL)
12534 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12535 TEST_DrawLevelField(x, y);
12537 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12538 element == EL_BD_MAGIC_WALL_FULL)
12540 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12541 TEST_DrawLevelField(x, y);
12543 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12544 element == EL_DC_MAGIC_WALL_FULL)
12546 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12547 TEST_DrawLevelField(x, y);
12551 game.magic_wall_active = FALSE;
12556 if (game.light_time_left > 0)
12558 game.light_time_left--;
12560 if (game.light_time_left == 0)
12561 RedrawAllLightSwitchesAndInvisibleElements();
12564 if (game.timegate_time_left > 0)
12566 game.timegate_time_left--;
12568 if (game.timegate_time_left == 0)
12569 CloseAllOpenTimegates();
12572 if (game.lenses_time_left > 0)
12574 game.lenses_time_left--;
12576 if (game.lenses_time_left == 0)
12577 RedrawAllInvisibleElementsForLenses();
12580 if (game.magnify_time_left > 0)
12582 game.magnify_time_left--;
12584 if (game.magnify_time_left == 0)
12585 RedrawAllInvisibleElementsForMagnifier();
12588 for (i = 0; i < MAX_PLAYERS; i++)
12590 struct PlayerInfo *player = &stored_player[i];
12592 if (SHIELD_ON(player))
12594 if (player->shield_deadly_time_left)
12595 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12596 else if (player->shield_normal_time_left)
12597 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12601 #if USE_DELAYED_GFX_REDRAW
12602 SCAN_PLAYFIELD(x, y)
12604 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12606 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12607 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12609 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12610 DrawLevelField(x, y);
12612 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12613 DrawLevelFieldCrumbled(x, y);
12615 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12616 DrawLevelFieldCrumbledNeighbours(x, y);
12618 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12619 DrawTwinkleOnField(x, y);
12622 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12627 PlayAllPlayersSound();
12629 for (i = 0; i < MAX_PLAYERS; i++)
12631 struct PlayerInfo *player = &stored_player[i];
12633 if (player->show_envelope != 0 && (!player->active ||
12634 player->MovPos == 0))
12636 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12638 player->show_envelope = 0;
12642 // use random number generator in every frame to make it less predictable
12643 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12646 mouse_action_last = mouse_action;
12649 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12651 int min_x = x, min_y = y, max_x = x, max_y = y;
12652 int scr_fieldx = getScreenFieldSizeX();
12653 int scr_fieldy = getScreenFieldSizeY();
12656 for (i = 0; i < MAX_PLAYERS; i++)
12658 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12660 if (!stored_player[i].active || &stored_player[i] == player)
12663 min_x = MIN(min_x, jx);
12664 min_y = MIN(min_y, jy);
12665 max_x = MAX(max_x, jx);
12666 max_y = MAX(max_y, jy);
12669 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12672 static boolean AllPlayersInVisibleScreen(void)
12676 for (i = 0; i < MAX_PLAYERS; i++)
12678 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12680 if (!stored_player[i].active)
12683 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12690 void ScrollLevel(int dx, int dy)
12692 int scroll_offset = 2 * TILEX_VAR;
12695 BlitBitmap(drawto_field, drawto_field,
12696 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12697 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12698 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12699 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12700 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12701 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12705 x = (dx == 1 ? BX1 : BX2);
12706 for (y = BY1; y <= BY2; y++)
12707 DrawScreenField(x, y);
12712 y = (dy == 1 ? BY1 : BY2);
12713 for (x = BX1; x <= BX2; x++)
12714 DrawScreenField(x, y);
12717 redraw_mask |= REDRAW_FIELD;
12720 static boolean canFallDown(struct PlayerInfo *player)
12722 int jx = player->jx, jy = player->jy;
12724 return (IN_LEV_FIELD(jx, jy + 1) &&
12725 (IS_FREE(jx, jy + 1) ||
12726 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12727 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12728 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12731 static boolean canPassField(int x, int y, int move_dir)
12733 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12734 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12735 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12736 int nextx = x + dx;
12737 int nexty = y + dy;
12738 int element = Tile[x][y];
12740 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12741 !CAN_MOVE(element) &&
12742 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12743 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12744 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12747 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12749 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12750 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12751 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12755 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12756 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12757 (IS_DIGGABLE(Tile[newx][newy]) ||
12758 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12759 canPassField(newx, newy, move_dir)));
12762 static void CheckGravityMovement(struct PlayerInfo *player)
12764 if (player->gravity && !player->programmed_action)
12766 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12767 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12768 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12769 int jx = player->jx, jy = player->jy;
12770 boolean player_is_moving_to_valid_field =
12771 (!player_is_snapping &&
12772 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12773 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12774 boolean player_can_fall_down = canFallDown(player);
12776 if (player_can_fall_down &&
12777 !player_is_moving_to_valid_field)
12778 player->programmed_action = MV_DOWN;
12782 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12784 return CheckGravityMovement(player);
12786 if (player->gravity && !player->programmed_action)
12788 int jx = player->jx, jy = player->jy;
12789 boolean field_under_player_is_free =
12790 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12791 boolean player_is_standing_on_valid_field =
12792 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12793 (IS_WALKABLE(Tile[jx][jy]) &&
12794 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12796 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12797 player->programmed_action = MV_DOWN;
12802 MovePlayerOneStep()
12803 -----------------------------------------------------------------------------
12804 dx, dy: direction (non-diagonal) to try to move the player to
12805 real_dx, real_dy: direction as read from input device (can be diagonal)
12808 boolean MovePlayerOneStep(struct PlayerInfo *player,
12809 int dx, int dy, int real_dx, int real_dy)
12811 int jx = player->jx, jy = player->jy;
12812 int new_jx = jx + dx, new_jy = jy + dy;
12814 boolean player_can_move = !player->cannot_move;
12816 if (!player->active || (!dx && !dy))
12817 return MP_NO_ACTION;
12819 player->MovDir = (dx < 0 ? MV_LEFT :
12820 dx > 0 ? MV_RIGHT :
12822 dy > 0 ? MV_DOWN : MV_NONE);
12824 if (!IN_LEV_FIELD(new_jx, new_jy))
12825 return MP_NO_ACTION;
12827 if (!player_can_move)
12829 if (player->MovPos == 0)
12831 player->is_moving = FALSE;
12832 player->is_digging = FALSE;
12833 player->is_collecting = FALSE;
12834 player->is_snapping = FALSE;
12835 player->is_pushing = FALSE;
12839 if (!network.enabled && game.centered_player_nr == -1 &&
12840 !AllPlayersInSight(player, new_jx, new_jy))
12841 return MP_NO_ACTION;
12843 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12844 if (can_move != MP_MOVING)
12847 // check if DigField() has caused relocation of the player
12848 if (player->jx != jx || player->jy != jy)
12849 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12851 StorePlayer[jx][jy] = 0;
12852 player->last_jx = jx;
12853 player->last_jy = jy;
12854 player->jx = new_jx;
12855 player->jy = new_jy;
12856 StorePlayer[new_jx][new_jy] = player->element_nr;
12858 if (player->move_delay_value_next != -1)
12860 player->move_delay_value = player->move_delay_value_next;
12861 player->move_delay_value_next = -1;
12865 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12867 player->step_counter++;
12869 PlayerVisit[jx][jy] = FrameCounter;
12871 player->is_moving = TRUE;
12874 // should better be called in MovePlayer(), but this breaks some tapes
12875 ScrollPlayer(player, SCROLL_INIT);
12881 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12883 int jx = player->jx, jy = player->jy;
12884 int old_jx = jx, old_jy = jy;
12885 int moved = MP_NO_ACTION;
12887 if (!player->active)
12892 if (player->MovPos == 0)
12894 player->is_moving = FALSE;
12895 player->is_digging = FALSE;
12896 player->is_collecting = FALSE;
12897 player->is_snapping = FALSE;
12898 player->is_pushing = FALSE;
12904 if (player->move_delay > 0)
12907 player->move_delay = -1; // set to "uninitialized" value
12909 // store if player is automatically moved to next field
12910 player->is_auto_moving = (player->programmed_action != MV_NONE);
12912 // remove the last programmed player action
12913 player->programmed_action = 0;
12915 if (player->MovPos)
12917 // should only happen if pre-1.2 tape recordings are played
12918 // this is only for backward compatibility
12920 int original_move_delay_value = player->move_delay_value;
12923 Debug("game:playing:MovePlayer",
12924 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12928 // scroll remaining steps with finest movement resolution
12929 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12931 while (player->MovPos)
12933 ScrollPlayer(player, SCROLL_GO_ON);
12934 ScrollScreen(NULL, SCROLL_GO_ON);
12936 AdvanceFrameAndPlayerCounters(player->index_nr);
12939 BackToFront_WithFrameDelay(0);
12942 player->move_delay_value = original_move_delay_value;
12945 player->is_active = FALSE;
12947 if (player->last_move_dir & MV_HORIZONTAL)
12949 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12950 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12954 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12955 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12958 if (!moved && !player->is_active)
12960 player->is_moving = FALSE;
12961 player->is_digging = FALSE;
12962 player->is_collecting = FALSE;
12963 player->is_snapping = FALSE;
12964 player->is_pushing = FALSE;
12970 if (moved & MP_MOVING && !ScreenMovPos &&
12971 (player->index_nr == game.centered_player_nr ||
12972 game.centered_player_nr == -1))
12974 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12976 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12978 // actual player has left the screen -- scroll in that direction
12979 if (jx != old_jx) // player has moved horizontally
12980 scroll_x += (jx - old_jx);
12981 else // player has moved vertically
12982 scroll_y += (jy - old_jy);
12986 int offset_raw = game.scroll_delay_value;
12988 if (jx != old_jx) // player has moved horizontally
12990 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12991 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12992 int new_scroll_x = jx - MIDPOSX + offset_x;
12994 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12995 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12996 scroll_x = new_scroll_x;
12998 // don't scroll over playfield boundaries
12999 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13001 // don't scroll more than one field at a time
13002 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13004 // don't scroll against the player's moving direction
13005 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13006 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13007 scroll_x = old_scroll_x;
13009 else // player has moved vertically
13011 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13012 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13013 int new_scroll_y = jy - MIDPOSY + offset_y;
13015 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13016 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13017 scroll_y = new_scroll_y;
13019 // don't scroll over playfield boundaries
13020 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13022 // don't scroll more than one field at a time
13023 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13025 // don't scroll against the player's moving direction
13026 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13027 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13028 scroll_y = old_scroll_y;
13032 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13034 if (!network.enabled && game.centered_player_nr == -1 &&
13035 !AllPlayersInVisibleScreen())
13037 scroll_x = old_scroll_x;
13038 scroll_y = old_scroll_y;
13042 ScrollScreen(player, SCROLL_INIT);
13043 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13048 player->StepFrame = 0;
13050 if (moved & MP_MOVING)
13052 if (old_jx != jx && old_jy == jy)
13053 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13054 else if (old_jx == jx && old_jy != jy)
13055 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13057 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13059 player->last_move_dir = player->MovDir;
13060 player->is_moving = TRUE;
13061 player->is_snapping = FALSE;
13062 player->is_switching = FALSE;
13063 player->is_dropping = FALSE;
13064 player->is_dropping_pressed = FALSE;
13065 player->drop_pressed_delay = 0;
13068 // should better be called here than above, but this breaks some tapes
13069 ScrollPlayer(player, SCROLL_INIT);
13074 CheckGravityMovementWhenNotMoving(player);
13076 player->is_moving = FALSE;
13078 /* at this point, the player is allowed to move, but cannot move right now
13079 (e.g. because of something blocking the way) -- ensure that the player
13080 is also allowed to move in the next frame (in old versions before 3.1.1,
13081 the player was forced to wait again for eight frames before next try) */
13083 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13084 player->move_delay = 0; // allow direct movement in the next frame
13087 if (player->move_delay == -1) // not yet initialized by DigField()
13088 player->move_delay = player->move_delay_value;
13090 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13092 TestIfPlayerTouchesBadThing(jx, jy);
13093 TestIfPlayerTouchesCustomElement(jx, jy);
13096 if (!player->active)
13097 RemovePlayer(player);
13102 void ScrollPlayer(struct PlayerInfo *player, int mode)
13104 int jx = player->jx, jy = player->jy;
13105 int last_jx = player->last_jx, last_jy = player->last_jy;
13106 int move_stepsize = TILEX / player->move_delay_value;
13108 if (!player->active)
13111 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13114 if (mode == SCROLL_INIT)
13116 player->actual_frame_counter = FrameCounter;
13117 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13119 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13120 Tile[last_jx][last_jy] == EL_EMPTY)
13122 int last_field_block_delay = 0; // start with no blocking at all
13123 int block_delay_adjustment = player->block_delay_adjustment;
13125 // if player blocks last field, add delay for exactly one move
13126 if (player->block_last_field)
13128 last_field_block_delay += player->move_delay_value;
13130 // when blocking enabled, prevent moving up despite gravity
13131 if (player->gravity && player->MovDir == MV_UP)
13132 block_delay_adjustment = -1;
13135 // add block delay adjustment (also possible when not blocking)
13136 last_field_block_delay += block_delay_adjustment;
13138 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13139 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13142 if (player->MovPos != 0) // player has not yet reached destination
13145 else if (!FrameReached(&player->actual_frame_counter, 1))
13148 if (player->MovPos != 0)
13150 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13151 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13153 // before DrawPlayer() to draw correct player graphic for this case
13154 if (player->MovPos == 0)
13155 CheckGravityMovement(player);
13158 if (player->MovPos == 0) // player reached destination field
13160 if (player->move_delay_reset_counter > 0)
13162 player->move_delay_reset_counter--;
13164 if (player->move_delay_reset_counter == 0)
13166 // continue with normal speed after quickly moving through gate
13167 HALVE_PLAYER_SPEED(player);
13169 // be able to make the next move without delay
13170 player->move_delay = 0;
13174 player->last_jx = jx;
13175 player->last_jy = jy;
13177 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13178 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13179 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13180 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13181 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13182 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13183 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13184 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13186 ExitPlayer(player);
13188 if (game.players_still_needed == 0 &&
13189 (game.friends_still_needed == 0 ||
13190 IS_SP_ELEMENT(Tile[jx][jy])))
13194 // this breaks one level: "machine", level 000
13196 int move_direction = player->MovDir;
13197 int enter_side = MV_DIR_OPPOSITE(move_direction);
13198 int leave_side = move_direction;
13199 int old_jx = last_jx;
13200 int old_jy = last_jy;
13201 int old_element = Tile[old_jx][old_jy];
13202 int new_element = Tile[jx][jy];
13204 if (IS_CUSTOM_ELEMENT(old_element))
13205 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13207 player->index_bit, leave_side);
13209 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13210 CE_PLAYER_LEAVES_X,
13211 player->index_bit, leave_side);
13213 if (IS_CUSTOM_ELEMENT(new_element))
13214 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13215 player->index_bit, enter_side);
13217 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13218 CE_PLAYER_ENTERS_X,
13219 player->index_bit, enter_side);
13221 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13222 CE_MOVE_OF_X, move_direction);
13225 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13227 TestIfPlayerTouchesBadThing(jx, jy);
13228 TestIfPlayerTouchesCustomElement(jx, jy);
13230 /* needed because pushed element has not yet reached its destination,
13231 so it would trigger a change event at its previous field location */
13232 if (!player->is_pushing)
13233 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13235 if (level.finish_dig_collect &&
13236 (player->is_digging || player->is_collecting))
13238 int last_element = player->last_removed_element;
13239 int move_direction = player->MovDir;
13240 int enter_side = MV_DIR_OPPOSITE(move_direction);
13241 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13242 CE_PLAYER_COLLECTS_X);
13244 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13245 player->index_bit, enter_side);
13247 player->last_removed_element = EL_UNDEFINED;
13250 if (!player->active)
13251 RemovePlayer(player);
13254 if (level.use_step_counter)
13264 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13265 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13267 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13269 DisplayGameControlValues();
13271 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13272 for (i = 0; i < MAX_PLAYERS; i++)
13273 KillPlayer(&stored_player[i]);
13275 else if (game.no_time_limit && !game.all_players_gone)
13277 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13279 DisplayGameControlValues();
13283 if (tape.single_step && tape.recording && !tape.pausing &&
13284 !player->programmed_action)
13285 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13287 if (!player->programmed_action)
13288 CheckSaveEngineSnapshot(player);
13292 void ScrollScreen(struct PlayerInfo *player, int mode)
13294 static unsigned int screen_frame_counter = 0;
13296 if (mode == SCROLL_INIT)
13298 // set scrolling step size according to actual player's moving speed
13299 ScrollStepSize = TILEX / player->move_delay_value;
13301 screen_frame_counter = FrameCounter;
13302 ScreenMovDir = player->MovDir;
13303 ScreenMovPos = player->MovPos;
13304 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13307 else if (!FrameReached(&screen_frame_counter, 1))
13312 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13313 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13314 redraw_mask |= REDRAW_FIELD;
13317 ScreenMovDir = MV_NONE;
13320 void CheckNextToConditions(int x, int y)
13322 int element = Tile[x][y];
13324 if (IS_PLAYER(x, y))
13325 TestIfPlayerNextToCustomElement(x, y);
13327 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13328 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13329 TestIfElementNextToCustomElement(x, y);
13332 void TestIfPlayerNextToCustomElement(int x, int y)
13334 static int xy[4][2] =
13341 static int trigger_sides[4][2] =
13343 // center side border side
13344 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13345 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13346 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13347 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13351 if (!IS_PLAYER(x, y))
13354 struct PlayerInfo *player = PLAYERINFO(x, y);
13356 if (player->is_moving)
13359 for (i = 0; i < NUM_DIRECTIONS; i++)
13361 int xx = x + xy[i][0];
13362 int yy = y + xy[i][1];
13363 int border_side = trigger_sides[i][1];
13364 int border_element;
13366 if (!IN_LEV_FIELD(xx, yy))
13369 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13370 continue; // center and border element not connected
13372 border_element = Tile[xx][yy];
13374 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13375 player->index_bit, border_side);
13376 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13377 CE_PLAYER_NEXT_TO_X,
13378 player->index_bit, border_side);
13380 /* use player element that is initially defined in the level playfield,
13381 not the player element that corresponds to the runtime player number
13382 (example: a level that contains EL_PLAYER_3 as the only player would
13383 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13385 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13386 CE_NEXT_TO_X, border_side);
13390 void TestIfPlayerTouchesCustomElement(int x, int y)
13392 static int xy[4][2] =
13399 static int trigger_sides[4][2] =
13401 // center side border side
13402 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13403 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13404 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13405 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13407 static int touch_dir[4] =
13409 MV_LEFT | MV_RIGHT,
13414 int center_element = Tile[x][y]; // should always be non-moving!
13417 for (i = 0; i < NUM_DIRECTIONS; i++)
13419 int xx = x + xy[i][0];
13420 int yy = y + xy[i][1];
13421 int center_side = trigger_sides[i][0];
13422 int border_side = trigger_sides[i][1];
13423 int border_element;
13425 if (!IN_LEV_FIELD(xx, yy))
13428 if (IS_PLAYER(x, y)) // player found at center element
13430 struct PlayerInfo *player = PLAYERINFO(x, y);
13432 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13433 border_element = Tile[xx][yy]; // may be moving!
13434 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13435 border_element = Tile[xx][yy];
13436 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13437 border_element = MovingOrBlocked2Element(xx, yy);
13439 continue; // center and border element do not touch
13441 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13442 player->index_bit, border_side);
13443 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13444 CE_PLAYER_TOUCHES_X,
13445 player->index_bit, border_side);
13448 /* use player element that is initially defined in the level playfield,
13449 not the player element that corresponds to the runtime player number
13450 (example: a level that contains EL_PLAYER_3 as the only player would
13451 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13452 int player_element = PLAYERINFO(x, y)->initial_element;
13454 CheckElementChangeBySide(xx, yy, border_element, player_element,
13455 CE_TOUCHING_X, border_side);
13458 else if (IS_PLAYER(xx, yy)) // player found at border element
13460 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13462 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13464 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13465 continue; // center and border element do not touch
13468 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13469 player->index_bit, center_side);
13470 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13471 CE_PLAYER_TOUCHES_X,
13472 player->index_bit, center_side);
13475 /* use player element that is initially defined in the level playfield,
13476 not the player element that corresponds to the runtime player number
13477 (example: a level that contains EL_PLAYER_3 as the only player would
13478 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13479 int player_element = PLAYERINFO(xx, yy)->initial_element;
13481 CheckElementChangeBySide(x, y, center_element, player_element,
13482 CE_TOUCHING_X, center_side);
13490 void TestIfElementNextToCustomElement(int x, int y)
13492 static int xy[4][2] =
13499 static int trigger_sides[4][2] =
13501 // center side border side
13502 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13503 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13504 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13505 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13507 int center_element = Tile[x][y]; // should always be non-moving!
13510 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13513 for (i = 0; i < NUM_DIRECTIONS; i++)
13515 int xx = x + xy[i][0];
13516 int yy = y + xy[i][1];
13517 int border_side = trigger_sides[i][1];
13518 int border_element;
13520 if (!IN_LEV_FIELD(xx, yy))
13523 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13524 continue; // center and border element not connected
13526 border_element = Tile[xx][yy];
13528 // check for change of center element (but change it only once)
13529 if (CheckElementChangeBySide(x, y, center_element, border_element,
13530 CE_NEXT_TO_X, border_side))
13535 void TestIfElementTouchesCustomElement(int x, int y)
13537 static int xy[4][2] =
13544 static int trigger_sides[4][2] =
13546 // center side border side
13547 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13548 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13549 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13550 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13552 static int touch_dir[4] =
13554 MV_LEFT | MV_RIGHT,
13559 boolean change_center_element = FALSE;
13560 int center_element = Tile[x][y]; // should always be non-moving!
13561 int border_element_old[NUM_DIRECTIONS];
13564 for (i = 0; i < NUM_DIRECTIONS; i++)
13566 int xx = x + xy[i][0];
13567 int yy = y + xy[i][1];
13568 int border_element;
13570 border_element_old[i] = -1;
13572 if (!IN_LEV_FIELD(xx, yy))
13575 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13576 border_element = Tile[xx][yy]; // may be moving!
13577 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13578 border_element = Tile[xx][yy];
13579 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13580 border_element = MovingOrBlocked2Element(xx, yy);
13582 continue; // center and border element do not touch
13584 border_element_old[i] = border_element;
13587 for (i = 0; i < NUM_DIRECTIONS; i++)
13589 int xx = x + xy[i][0];
13590 int yy = y + xy[i][1];
13591 int center_side = trigger_sides[i][0];
13592 int border_element = border_element_old[i];
13594 if (border_element == -1)
13597 // check for change of border element
13598 CheckElementChangeBySide(xx, yy, border_element, center_element,
13599 CE_TOUCHING_X, center_side);
13601 // (center element cannot be player, so we dont have to check this here)
13604 for (i = 0; i < NUM_DIRECTIONS; i++)
13606 int xx = x + xy[i][0];
13607 int yy = y + xy[i][1];
13608 int border_side = trigger_sides[i][1];
13609 int border_element = border_element_old[i];
13611 if (border_element == -1)
13614 // check for change of center element (but change it only once)
13615 if (!change_center_element)
13616 change_center_element =
13617 CheckElementChangeBySide(x, y, center_element, border_element,
13618 CE_TOUCHING_X, border_side);
13620 if (IS_PLAYER(xx, yy))
13622 /* use player element that is initially defined in the level playfield,
13623 not the player element that corresponds to the runtime player number
13624 (example: a level that contains EL_PLAYER_3 as the only player would
13625 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13626 int player_element = PLAYERINFO(xx, yy)->initial_element;
13628 CheckElementChangeBySide(x, y, center_element, player_element,
13629 CE_TOUCHING_X, border_side);
13634 void TestIfElementHitsCustomElement(int x, int y, int direction)
13636 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13637 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13638 int hitx = x + dx, hity = y + dy;
13639 int hitting_element = Tile[x][y];
13640 int touched_element;
13642 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13645 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13646 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13648 if (IN_LEV_FIELD(hitx, hity))
13650 int opposite_direction = MV_DIR_OPPOSITE(direction);
13651 int hitting_side = direction;
13652 int touched_side = opposite_direction;
13653 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13654 MovDir[hitx][hity] != direction ||
13655 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13661 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13662 CE_HITTING_X, touched_side);
13664 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13665 CE_HIT_BY_X, hitting_side);
13667 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13668 CE_HIT_BY_SOMETHING, opposite_direction);
13670 if (IS_PLAYER(hitx, hity))
13672 /* use player element that is initially defined in the level playfield,
13673 not the player element that corresponds to the runtime player number
13674 (example: a level that contains EL_PLAYER_3 as the only player would
13675 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13676 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13678 CheckElementChangeBySide(x, y, hitting_element, player_element,
13679 CE_HITTING_X, touched_side);
13684 // "hitting something" is also true when hitting the playfield border
13685 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13686 CE_HITTING_SOMETHING, direction);
13689 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13691 int i, kill_x = -1, kill_y = -1;
13693 int bad_element = -1;
13694 static int test_xy[4][2] =
13701 static int test_dir[4] =
13709 for (i = 0; i < NUM_DIRECTIONS; i++)
13711 int test_x, test_y, test_move_dir, test_element;
13713 test_x = good_x + test_xy[i][0];
13714 test_y = good_y + test_xy[i][1];
13716 if (!IN_LEV_FIELD(test_x, test_y))
13720 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13722 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13724 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13725 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13727 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13728 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13732 bad_element = test_element;
13738 if (kill_x != -1 || kill_y != -1)
13740 if (IS_PLAYER(good_x, good_y))
13742 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13744 if (player->shield_deadly_time_left > 0 &&
13745 !IS_INDESTRUCTIBLE(bad_element))
13746 Bang(kill_x, kill_y);
13747 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13748 KillPlayer(player);
13751 Bang(good_x, good_y);
13755 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13757 int i, kill_x = -1, kill_y = -1;
13758 int bad_element = Tile[bad_x][bad_y];
13759 static int test_xy[4][2] =
13766 static int touch_dir[4] =
13768 MV_LEFT | MV_RIGHT,
13773 static int test_dir[4] =
13781 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13784 for (i = 0; i < NUM_DIRECTIONS; i++)
13786 int test_x, test_y, test_move_dir, test_element;
13788 test_x = bad_x + test_xy[i][0];
13789 test_y = bad_y + test_xy[i][1];
13791 if (!IN_LEV_FIELD(test_x, test_y))
13795 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13797 test_element = Tile[test_x][test_y];
13799 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13800 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13802 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13803 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13805 // good thing is player or penguin that does not move away
13806 if (IS_PLAYER(test_x, test_y))
13808 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13810 if (bad_element == EL_ROBOT && player->is_moving)
13811 continue; // robot does not kill player if he is moving
13813 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13815 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13816 continue; // center and border element do not touch
13824 else if (test_element == EL_PENGUIN)
13834 if (kill_x != -1 || kill_y != -1)
13836 if (IS_PLAYER(kill_x, kill_y))
13838 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13840 if (player->shield_deadly_time_left > 0 &&
13841 !IS_INDESTRUCTIBLE(bad_element))
13842 Bang(bad_x, bad_y);
13843 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13844 KillPlayer(player);
13847 Bang(kill_x, kill_y);
13851 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13853 int bad_element = Tile[bad_x][bad_y];
13854 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13855 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13856 int test_x = bad_x + dx, test_y = bad_y + dy;
13857 int test_move_dir, test_element;
13858 int kill_x = -1, kill_y = -1;
13860 if (!IN_LEV_FIELD(test_x, test_y))
13864 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13866 test_element = Tile[test_x][test_y];
13868 if (test_move_dir != bad_move_dir)
13870 // good thing can be player or penguin that does not move away
13871 if (IS_PLAYER(test_x, test_y))
13873 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13875 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13876 player as being hit when he is moving towards the bad thing, because
13877 the "get hit by" condition would be lost after the player stops) */
13878 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13879 return; // player moves away from bad thing
13884 else if (test_element == EL_PENGUIN)
13891 if (kill_x != -1 || kill_y != -1)
13893 if (IS_PLAYER(kill_x, kill_y))
13895 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13897 if (player->shield_deadly_time_left > 0 &&
13898 !IS_INDESTRUCTIBLE(bad_element))
13899 Bang(bad_x, bad_y);
13900 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13901 KillPlayer(player);
13904 Bang(kill_x, kill_y);
13908 void TestIfPlayerTouchesBadThing(int x, int y)
13910 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13913 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13915 TestIfGoodThingHitsBadThing(x, y, move_dir);
13918 void TestIfBadThingTouchesPlayer(int x, int y)
13920 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13923 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13925 TestIfBadThingHitsGoodThing(x, y, move_dir);
13928 void TestIfFriendTouchesBadThing(int x, int y)
13930 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13933 void TestIfBadThingTouchesFriend(int x, int y)
13935 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13938 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13940 int i, kill_x = bad_x, kill_y = bad_y;
13941 static int xy[4][2] =
13949 for (i = 0; i < NUM_DIRECTIONS; i++)
13953 x = bad_x + xy[i][0];
13954 y = bad_y + xy[i][1];
13955 if (!IN_LEV_FIELD(x, y))
13958 element = Tile[x][y];
13959 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13960 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13968 if (kill_x != bad_x || kill_y != bad_y)
13969 Bang(bad_x, bad_y);
13972 void KillPlayer(struct PlayerInfo *player)
13974 int jx = player->jx, jy = player->jy;
13976 if (!player->active)
13980 Debug("game:playing:KillPlayer",
13981 "0: killed == %d, active == %d, reanimated == %d",
13982 player->killed, player->active, player->reanimated);
13985 /* the following code was introduced to prevent an infinite loop when calling
13987 -> CheckTriggeredElementChangeExt()
13988 -> ExecuteCustomElementAction()
13990 -> (infinitely repeating the above sequence of function calls)
13991 which occurs when killing the player while having a CE with the setting
13992 "kill player X when explosion of <player X>"; the solution using a new
13993 field "player->killed" was chosen for backwards compatibility, although
13994 clever use of the fields "player->active" etc. would probably also work */
13996 if (player->killed)
14000 player->killed = TRUE;
14002 // remove accessible field at the player's position
14003 Tile[jx][jy] = EL_EMPTY;
14005 // deactivate shield (else Bang()/Explode() would not work right)
14006 player->shield_normal_time_left = 0;
14007 player->shield_deadly_time_left = 0;
14010 Debug("game:playing:KillPlayer",
14011 "1: killed == %d, active == %d, reanimated == %d",
14012 player->killed, player->active, player->reanimated);
14018 Debug("game:playing:KillPlayer",
14019 "2: killed == %d, active == %d, reanimated == %d",
14020 player->killed, player->active, player->reanimated);
14023 if (player->reanimated) // killed player may have been reanimated
14024 player->killed = player->reanimated = FALSE;
14026 BuryPlayer(player);
14029 static void KillPlayerUnlessEnemyProtected(int x, int y)
14031 if (!PLAYER_ENEMY_PROTECTED(x, y))
14032 KillPlayer(PLAYERINFO(x, y));
14035 static void KillPlayerUnlessExplosionProtected(int x, int y)
14037 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14038 KillPlayer(PLAYERINFO(x, y));
14041 void BuryPlayer(struct PlayerInfo *player)
14043 int jx = player->jx, jy = player->jy;
14045 if (!player->active)
14048 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14049 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14051 RemovePlayer(player);
14053 player->buried = TRUE;
14055 if (game.all_players_gone)
14056 game.GameOver = TRUE;
14059 void RemovePlayer(struct PlayerInfo *player)
14061 int jx = player->jx, jy = player->jy;
14062 int i, found = FALSE;
14064 player->present = FALSE;
14065 player->active = FALSE;
14067 // required for some CE actions (even if the player is not active anymore)
14068 player->MovPos = 0;
14070 if (!ExplodeField[jx][jy])
14071 StorePlayer[jx][jy] = 0;
14073 if (player->is_moving)
14074 TEST_DrawLevelField(player->last_jx, player->last_jy);
14076 for (i = 0; i < MAX_PLAYERS; i++)
14077 if (stored_player[i].active)
14082 game.all_players_gone = TRUE;
14083 game.GameOver = TRUE;
14086 game.exit_x = game.robot_wheel_x = jx;
14087 game.exit_y = game.robot_wheel_y = jy;
14090 void ExitPlayer(struct PlayerInfo *player)
14092 DrawPlayer(player); // needed here only to cleanup last field
14093 RemovePlayer(player);
14095 if (game.players_still_needed > 0)
14096 game.players_still_needed--;
14099 static void SetFieldForSnapping(int x, int y, int element, int direction,
14100 int player_index_bit)
14102 struct ElementInfo *ei = &element_info[element];
14103 int direction_bit = MV_DIR_TO_BIT(direction);
14104 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14105 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14106 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14108 Tile[x][y] = EL_ELEMENT_SNAPPING;
14109 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14110 MovDir[x][y] = direction;
14111 Store[x][y] = element;
14112 Store2[x][y] = player_index_bit;
14114 ResetGfxAnimation(x, y);
14116 GfxElement[x][y] = element;
14117 GfxAction[x][y] = action;
14118 GfxDir[x][y] = direction;
14119 GfxFrame[x][y] = -1;
14122 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14123 int player_index_bit)
14125 TestIfElementTouchesCustomElement(x, y); // for empty space
14127 if (level.finish_dig_collect)
14129 int dig_side = MV_DIR_OPPOSITE(direction);
14130 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14131 CE_PLAYER_COLLECTS_X);
14133 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14134 player_index_bit, dig_side);
14135 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14136 player_index_bit, dig_side);
14141 =============================================================================
14142 checkDiagonalPushing()
14143 -----------------------------------------------------------------------------
14144 check if diagonal input device direction results in pushing of object
14145 (by checking if the alternative direction is walkable, diggable, ...)
14146 =============================================================================
14149 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14150 int x, int y, int real_dx, int real_dy)
14152 int jx, jy, dx, dy, xx, yy;
14154 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14157 // diagonal direction: check alternative direction
14162 xx = jx + (dx == 0 ? real_dx : 0);
14163 yy = jy + (dy == 0 ? real_dy : 0);
14165 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14169 =============================================================================
14171 -----------------------------------------------------------------------------
14172 x, y: field next to player (non-diagonal) to try to dig to
14173 real_dx, real_dy: direction as read from input device (can be diagonal)
14174 =============================================================================
14177 static int DigField(struct PlayerInfo *player,
14178 int oldx, int oldy, int x, int y,
14179 int real_dx, int real_dy, int mode)
14181 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14182 boolean player_was_pushing = player->is_pushing;
14183 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14184 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14185 int jx = oldx, jy = oldy;
14186 int dx = x - jx, dy = y - jy;
14187 int nextx = x + dx, nexty = y + dy;
14188 int move_direction = (dx == -1 ? MV_LEFT :
14189 dx == +1 ? MV_RIGHT :
14191 dy == +1 ? MV_DOWN : MV_NONE);
14192 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14193 int dig_side = MV_DIR_OPPOSITE(move_direction);
14194 int old_element = Tile[jx][jy];
14195 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14198 if (is_player) // function can also be called by EL_PENGUIN
14200 if (player->MovPos == 0)
14202 player->is_digging = FALSE;
14203 player->is_collecting = FALSE;
14206 if (player->MovPos == 0) // last pushing move finished
14207 player->is_pushing = FALSE;
14209 if (mode == DF_NO_PUSH) // player just stopped pushing
14211 player->is_switching = FALSE;
14212 player->push_delay = -1;
14214 return MP_NO_ACTION;
14217 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14218 old_element = Back[jx][jy];
14220 // in case of element dropped at player position, check background
14221 else if (Back[jx][jy] != EL_EMPTY &&
14222 game.engine_version >= VERSION_IDENT(2,2,0,0))
14223 old_element = Back[jx][jy];
14225 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14226 return MP_NO_ACTION; // field has no opening in this direction
14228 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14229 return MP_NO_ACTION; // field has no opening in this direction
14231 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14235 Tile[jx][jy] = player->artwork_element;
14236 InitMovingField(jx, jy, MV_DOWN);
14237 Store[jx][jy] = EL_ACID;
14238 ContinueMoving(jx, jy);
14239 BuryPlayer(player);
14241 return MP_DONT_RUN_INTO;
14244 if (player_can_move && DONT_RUN_INTO(element))
14246 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14248 return MP_DONT_RUN_INTO;
14251 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14252 return MP_NO_ACTION;
14254 collect_count = element_info[element].collect_count_initial;
14256 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14257 return MP_NO_ACTION;
14259 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14260 player_can_move = player_can_move_or_snap;
14262 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14263 game.engine_version >= VERSION_IDENT(2,2,0,0))
14265 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14266 player->index_bit, dig_side);
14267 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14268 player->index_bit, dig_side);
14270 if (element == EL_DC_LANDMINE)
14273 if (Tile[x][y] != element) // field changed by snapping
14276 return MP_NO_ACTION;
14279 if (player->gravity && is_player && !player->is_auto_moving &&
14280 canFallDown(player) && move_direction != MV_DOWN &&
14281 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14282 return MP_NO_ACTION; // player cannot walk here due to gravity
14284 if (player_can_move &&
14285 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14287 int sound_element = SND_ELEMENT(element);
14288 int sound_action = ACTION_WALKING;
14290 if (IS_RND_GATE(element))
14292 if (!player->key[RND_GATE_NR(element)])
14293 return MP_NO_ACTION;
14295 else if (IS_RND_GATE_GRAY(element))
14297 if (!player->key[RND_GATE_GRAY_NR(element)])
14298 return MP_NO_ACTION;
14300 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14302 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14303 return MP_NO_ACTION;
14305 else if (element == EL_EXIT_OPEN ||
14306 element == EL_EM_EXIT_OPEN ||
14307 element == EL_EM_EXIT_OPENING ||
14308 element == EL_STEEL_EXIT_OPEN ||
14309 element == EL_EM_STEEL_EXIT_OPEN ||
14310 element == EL_EM_STEEL_EXIT_OPENING ||
14311 element == EL_SP_EXIT_OPEN ||
14312 element == EL_SP_EXIT_OPENING)
14314 sound_action = ACTION_PASSING; // player is passing exit
14316 else if (element == EL_EMPTY)
14318 sound_action = ACTION_MOVING; // nothing to walk on
14321 // play sound from background or player, whatever is available
14322 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14323 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14325 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14327 else if (player_can_move &&
14328 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14330 if (!ACCESS_FROM(element, opposite_direction))
14331 return MP_NO_ACTION; // field not accessible from this direction
14333 if (CAN_MOVE(element)) // only fixed elements can be passed!
14334 return MP_NO_ACTION;
14336 if (IS_EM_GATE(element))
14338 if (!player->key[EM_GATE_NR(element)])
14339 return MP_NO_ACTION;
14341 else if (IS_EM_GATE_GRAY(element))
14343 if (!player->key[EM_GATE_GRAY_NR(element)])
14344 return MP_NO_ACTION;
14346 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14348 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (IS_EMC_GATE(element))
14353 if (!player->key[EMC_GATE_NR(element)])
14354 return MP_NO_ACTION;
14356 else if (IS_EMC_GATE_GRAY(element))
14358 if (!player->key[EMC_GATE_GRAY_NR(element)])
14359 return MP_NO_ACTION;
14361 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14363 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14364 return MP_NO_ACTION;
14366 else if (element == EL_DC_GATE_WHITE ||
14367 element == EL_DC_GATE_WHITE_GRAY ||
14368 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14370 if (player->num_white_keys == 0)
14371 return MP_NO_ACTION;
14373 player->num_white_keys--;
14375 else if (IS_SP_PORT(element))
14377 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14378 element == EL_SP_GRAVITY_PORT_RIGHT ||
14379 element == EL_SP_GRAVITY_PORT_UP ||
14380 element == EL_SP_GRAVITY_PORT_DOWN)
14381 player->gravity = !player->gravity;
14382 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14383 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14384 element == EL_SP_GRAVITY_ON_PORT_UP ||
14385 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14386 player->gravity = TRUE;
14387 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14388 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14389 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14390 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14391 player->gravity = FALSE;
14394 // automatically move to the next field with double speed
14395 player->programmed_action = move_direction;
14397 if (player->move_delay_reset_counter == 0)
14399 player->move_delay_reset_counter = 2; // two double speed steps
14401 DOUBLE_PLAYER_SPEED(player);
14404 PlayLevelSoundAction(x, y, ACTION_PASSING);
14406 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14410 if (mode != DF_SNAP)
14412 GfxElement[x][y] = GFX_ELEMENT(element);
14413 player->is_digging = TRUE;
14416 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14418 // use old behaviour for old levels (digging)
14419 if (!level.finish_dig_collect)
14421 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14422 player->index_bit, dig_side);
14424 // if digging triggered player relocation, finish digging tile
14425 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14426 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14429 if (mode == DF_SNAP)
14431 if (level.block_snap_field)
14432 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14434 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14436 // use old behaviour for old levels (snapping)
14437 if (!level.finish_dig_collect)
14438 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14439 player->index_bit, dig_side);
14442 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14446 if (is_player && mode != DF_SNAP)
14448 GfxElement[x][y] = element;
14449 player->is_collecting = TRUE;
14452 if (element == EL_SPEED_PILL)
14454 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14456 else if (element == EL_EXTRA_TIME && level.time > 0)
14458 TimeLeft += level.extra_time;
14460 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14462 DisplayGameControlValues();
14464 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14466 player->shield_normal_time_left += level.shield_normal_time;
14467 if (element == EL_SHIELD_DEADLY)
14468 player->shield_deadly_time_left += level.shield_deadly_time;
14470 else if (element == EL_DYNAMITE ||
14471 element == EL_EM_DYNAMITE ||
14472 element == EL_SP_DISK_RED)
14474 if (player->inventory_size < MAX_INVENTORY_SIZE)
14475 player->inventory_element[player->inventory_size++] = element;
14477 DrawGameDoorValues();
14479 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14481 player->dynabomb_count++;
14482 player->dynabombs_left++;
14484 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14486 player->dynabomb_size++;
14488 else if (element == EL_DYNABOMB_INCREASE_POWER)
14490 player->dynabomb_xl = TRUE;
14492 else if (IS_KEY(element))
14494 player->key[KEY_NR(element)] = TRUE;
14496 DrawGameDoorValues();
14498 else if (element == EL_DC_KEY_WHITE)
14500 player->num_white_keys++;
14502 // display white keys?
14503 // DrawGameDoorValues();
14505 else if (IS_ENVELOPE(element))
14507 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14509 if (!wait_for_snapping)
14510 player->show_envelope = element;
14512 else if (element == EL_EMC_LENSES)
14514 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14516 RedrawAllInvisibleElementsForLenses();
14518 else if (element == EL_EMC_MAGNIFIER)
14520 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14522 RedrawAllInvisibleElementsForMagnifier();
14524 else if (IS_DROPPABLE(element) ||
14525 IS_THROWABLE(element)) // can be collected and dropped
14529 if (collect_count == 0)
14530 player->inventory_infinite_element = element;
14532 for (i = 0; i < collect_count; i++)
14533 if (player->inventory_size < MAX_INVENTORY_SIZE)
14534 player->inventory_element[player->inventory_size++] = element;
14536 DrawGameDoorValues();
14538 else if (collect_count > 0)
14540 game.gems_still_needed -= collect_count;
14541 if (game.gems_still_needed < 0)
14542 game.gems_still_needed = 0;
14544 game.snapshot.collected_item = TRUE;
14546 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14548 DisplayGameControlValues();
14551 RaiseScoreElement(element);
14552 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14554 // use old behaviour for old levels (collecting)
14555 if (!level.finish_dig_collect && is_player)
14557 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14558 player->index_bit, dig_side);
14560 // if collecting triggered player relocation, finish collecting tile
14561 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14562 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14565 if (mode == DF_SNAP)
14567 if (level.block_snap_field)
14568 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14570 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14572 // use old behaviour for old levels (snapping)
14573 if (!level.finish_dig_collect)
14574 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14575 player->index_bit, dig_side);
14578 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14580 if (mode == DF_SNAP && element != EL_BD_ROCK)
14581 return MP_NO_ACTION;
14583 if (CAN_FALL(element) && dy)
14584 return MP_NO_ACTION;
14586 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14587 !(element == EL_SPRING && level.use_spring_bug))
14588 return MP_NO_ACTION;
14590 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14591 ((move_direction & MV_VERTICAL &&
14592 ((element_info[element].move_pattern & MV_LEFT &&
14593 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14594 (element_info[element].move_pattern & MV_RIGHT &&
14595 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14596 (move_direction & MV_HORIZONTAL &&
14597 ((element_info[element].move_pattern & MV_UP &&
14598 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14599 (element_info[element].move_pattern & MV_DOWN &&
14600 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14601 return MP_NO_ACTION;
14603 // do not push elements already moving away faster than player
14604 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14605 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14606 return MP_NO_ACTION;
14608 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14610 if (player->push_delay_value == -1 || !player_was_pushing)
14611 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14613 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14615 if (player->push_delay_value == -1)
14616 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14618 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14620 if (!player->is_pushing)
14621 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14624 player->is_pushing = TRUE;
14625 player->is_active = TRUE;
14627 if (!(IN_LEV_FIELD(nextx, nexty) &&
14628 (IS_FREE(nextx, nexty) ||
14629 (IS_SB_ELEMENT(element) &&
14630 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14631 (IS_CUSTOM_ELEMENT(element) &&
14632 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14633 return MP_NO_ACTION;
14635 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14636 return MP_NO_ACTION;
14638 if (player->push_delay == -1) // new pushing; restart delay
14639 player->push_delay = 0;
14641 if (player->push_delay < player->push_delay_value &&
14642 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14643 element != EL_SPRING && element != EL_BALLOON)
14645 // make sure that there is no move delay before next try to push
14646 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14647 player->move_delay = 0;
14649 return MP_NO_ACTION;
14652 if (IS_CUSTOM_ELEMENT(element) &&
14653 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14655 if (!DigFieldByCE(nextx, nexty, element))
14656 return MP_NO_ACTION;
14659 if (IS_SB_ELEMENT(element))
14661 boolean sokoban_task_solved = FALSE;
14663 if (element == EL_SOKOBAN_FIELD_FULL)
14665 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14667 IncrementSokobanFieldsNeeded();
14668 IncrementSokobanObjectsNeeded();
14671 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14673 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14675 DecrementSokobanFieldsNeeded();
14676 DecrementSokobanObjectsNeeded();
14678 // sokoban object was pushed from empty field to sokoban field
14679 if (Back[x][y] == EL_EMPTY)
14680 sokoban_task_solved = TRUE;
14683 Tile[x][y] = EL_SOKOBAN_OBJECT;
14685 if (Back[x][y] == Back[nextx][nexty])
14686 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14687 else if (Back[x][y] != 0)
14688 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14691 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14694 if (sokoban_task_solved &&
14695 game.sokoban_fields_still_needed == 0 &&
14696 game.sokoban_objects_still_needed == 0 &&
14697 level.auto_exit_sokoban)
14699 game.players_still_needed = 0;
14703 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14707 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14709 InitMovingField(x, y, move_direction);
14710 GfxAction[x][y] = ACTION_PUSHING;
14712 if (mode == DF_SNAP)
14713 ContinueMoving(x, y);
14715 MovPos[x][y] = (dx != 0 ? dx : dy);
14717 Pushed[x][y] = TRUE;
14718 Pushed[nextx][nexty] = TRUE;
14720 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14721 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14723 player->push_delay_value = -1; // get new value later
14725 // check for element change _after_ element has been pushed
14726 if (game.use_change_when_pushing_bug)
14728 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14729 player->index_bit, dig_side);
14730 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14731 player->index_bit, dig_side);
14734 else if (IS_SWITCHABLE(element))
14736 if (PLAYER_SWITCHING(player, x, y))
14738 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14739 player->index_bit, dig_side);
14744 player->is_switching = TRUE;
14745 player->switch_x = x;
14746 player->switch_y = y;
14748 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14750 if (element == EL_ROBOT_WHEEL)
14752 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14754 game.robot_wheel_x = x;
14755 game.robot_wheel_y = y;
14756 game.robot_wheel_active = TRUE;
14758 TEST_DrawLevelField(x, y);
14760 else if (element == EL_SP_TERMINAL)
14764 SCAN_PLAYFIELD(xx, yy)
14766 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14770 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14772 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14774 ResetGfxAnimation(xx, yy);
14775 TEST_DrawLevelField(xx, yy);
14779 else if (IS_BELT_SWITCH(element))
14781 ToggleBeltSwitch(x, y);
14783 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14784 element == EL_SWITCHGATE_SWITCH_DOWN ||
14785 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14786 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14788 ToggleSwitchgateSwitch(x, y);
14790 else if (element == EL_LIGHT_SWITCH ||
14791 element == EL_LIGHT_SWITCH_ACTIVE)
14793 ToggleLightSwitch(x, y);
14795 else if (element == EL_TIMEGATE_SWITCH ||
14796 element == EL_DC_TIMEGATE_SWITCH)
14798 ActivateTimegateSwitch(x, y);
14800 else if (element == EL_BALLOON_SWITCH_LEFT ||
14801 element == EL_BALLOON_SWITCH_RIGHT ||
14802 element == EL_BALLOON_SWITCH_UP ||
14803 element == EL_BALLOON_SWITCH_DOWN ||
14804 element == EL_BALLOON_SWITCH_NONE ||
14805 element == EL_BALLOON_SWITCH_ANY)
14807 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14808 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14809 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14810 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14811 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14814 else if (element == EL_LAMP)
14816 Tile[x][y] = EL_LAMP_ACTIVE;
14817 game.lights_still_needed--;
14819 ResetGfxAnimation(x, y);
14820 TEST_DrawLevelField(x, y);
14822 else if (element == EL_TIME_ORB_FULL)
14824 Tile[x][y] = EL_TIME_ORB_EMPTY;
14826 if (level.time > 0 || level.use_time_orb_bug)
14828 TimeLeft += level.time_orb_time;
14829 game.no_time_limit = FALSE;
14831 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14833 DisplayGameControlValues();
14836 ResetGfxAnimation(x, y);
14837 TEST_DrawLevelField(x, y);
14839 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14840 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14844 game.ball_active = !game.ball_active;
14846 SCAN_PLAYFIELD(xx, yy)
14848 int e = Tile[xx][yy];
14850 if (game.ball_active)
14852 if (e == EL_EMC_MAGIC_BALL)
14853 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14854 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14855 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14859 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14860 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14861 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14862 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14867 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14868 player->index_bit, dig_side);
14870 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14871 player->index_bit, dig_side);
14873 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14874 player->index_bit, dig_side);
14880 if (!PLAYER_SWITCHING(player, x, y))
14882 player->is_switching = TRUE;
14883 player->switch_x = x;
14884 player->switch_y = y;
14886 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14887 player->index_bit, dig_side);
14888 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14889 player->index_bit, dig_side);
14891 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14892 player->index_bit, dig_side);
14893 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14894 player->index_bit, dig_side);
14897 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14898 player->index_bit, dig_side);
14899 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14900 player->index_bit, dig_side);
14902 return MP_NO_ACTION;
14905 player->push_delay = -1;
14907 if (is_player) // function can also be called by EL_PENGUIN
14909 if (Tile[x][y] != element) // really digged/collected something
14911 player->is_collecting = !player->is_digging;
14912 player->is_active = TRUE;
14914 player->last_removed_element = element;
14921 static boolean DigFieldByCE(int x, int y, int digging_element)
14923 int element = Tile[x][y];
14925 if (!IS_FREE(x, y))
14927 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14928 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14931 // no element can dig solid indestructible elements
14932 if (IS_INDESTRUCTIBLE(element) &&
14933 !IS_DIGGABLE(element) &&
14934 !IS_COLLECTIBLE(element))
14937 if (AmoebaNr[x][y] &&
14938 (element == EL_AMOEBA_FULL ||
14939 element == EL_BD_AMOEBA ||
14940 element == EL_AMOEBA_GROWING))
14942 AmoebaCnt[AmoebaNr[x][y]]--;
14943 AmoebaCnt2[AmoebaNr[x][y]]--;
14946 if (IS_MOVING(x, y))
14947 RemoveMovingField(x, y);
14951 TEST_DrawLevelField(x, y);
14954 // if digged element was about to explode, prevent the explosion
14955 ExplodeField[x][y] = EX_TYPE_NONE;
14957 PlayLevelSoundAction(x, y, action);
14960 Store[x][y] = EL_EMPTY;
14962 // this makes it possible to leave the removed element again
14963 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14964 Store[x][y] = element;
14969 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14971 int jx = player->jx, jy = player->jy;
14972 int x = jx + dx, y = jy + dy;
14973 int snap_direction = (dx == -1 ? MV_LEFT :
14974 dx == +1 ? MV_RIGHT :
14976 dy == +1 ? MV_DOWN : MV_NONE);
14977 boolean can_continue_snapping = (level.continuous_snapping &&
14978 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14980 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14983 if (!player->active || !IN_LEV_FIELD(x, y))
14991 if (player->MovPos == 0)
14992 player->is_pushing = FALSE;
14994 player->is_snapping = FALSE;
14996 if (player->MovPos == 0)
14998 player->is_moving = FALSE;
14999 player->is_digging = FALSE;
15000 player->is_collecting = FALSE;
15006 // prevent snapping with already pressed snap key when not allowed
15007 if (player->is_snapping && !can_continue_snapping)
15010 player->MovDir = snap_direction;
15012 if (player->MovPos == 0)
15014 player->is_moving = FALSE;
15015 player->is_digging = FALSE;
15016 player->is_collecting = FALSE;
15019 player->is_dropping = FALSE;
15020 player->is_dropping_pressed = FALSE;
15021 player->drop_pressed_delay = 0;
15023 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15026 player->is_snapping = TRUE;
15027 player->is_active = TRUE;
15029 if (player->MovPos == 0)
15031 player->is_moving = FALSE;
15032 player->is_digging = FALSE;
15033 player->is_collecting = FALSE;
15036 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15037 TEST_DrawLevelField(player->last_jx, player->last_jy);
15039 TEST_DrawLevelField(x, y);
15044 static boolean DropElement(struct PlayerInfo *player)
15046 int old_element, new_element;
15047 int dropx = player->jx, dropy = player->jy;
15048 int drop_direction = player->MovDir;
15049 int drop_side = drop_direction;
15050 int drop_element = get_next_dropped_element(player);
15052 /* do not drop an element on top of another element; when holding drop key
15053 pressed without moving, dropped element must move away before the next
15054 element can be dropped (this is especially important if the next element
15055 is dynamite, which can be placed on background for historical reasons) */
15056 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15059 if (IS_THROWABLE(drop_element))
15061 dropx += GET_DX_FROM_DIR(drop_direction);
15062 dropy += GET_DY_FROM_DIR(drop_direction);
15064 if (!IN_LEV_FIELD(dropx, dropy))
15068 old_element = Tile[dropx][dropy]; // old element at dropping position
15069 new_element = drop_element; // default: no change when dropping
15071 // check if player is active, not moving and ready to drop
15072 if (!player->active || player->MovPos || player->drop_delay > 0)
15075 // check if player has anything that can be dropped
15076 if (new_element == EL_UNDEFINED)
15079 // only set if player has anything that can be dropped
15080 player->is_dropping_pressed = TRUE;
15082 // check if drop key was pressed long enough for EM style dynamite
15083 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15086 // check if anything can be dropped at the current position
15087 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15090 // collected custom elements can only be dropped on empty fields
15091 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15094 if (old_element != EL_EMPTY)
15095 Back[dropx][dropy] = old_element; // store old element on this field
15097 ResetGfxAnimation(dropx, dropy);
15098 ResetRandomAnimationValue(dropx, dropy);
15100 if (player->inventory_size > 0 ||
15101 player->inventory_infinite_element != EL_UNDEFINED)
15103 if (player->inventory_size > 0)
15105 player->inventory_size--;
15107 DrawGameDoorValues();
15109 if (new_element == EL_DYNAMITE)
15110 new_element = EL_DYNAMITE_ACTIVE;
15111 else if (new_element == EL_EM_DYNAMITE)
15112 new_element = EL_EM_DYNAMITE_ACTIVE;
15113 else if (new_element == EL_SP_DISK_RED)
15114 new_element = EL_SP_DISK_RED_ACTIVE;
15117 Tile[dropx][dropy] = new_element;
15119 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15120 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15121 el2img(Tile[dropx][dropy]), 0);
15123 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15125 // needed if previous element just changed to "empty" in the last frame
15126 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15128 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15129 player->index_bit, drop_side);
15130 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15132 player->index_bit, drop_side);
15134 TestIfElementTouchesCustomElement(dropx, dropy);
15136 else // player is dropping a dyna bomb
15138 player->dynabombs_left--;
15140 Tile[dropx][dropy] = new_element;
15142 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15143 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15144 el2img(Tile[dropx][dropy]), 0);
15146 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15149 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15150 InitField_WithBug1(dropx, dropy, FALSE);
15152 new_element = Tile[dropx][dropy]; // element might have changed
15154 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15155 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15157 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15158 MovDir[dropx][dropy] = drop_direction;
15160 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15162 // do not cause impact style collision by dropping elements that can fall
15163 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15166 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15167 player->is_dropping = TRUE;
15169 player->drop_pressed_delay = 0;
15170 player->is_dropping_pressed = FALSE;
15172 player->drop_x = dropx;
15173 player->drop_y = dropy;
15178 // ----------------------------------------------------------------------------
15179 // game sound playing functions
15180 // ----------------------------------------------------------------------------
15182 static int *loop_sound_frame = NULL;
15183 static int *loop_sound_volume = NULL;
15185 void InitPlayLevelSound(void)
15187 int num_sounds = getSoundListSize();
15189 checked_free(loop_sound_frame);
15190 checked_free(loop_sound_volume);
15192 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15193 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15196 static void PlayLevelSound(int x, int y, int nr)
15198 int sx = SCREENX(x), sy = SCREENY(y);
15199 int volume, stereo_position;
15200 int max_distance = 8;
15201 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15203 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15204 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15207 if (!IN_LEV_FIELD(x, y) ||
15208 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15209 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15212 volume = SOUND_MAX_VOLUME;
15214 if (!IN_SCR_FIELD(sx, sy))
15216 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15217 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15219 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15222 stereo_position = (SOUND_MAX_LEFT +
15223 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15224 (SCR_FIELDX + 2 * max_distance));
15226 if (IS_LOOP_SOUND(nr))
15228 /* This assures that quieter loop sounds do not overwrite louder ones,
15229 while restarting sound volume comparison with each new game frame. */
15231 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15234 loop_sound_volume[nr] = volume;
15235 loop_sound_frame[nr] = FrameCounter;
15238 PlaySoundExt(nr, volume, stereo_position, type);
15241 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15243 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15244 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15245 y < LEVELY(BY1) ? LEVELY(BY1) :
15246 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15250 static void PlayLevelSoundAction(int x, int y, int action)
15252 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15255 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15257 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15259 if (sound_effect != SND_UNDEFINED)
15260 PlayLevelSound(x, y, sound_effect);
15263 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15266 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15268 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15269 PlayLevelSound(x, y, sound_effect);
15272 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15274 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15276 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15277 PlayLevelSound(x, y, sound_effect);
15280 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15282 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15284 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15285 StopSound(sound_effect);
15288 static int getLevelMusicNr(void)
15290 if (levelset.music[level_nr] != MUS_UNDEFINED)
15291 return levelset.music[level_nr]; // from config file
15293 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15296 static void FadeLevelSounds(void)
15301 static void FadeLevelMusic(void)
15303 int music_nr = getLevelMusicNr();
15304 char *curr_music = getCurrentlyPlayingMusicFilename();
15305 char *next_music = getMusicInfoEntryFilename(music_nr);
15307 if (!strEqual(curr_music, next_music))
15311 void FadeLevelSoundsAndMusic(void)
15317 static void PlayLevelMusic(void)
15319 int music_nr = getLevelMusicNr();
15320 char *curr_music = getCurrentlyPlayingMusicFilename();
15321 char *next_music = getMusicInfoEntryFilename(music_nr);
15323 if (!strEqual(curr_music, next_music))
15324 PlayMusicLoop(music_nr);
15327 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15329 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15331 int x = xx - offset;
15332 int y = yy - offset;
15337 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15341 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15345 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15349 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15353 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15357 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15361 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15364 case SOUND_android_clone:
15365 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15368 case SOUND_android_move:
15369 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15373 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15377 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15381 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15384 case SOUND_eater_eat:
15385 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15389 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15392 case SOUND_collect:
15393 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15396 case SOUND_diamond:
15397 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15401 // !!! CHECK THIS !!!
15403 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15405 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15409 case SOUND_wonderfall:
15410 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15414 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15418 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15422 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15426 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15430 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15434 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15438 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15442 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15445 case SOUND_exit_open:
15446 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15449 case SOUND_exit_leave:
15450 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15453 case SOUND_dynamite:
15454 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15458 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15462 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15466 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15470 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15474 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15478 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15482 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15487 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15489 int element = map_element_SP_to_RND(element_sp);
15490 int action = map_action_SP_to_RND(action_sp);
15491 int offset = (setup.sp_show_border_elements ? 0 : 1);
15492 int x = xx - offset;
15493 int y = yy - offset;
15495 PlayLevelSoundElementAction(x, y, element, action);
15498 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15500 int element = map_element_MM_to_RND(element_mm);
15501 int action = map_action_MM_to_RND(action_mm);
15503 int x = xx - offset;
15504 int y = yy - offset;
15506 if (!IS_MM_ELEMENT(element))
15507 element = EL_MM_DEFAULT;
15509 PlayLevelSoundElementAction(x, y, element, action);
15512 void PlaySound_MM(int sound_mm)
15514 int sound = map_sound_MM_to_RND(sound_mm);
15516 if (sound == SND_UNDEFINED)
15522 void PlaySoundLoop_MM(int sound_mm)
15524 int sound = map_sound_MM_to_RND(sound_mm);
15526 if (sound == SND_UNDEFINED)
15529 PlaySoundLoop(sound);
15532 void StopSound_MM(int sound_mm)
15534 int sound = map_sound_MM_to_RND(sound_mm);
15536 if (sound == SND_UNDEFINED)
15542 void RaiseScore(int value)
15544 game.score += value;
15546 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15548 DisplayGameControlValues();
15551 void RaiseScoreElement(int element)
15556 case EL_BD_DIAMOND:
15557 case EL_EMERALD_YELLOW:
15558 case EL_EMERALD_RED:
15559 case EL_EMERALD_PURPLE:
15560 case EL_SP_INFOTRON:
15561 RaiseScore(level.score[SC_EMERALD]);
15564 RaiseScore(level.score[SC_DIAMOND]);
15567 RaiseScore(level.score[SC_CRYSTAL]);
15570 RaiseScore(level.score[SC_PEARL]);
15573 case EL_BD_BUTTERFLY:
15574 case EL_SP_ELECTRON:
15575 RaiseScore(level.score[SC_BUG]);
15578 case EL_BD_FIREFLY:
15579 case EL_SP_SNIKSNAK:
15580 RaiseScore(level.score[SC_SPACESHIP]);
15583 case EL_DARK_YAMYAM:
15584 RaiseScore(level.score[SC_YAMYAM]);
15587 RaiseScore(level.score[SC_ROBOT]);
15590 RaiseScore(level.score[SC_PACMAN]);
15593 RaiseScore(level.score[SC_NUT]);
15596 case EL_EM_DYNAMITE:
15597 case EL_SP_DISK_RED:
15598 case EL_DYNABOMB_INCREASE_NUMBER:
15599 case EL_DYNABOMB_INCREASE_SIZE:
15600 case EL_DYNABOMB_INCREASE_POWER:
15601 RaiseScore(level.score[SC_DYNAMITE]);
15603 case EL_SHIELD_NORMAL:
15604 case EL_SHIELD_DEADLY:
15605 RaiseScore(level.score[SC_SHIELD]);
15607 case EL_EXTRA_TIME:
15608 RaiseScore(level.extra_time_score);
15622 case EL_DC_KEY_WHITE:
15623 RaiseScore(level.score[SC_KEY]);
15626 RaiseScore(element_info[element].collect_score);
15631 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15633 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15637 // prevent short reactivation of overlay buttons while closing door
15638 SetOverlayActive(FALSE);
15640 // door may still be open due to skipped or envelope style request
15641 CloseDoor(DOOR_CLOSE_1);
15644 if (network.enabled)
15645 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15649 FadeSkipNextFadeIn();
15651 SetGameStatus(GAME_MODE_MAIN);
15656 else // continue playing the game
15658 if (tape.playing && tape.deactivate_display)
15659 TapeDeactivateDisplayOff(TRUE);
15661 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15663 if (tape.playing && tape.deactivate_display)
15664 TapeDeactivateDisplayOn();
15668 void RequestQuitGame(boolean escape_key_pressed)
15670 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15671 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15672 level_editor_test_game);
15673 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15676 RequestQuitGameExt(skip_request, quick_quit,
15677 "Do you really want to quit the game?");
15680 void RequestRestartGame(char *message)
15682 game.restart_game_message = NULL;
15684 boolean has_started_game = hasStartedNetworkGame();
15685 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15687 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15689 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15693 // needed in case of envelope request to close game panel
15694 CloseDoor(DOOR_CLOSE_1);
15696 SetGameStatus(GAME_MODE_MAIN);
15702 void CheckGameOver(void)
15704 static boolean last_game_over = FALSE;
15705 static int game_over_delay = 0;
15706 int game_over_delay_value = 50;
15707 boolean game_over = checkGameFailed();
15709 // do not handle game over if request dialog is already active
15710 if (game.request_active)
15713 // do not ask to play again if game was never actually played
15714 if (!game.GamePlayed)
15719 last_game_over = FALSE;
15720 game_over_delay = game_over_delay_value;
15725 if (game_over_delay > 0)
15732 if (last_game_over != game_over)
15733 game.restart_game_message = (hasStartedNetworkGame() ?
15734 "Game over! Play it again?" :
15737 last_game_over = game_over;
15740 boolean checkGameSolved(void)
15742 // set for all game engines if level was solved
15743 return game.LevelSolved_GameEnd;
15746 boolean checkGameFailed(void)
15748 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15749 return (game_em.game_over && !game_em.level_solved);
15750 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15751 return (game_sp.game_over && !game_sp.level_solved);
15752 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15753 return (game_mm.game_over && !game_mm.level_solved);
15754 else // GAME_ENGINE_TYPE_RND
15755 return (game.GameOver && !game.LevelSolved);
15758 boolean checkGameEnded(void)
15760 return (checkGameSolved() || checkGameFailed());
15764 // ----------------------------------------------------------------------------
15765 // random generator functions
15766 // ----------------------------------------------------------------------------
15768 unsigned int InitEngineRandom_RND(int seed)
15770 game.num_random_calls = 0;
15772 return InitEngineRandom(seed);
15775 unsigned int RND(int max)
15779 game.num_random_calls++;
15781 return GetEngineRandom(max);
15788 // ----------------------------------------------------------------------------
15789 // game engine snapshot handling functions
15790 // ----------------------------------------------------------------------------
15792 struct EngineSnapshotInfo
15794 // runtime values for custom element collect score
15795 int collect_score[NUM_CUSTOM_ELEMENTS];
15797 // runtime values for group element choice position
15798 int choice_pos[NUM_GROUP_ELEMENTS];
15800 // runtime values for belt position animations
15801 int belt_graphic[4][NUM_BELT_PARTS];
15802 int belt_anim_mode[4][NUM_BELT_PARTS];
15805 static struct EngineSnapshotInfo engine_snapshot_rnd;
15806 static char *snapshot_level_identifier = NULL;
15807 static int snapshot_level_nr = -1;
15809 static void SaveEngineSnapshotValues_RND(void)
15811 static int belt_base_active_element[4] =
15813 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15814 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15815 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15816 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15820 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15822 int element = EL_CUSTOM_START + i;
15824 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15827 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15829 int element = EL_GROUP_START + i;
15831 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15834 for (i = 0; i < 4; i++)
15836 for (j = 0; j < NUM_BELT_PARTS; j++)
15838 int element = belt_base_active_element[i] + j;
15839 int graphic = el2img(element);
15840 int anim_mode = graphic_info[graphic].anim_mode;
15842 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15843 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15848 static void LoadEngineSnapshotValues_RND(void)
15850 unsigned int num_random_calls = game.num_random_calls;
15853 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15855 int element = EL_CUSTOM_START + i;
15857 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15860 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15862 int element = EL_GROUP_START + i;
15864 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15867 for (i = 0; i < 4; i++)
15869 for (j = 0; j < NUM_BELT_PARTS; j++)
15871 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15872 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15874 graphic_info[graphic].anim_mode = anim_mode;
15878 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15880 InitRND(tape.random_seed);
15881 for (i = 0; i < num_random_calls; i++)
15885 if (game.num_random_calls != num_random_calls)
15887 Error("number of random calls out of sync");
15888 Error("number of random calls should be %d", num_random_calls);
15889 Error("number of random calls is %d", game.num_random_calls);
15891 Fail("this should not happen -- please debug");
15895 void FreeEngineSnapshotSingle(void)
15897 FreeSnapshotSingle();
15899 setString(&snapshot_level_identifier, NULL);
15900 snapshot_level_nr = -1;
15903 void FreeEngineSnapshotList(void)
15905 FreeSnapshotList();
15908 static ListNode *SaveEngineSnapshotBuffers(void)
15910 ListNode *buffers = NULL;
15912 // copy some special values to a structure better suited for the snapshot
15914 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15915 SaveEngineSnapshotValues_RND();
15916 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15917 SaveEngineSnapshotValues_EM();
15918 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15919 SaveEngineSnapshotValues_SP(&buffers);
15920 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15921 SaveEngineSnapshotValues_MM(&buffers);
15923 // save values stored in special snapshot structure
15925 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15927 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15928 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15929 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15930 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15931 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15934 // save further RND engine values
15936 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15937 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15940 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15966 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15974 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15979 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15985 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15987 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15989 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15995 ListNode *node = engine_snapshot_list_rnd;
15998 while (node != NULL)
16000 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16005 Debug("game:playing:SaveEngineSnapshotBuffers",
16006 "size of engine snapshot: %d bytes", num_bytes);
16012 void SaveEngineSnapshotSingle(void)
16014 ListNode *buffers = SaveEngineSnapshotBuffers();
16016 // finally save all snapshot buffers to single snapshot
16017 SaveSnapshotSingle(buffers);
16019 // save level identification information
16020 setString(&snapshot_level_identifier, leveldir_current->identifier);
16021 snapshot_level_nr = level_nr;
16024 boolean CheckSaveEngineSnapshotToList(void)
16026 boolean save_snapshot =
16027 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16028 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16029 game.snapshot.changed_action) ||
16030 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16031 game.snapshot.collected_item));
16033 game.snapshot.changed_action = FALSE;
16034 game.snapshot.collected_item = FALSE;
16035 game.snapshot.save_snapshot = save_snapshot;
16037 return save_snapshot;
16040 void SaveEngineSnapshotToList(void)
16042 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16046 ListNode *buffers = SaveEngineSnapshotBuffers();
16048 // finally save all snapshot buffers to snapshot list
16049 SaveSnapshotToList(buffers);
16052 void SaveEngineSnapshotToListInitial(void)
16054 FreeEngineSnapshotList();
16056 SaveEngineSnapshotToList();
16059 static void LoadEngineSnapshotValues(void)
16061 // restore special values from snapshot structure
16063 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16064 LoadEngineSnapshotValues_RND();
16065 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16066 LoadEngineSnapshotValues_EM();
16067 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16068 LoadEngineSnapshotValues_SP();
16069 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16070 LoadEngineSnapshotValues_MM();
16073 void LoadEngineSnapshotSingle(void)
16075 LoadSnapshotSingle();
16077 LoadEngineSnapshotValues();
16080 static void LoadEngineSnapshot_Undo(int steps)
16082 LoadSnapshotFromList_Older(steps);
16084 LoadEngineSnapshotValues();
16087 static void LoadEngineSnapshot_Redo(int steps)
16089 LoadSnapshotFromList_Newer(steps);
16091 LoadEngineSnapshotValues();
16094 boolean CheckEngineSnapshotSingle(void)
16096 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16097 snapshot_level_nr == level_nr);
16100 boolean CheckEngineSnapshotList(void)
16102 return CheckSnapshotList();
16106 // ---------- new game button stuff -------------------------------------------
16113 boolean *setup_value;
16114 boolean allowed_on_tape;
16115 boolean is_touch_button;
16117 } gamebutton_info[NUM_GAME_BUTTONS] =
16120 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16121 GAME_CTRL_ID_STOP, NULL,
16122 TRUE, FALSE, "stop game"
16125 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16126 GAME_CTRL_ID_PAUSE, NULL,
16127 TRUE, FALSE, "pause game"
16130 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16131 GAME_CTRL_ID_PLAY, NULL,
16132 TRUE, FALSE, "play game"
16135 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16136 GAME_CTRL_ID_UNDO, NULL,
16137 TRUE, FALSE, "undo step"
16140 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16141 GAME_CTRL_ID_REDO, NULL,
16142 TRUE, FALSE, "redo step"
16145 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16146 GAME_CTRL_ID_SAVE, NULL,
16147 TRUE, FALSE, "save game"
16150 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16151 GAME_CTRL_ID_PAUSE2, NULL,
16152 TRUE, FALSE, "pause game"
16155 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16156 GAME_CTRL_ID_LOAD, NULL,
16157 TRUE, FALSE, "load game"
16160 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16161 GAME_CTRL_ID_PANEL_STOP, NULL,
16162 FALSE, FALSE, "stop game"
16165 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16166 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16167 FALSE, FALSE, "pause game"
16170 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16171 GAME_CTRL_ID_PANEL_PLAY, NULL,
16172 FALSE, FALSE, "play game"
16175 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16176 GAME_CTRL_ID_TOUCH_STOP, NULL,
16177 FALSE, TRUE, "stop game"
16180 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16181 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16182 FALSE, TRUE, "pause game"
16185 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16186 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16187 TRUE, FALSE, "background music on/off"
16190 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16191 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16192 TRUE, FALSE, "sound loops on/off"
16195 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16196 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16197 TRUE, FALSE, "normal sounds on/off"
16200 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16201 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16202 FALSE, FALSE, "background music on/off"
16205 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16206 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16207 FALSE, FALSE, "sound loops on/off"
16210 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16211 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16212 FALSE, FALSE, "normal sounds on/off"
16216 void CreateGameButtons(void)
16220 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16222 int graphic = gamebutton_info[i].graphic;
16223 struct GraphicInfo *gfx = &graphic_info[graphic];
16224 struct XY *pos = gamebutton_info[i].pos;
16225 struct GadgetInfo *gi;
16228 unsigned int event_mask;
16229 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16230 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16231 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16232 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16233 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16234 int gd_x = gfx->src_x;
16235 int gd_y = gfx->src_y;
16236 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16237 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16238 int gd_xa = gfx->src_x + gfx->active_xoffset;
16239 int gd_ya = gfx->src_y + gfx->active_yoffset;
16240 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16241 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16242 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16243 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16246 if (gfx->bitmap == NULL)
16248 game_gadget[id] = NULL;
16253 if (id == GAME_CTRL_ID_STOP ||
16254 id == GAME_CTRL_ID_PANEL_STOP ||
16255 id == GAME_CTRL_ID_TOUCH_STOP ||
16256 id == GAME_CTRL_ID_PLAY ||
16257 id == GAME_CTRL_ID_PANEL_PLAY ||
16258 id == GAME_CTRL_ID_SAVE ||
16259 id == GAME_CTRL_ID_LOAD)
16261 button_type = GD_TYPE_NORMAL_BUTTON;
16263 event_mask = GD_EVENT_RELEASED;
16265 else if (id == GAME_CTRL_ID_UNDO ||
16266 id == GAME_CTRL_ID_REDO)
16268 button_type = GD_TYPE_NORMAL_BUTTON;
16270 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16274 button_type = GD_TYPE_CHECK_BUTTON;
16275 checked = (gamebutton_info[i].setup_value != NULL ?
16276 *gamebutton_info[i].setup_value : FALSE);
16277 event_mask = GD_EVENT_PRESSED;
16280 gi = CreateGadget(GDI_CUSTOM_ID, id,
16281 GDI_IMAGE_ID, graphic,
16282 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16285 GDI_WIDTH, gfx->width,
16286 GDI_HEIGHT, gfx->height,
16287 GDI_TYPE, button_type,
16288 GDI_STATE, GD_BUTTON_UNPRESSED,
16289 GDI_CHECKED, checked,
16290 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16291 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16292 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16293 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16294 GDI_DIRECT_DRAW, FALSE,
16295 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16296 GDI_EVENT_MASK, event_mask,
16297 GDI_CALLBACK_ACTION, HandleGameButtons,
16301 Fail("cannot create gadget");
16303 game_gadget[id] = gi;
16307 void FreeGameButtons(void)
16311 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16312 FreeGadget(game_gadget[i]);
16315 static void UnmapGameButtonsAtSamePosition(int id)
16319 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16321 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16322 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16323 UnmapGadget(game_gadget[i]);
16326 static void UnmapGameButtonsAtSamePosition_All(void)
16328 if (setup.show_load_save_buttons)
16330 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16331 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16332 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16334 else if (setup.show_undo_redo_buttons)
16336 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16337 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16338 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16342 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16343 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16344 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16346 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16347 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16348 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16352 void MapLoadSaveButtons(void)
16354 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16355 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16357 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16358 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16361 void MapUndoRedoButtons(void)
16363 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16364 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16366 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16367 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16370 void ModifyPauseButtons(void)
16374 GAME_CTRL_ID_PAUSE,
16375 GAME_CTRL_ID_PAUSE2,
16376 GAME_CTRL_ID_PANEL_PAUSE,
16377 GAME_CTRL_ID_TOUCH_PAUSE,
16382 for (i = 0; ids[i] > -1; i++)
16383 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16386 static void MapGameButtonsExt(boolean on_tape)
16390 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16391 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16392 MapGadget(game_gadget[i]);
16394 UnmapGameButtonsAtSamePosition_All();
16396 RedrawGameButtons();
16399 static void UnmapGameButtonsExt(boolean on_tape)
16403 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16404 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16405 UnmapGadget(game_gadget[i]);
16408 static void RedrawGameButtonsExt(boolean on_tape)
16412 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16413 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16414 RedrawGadget(game_gadget[i]);
16417 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16422 gi->checked = state;
16425 static void RedrawSoundButtonGadget(int id)
16427 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16428 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16429 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16430 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16431 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16432 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16435 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16436 RedrawGadget(game_gadget[id2]);
16439 void MapGameButtons(void)
16441 MapGameButtonsExt(FALSE);
16444 void UnmapGameButtons(void)
16446 UnmapGameButtonsExt(FALSE);
16449 void RedrawGameButtons(void)
16451 RedrawGameButtonsExt(FALSE);
16454 void MapGameButtonsOnTape(void)
16456 MapGameButtonsExt(TRUE);
16459 void UnmapGameButtonsOnTape(void)
16461 UnmapGameButtonsExt(TRUE);
16464 void RedrawGameButtonsOnTape(void)
16466 RedrawGameButtonsExt(TRUE);
16469 static void GameUndoRedoExt(void)
16471 ClearPlayerAction();
16473 tape.pausing = TRUE;
16476 UpdateAndDisplayGameControlValues();
16478 DrawCompleteVideoDisplay();
16479 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16480 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16481 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16483 ModifyPauseButtons();
16488 static void GameUndo(int steps)
16490 if (!CheckEngineSnapshotList())
16493 int tape_property_bits = tape.property_bits;
16495 LoadEngineSnapshot_Undo(steps);
16497 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16502 static void GameRedo(int steps)
16504 if (!CheckEngineSnapshotList())
16507 int tape_property_bits = tape.property_bits;
16509 LoadEngineSnapshot_Redo(steps);
16511 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16516 static void HandleGameButtonsExt(int id, int button)
16518 static boolean game_undo_executed = FALSE;
16519 int steps = BUTTON_STEPSIZE(button);
16520 boolean handle_game_buttons =
16521 (game_status == GAME_MODE_PLAYING ||
16522 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16524 if (!handle_game_buttons)
16529 case GAME_CTRL_ID_STOP:
16530 case GAME_CTRL_ID_PANEL_STOP:
16531 case GAME_CTRL_ID_TOUCH_STOP:
16532 if (game_status == GAME_MODE_MAIN)
16538 RequestQuitGame(FALSE);
16542 case GAME_CTRL_ID_PAUSE:
16543 case GAME_CTRL_ID_PAUSE2:
16544 case GAME_CTRL_ID_PANEL_PAUSE:
16545 case GAME_CTRL_ID_TOUCH_PAUSE:
16546 if (network.enabled && game_status == GAME_MODE_PLAYING)
16549 SendToServer_ContinuePlaying();
16551 SendToServer_PausePlaying();
16554 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16556 game_undo_executed = FALSE;
16560 case GAME_CTRL_ID_PLAY:
16561 case GAME_CTRL_ID_PANEL_PLAY:
16562 if (game_status == GAME_MODE_MAIN)
16564 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16566 else if (tape.pausing)
16568 if (network.enabled)
16569 SendToServer_ContinuePlaying();
16571 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16575 case GAME_CTRL_ID_UNDO:
16576 // Important: When using "save snapshot when collecting an item" mode,
16577 // load last (current) snapshot for first "undo" after pressing "pause"
16578 // (else the last-but-one snapshot would be loaded, because the snapshot
16579 // pointer already points to the last snapshot when pressing "pause",
16580 // which is fine for "every step/move" mode, but not for "every collect")
16581 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16582 !game_undo_executed)
16585 game_undo_executed = TRUE;
16590 case GAME_CTRL_ID_REDO:
16594 case GAME_CTRL_ID_SAVE:
16598 case GAME_CTRL_ID_LOAD:
16602 case SOUND_CTRL_ID_MUSIC:
16603 case SOUND_CTRL_ID_PANEL_MUSIC:
16604 if (setup.sound_music)
16606 setup.sound_music = FALSE;
16610 else if (audio.music_available)
16612 setup.sound = setup.sound_music = TRUE;
16614 SetAudioMode(setup.sound);
16616 if (game_status == GAME_MODE_PLAYING)
16620 RedrawSoundButtonGadget(id);
16624 case SOUND_CTRL_ID_LOOPS:
16625 case SOUND_CTRL_ID_PANEL_LOOPS:
16626 if (setup.sound_loops)
16627 setup.sound_loops = FALSE;
16628 else if (audio.loops_available)
16630 setup.sound = setup.sound_loops = TRUE;
16632 SetAudioMode(setup.sound);
16635 RedrawSoundButtonGadget(id);
16639 case SOUND_CTRL_ID_SIMPLE:
16640 case SOUND_CTRL_ID_PANEL_SIMPLE:
16641 if (setup.sound_simple)
16642 setup.sound_simple = FALSE;
16643 else if (audio.sound_available)
16645 setup.sound = setup.sound_simple = TRUE;
16647 SetAudioMode(setup.sound);
16650 RedrawSoundButtonGadget(id);
16659 static void HandleGameButtons(struct GadgetInfo *gi)
16661 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16664 void HandleSoundButtonKeys(Key key)
16666 if (key == setup.shortcut.sound_simple)
16667 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16668 else if (key == setup.shortcut.sound_loops)
16669 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16670 else if (key == setup.shortcut.sound_music)
16671 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);