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 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8426 DrawLevelGraphicAnimation(x, y, graphic);
8427 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8429 if (Tile[newx][newy] == EL_ACID)
8431 SplashAcid(newx, newy);
8436 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8438 Store[newx][newy] = EL_EMC_ANDROID;
8439 GfxElement[newx][newy] = EL_EMC_ANDROID;
8440 GfxAction[newx][newy] = ACTION_GROWING;
8441 GfxDir[newx][newy] = diagonal_move_dir;
8442 ChangeDelay[newx][newy] = change_delay;
8444 graphic = el_act_dir2img(GfxElement[newx][newy],
8445 GfxAction[newx][newy], GfxDir[newx][newy]);
8447 DrawLevelGraphicAnimation(newx, newy, graphic);
8448 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8454 Tile[newx][newy] = EL_EMPTY;
8455 TEST_DrawLevelField(newx, newy);
8457 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8460 else if (!IS_FREE(newx, newy))
8465 else if (IS_CUSTOM_ELEMENT(element) &&
8466 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8468 if (!DigFieldByCE(newx, newy, element))
8471 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8473 RunnerVisit[x][y] = FrameCounter;
8474 PlayerVisit[x][y] /= 8; // expire player visit path
8477 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8479 if (!IS_FREE(newx, newy))
8481 if (IS_PLAYER(x, y))
8482 DrawPlayerField(x, y);
8484 TEST_DrawLevelField(x, y);
8490 boolean wanna_flame = !RND(10);
8491 int dx = newx - x, dy = newy - y;
8492 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8493 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8494 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8495 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8496 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8497 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8500 IS_CLASSIC_ENEMY(element1) ||
8501 IS_CLASSIC_ENEMY(element2)) &&
8502 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8503 element1 != EL_FLAMES && element2 != EL_FLAMES)
8505 ResetGfxAnimation(x, y);
8506 GfxAction[x][y] = ACTION_ATTACKING;
8508 if (IS_PLAYER(x, y))
8509 DrawPlayerField(x, y);
8511 TEST_DrawLevelField(x, y);
8513 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8515 MovDelay[x][y] = 50;
8517 Tile[newx][newy] = EL_FLAMES;
8518 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8519 Tile[newx1][newy1] = EL_FLAMES;
8520 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8521 Tile[newx2][newy2] = EL_FLAMES;
8527 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8528 Tile[newx][newy] == EL_DIAMOND)
8530 if (IS_MOVING(newx, newy))
8531 RemoveMovingField(newx, newy);
8534 Tile[newx][newy] = EL_EMPTY;
8535 TEST_DrawLevelField(newx, newy);
8538 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8540 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8541 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8543 if (AmoebaNr[newx][newy])
8545 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8546 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8547 Tile[newx][newy] == EL_BD_AMOEBA)
8548 AmoebaCnt[AmoebaNr[newx][newy]]--;
8551 if (IS_MOVING(newx, newy))
8553 RemoveMovingField(newx, newy);
8557 Tile[newx][newy] = EL_EMPTY;
8558 TEST_DrawLevelField(newx, newy);
8561 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8563 else if ((element == EL_PACMAN || element == EL_MOLE)
8564 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8566 if (AmoebaNr[newx][newy])
8568 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8569 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8570 Tile[newx][newy] == EL_BD_AMOEBA)
8571 AmoebaCnt[AmoebaNr[newx][newy]]--;
8574 if (element == EL_MOLE)
8576 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8577 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8579 ResetGfxAnimation(x, y);
8580 GfxAction[x][y] = ACTION_DIGGING;
8581 TEST_DrawLevelField(x, y);
8583 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8585 return; // wait for shrinking amoeba
8587 else // element == EL_PACMAN
8589 Tile[newx][newy] = EL_EMPTY;
8590 TEST_DrawLevelField(newx, newy);
8591 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8594 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8595 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8596 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8598 // wait for shrinking amoeba to completely disappear
8601 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8603 // object was running against a wall
8607 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8608 DrawLevelElementAnimation(x, y, element);
8610 if (DONT_TOUCH(element))
8611 TestIfBadThingTouchesPlayer(x, y);
8616 InitMovingField(x, y, MovDir[x][y]);
8618 PlayLevelSoundAction(x, y, ACTION_MOVING);
8622 ContinueMoving(x, y);
8625 void ContinueMoving(int x, int y)
8627 int element = Tile[x][y];
8628 struct ElementInfo *ei = &element_info[element];
8629 int direction = MovDir[x][y];
8630 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8631 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8632 int newx = x + dx, newy = y + dy;
8633 int stored = Store[x][y];
8634 int stored_new = Store[newx][newy];
8635 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8636 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8637 boolean last_line = (newy == lev_fieldy - 1);
8638 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8640 if (pushed_by_player) // special case: moving object pushed by player
8642 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8644 else if (use_step_delay) // special case: moving object has step delay
8646 if (!MovDelay[x][y])
8647 MovPos[x][y] += getElementMoveStepsize(x, y);
8652 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8656 TEST_DrawLevelField(x, y);
8658 return; // element is still waiting
8661 else // normal case: generically moving object
8663 MovPos[x][y] += getElementMoveStepsize(x, y);
8666 if (ABS(MovPos[x][y]) < TILEX)
8668 TEST_DrawLevelField(x, y);
8670 return; // element is still moving
8673 // element reached destination field
8675 Tile[x][y] = EL_EMPTY;
8676 Tile[newx][newy] = element;
8677 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8679 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8681 element = Tile[newx][newy] = EL_ACID;
8683 else if (element == EL_MOLE)
8685 Tile[x][y] = EL_SAND;
8687 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8689 else if (element == EL_QUICKSAND_FILLING)
8691 element = Tile[newx][newy] = get_next_element(element);
8692 Store[newx][newy] = Store[x][y];
8694 else if (element == EL_QUICKSAND_EMPTYING)
8696 Tile[x][y] = get_next_element(element);
8697 element = Tile[newx][newy] = Store[x][y];
8699 else if (element == EL_QUICKSAND_FAST_FILLING)
8701 element = Tile[newx][newy] = get_next_element(element);
8702 Store[newx][newy] = Store[x][y];
8704 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8706 Tile[x][y] = get_next_element(element);
8707 element = Tile[newx][newy] = Store[x][y];
8709 else if (element == EL_MAGIC_WALL_FILLING)
8711 element = Tile[newx][newy] = get_next_element(element);
8712 if (!game.magic_wall_active)
8713 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8714 Store[newx][newy] = Store[x][y];
8716 else if (element == EL_MAGIC_WALL_EMPTYING)
8718 Tile[x][y] = get_next_element(element);
8719 if (!game.magic_wall_active)
8720 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8721 element = Tile[newx][newy] = Store[x][y];
8723 InitField(newx, newy, FALSE);
8725 else if (element == EL_BD_MAGIC_WALL_FILLING)
8727 element = Tile[newx][newy] = get_next_element(element);
8728 if (!game.magic_wall_active)
8729 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8730 Store[newx][newy] = Store[x][y];
8732 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8734 Tile[x][y] = get_next_element(element);
8735 if (!game.magic_wall_active)
8736 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8737 element = Tile[newx][newy] = Store[x][y];
8739 InitField(newx, newy, FALSE);
8741 else if (element == EL_DC_MAGIC_WALL_FILLING)
8743 element = Tile[newx][newy] = get_next_element(element);
8744 if (!game.magic_wall_active)
8745 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8746 Store[newx][newy] = Store[x][y];
8748 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8750 Tile[x][y] = get_next_element(element);
8751 if (!game.magic_wall_active)
8752 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8753 element = Tile[newx][newy] = Store[x][y];
8755 InitField(newx, newy, FALSE);
8757 else if (element == EL_AMOEBA_DROPPING)
8759 Tile[x][y] = get_next_element(element);
8760 element = Tile[newx][newy] = Store[x][y];
8762 else if (element == EL_SOKOBAN_OBJECT)
8765 Tile[x][y] = Back[x][y];
8767 if (Back[newx][newy])
8768 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8770 Back[x][y] = Back[newx][newy] = 0;
8773 Store[x][y] = EL_EMPTY;
8778 MovDelay[newx][newy] = 0;
8780 if (CAN_CHANGE_OR_HAS_ACTION(element))
8782 // copy element change control values to new field
8783 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8784 ChangePage[newx][newy] = ChangePage[x][y];
8785 ChangeCount[newx][newy] = ChangeCount[x][y];
8786 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8789 CustomValue[newx][newy] = CustomValue[x][y];
8791 ChangeDelay[x][y] = 0;
8792 ChangePage[x][y] = -1;
8793 ChangeCount[x][y] = 0;
8794 ChangeEvent[x][y] = -1;
8796 CustomValue[x][y] = 0;
8798 // copy animation control values to new field
8799 GfxFrame[newx][newy] = GfxFrame[x][y];
8800 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8801 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8802 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8804 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8806 // some elements can leave other elements behind after moving
8807 if (ei->move_leave_element != EL_EMPTY &&
8808 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8809 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8811 int move_leave_element = ei->move_leave_element;
8813 // this makes it possible to leave the removed element again
8814 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8815 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8817 Tile[x][y] = move_leave_element;
8819 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8820 MovDir[x][y] = direction;
8822 InitField(x, y, FALSE);
8824 if (GFX_CRUMBLED(Tile[x][y]))
8825 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8827 if (IS_PLAYER_ELEMENT(move_leave_element))
8828 RelocatePlayer(x, y, move_leave_element);
8831 // do this after checking for left-behind element
8832 ResetGfxAnimation(x, y); // reset animation values for old field
8834 if (!CAN_MOVE(element) ||
8835 (CAN_FALL(element) && direction == MV_DOWN &&
8836 (element == EL_SPRING ||
8837 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8838 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8839 GfxDir[x][y] = MovDir[newx][newy] = 0;
8841 TEST_DrawLevelField(x, y);
8842 TEST_DrawLevelField(newx, newy);
8844 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8846 // prevent pushed element from moving on in pushed direction
8847 if (pushed_by_player && CAN_MOVE(element) &&
8848 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8849 !(element_info[element].move_pattern & direction))
8850 TurnRound(newx, newy);
8852 // prevent elements on conveyor belt from moving on in last direction
8853 if (pushed_by_conveyor && CAN_FALL(element) &&
8854 direction & MV_HORIZONTAL)
8855 MovDir[newx][newy] = 0;
8857 if (!pushed_by_player)
8859 int nextx = newx + dx, nexty = newy + dy;
8860 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8862 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8864 if (CAN_FALL(element) && direction == MV_DOWN)
8865 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8867 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8868 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8870 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8871 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8874 if (DONT_TOUCH(element)) // object may be nasty to player or others
8876 TestIfBadThingTouchesPlayer(newx, newy);
8877 TestIfBadThingTouchesFriend(newx, newy);
8879 if (!IS_CUSTOM_ELEMENT(element))
8880 TestIfBadThingTouchesOtherBadThing(newx, newy);
8882 else if (element == EL_PENGUIN)
8883 TestIfFriendTouchesBadThing(newx, newy);
8885 if (DONT_GET_HIT_BY(element))
8887 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8890 // give the player one last chance (one more frame) to move away
8891 if (CAN_FALL(element) && direction == MV_DOWN &&
8892 (last_line || (!IS_FREE(x, newy + 1) &&
8893 (!IS_PLAYER(x, newy + 1) ||
8894 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8897 if (pushed_by_player && !game.use_change_when_pushing_bug)
8899 int push_side = MV_DIR_OPPOSITE(direction);
8900 struct PlayerInfo *player = PLAYERINFO(x, y);
8902 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8903 player->index_bit, push_side);
8904 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8905 player->index_bit, push_side);
8908 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8909 MovDelay[newx][newy] = 1;
8911 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8913 TestIfElementTouchesCustomElement(x, y); // empty or new element
8914 TestIfElementHitsCustomElement(newx, newy, direction);
8915 TestIfPlayerTouchesCustomElement(newx, newy);
8916 TestIfElementTouchesCustomElement(newx, newy);
8918 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8919 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8920 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8921 MV_DIR_OPPOSITE(direction));
8924 int AmoebaNeighbourNr(int ax, int ay)
8927 int element = Tile[ax][ay];
8929 static int xy[4][2] =
8937 for (i = 0; i < NUM_DIRECTIONS; i++)
8939 int x = ax + xy[i][0];
8940 int y = ay + xy[i][1];
8942 if (!IN_LEV_FIELD(x, y))
8945 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8946 group_nr = AmoebaNr[x][y];
8952 static void AmoebaMerge(int ax, int ay)
8954 int i, x, y, xx, yy;
8955 int new_group_nr = AmoebaNr[ax][ay];
8956 static int xy[4][2] =
8964 if (new_group_nr == 0)
8967 for (i = 0; i < NUM_DIRECTIONS; i++)
8972 if (!IN_LEV_FIELD(x, y))
8975 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8976 Tile[x][y] == EL_BD_AMOEBA ||
8977 Tile[x][y] == EL_AMOEBA_DEAD) &&
8978 AmoebaNr[x][y] != new_group_nr)
8980 int old_group_nr = AmoebaNr[x][y];
8982 if (old_group_nr == 0)
8985 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8986 AmoebaCnt[old_group_nr] = 0;
8987 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8988 AmoebaCnt2[old_group_nr] = 0;
8990 SCAN_PLAYFIELD(xx, yy)
8992 if (AmoebaNr[xx][yy] == old_group_nr)
8993 AmoebaNr[xx][yy] = new_group_nr;
8999 void AmoebaToDiamond(int ax, int ay)
9003 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9005 int group_nr = AmoebaNr[ax][ay];
9010 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9011 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9017 SCAN_PLAYFIELD(x, y)
9019 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9022 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9026 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9027 SND_AMOEBA_TURNING_TO_GEM :
9028 SND_AMOEBA_TURNING_TO_ROCK));
9033 static int xy[4][2] =
9041 for (i = 0; i < NUM_DIRECTIONS; i++)
9046 if (!IN_LEV_FIELD(x, y))
9049 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9051 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9052 SND_AMOEBA_TURNING_TO_GEM :
9053 SND_AMOEBA_TURNING_TO_ROCK));
9060 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9063 int group_nr = AmoebaNr[ax][ay];
9064 boolean done = FALSE;
9069 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9070 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9076 SCAN_PLAYFIELD(x, y)
9078 if (AmoebaNr[x][y] == group_nr &&
9079 (Tile[x][y] == EL_AMOEBA_DEAD ||
9080 Tile[x][y] == EL_BD_AMOEBA ||
9081 Tile[x][y] == EL_AMOEBA_GROWING))
9084 Tile[x][y] = new_element;
9085 InitField(x, y, FALSE);
9086 TEST_DrawLevelField(x, y);
9092 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9093 SND_BD_AMOEBA_TURNING_TO_ROCK :
9094 SND_BD_AMOEBA_TURNING_TO_GEM));
9097 static void AmoebaGrowing(int x, int y)
9099 static unsigned int sound_delay = 0;
9100 static unsigned int sound_delay_value = 0;
9102 if (!MovDelay[x][y]) // start new growing cycle
9106 if (DelayReached(&sound_delay, sound_delay_value))
9108 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9109 sound_delay_value = 30;
9113 if (MovDelay[x][y]) // wait some time before growing bigger
9116 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9118 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9119 6 - MovDelay[x][y]);
9121 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9124 if (!MovDelay[x][y])
9126 Tile[x][y] = Store[x][y];
9128 TEST_DrawLevelField(x, y);
9133 static void AmoebaShrinking(int x, int y)
9135 static unsigned int sound_delay = 0;
9136 static unsigned int sound_delay_value = 0;
9138 if (!MovDelay[x][y]) // start new shrinking cycle
9142 if (DelayReached(&sound_delay, sound_delay_value))
9143 sound_delay_value = 30;
9146 if (MovDelay[x][y]) // wait some time before shrinking
9149 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9151 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9152 6 - MovDelay[x][y]);
9154 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9157 if (!MovDelay[x][y])
9159 Tile[x][y] = EL_EMPTY;
9160 TEST_DrawLevelField(x, y);
9162 // don't let mole enter this field in this cycle;
9163 // (give priority to objects falling to this field from above)
9169 static void AmoebaReproduce(int ax, int ay)
9172 int element = Tile[ax][ay];
9173 int graphic = el2img(element);
9174 int newax = ax, neway = ay;
9175 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9176 static int xy[4][2] =
9184 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9186 Tile[ax][ay] = EL_AMOEBA_DEAD;
9187 TEST_DrawLevelField(ax, ay);
9191 if (IS_ANIMATED(graphic))
9192 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9194 if (!MovDelay[ax][ay]) // start making new amoeba field
9195 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9197 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9200 if (MovDelay[ax][ay])
9204 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9207 int x = ax + xy[start][0];
9208 int y = ay + xy[start][1];
9210 if (!IN_LEV_FIELD(x, y))
9213 if (IS_FREE(x, y) ||
9214 CAN_GROW_INTO(Tile[x][y]) ||
9215 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9216 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9222 if (newax == ax && neway == ay)
9225 else // normal or "filled" (BD style) amoeba
9228 boolean waiting_for_player = FALSE;
9230 for (i = 0; i < NUM_DIRECTIONS; i++)
9232 int j = (start + i) % 4;
9233 int x = ax + xy[j][0];
9234 int y = ay + xy[j][1];
9236 if (!IN_LEV_FIELD(x, y))
9239 if (IS_FREE(x, y) ||
9240 CAN_GROW_INTO(Tile[x][y]) ||
9241 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9242 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9248 else if (IS_PLAYER(x, y))
9249 waiting_for_player = TRUE;
9252 if (newax == ax && neway == ay) // amoeba cannot grow
9254 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9256 Tile[ax][ay] = EL_AMOEBA_DEAD;
9257 TEST_DrawLevelField(ax, ay);
9258 AmoebaCnt[AmoebaNr[ax][ay]]--;
9260 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9262 if (element == EL_AMOEBA_FULL)
9263 AmoebaToDiamond(ax, ay);
9264 else if (element == EL_BD_AMOEBA)
9265 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9270 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9272 // amoeba gets larger by growing in some direction
9274 int new_group_nr = AmoebaNr[ax][ay];
9277 if (new_group_nr == 0)
9279 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9281 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9287 AmoebaNr[newax][neway] = new_group_nr;
9288 AmoebaCnt[new_group_nr]++;
9289 AmoebaCnt2[new_group_nr]++;
9291 // if amoeba touches other amoeba(s) after growing, unify them
9292 AmoebaMerge(newax, neway);
9294 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9296 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9302 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9303 (neway == lev_fieldy - 1 && newax != ax))
9305 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9306 Store[newax][neway] = element;
9308 else if (neway == ay || element == EL_EMC_DRIPPER)
9310 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9312 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9316 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9317 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9318 Store[ax][ay] = EL_AMOEBA_DROP;
9319 ContinueMoving(ax, ay);
9323 TEST_DrawLevelField(newax, neway);
9326 static void Life(int ax, int ay)
9330 int element = Tile[ax][ay];
9331 int graphic = el2img(element);
9332 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9334 boolean changed = FALSE;
9336 if (IS_ANIMATED(graphic))
9337 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9342 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9343 MovDelay[ax][ay] = life_time;
9345 if (MovDelay[ax][ay]) // wait some time before next cycle
9348 if (MovDelay[ax][ay])
9352 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9354 int xx = ax+x1, yy = ay+y1;
9355 int old_element = Tile[xx][yy];
9356 int num_neighbours = 0;
9358 if (!IN_LEV_FIELD(xx, yy))
9361 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9363 int x = xx+x2, y = yy+y2;
9365 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9368 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9369 boolean is_neighbour = FALSE;
9371 if (level.use_life_bugs)
9373 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9374 (IS_FREE(x, y) && Stop[x][y]));
9377 (Last[x][y] == element || is_player_cell);
9383 boolean is_free = FALSE;
9385 if (level.use_life_bugs)
9386 is_free = (IS_FREE(xx, yy));
9388 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9390 if (xx == ax && yy == ay) // field in the middle
9392 if (num_neighbours < life_parameter[0] ||
9393 num_neighbours > life_parameter[1])
9395 Tile[xx][yy] = EL_EMPTY;
9396 if (Tile[xx][yy] != old_element)
9397 TEST_DrawLevelField(xx, yy);
9398 Stop[xx][yy] = TRUE;
9402 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9403 { // free border field
9404 if (num_neighbours >= life_parameter[2] &&
9405 num_neighbours <= life_parameter[3])
9407 Tile[xx][yy] = element;
9408 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9409 if (Tile[xx][yy] != old_element)
9410 TEST_DrawLevelField(xx, yy);
9411 Stop[xx][yy] = TRUE;
9418 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9419 SND_GAME_OF_LIFE_GROWING);
9422 static void InitRobotWheel(int x, int y)
9424 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9427 static void RunRobotWheel(int x, int y)
9429 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9432 static void StopRobotWheel(int x, int y)
9434 if (game.robot_wheel_x == x &&
9435 game.robot_wheel_y == y)
9437 game.robot_wheel_x = -1;
9438 game.robot_wheel_y = -1;
9439 game.robot_wheel_active = FALSE;
9443 static void InitTimegateWheel(int x, int y)
9445 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9448 static void RunTimegateWheel(int x, int y)
9450 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9453 static void InitMagicBallDelay(int x, int y)
9455 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9458 static void ActivateMagicBall(int bx, int by)
9462 if (level.ball_random)
9464 int pos_border = RND(8); // select one of the eight border elements
9465 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9466 int xx = pos_content % 3;
9467 int yy = pos_content / 3;
9472 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9473 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9477 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9479 int xx = x - bx + 1;
9480 int yy = y - by + 1;
9482 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9483 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9487 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9490 static void CheckExit(int x, int y)
9492 if (game.gems_still_needed > 0 ||
9493 game.sokoban_fields_still_needed > 0 ||
9494 game.sokoban_objects_still_needed > 0 ||
9495 game.lights_still_needed > 0)
9497 int element = Tile[x][y];
9498 int graphic = el2img(element);
9500 if (IS_ANIMATED(graphic))
9501 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9506 // do not re-open exit door closed after last player
9507 if (game.all_players_gone)
9510 Tile[x][y] = EL_EXIT_OPENING;
9512 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9515 static void CheckExitEM(int x, int y)
9517 if (game.gems_still_needed > 0 ||
9518 game.sokoban_fields_still_needed > 0 ||
9519 game.sokoban_objects_still_needed > 0 ||
9520 game.lights_still_needed > 0)
9522 int element = Tile[x][y];
9523 int graphic = el2img(element);
9525 if (IS_ANIMATED(graphic))
9526 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9531 // do not re-open exit door closed after last player
9532 if (game.all_players_gone)
9535 Tile[x][y] = EL_EM_EXIT_OPENING;
9537 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9540 static void CheckExitSteel(int x, int y)
9542 if (game.gems_still_needed > 0 ||
9543 game.sokoban_fields_still_needed > 0 ||
9544 game.sokoban_objects_still_needed > 0 ||
9545 game.lights_still_needed > 0)
9547 int element = Tile[x][y];
9548 int graphic = el2img(element);
9550 if (IS_ANIMATED(graphic))
9551 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9556 // do not re-open exit door closed after last player
9557 if (game.all_players_gone)
9560 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9562 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9565 static void CheckExitSteelEM(int x, int y)
9567 if (game.gems_still_needed > 0 ||
9568 game.sokoban_fields_still_needed > 0 ||
9569 game.sokoban_objects_still_needed > 0 ||
9570 game.lights_still_needed > 0)
9572 int element = Tile[x][y];
9573 int graphic = el2img(element);
9575 if (IS_ANIMATED(graphic))
9576 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9581 // do not re-open exit door closed after last player
9582 if (game.all_players_gone)
9585 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9587 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9590 static void CheckExitSP(int x, int y)
9592 if (game.gems_still_needed > 0)
9594 int element = Tile[x][y];
9595 int graphic = el2img(element);
9597 if (IS_ANIMATED(graphic))
9598 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9603 // do not re-open exit door closed after last player
9604 if (game.all_players_gone)
9607 Tile[x][y] = EL_SP_EXIT_OPENING;
9609 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9612 static void CloseAllOpenTimegates(void)
9616 SCAN_PLAYFIELD(x, y)
9618 int element = Tile[x][y];
9620 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9622 Tile[x][y] = EL_TIMEGATE_CLOSING;
9624 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9629 static void DrawTwinkleOnField(int x, int y)
9631 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9634 if (Tile[x][y] == EL_BD_DIAMOND)
9637 if (MovDelay[x][y] == 0) // next animation frame
9638 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9640 if (MovDelay[x][y] != 0) // wait some time before next frame
9644 DrawLevelElementAnimation(x, y, Tile[x][y]);
9646 if (MovDelay[x][y] != 0)
9648 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9649 10 - MovDelay[x][y]);
9651 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9656 static void MauerWaechst(int x, int y)
9660 if (!MovDelay[x][y]) // next animation frame
9661 MovDelay[x][y] = 3 * delay;
9663 if (MovDelay[x][y]) // wait some time before next frame
9667 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9669 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9670 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9672 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9675 if (!MovDelay[x][y])
9677 if (MovDir[x][y] == MV_LEFT)
9679 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9680 TEST_DrawLevelField(x - 1, y);
9682 else if (MovDir[x][y] == MV_RIGHT)
9684 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9685 TEST_DrawLevelField(x + 1, y);
9687 else if (MovDir[x][y] == MV_UP)
9689 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9690 TEST_DrawLevelField(x, y - 1);
9694 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9695 TEST_DrawLevelField(x, y + 1);
9698 Tile[x][y] = Store[x][y];
9700 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9701 TEST_DrawLevelField(x, y);
9706 static void MauerAbleger(int ax, int ay)
9708 int element = Tile[ax][ay];
9709 int graphic = el2img(element);
9710 boolean oben_frei = FALSE, unten_frei = FALSE;
9711 boolean links_frei = FALSE, rechts_frei = FALSE;
9712 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9713 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9714 boolean new_wall = FALSE;
9716 if (IS_ANIMATED(graphic))
9717 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9719 if (!MovDelay[ax][ay]) // start building new wall
9720 MovDelay[ax][ay] = 6;
9722 if (MovDelay[ax][ay]) // wait some time before building new wall
9725 if (MovDelay[ax][ay])
9729 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9731 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9733 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9735 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9738 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9739 element == EL_EXPANDABLE_WALL_ANY)
9743 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9744 Store[ax][ay-1] = element;
9745 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9746 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9747 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9748 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9753 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9754 Store[ax][ay+1] = element;
9755 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9756 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9757 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9758 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9763 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9764 element == EL_EXPANDABLE_WALL_ANY ||
9765 element == EL_EXPANDABLE_WALL ||
9766 element == EL_BD_EXPANDABLE_WALL)
9770 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9771 Store[ax-1][ay] = element;
9772 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9773 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9774 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9775 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9781 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9782 Store[ax+1][ay] = element;
9783 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9784 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9785 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9786 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9791 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9792 TEST_DrawLevelField(ax, ay);
9794 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9796 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9797 unten_massiv = TRUE;
9798 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9799 links_massiv = TRUE;
9800 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9801 rechts_massiv = TRUE;
9803 if (((oben_massiv && unten_massiv) ||
9804 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9805 element == EL_EXPANDABLE_WALL) &&
9806 ((links_massiv && rechts_massiv) ||
9807 element == EL_EXPANDABLE_WALL_VERTICAL))
9808 Tile[ax][ay] = EL_WALL;
9811 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9814 static void MauerAblegerStahl(int ax, int ay)
9816 int element = Tile[ax][ay];
9817 int graphic = el2img(element);
9818 boolean oben_frei = FALSE, unten_frei = FALSE;
9819 boolean links_frei = FALSE, rechts_frei = FALSE;
9820 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9821 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9822 boolean new_wall = FALSE;
9824 if (IS_ANIMATED(graphic))
9825 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9827 if (!MovDelay[ax][ay]) // start building new wall
9828 MovDelay[ax][ay] = 6;
9830 if (MovDelay[ax][ay]) // wait some time before building new wall
9833 if (MovDelay[ax][ay])
9837 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9839 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9841 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9843 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9846 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9847 element == EL_EXPANDABLE_STEELWALL_ANY)
9851 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9852 Store[ax][ay-1] = element;
9853 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9854 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9855 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9856 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9861 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9862 Store[ax][ay+1] = element;
9863 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9864 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9865 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9866 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9871 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9872 element == EL_EXPANDABLE_STEELWALL_ANY)
9876 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9877 Store[ax-1][ay] = element;
9878 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9879 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9880 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9881 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9887 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9888 Store[ax+1][ay] = element;
9889 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9890 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9891 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9892 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9897 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9899 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9900 unten_massiv = TRUE;
9901 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9902 links_massiv = TRUE;
9903 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9904 rechts_massiv = TRUE;
9906 if (((oben_massiv && unten_massiv) ||
9907 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9908 ((links_massiv && rechts_massiv) ||
9909 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9910 Tile[ax][ay] = EL_STEELWALL;
9913 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9916 static void CheckForDragon(int x, int y)
9919 boolean dragon_found = FALSE;
9920 static int xy[4][2] =
9928 for (i = 0; i < NUM_DIRECTIONS; i++)
9930 for (j = 0; j < 4; j++)
9932 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9934 if (IN_LEV_FIELD(xx, yy) &&
9935 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9937 if (Tile[xx][yy] == EL_DRAGON)
9938 dragon_found = TRUE;
9947 for (i = 0; i < NUM_DIRECTIONS; i++)
9949 for (j = 0; j < 3; j++)
9951 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9953 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9955 Tile[xx][yy] = EL_EMPTY;
9956 TEST_DrawLevelField(xx, yy);
9965 static void InitBuggyBase(int x, int y)
9967 int element = Tile[x][y];
9968 int activating_delay = FRAMES_PER_SECOND / 4;
9971 (element == EL_SP_BUGGY_BASE ?
9972 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9973 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9975 element == EL_SP_BUGGY_BASE_ACTIVE ?
9976 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9979 static void WarnBuggyBase(int x, int y)
9982 static int xy[4][2] =
9990 for (i = 0; i < NUM_DIRECTIONS; i++)
9992 int xx = x + xy[i][0];
9993 int yy = y + xy[i][1];
9995 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9997 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10004 static void InitTrap(int x, int y)
10006 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10009 static void ActivateTrap(int x, int y)
10011 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10014 static void ChangeActiveTrap(int x, int y)
10016 int graphic = IMG_TRAP_ACTIVE;
10018 // if new animation frame was drawn, correct crumbled sand border
10019 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10020 TEST_DrawLevelFieldCrumbled(x, y);
10023 static int getSpecialActionElement(int element, int number, int base_element)
10025 return (element != EL_EMPTY ? element :
10026 number != -1 ? base_element + number - 1 :
10030 static int getModifiedActionNumber(int value_old, int operator, int operand,
10031 int value_min, int value_max)
10033 int value_new = (operator == CA_MODE_SET ? operand :
10034 operator == CA_MODE_ADD ? value_old + operand :
10035 operator == CA_MODE_SUBTRACT ? value_old - operand :
10036 operator == CA_MODE_MULTIPLY ? value_old * operand :
10037 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10038 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10041 return (value_new < value_min ? value_min :
10042 value_new > value_max ? value_max :
10046 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10048 struct ElementInfo *ei = &element_info[element];
10049 struct ElementChangeInfo *change = &ei->change_page[page];
10050 int target_element = change->target_element;
10051 int action_type = change->action_type;
10052 int action_mode = change->action_mode;
10053 int action_arg = change->action_arg;
10054 int action_element = change->action_element;
10057 if (!change->has_action)
10060 // ---------- determine action paramater values -----------------------------
10062 int level_time_value =
10063 (level.time > 0 ? TimeLeft :
10066 int action_arg_element_raw =
10067 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10068 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10069 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10070 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10071 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10072 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10073 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10075 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10077 int action_arg_direction =
10078 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10079 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10080 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10081 change->actual_trigger_side :
10082 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10083 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10086 int action_arg_number_min =
10087 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10090 int action_arg_number_max =
10091 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10092 action_type == CA_SET_LEVEL_GEMS ? 999 :
10093 action_type == CA_SET_LEVEL_TIME ? 9999 :
10094 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10095 action_type == CA_SET_CE_VALUE ? 9999 :
10096 action_type == CA_SET_CE_SCORE ? 9999 :
10099 int action_arg_number_reset =
10100 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10101 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10102 action_type == CA_SET_LEVEL_TIME ? level.time :
10103 action_type == CA_SET_LEVEL_SCORE ? 0 :
10104 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10105 action_type == CA_SET_CE_SCORE ? 0 :
10108 int action_arg_number =
10109 (action_arg <= CA_ARG_MAX ? action_arg :
10110 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10111 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10112 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10113 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10114 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10115 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10116 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10117 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10118 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10119 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10120 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10121 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10122 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10123 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10124 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10125 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10126 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10127 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10128 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10129 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10130 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10133 int action_arg_number_old =
10134 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10135 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10136 action_type == CA_SET_LEVEL_SCORE ? game.score :
10137 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10138 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10141 int action_arg_number_new =
10142 getModifiedActionNumber(action_arg_number_old,
10143 action_mode, action_arg_number,
10144 action_arg_number_min, action_arg_number_max);
10146 int trigger_player_bits =
10147 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10148 change->actual_trigger_player_bits : change->trigger_player);
10150 int action_arg_player_bits =
10151 (action_arg >= CA_ARG_PLAYER_1 &&
10152 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10153 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10154 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10157 // ---------- execute action -----------------------------------------------
10159 switch (action_type)
10166 // ---------- level actions ----------------------------------------------
10168 case CA_RESTART_LEVEL:
10170 game.restart_level = TRUE;
10175 case CA_SHOW_ENVELOPE:
10177 int element = getSpecialActionElement(action_arg_element,
10178 action_arg_number, EL_ENVELOPE_1);
10180 if (IS_ENVELOPE(element))
10181 local_player->show_envelope = element;
10186 case CA_SET_LEVEL_TIME:
10188 if (level.time > 0) // only modify limited time value
10190 TimeLeft = action_arg_number_new;
10192 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10194 DisplayGameControlValues();
10196 if (!TimeLeft && setup.time_limit)
10197 for (i = 0; i < MAX_PLAYERS; i++)
10198 KillPlayer(&stored_player[i]);
10204 case CA_SET_LEVEL_SCORE:
10206 game.score = action_arg_number_new;
10208 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10210 DisplayGameControlValues();
10215 case CA_SET_LEVEL_GEMS:
10217 game.gems_still_needed = action_arg_number_new;
10219 game.snapshot.collected_item = TRUE;
10221 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10223 DisplayGameControlValues();
10228 case CA_SET_LEVEL_WIND:
10230 game.wind_direction = action_arg_direction;
10235 case CA_SET_LEVEL_RANDOM_SEED:
10237 // ensure that setting a new random seed while playing is predictable
10238 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10243 // ---------- player actions ---------------------------------------------
10245 case CA_MOVE_PLAYER:
10246 case CA_MOVE_PLAYER_NEW:
10248 // automatically move to the next field in specified direction
10249 for (i = 0; i < MAX_PLAYERS; i++)
10250 if (trigger_player_bits & (1 << i))
10251 if (action_type == CA_MOVE_PLAYER ||
10252 stored_player[i].MovPos == 0)
10253 stored_player[i].programmed_action = action_arg_direction;
10258 case CA_EXIT_PLAYER:
10260 for (i = 0; i < MAX_PLAYERS; i++)
10261 if (action_arg_player_bits & (1 << i))
10262 ExitPlayer(&stored_player[i]);
10264 if (game.players_still_needed == 0)
10270 case CA_KILL_PLAYER:
10272 for (i = 0; i < MAX_PLAYERS; i++)
10273 if (action_arg_player_bits & (1 << i))
10274 KillPlayer(&stored_player[i]);
10279 case CA_SET_PLAYER_KEYS:
10281 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10282 int element = getSpecialActionElement(action_arg_element,
10283 action_arg_number, EL_KEY_1);
10285 if (IS_KEY(element))
10287 for (i = 0; i < MAX_PLAYERS; i++)
10289 if (trigger_player_bits & (1 << i))
10291 stored_player[i].key[KEY_NR(element)] = key_state;
10293 DrawGameDoorValues();
10301 case CA_SET_PLAYER_SPEED:
10303 for (i = 0; i < MAX_PLAYERS; i++)
10305 if (trigger_player_bits & (1 << i))
10307 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10309 if (action_arg == CA_ARG_SPEED_FASTER &&
10310 stored_player[i].cannot_move)
10312 action_arg_number = STEPSIZE_VERY_SLOW;
10314 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10315 action_arg == CA_ARG_SPEED_FASTER)
10317 action_arg_number = 2;
10318 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10321 else if (action_arg == CA_ARG_NUMBER_RESET)
10323 action_arg_number = level.initial_player_stepsize[i];
10327 getModifiedActionNumber(move_stepsize,
10330 action_arg_number_min,
10331 action_arg_number_max);
10333 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10340 case CA_SET_PLAYER_SHIELD:
10342 for (i = 0; i < MAX_PLAYERS; i++)
10344 if (trigger_player_bits & (1 << i))
10346 if (action_arg == CA_ARG_SHIELD_OFF)
10348 stored_player[i].shield_normal_time_left = 0;
10349 stored_player[i].shield_deadly_time_left = 0;
10351 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10353 stored_player[i].shield_normal_time_left = 999999;
10355 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10357 stored_player[i].shield_normal_time_left = 999999;
10358 stored_player[i].shield_deadly_time_left = 999999;
10366 case CA_SET_PLAYER_GRAVITY:
10368 for (i = 0; i < MAX_PLAYERS; i++)
10370 if (trigger_player_bits & (1 << i))
10372 stored_player[i].gravity =
10373 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10374 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10375 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10376 stored_player[i].gravity);
10383 case CA_SET_PLAYER_ARTWORK:
10385 for (i = 0; i < MAX_PLAYERS; i++)
10387 if (trigger_player_bits & (1 << i))
10389 int artwork_element = action_arg_element;
10391 if (action_arg == CA_ARG_ELEMENT_RESET)
10393 (level.use_artwork_element[i] ? level.artwork_element[i] :
10394 stored_player[i].element_nr);
10396 if (stored_player[i].artwork_element != artwork_element)
10397 stored_player[i].Frame = 0;
10399 stored_player[i].artwork_element = artwork_element;
10401 SetPlayerWaiting(&stored_player[i], FALSE);
10403 // set number of special actions for bored and sleeping animation
10404 stored_player[i].num_special_action_bored =
10405 get_num_special_action(artwork_element,
10406 ACTION_BORING_1, ACTION_BORING_LAST);
10407 stored_player[i].num_special_action_sleeping =
10408 get_num_special_action(artwork_element,
10409 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10416 case CA_SET_PLAYER_INVENTORY:
10418 for (i = 0; i < MAX_PLAYERS; i++)
10420 struct PlayerInfo *player = &stored_player[i];
10423 if (trigger_player_bits & (1 << i))
10425 int inventory_element = action_arg_element;
10427 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10428 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10429 action_arg == CA_ARG_ELEMENT_ACTION)
10431 int element = inventory_element;
10432 int collect_count = element_info[element].collect_count_initial;
10434 if (!IS_CUSTOM_ELEMENT(element))
10437 if (collect_count == 0)
10438 player->inventory_infinite_element = element;
10440 for (k = 0; k < collect_count; k++)
10441 if (player->inventory_size < MAX_INVENTORY_SIZE)
10442 player->inventory_element[player->inventory_size++] =
10445 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10446 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10447 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10449 if (player->inventory_infinite_element != EL_UNDEFINED &&
10450 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10451 action_arg_element_raw))
10452 player->inventory_infinite_element = EL_UNDEFINED;
10454 for (k = 0, j = 0; j < player->inventory_size; j++)
10456 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10457 action_arg_element_raw))
10458 player->inventory_element[k++] = player->inventory_element[j];
10461 player->inventory_size = k;
10463 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10465 if (player->inventory_size > 0)
10467 for (j = 0; j < player->inventory_size - 1; j++)
10468 player->inventory_element[j] = player->inventory_element[j + 1];
10470 player->inventory_size--;
10473 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10475 if (player->inventory_size > 0)
10476 player->inventory_size--;
10478 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10480 player->inventory_infinite_element = EL_UNDEFINED;
10481 player->inventory_size = 0;
10483 else if (action_arg == CA_ARG_INVENTORY_RESET)
10485 player->inventory_infinite_element = EL_UNDEFINED;
10486 player->inventory_size = 0;
10488 if (level.use_initial_inventory[i])
10490 for (j = 0; j < level.initial_inventory_size[i]; j++)
10492 int element = level.initial_inventory_content[i][j];
10493 int collect_count = element_info[element].collect_count_initial;
10495 if (!IS_CUSTOM_ELEMENT(element))
10498 if (collect_count == 0)
10499 player->inventory_infinite_element = element;
10501 for (k = 0; k < collect_count; k++)
10502 if (player->inventory_size < MAX_INVENTORY_SIZE)
10503 player->inventory_element[player->inventory_size++] =
10514 // ---------- CE actions -------------------------------------------------
10516 case CA_SET_CE_VALUE:
10518 int last_ce_value = CustomValue[x][y];
10520 CustomValue[x][y] = action_arg_number_new;
10522 if (CustomValue[x][y] != last_ce_value)
10524 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10525 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10527 if (CustomValue[x][y] == 0)
10529 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10530 ChangeCount[x][y] = 0; // allow at least one more change
10532 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10533 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10540 case CA_SET_CE_SCORE:
10542 int last_ce_score = ei->collect_score;
10544 ei->collect_score = action_arg_number_new;
10546 if (ei->collect_score != last_ce_score)
10548 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10549 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10551 if (ei->collect_score == 0)
10555 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10556 ChangeCount[x][y] = 0; // allow at least one more change
10558 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10559 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10562 This is a very special case that seems to be a mixture between
10563 CheckElementChange() and CheckTriggeredElementChange(): while
10564 the first one only affects single elements that are triggered
10565 directly, the second one affects multiple elements in the playfield
10566 that are triggered indirectly by another element. This is a third
10567 case: Changing the CE score always affects multiple identical CEs,
10568 so every affected CE must be checked, not only the single CE for
10569 which the CE score was changed in the first place (as every instance
10570 of that CE shares the same CE score, and therefore also can change)!
10572 SCAN_PLAYFIELD(xx, yy)
10574 if (Tile[xx][yy] == element)
10575 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10576 CE_SCORE_GETS_ZERO);
10584 case CA_SET_CE_ARTWORK:
10586 int artwork_element = action_arg_element;
10587 boolean reset_frame = FALSE;
10590 if (action_arg == CA_ARG_ELEMENT_RESET)
10591 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10594 if (ei->gfx_element != artwork_element)
10595 reset_frame = TRUE;
10597 ei->gfx_element = artwork_element;
10599 SCAN_PLAYFIELD(xx, yy)
10601 if (Tile[xx][yy] == element)
10605 ResetGfxAnimation(xx, yy);
10606 ResetRandomAnimationValue(xx, yy);
10609 TEST_DrawLevelField(xx, yy);
10616 // ---------- engine actions ---------------------------------------------
10618 case CA_SET_ENGINE_SCAN_MODE:
10620 InitPlayfieldScanMode(action_arg);
10630 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10632 int old_element = Tile[x][y];
10633 int new_element = GetElementFromGroupElement(element);
10634 int previous_move_direction = MovDir[x][y];
10635 int last_ce_value = CustomValue[x][y];
10636 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10637 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10638 boolean add_player_onto_element = (new_element_is_player &&
10639 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10640 IS_WALKABLE(old_element));
10642 if (!add_player_onto_element)
10644 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10645 RemoveMovingField(x, y);
10649 Tile[x][y] = new_element;
10651 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10652 MovDir[x][y] = previous_move_direction;
10654 if (element_info[new_element].use_last_ce_value)
10655 CustomValue[x][y] = last_ce_value;
10657 InitField_WithBug1(x, y, FALSE);
10659 new_element = Tile[x][y]; // element may have changed
10661 ResetGfxAnimation(x, y);
10662 ResetRandomAnimationValue(x, y);
10664 TEST_DrawLevelField(x, y);
10666 if (GFX_CRUMBLED(new_element))
10667 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10670 // check if element under the player changes from accessible to unaccessible
10671 // (needed for special case of dropping element which then changes)
10672 // (must be checked after creating new element for walkable group elements)
10673 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10674 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10681 // "ChangeCount" not set yet to allow "entered by player" change one time
10682 if (new_element_is_player)
10683 RelocatePlayer(x, y, new_element);
10686 ChangeCount[x][y]++; // count number of changes in the same frame
10688 TestIfBadThingTouchesPlayer(x, y);
10689 TestIfPlayerTouchesCustomElement(x, y);
10690 TestIfElementTouchesCustomElement(x, y);
10693 static void CreateField(int x, int y, int element)
10695 CreateFieldExt(x, y, element, FALSE);
10698 static void CreateElementFromChange(int x, int y, int element)
10700 element = GET_VALID_RUNTIME_ELEMENT(element);
10702 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10704 int old_element = Tile[x][y];
10706 // prevent changed element from moving in same engine frame
10707 // unless both old and new element can either fall or move
10708 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10709 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10713 CreateFieldExt(x, y, element, TRUE);
10716 static boolean ChangeElement(int x, int y, int element, int page)
10718 struct ElementInfo *ei = &element_info[element];
10719 struct ElementChangeInfo *change = &ei->change_page[page];
10720 int ce_value = CustomValue[x][y];
10721 int ce_score = ei->collect_score;
10722 int target_element;
10723 int old_element = Tile[x][y];
10725 // always use default change event to prevent running into a loop
10726 if (ChangeEvent[x][y] == -1)
10727 ChangeEvent[x][y] = CE_DELAY;
10729 if (ChangeEvent[x][y] == CE_DELAY)
10731 // reset actual trigger element, trigger player and action element
10732 change->actual_trigger_element = EL_EMPTY;
10733 change->actual_trigger_player = EL_EMPTY;
10734 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10735 change->actual_trigger_side = CH_SIDE_NONE;
10736 change->actual_trigger_ce_value = 0;
10737 change->actual_trigger_ce_score = 0;
10740 // do not change elements more than a specified maximum number of changes
10741 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10744 ChangeCount[x][y]++; // count number of changes in the same frame
10746 if (change->explode)
10753 if (change->use_target_content)
10755 boolean complete_replace = TRUE;
10756 boolean can_replace[3][3];
10759 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10762 boolean is_walkable;
10763 boolean is_diggable;
10764 boolean is_collectible;
10765 boolean is_removable;
10766 boolean is_destructible;
10767 int ex = x + xx - 1;
10768 int ey = y + yy - 1;
10769 int content_element = change->target_content.e[xx][yy];
10772 can_replace[xx][yy] = TRUE;
10774 if (ex == x && ey == y) // do not check changing element itself
10777 if (content_element == EL_EMPTY_SPACE)
10779 can_replace[xx][yy] = FALSE; // do not replace border with space
10784 if (!IN_LEV_FIELD(ex, ey))
10786 can_replace[xx][yy] = FALSE;
10787 complete_replace = FALSE;
10794 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10795 e = MovingOrBlocked2Element(ex, ey);
10797 is_empty = (IS_FREE(ex, ey) ||
10798 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10800 is_walkable = (is_empty || IS_WALKABLE(e));
10801 is_diggable = (is_empty || IS_DIGGABLE(e));
10802 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10803 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10804 is_removable = (is_diggable || is_collectible);
10806 can_replace[xx][yy] =
10807 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10808 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10809 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10810 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10811 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10812 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10813 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10815 if (!can_replace[xx][yy])
10816 complete_replace = FALSE;
10819 if (!change->only_if_complete || complete_replace)
10821 boolean something_has_changed = FALSE;
10823 if (change->only_if_complete && change->use_random_replace &&
10824 RND(100) < change->random_percentage)
10827 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10829 int ex = x + xx - 1;
10830 int ey = y + yy - 1;
10831 int content_element;
10833 if (can_replace[xx][yy] && (!change->use_random_replace ||
10834 RND(100) < change->random_percentage))
10836 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10837 RemoveMovingField(ex, ey);
10839 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10841 content_element = change->target_content.e[xx][yy];
10842 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10843 ce_value, ce_score);
10845 CreateElementFromChange(ex, ey, target_element);
10847 something_has_changed = TRUE;
10849 // for symmetry reasons, freeze newly created border elements
10850 if (ex != x || ey != y)
10851 Stop[ex][ey] = TRUE; // no more moving in this frame
10855 if (something_has_changed)
10857 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10858 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10864 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10865 ce_value, ce_score);
10867 if (element == EL_DIAGONAL_GROWING ||
10868 element == EL_DIAGONAL_SHRINKING)
10870 target_element = Store[x][y];
10872 Store[x][y] = EL_EMPTY;
10875 // special case: element changes to player (and may be kept if walkable)
10876 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10877 CreateElementFromChange(x, y, EL_EMPTY);
10879 CreateElementFromChange(x, y, target_element);
10881 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10882 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10885 // this uses direct change before indirect change
10886 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10891 static void HandleElementChange(int x, int y, int page)
10893 int element = MovingOrBlocked2Element(x, y);
10894 struct ElementInfo *ei = &element_info[element];
10895 struct ElementChangeInfo *change = &ei->change_page[page];
10896 boolean handle_action_before_change = FALSE;
10899 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10900 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10902 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10903 x, y, element, element_info[element].token_name);
10904 Debug("game:playing:HandleElementChange", "This should never happen!");
10908 // this can happen with classic bombs on walkable, changing elements
10909 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10914 if (ChangeDelay[x][y] == 0) // initialize element change
10916 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10918 if (change->can_change)
10920 // !!! not clear why graphic animation should be reset at all here !!!
10921 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10922 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10925 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10927 When using an animation frame delay of 1 (this only happens with
10928 "sp_zonk.moving.left/right" in the classic graphics), the default
10929 (non-moving) animation shows wrong animation frames (while the
10930 moving animation, like "sp_zonk.moving.left/right", is correct,
10931 so this graphical bug never shows up with the classic graphics).
10932 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10933 be drawn instead of the correct frames 0,1,2,3. This is caused by
10934 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10935 an element change: First when the change delay ("ChangeDelay[][]")
10936 counter has reached zero after decrementing, then a second time in
10937 the next frame (after "GfxFrame[][]" was already incremented) when
10938 "ChangeDelay[][]" is reset to the initial delay value again.
10940 This causes frame 0 to be drawn twice, while the last frame won't
10941 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10943 As some animations may already be cleverly designed around this bug
10944 (at least the "Snake Bite" snake tail animation does this), it cannot
10945 simply be fixed here without breaking such existing animations.
10946 Unfortunately, it cannot easily be detected if a graphics set was
10947 designed "before" or "after" the bug was fixed. As a workaround,
10948 a new graphics set option "game.graphics_engine_version" was added
10949 to be able to specify the game's major release version for which the
10950 graphics set was designed, which can then be used to decide if the
10951 bugfix should be used (version 4 and above) or not (version 3 or
10952 below, or if no version was specified at all, as with old sets).
10954 (The wrong/fixed animation frames can be tested with the test level set
10955 "test_gfxframe" and level "000", which contains a specially prepared
10956 custom element at level position (x/y) == (11/9) which uses the zonk
10957 animation mentioned above. Using "game.graphics_engine_version: 4"
10958 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10959 This can also be seen from the debug output for this test element.)
10962 // when a custom element is about to change (for example by change delay),
10963 // do not reset graphic animation when the custom element is moving
10964 if (game.graphics_engine_version < 4 &&
10967 ResetGfxAnimation(x, y);
10968 ResetRandomAnimationValue(x, y);
10971 if (change->pre_change_function)
10972 change->pre_change_function(x, y);
10976 ChangeDelay[x][y]--;
10978 if (ChangeDelay[x][y] != 0) // continue element change
10980 if (change->can_change)
10982 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10984 if (IS_ANIMATED(graphic))
10985 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10987 if (change->change_function)
10988 change->change_function(x, y);
10991 else // finish element change
10993 if (ChangePage[x][y] != -1) // remember page from delayed change
10995 page = ChangePage[x][y];
10996 ChangePage[x][y] = -1;
10998 change = &ei->change_page[page];
11001 if (IS_MOVING(x, y)) // never change a running system ;-)
11003 ChangeDelay[x][y] = 1; // try change after next move step
11004 ChangePage[x][y] = page; // remember page to use for change
11009 // special case: set new level random seed before changing element
11010 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11011 handle_action_before_change = TRUE;
11013 if (change->has_action && handle_action_before_change)
11014 ExecuteCustomElementAction(x, y, element, page);
11016 if (change->can_change)
11018 if (ChangeElement(x, y, element, page))
11020 if (change->post_change_function)
11021 change->post_change_function(x, y);
11025 if (change->has_action && !handle_action_before_change)
11026 ExecuteCustomElementAction(x, y, element, page);
11030 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11031 int trigger_element,
11033 int trigger_player,
11037 boolean change_done_any = FALSE;
11038 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11041 if (!(trigger_events[trigger_element][trigger_event]))
11044 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11046 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11048 int element = EL_CUSTOM_START + i;
11049 boolean change_done = FALSE;
11052 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11053 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11056 for (p = 0; p < element_info[element].num_change_pages; p++)
11058 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11060 if (change->can_change_or_has_action &&
11061 change->has_event[trigger_event] &&
11062 change->trigger_side & trigger_side &&
11063 change->trigger_player & trigger_player &&
11064 change->trigger_page & trigger_page_bits &&
11065 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11067 change->actual_trigger_element = trigger_element;
11068 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11069 change->actual_trigger_player_bits = trigger_player;
11070 change->actual_trigger_side = trigger_side;
11071 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11072 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11074 if ((change->can_change && !change_done) || change->has_action)
11078 SCAN_PLAYFIELD(x, y)
11080 if (Tile[x][y] == element)
11082 if (change->can_change && !change_done)
11084 // if element already changed in this frame, not only prevent
11085 // another element change (checked in ChangeElement()), but
11086 // also prevent additional element actions for this element
11088 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11089 !level.use_action_after_change_bug)
11092 ChangeDelay[x][y] = 1;
11093 ChangeEvent[x][y] = trigger_event;
11095 HandleElementChange(x, y, p);
11097 else if (change->has_action)
11099 // if element already changed in this frame, not only prevent
11100 // another element change (checked in ChangeElement()), but
11101 // also prevent additional element actions for this element
11103 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11104 !level.use_action_after_change_bug)
11107 ExecuteCustomElementAction(x, y, element, p);
11108 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11113 if (change->can_change)
11115 change_done = TRUE;
11116 change_done_any = TRUE;
11123 RECURSION_LOOP_DETECTION_END();
11125 return change_done_any;
11128 static boolean CheckElementChangeExt(int x, int y,
11130 int trigger_element,
11132 int trigger_player,
11135 boolean change_done = FALSE;
11138 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11139 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11142 if (Tile[x][y] == EL_BLOCKED)
11144 Blocked2Moving(x, y, &x, &y);
11145 element = Tile[x][y];
11148 // check if element has already changed or is about to change after moving
11149 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11150 Tile[x][y] != element) ||
11152 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11153 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11154 ChangePage[x][y] != -1)))
11157 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11159 for (p = 0; p < element_info[element].num_change_pages; p++)
11161 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11163 /* check trigger element for all events where the element that is checked
11164 for changing interacts with a directly adjacent element -- this is
11165 different to element changes that affect other elements to change on the
11166 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11167 boolean check_trigger_element =
11168 (trigger_event == CE_NEXT_TO_X ||
11169 trigger_event == CE_TOUCHING_X ||
11170 trigger_event == CE_HITTING_X ||
11171 trigger_event == CE_HIT_BY_X ||
11172 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11174 if (change->can_change_or_has_action &&
11175 change->has_event[trigger_event] &&
11176 change->trigger_side & trigger_side &&
11177 change->trigger_player & trigger_player &&
11178 (!check_trigger_element ||
11179 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11181 change->actual_trigger_element = trigger_element;
11182 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11183 change->actual_trigger_player_bits = trigger_player;
11184 change->actual_trigger_side = trigger_side;
11185 change->actual_trigger_ce_value = CustomValue[x][y];
11186 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11188 // special case: trigger element not at (x,y) position for some events
11189 if (check_trigger_element)
11201 { 0, 0 }, { 0, 0 }, { 0, 0 },
11205 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11206 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11208 change->actual_trigger_ce_value = CustomValue[xx][yy];
11209 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11212 if (change->can_change && !change_done)
11214 ChangeDelay[x][y] = 1;
11215 ChangeEvent[x][y] = trigger_event;
11217 HandleElementChange(x, y, p);
11219 change_done = TRUE;
11221 else if (change->has_action)
11223 ExecuteCustomElementAction(x, y, element, p);
11224 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11229 RECURSION_LOOP_DETECTION_END();
11231 return change_done;
11234 static void PlayPlayerSound(struct PlayerInfo *player)
11236 int jx = player->jx, jy = player->jy;
11237 int sound_element = player->artwork_element;
11238 int last_action = player->last_action_waiting;
11239 int action = player->action_waiting;
11241 if (player->is_waiting)
11243 if (action != last_action)
11244 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11246 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11250 if (action != last_action)
11251 StopSound(element_info[sound_element].sound[last_action]);
11253 if (last_action == ACTION_SLEEPING)
11254 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11258 static void PlayAllPlayersSound(void)
11262 for (i = 0; i < MAX_PLAYERS; i++)
11263 if (stored_player[i].active)
11264 PlayPlayerSound(&stored_player[i]);
11267 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11269 boolean last_waiting = player->is_waiting;
11270 int move_dir = player->MovDir;
11272 player->dir_waiting = move_dir;
11273 player->last_action_waiting = player->action_waiting;
11277 if (!last_waiting) // not waiting -> waiting
11279 player->is_waiting = TRUE;
11281 player->frame_counter_bored =
11283 game.player_boring_delay_fixed +
11284 GetSimpleRandom(game.player_boring_delay_random);
11285 player->frame_counter_sleeping =
11287 game.player_sleeping_delay_fixed +
11288 GetSimpleRandom(game.player_sleeping_delay_random);
11290 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11293 if (game.player_sleeping_delay_fixed +
11294 game.player_sleeping_delay_random > 0 &&
11295 player->anim_delay_counter == 0 &&
11296 player->post_delay_counter == 0 &&
11297 FrameCounter >= player->frame_counter_sleeping)
11298 player->is_sleeping = TRUE;
11299 else if (game.player_boring_delay_fixed +
11300 game.player_boring_delay_random > 0 &&
11301 FrameCounter >= player->frame_counter_bored)
11302 player->is_bored = TRUE;
11304 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11305 player->is_bored ? ACTION_BORING :
11308 if (player->is_sleeping && player->use_murphy)
11310 // special case for sleeping Murphy when leaning against non-free tile
11312 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11313 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11314 !IS_MOVING(player->jx - 1, player->jy)))
11315 move_dir = MV_LEFT;
11316 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11317 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11318 !IS_MOVING(player->jx + 1, player->jy)))
11319 move_dir = MV_RIGHT;
11321 player->is_sleeping = FALSE;
11323 player->dir_waiting = move_dir;
11326 if (player->is_sleeping)
11328 if (player->num_special_action_sleeping > 0)
11330 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11332 int last_special_action = player->special_action_sleeping;
11333 int num_special_action = player->num_special_action_sleeping;
11334 int special_action =
11335 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11336 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11337 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11338 last_special_action + 1 : ACTION_SLEEPING);
11339 int special_graphic =
11340 el_act_dir2img(player->artwork_element, special_action, move_dir);
11342 player->anim_delay_counter =
11343 graphic_info[special_graphic].anim_delay_fixed +
11344 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11345 player->post_delay_counter =
11346 graphic_info[special_graphic].post_delay_fixed +
11347 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11349 player->special_action_sleeping = special_action;
11352 if (player->anim_delay_counter > 0)
11354 player->action_waiting = player->special_action_sleeping;
11355 player->anim_delay_counter--;
11357 else if (player->post_delay_counter > 0)
11359 player->post_delay_counter--;
11363 else if (player->is_bored)
11365 if (player->num_special_action_bored > 0)
11367 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11369 int special_action =
11370 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11371 int special_graphic =
11372 el_act_dir2img(player->artwork_element, special_action, move_dir);
11374 player->anim_delay_counter =
11375 graphic_info[special_graphic].anim_delay_fixed +
11376 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11377 player->post_delay_counter =
11378 graphic_info[special_graphic].post_delay_fixed +
11379 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11381 player->special_action_bored = special_action;
11384 if (player->anim_delay_counter > 0)
11386 player->action_waiting = player->special_action_bored;
11387 player->anim_delay_counter--;
11389 else if (player->post_delay_counter > 0)
11391 player->post_delay_counter--;
11396 else if (last_waiting) // waiting -> not waiting
11398 player->is_waiting = FALSE;
11399 player->is_bored = FALSE;
11400 player->is_sleeping = FALSE;
11402 player->frame_counter_bored = -1;
11403 player->frame_counter_sleeping = -1;
11405 player->anim_delay_counter = 0;
11406 player->post_delay_counter = 0;
11408 player->dir_waiting = player->MovDir;
11409 player->action_waiting = ACTION_DEFAULT;
11411 player->special_action_bored = ACTION_DEFAULT;
11412 player->special_action_sleeping = ACTION_DEFAULT;
11416 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11418 if ((!player->is_moving && player->was_moving) ||
11419 (player->MovPos == 0 && player->was_moving) ||
11420 (player->is_snapping && !player->was_snapping) ||
11421 (player->is_dropping && !player->was_dropping))
11423 if (!CheckSaveEngineSnapshotToList())
11426 player->was_moving = FALSE;
11427 player->was_snapping = TRUE;
11428 player->was_dropping = TRUE;
11432 if (player->is_moving)
11433 player->was_moving = TRUE;
11435 if (!player->is_snapping)
11436 player->was_snapping = FALSE;
11438 if (!player->is_dropping)
11439 player->was_dropping = FALSE;
11442 static struct MouseActionInfo mouse_action_last = { 0 };
11443 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11444 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11447 CheckSaveEngineSnapshotToList();
11449 mouse_action_last = mouse_action;
11452 static void CheckSingleStepMode(struct PlayerInfo *player)
11454 if (tape.single_step && tape.recording && !tape.pausing)
11456 // as it is called "single step mode", just return to pause mode when the
11457 // player stopped moving after one tile (or never starts moving at all)
11458 // (reverse logic needed here in case single step mode used in team mode)
11459 if (player->is_moving ||
11460 player->is_pushing ||
11461 player->is_dropping_pressed ||
11462 player->effective_mouse_action.button)
11463 game.enter_single_step_mode = FALSE;
11466 CheckSaveEngineSnapshot(player);
11469 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11471 int left = player_action & JOY_LEFT;
11472 int right = player_action & JOY_RIGHT;
11473 int up = player_action & JOY_UP;
11474 int down = player_action & JOY_DOWN;
11475 int button1 = player_action & JOY_BUTTON_1;
11476 int button2 = player_action & JOY_BUTTON_2;
11477 int dx = (left ? -1 : right ? 1 : 0);
11478 int dy = (up ? -1 : down ? 1 : 0);
11480 if (!player->active || tape.pausing)
11486 SnapField(player, dx, dy);
11490 DropElement(player);
11492 MovePlayer(player, dx, dy);
11495 CheckSingleStepMode(player);
11497 SetPlayerWaiting(player, FALSE);
11499 return player_action;
11503 // no actions for this player (no input at player's configured device)
11505 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11506 SnapField(player, 0, 0);
11507 CheckGravityMovementWhenNotMoving(player);
11509 if (player->MovPos == 0)
11510 SetPlayerWaiting(player, TRUE);
11512 if (player->MovPos == 0) // needed for tape.playing
11513 player->is_moving = FALSE;
11515 player->is_dropping = FALSE;
11516 player->is_dropping_pressed = FALSE;
11517 player->drop_pressed_delay = 0;
11519 CheckSingleStepMode(player);
11525 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11528 if (!tape.use_mouse_actions)
11531 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11532 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11533 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11536 static void SetTapeActionFromMouseAction(byte *tape_action,
11537 struct MouseActionInfo *mouse_action)
11539 if (!tape.use_mouse_actions)
11542 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11543 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11544 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11547 static void CheckLevelSolved(void)
11549 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11551 if (game_em.level_solved &&
11552 !game_em.game_over) // game won
11556 game_em.game_over = TRUE;
11558 game.all_players_gone = TRUE;
11561 if (game_em.game_over) // game lost
11562 game.all_players_gone = TRUE;
11564 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11566 if (game_sp.level_solved &&
11567 !game_sp.game_over) // game won
11571 game_sp.game_over = TRUE;
11573 game.all_players_gone = TRUE;
11576 if (game_sp.game_over) // game lost
11577 game.all_players_gone = TRUE;
11579 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11581 if (game_mm.level_solved &&
11582 !game_mm.game_over) // game won
11586 game_mm.game_over = TRUE;
11588 game.all_players_gone = TRUE;
11591 if (game_mm.game_over) // game lost
11592 game.all_players_gone = TRUE;
11596 static void CheckLevelTime(void)
11600 if (TimeFrames >= FRAMES_PER_SECOND)
11605 for (i = 0; i < MAX_PLAYERS; i++)
11607 struct PlayerInfo *player = &stored_player[i];
11609 if (SHIELD_ON(player))
11611 player->shield_normal_time_left--;
11613 if (player->shield_deadly_time_left > 0)
11614 player->shield_deadly_time_left--;
11618 if (!game.LevelSolved && !level.use_step_counter)
11626 if (TimeLeft <= 10 && setup.time_limit)
11627 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11629 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11630 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11632 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11634 if (!TimeLeft && setup.time_limit)
11636 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11637 game_em.lev->killed_out_of_time = TRUE;
11639 for (i = 0; i < MAX_PLAYERS; i++)
11640 KillPlayer(&stored_player[i]);
11643 else if (game.no_time_limit && !game.all_players_gone)
11645 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11648 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11651 if (tape.recording || tape.playing)
11652 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11655 if (tape.recording || tape.playing)
11656 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11658 UpdateAndDisplayGameControlValues();
11661 void AdvanceFrameAndPlayerCounters(int player_nr)
11665 // advance frame counters (global frame counter and time frame counter)
11669 // advance player counters (counters for move delay, move animation etc.)
11670 for (i = 0; i < MAX_PLAYERS; i++)
11672 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11673 int move_delay_value = stored_player[i].move_delay_value;
11674 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11676 if (!advance_player_counters) // not all players may be affected
11679 if (move_frames == 0) // less than one move per game frame
11681 int stepsize = TILEX / move_delay_value;
11682 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11683 int count = (stored_player[i].is_moving ?
11684 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11686 if (count % delay == 0)
11690 stored_player[i].Frame += move_frames;
11692 if (stored_player[i].MovPos != 0)
11693 stored_player[i].StepFrame += move_frames;
11695 if (stored_player[i].move_delay > 0)
11696 stored_player[i].move_delay--;
11698 // due to bugs in previous versions, counter must count up, not down
11699 if (stored_player[i].push_delay != -1)
11700 stored_player[i].push_delay++;
11702 if (stored_player[i].drop_delay > 0)
11703 stored_player[i].drop_delay--;
11705 if (stored_player[i].is_dropping_pressed)
11706 stored_player[i].drop_pressed_delay++;
11710 void StartGameActions(boolean init_network_game, boolean record_tape,
11713 unsigned int new_random_seed = InitRND(random_seed);
11716 TapeStartRecording(new_random_seed);
11718 if (init_network_game)
11720 SendToServer_LevelFile();
11721 SendToServer_StartPlaying();
11729 static void GameActionsExt(void)
11732 static unsigned int game_frame_delay = 0;
11734 unsigned int game_frame_delay_value;
11735 byte *recorded_player_action;
11736 byte summarized_player_action = 0;
11737 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11740 // detect endless loops, caused by custom element programming
11741 if (recursion_loop_detected && recursion_loop_depth == 0)
11743 char *message = getStringCat3("Internal Error! Element ",
11744 EL_NAME(recursion_loop_element),
11745 " caused endless loop! Quit the game?");
11747 Warn("element '%s' caused endless loop in game engine",
11748 EL_NAME(recursion_loop_element));
11750 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11752 recursion_loop_detected = FALSE; // if game should be continued
11759 if (game.restart_level)
11760 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11762 CheckLevelSolved();
11764 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11767 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11770 if (game_status != GAME_MODE_PLAYING) // status might have changed
11773 game_frame_delay_value =
11774 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11776 if (tape.playing && tape.warp_forward && !tape.pausing)
11777 game_frame_delay_value = 0;
11779 SetVideoFrameDelay(game_frame_delay_value);
11781 // (de)activate virtual buttons depending on current game status
11782 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11784 if (game.all_players_gone) // if no players there to be controlled anymore
11785 SetOverlayActive(FALSE);
11786 else if (!tape.playing) // if game continues after tape stopped playing
11787 SetOverlayActive(TRUE);
11792 // ---------- main game synchronization point ----------
11794 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11796 Debug("game:playing:skip", "skip == %d", skip);
11799 // ---------- main game synchronization point ----------
11801 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11805 if (network_playing && !network_player_action_received)
11807 // try to get network player actions in time
11809 // last chance to get network player actions without main loop delay
11810 HandleNetworking();
11812 // game was quit by network peer
11813 if (game_status != GAME_MODE_PLAYING)
11816 // check if network player actions still missing and game still running
11817 if (!network_player_action_received && !checkGameEnded())
11818 return; // failed to get network player actions in time
11820 // do not yet reset "network_player_action_received" (for tape.pausing)
11826 // at this point we know that we really continue executing the game
11828 network_player_action_received = FALSE;
11830 // when playing tape, read previously recorded player input from tape data
11831 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11833 local_player->effective_mouse_action = local_player->mouse_action;
11835 if (recorded_player_action != NULL)
11836 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11837 recorded_player_action);
11839 // TapePlayAction() may return NULL when toggling to "pause before death"
11843 if (tape.set_centered_player)
11845 game.centered_player_nr_next = tape.centered_player_nr_next;
11846 game.set_centered_player = TRUE;
11849 for (i = 0; i < MAX_PLAYERS; i++)
11851 summarized_player_action |= stored_player[i].action;
11853 if (!network_playing && (game.team_mode || tape.playing))
11854 stored_player[i].effective_action = stored_player[i].action;
11857 if (network_playing && !checkGameEnded())
11858 SendToServer_MovePlayer(summarized_player_action);
11860 // summarize all actions at local players mapped input device position
11861 // (this allows using different input devices in single player mode)
11862 if (!network.enabled && !game.team_mode)
11863 stored_player[map_player_action[local_player->index_nr]].effective_action =
11864 summarized_player_action;
11866 // summarize all actions at centered player in local team mode
11867 if (tape.recording &&
11868 setup.team_mode && !network.enabled &&
11869 setup.input_on_focus &&
11870 game.centered_player_nr != -1)
11872 for (i = 0; i < MAX_PLAYERS; i++)
11873 stored_player[map_player_action[i]].effective_action =
11874 (i == game.centered_player_nr ? summarized_player_action : 0);
11877 if (recorded_player_action != NULL)
11878 for (i = 0; i < MAX_PLAYERS; i++)
11879 stored_player[i].effective_action = recorded_player_action[i];
11881 for (i = 0; i < MAX_PLAYERS; i++)
11883 tape_action[i] = stored_player[i].effective_action;
11885 /* (this may happen in the RND game engine if a player was not present on
11886 the playfield on level start, but appeared later from a custom element */
11887 if (setup.team_mode &&
11890 !tape.player_participates[i])
11891 tape.player_participates[i] = TRUE;
11894 SetTapeActionFromMouseAction(tape_action,
11895 &local_player->effective_mouse_action);
11897 // only record actions from input devices, but not programmed actions
11898 if (tape.recording)
11899 TapeRecordAction(tape_action);
11901 // remember if game was played (especially after tape stopped playing)
11902 if (!tape.playing && summarized_player_action)
11903 game.GamePlayed = TRUE;
11905 #if USE_NEW_PLAYER_ASSIGNMENTS
11906 // !!! also map player actions in single player mode !!!
11907 // if (game.team_mode)
11910 byte mapped_action[MAX_PLAYERS];
11912 #if DEBUG_PLAYER_ACTIONS
11913 for (i = 0; i < MAX_PLAYERS; i++)
11914 DebugContinued("", "%d, ", stored_player[i].effective_action);
11917 for (i = 0; i < MAX_PLAYERS; i++)
11918 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11920 for (i = 0; i < MAX_PLAYERS; i++)
11921 stored_player[i].effective_action = mapped_action[i];
11923 #if DEBUG_PLAYER_ACTIONS
11924 DebugContinued("", "=> ");
11925 for (i = 0; i < MAX_PLAYERS; i++)
11926 DebugContinued("", "%d, ", stored_player[i].effective_action);
11927 DebugContinued("game:playing:player", "\n");
11930 #if DEBUG_PLAYER_ACTIONS
11933 for (i = 0; i < MAX_PLAYERS; i++)
11934 DebugContinued("", "%d, ", stored_player[i].effective_action);
11935 DebugContinued("game:playing:player", "\n");
11940 for (i = 0; i < MAX_PLAYERS; i++)
11942 // allow engine snapshot in case of changed movement attempt
11943 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11944 (stored_player[i].effective_action & KEY_MOTION))
11945 game.snapshot.changed_action = TRUE;
11947 // allow engine snapshot in case of snapping/dropping attempt
11948 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11949 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11950 game.snapshot.changed_action = TRUE;
11952 game.snapshot.last_action[i] = stored_player[i].effective_action;
11955 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11957 GameActions_EM_Main();
11959 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11961 GameActions_SP_Main();
11963 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11965 GameActions_MM_Main();
11969 GameActions_RND_Main();
11972 BlitScreenToBitmap(backbuffer);
11974 CheckLevelSolved();
11977 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11979 if (global.show_frames_per_second)
11981 static unsigned int fps_counter = 0;
11982 static int fps_frames = 0;
11983 unsigned int fps_delay_ms = Counter() - fps_counter;
11987 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11989 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11992 fps_counter = Counter();
11994 // always draw FPS to screen after FPS value was updated
11995 redraw_mask |= REDRAW_FPS;
11998 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11999 if (GetDrawDeactivationMask() == REDRAW_NONE)
12000 redraw_mask |= REDRAW_FPS;
12004 static void GameActions_CheckSaveEngineSnapshot(void)
12006 if (!game.snapshot.save_snapshot)
12009 // clear flag for saving snapshot _before_ saving snapshot
12010 game.snapshot.save_snapshot = FALSE;
12012 SaveEngineSnapshotToList();
12015 void GameActions(void)
12019 GameActions_CheckSaveEngineSnapshot();
12022 void GameActions_EM_Main(void)
12024 byte effective_action[MAX_PLAYERS];
12025 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12028 for (i = 0; i < MAX_PLAYERS; i++)
12029 effective_action[i] = stored_player[i].effective_action;
12031 GameActions_EM(effective_action, warp_mode);
12034 void GameActions_SP_Main(void)
12036 byte effective_action[MAX_PLAYERS];
12037 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12040 for (i = 0; i < MAX_PLAYERS; i++)
12041 effective_action[i] = stored_player[i].effective_action;
12043 GameActions_SP(effective_action, warp_mode);
12045 for (i = 0; i < MAX_PLAYERS; i++)
12047 if (stored_player[i].force_dropping)
12048 stored_player[i].action |= KEY_BUTTON_DROP;
12050 stored_player[i].force_dropping = FALSE;
12054 void GameActions_MM_Main(void)
12056 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12058 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12061 void GameActions_RND_Main(void)
12066 void GameActions_RND(void)
12068 static struct MouseActionInfo mouse_action_last = { 0 };
12069 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12070 int magic_wall_x = 0, magic_wall_y = 0;
12071 int i, x, y, element, graphic, last_gfx_frame;
12073 InitPlayfieldScanModeVars();
12075 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12077 SCAN_PLAYFIELD(x, y)
12079 ChangeCount[x][y] = 0;
12080 ChangeEvent[x][y] = -1;
12084 if (game.set_centered_player)
12086 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12088 // switching to "all players" only possible if all players fit to screen
12089 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12091 game.centered_player_nr_next = game.centered_player_nr;
12092 game.set_centered_player = FALSE;
12095 // do not switch focus to non-existing (or non-active) player
12096 if (game.centered_player_nr_next >= 0 &&
12097 !stored_player[game.centered_player_nr_next].active)
12099 game.centered_player_nr_next = game.centered_player_nr;
12100 game.set_centered_player = FALSE;
12104 if (game.set_centered_player &&
12105 ScreenMovPos == 0) // screen currently aligned at tile position
12109 if (game.centered_player_nr_next == -1)
12111 setScreenCenteredToAllPlayers(&sx, &sy);
12115 sx = stored_player[game.centered_player_nr_next].jx;
12116 sy = stored_player[game.centered_player_nr_next].jy;
12119 game.centered_player_nr = game.centered_player_nr_next;
12120 game.set_centered_player = FALSE;
12122 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12123 DrawGameDoorValues();
12126 // check single step mode (set flag and clear again if any player is active)
12127 game.enter_single_step_mode =
12128 (tape.single_step && tape.recording && !tape.pausing);
12130 for (i = 0; i < MAX_PLAYERS; i++)
12132 int actual_player_action = stored_player[i].effective_action;
12135 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12136 - rnd_equinox_tetrachloride 048
12137 - rnd_equinox_tetrachloride_ii 096
12138 - rnd_emanuel_schmieg 002
12139 - doctor_sloan_ww 001, 020
12141 if (stored_player[i].MovPos == 0)
12142 CheckGravityMovement(&stored_player[i]);
12145 // overwrite programmed action with tape action
12146 if (stored_player[i].programmed_action)
12147 actual_player_action = stored_player[i].programmed_action;
12149 PlayerActions(&stored_player[i], actual_player_action);
12151 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12154 // single step pause mode may already have been toggled by "ScrollPlayer()"
12155 if (game.enter_single_step_mode && !tape.pausing)
12156 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12158 ScrollScreen(NULL, SCROLL_GO_ON);
12160 /* for backwards compatibility, the following code emulates a fixed bug that
12161 occured when pushing elements (causing elements that just made their last
12162 pushing step to already (if possible) make their first falling step in the
12163 same game frame, which is bad); this code is also needed to use the famous
12164 "spring push bug" which is used in older levels and might be wanted to be
12165 used also in newer levels, but in this case the buggy pushing code is only
12166 affecting the "spring" element and no other elements */
12168 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12170 for (i = 0; i < MAX_PLAYERS; i++)
12172 struct PlayerInfo *player = &stored_player[i];
12173 int x = player->jx;
12174 int y = player->jy;
12176 if (player->active && player->is_pushing && player->is_moving &&
12178 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12179 Tile[x][y] == EL_SPRING))
12181 ContinueMoving(x, y);
12183 // continue moving after pushing (this is actually a bug)
12184 if (!IS_MOVING(x, y))
12185 Stop[x][y] = FALSE;
12190 SCAN_PLAYFIELD(x, y)
12192 Last[x][y] = Tile[x][y];
12194 ChangeCount[x][y] = 0;
12195 ChangeEvent[x][y] = -1;
12197 // this must be handled before main playfield loop
12198 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12201 if (MovDelay[x][y] <= 0)
12205 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12208 if (MovDelay[x][y] <= 0)
12210 int element = Store[x][y];
12211 int move_direction = MovDir[x][y];
12212 int player_index_bit = Store2[x][y];
12218 TEST_DrawLevelField(x, y);
12220 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12222 if (IS_ENVELOPE(element))
12223 local_player->show_envelope = element;
12228 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12230 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12232 Debug("game:playing:GameActions_RND", "This should never happen!");
12234 ChangePage[x][y] = -1;
12238 Stop[x][y] = FALSE;
12239 if (WasJustMoving[x][y] > 0)
12240 WasJustMoving[x][y]--;
12241 if (WasJustFalling[x][y] > 0)
12242 WasJustFalling[x][y]--;
12243 if (CheckCollision[x][y] > 0)
12244 CheckCollision[x][y]--;
12245 if (CheckImpact[x][y] > 0)
12246 CheckImpact[x][y]--;
12250 /* reset finished pushing action (not done in ContinueMoving() to allow
12251 continuous pushing animation for elements with zero push delay) */
12252 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12254 ResetGfxAnimation(x, y);
12255 TEST_DrawLevelField(x, y);
12259 if (IS_BLOCKED(x, y))
12263 Blocked2Moving(x, y, &oldx, &oldy);
12264 if (!IS_MOVING(oldx, oldy))
12266 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12267 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12268 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12269 Debug("game:playing:GameActions_RND", "This should never happen!");
12275 if (mouse_action.button)
12277 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12278 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12280 x = mouse_action.lx;
12281 y = mouse_action.ly;
12282 element = Tile[x][y];
12286 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12287 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12291 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12292 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12296 SCAN_PLAYFIELD(x, y)
12298 element = Tile[x][y];
12299 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12300 last_gfx_frame = GfxFrame[x][y];
12302 ResetGfxFrame(x, y);
12304 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12305 DrawLevelGraphicAnimation(x, y, graphic);
12307 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12308 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12309 ResetRandomAnimationValue(x, y);
12311 SetRandomAnimationValue(x, y);
12313 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12315 if (IS_INACTIVE(element))
12317 if (IS_ANIMATED(graphic))
12318 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12323 // this may take place after moving, so 'element' may have changed
12324 if (IS_CHANGING(x, y) &&
12325 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12327 int page = element_info[element].event_page_nr[CE_DELAY];
12329 HandleElementChange(x, y, page);
12331 element = Tile[x][y];
12332 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12335 CheckNextToConditions(x, y);
12337 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12341 element = Tile[x][y];
12342 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12344 if (IS_ANIMATED(graphic) &&
12345 !IS_MOVING(x, y) &&
12347 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12349 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12350 TEST_DrawTwinkleOnField(x, y);
12352 else if (element == EL_ACID)
12355 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12357 else if ((element == EL_EXIT_OPEN ||
12358 element == EL_EM_EXIT_OPEN ||
12359 element == EL_SP_EXIT_OPEN ||
12360 element == EL_STEEL_EXIT_OPEN ||
12361 element == EL_EM_STEEL_EXIT_OPEN ||
12362 element == EL_SP_TERMINAL ||
12363 element == EL_SP_TERMINAL_ACTIVE ||
12364 element == EL_EXTRA_TIME ||
12365 element == EL_SHIELD_NORMAL ||
12366 element == EL_SHIELD_DEADLY) &&
12367 IS_ANIMATED(graphic))
12368 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12369 else if (IS_MOVING(x, y))
12370 ContinueMoving(x, y);
12371 else if (IS_ACTIVE_BOMB(element))
12372 CheckDynamite(x, y);
12373 else if (element == EL_AMOEBA_GROWING)
12374 AmoebaGrowing(x, y);
12375 else if (element == EL_AMOEBA_SHRINKING)
12376 AmoebaShrinking(x, y);
12378 #if !USE_NEW_AMOEBA_CODE
12379 else if (IS_AMOEBALIVE(element))
12380 AmoebaReproduce(x, y);
12383 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12385 else if (element == EL_EXIT_CLOSED)
12387 else if (element == EL_EM_EXIT_CLOSED)
12389 else if (element == EL_STEEL_EXIT_CLOSED)
12390 CheckExitSteel(x, y);
12391 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12392 CheckExitSteelEM(x, y);
12393 else if (element == EL_SP_EXIT_CLOSED)
12395 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12396 element == EL_EXPANDABLE_STEELWALL_GROWING)
12397 MauerWaechst(x, y);
12398 else if (element == EL_EXPANDABLE_WALL ||
12399 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12400 element == EL_EXPANDABLE_WALL_VERTICAL ||
12401 element == EL_EXPANDABLE_WALL_ANY ||
12402 element == EL_BD_EXPANDABLE_WALL)
12403 MauerAbleger(x, y);
12404 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12405 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12406 element == EL_EXPANDABLE_STEELWALL_ANY)
12407 MauerAblegerStahl(x, y);
12408 else if (element == EL_FLAMES)
12409 CheckForDragon(x, y);
12410 else if (element == EL_EXPLOSION)
12411 ; // drawing of correct explosion animation is handled separately
12412 else if (element == EL_ELEMENT_SNAPPING ||
12413 element == EL_DIAGONAL_SHRINKING ||
12414 element == EL_DIAGONAL_GROWING)
12416 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12418 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12420 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12421 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12423 if (IS_BELT_ACTIVE(element))
12424 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12426 if (game.magic_wall_active)
12428 int jx = local_player->jx, jy = local_player->jy;
12430 // play the element sound at the position nearest to the player
12431 if ((element == EL_MAGIC_WALL_FULL ||
12432 element == EL_MAGIC_WALL_ACTIVE ||
12433 element == EL_MAGIC_WALL_EMPTYING ||
12434 element == EL_BD_MAGIC_WALL_FULL ||
12435 element == EL_BD_MAGIC_WALL_ACTIVE ||
12436 element == EL_BD_MAGIC_WALL_EMPTYING ||
12437 element == EL_DC_MAGIC_WALL_FULL ||
12438 element == EL_DC_MAGIC_WALL_ACTIVE ||
12439 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12440 ABS(x - jx) + ABS(y - jy) <
12441 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12449 #if USE_NEW_AMOEBA_CODE
12450 // new experimental amoeba growth stuff
12451 if (!(FrameCounter % 8))
12453 static unsigned int random = 1684108901;
12455 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12457 x = RND(lev_fieldx);
12458 y = RND(lev_fieldy);
12459 element = Tile[x][y];
12461 if (!IS_PLAYER(x,y) &&
12462 (element == EL_EMPTY ||
12463 CAN_GROW_INTO(element) ||
12464 element == EL_QUICKSAND_EMPTY ||
12465 element == EL_QUICKSAND_FAST_EMPTY ||
12466 element == EL_ACID_SPLASH_LEFT ||
12467 element == EL_ACID_SPLASH_RIGHT))
12469 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12470 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12471 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12472 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12473 Tile[x][y] = EL_AMOEBA_DROP;
12476 random = random * 129 + 1;
12481 game.explosions_delayed = FALSE;
12483 SCAN_PLAYFIELD(x, y)
12485 element = Tile[x][y];
12487 if (ExplodeField[x][y])
12488 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12489 else if (element == EL_EXPLOSION)
12490 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12492 ExplodeField[x][y] = EX_TYPE_NONE;
12495 game.explosions_delayed = TRUE;
12497 if (game.magic_wall_active)
12499 if (!(game.magic_wall_time_left % 4))
12501 int element = Tile[magic_wall_x][magic_wall_y];
12503 if (element == EL_BD_MAGIC_WALL_FULL ||
12504 element == EL_BD_MAGIC_WALL_ACTIVE ||
12505 element == EL_BD_MAGIC_WALL_EMPTYING)
12506 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12507 else if (element == EL_DC_MAGIC_WALL_FULL ||
12508 element == EL_DC_MAGIC_WALL_ACTIVE ||
12509 element == EL_DC_MAGIC_WALL_EMPTYING)
12510 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12512 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12515 if (game.magic_wall_time_left > 0)
12517 game.magic_wall_time_left--;
12519 if (!game.magic_wall_time_left)
12521 SCAN_PLAYFIELD(x, y)
12523 element = Tile[x][y];
12525 if (element == EL_MAGIC_WALL_ACTIVE ||
12526 element == EL_MAGIC_WALL_FULL)
12528 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12529 TEST_DrawLevelField(x, y);
12531 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12532 element == EL_BD_MAGIC_WALL_FULL)
12534 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12535 TEST_DrawLevelField(x, y);
12537 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12538 element == EL_DC_MAGIC_WALL_FULL)
12540 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12541 TEST_DrawLevelField(x, y);
12545 game.magic_wall_active = FALSE;
12550 if (game.light_time_left > 0)
12552 game.light_time_left--;
12554 if (game.light_time_left == 0)
12555 RedrawAllLightSwitchesAndInvisibleElements();
12558 if (game.timegate_time_left > 0)
12560 game.timegate_time_left--;
12562 if (game.timegate_time_left == 0)
12563 CloseAllOpenTimegates();
12566 if (game.lenses_time_left > 0)
12568 game.lenses_time_left--;
12570 if (game.lenses_time_left == 0)
12571 RedrawAllInvisibleElementsForLenses();
12574 if (game.magnify_time_left > 0)
12576 game.magnify_time_left--;
12578 if (game.magnify_time_left == 0)
12579 RedrawAllInvisibleElementsForMagnifier();
12582 for (i = 0; i < MAX_PLAYERS; i++)
12584 struct PlayerInfo *player = &stored_player[i];
12586 if (SHIELD_ON(player))
12588 if (player->shield_deadly_time_left)
12589 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12590 else if (player->shield_normal_time_left)
12591 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12595 #if USE_DELAYED_GFX_REDRAW
12596 SCAN_PLAYFIELD(x, y)
12598 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12600 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12601 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12603 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12604 DrawLevelField(x, y);
12606 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12607 DrawLevelFieldCrumbled(x, y);
12609 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12610 DrawLevelFieldCrumbledNeighbours(x, y);
12612 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12613 DrawTwinkleOnField(x, y);
12616 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12621 PlayAllPlayersSound();
12623 for (i = 0; i < MAX_PLAYERS; i++)
12625 struct PlayerInfo *player = &stored_player[i];
12627 if (player->show_envelope != 0 && (!player->active ||
12628 player->MovPos == 0))
12630 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12632 player->show_envelope = 0;
12636 // use random number generator in every frame to make it less predictable
12637 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12640 mouse_action_last = mouse_action;
12643 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12645 int min_x = x, min_y = y, max_x = x, max_y = y;
12646 int scr_fieldx = getScreenFieldSizeX();
12647 int scr_fieldy = getScreenFieldSizeY();
12650 for (i = 0; i < MAX_PLAYERS; i++)
12652 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12654 if (!stored_player[i].active || &stored_player[i] == player)
12657 min_x = MIN(min_x, jx);
12658 min_y = MIN(min_y, jy);
12659 max_x = MAX(max_x, jx);
12660 max_y = MAX(max_y, jy);
12663 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12666 static boolean AllPlayersInVisibleScreen(void)
12670 for (i = 0; i < MAX_PLAYERS; i++)
12672 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12674 if (!stored_player[i].active)
12677 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12684 void ScrollLevel(int dx, int dy)
12686 int scroll_offset = 2 * TILEX_VAR;
12689 BlitBitmap(drawto_field, drawto_field,
12690 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12691 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12692 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12693 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12694 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12695 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12699 x = (dx == 1 ? BX1 : BX2);
12700 for (y = BY1; y <= BY2; y++)
12701 DrawScreenField(x, y);
12706 y = (dy == 1 ? BY1 : BY2);
12707 for (x = BX1; x <= BX2; x++)
12708 DrawScreenField(x, y);
12711 redraw_mask |= REDRAW_FIELD;
12714 static boolean canFallDown(struct PlayerInfo *player)
12716 int jx = player->jx, jy = player->jy;
12718 return (IN_LEV_FIELD(jx, jy + 1) &&
12719 (IS_FREE(jx, jy + 1) ||
12720 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12721 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12722 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12725 static boolean canPassField(int x, int y, int move_dir)
12727 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12728 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12729 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12730 int nextx = x + dx;
12731 int nexty = y + dy;
12732 int element = Tile[x][y];
12734 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12735 !CAN_MOVE(element) &&
12736 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12737 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12738 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12741 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12743 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12744 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12745 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12749 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12750 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12751 (IS_DIGGABLE(Tile[newx][newy]) ||
12752 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12753 canPassField(newx, newy, move_dir)));
12756 static void CheckGravityMovement(struct PlayerInfo *player)
12758 if (player->gravity && !player->programmed_action)
12760 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12761 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12762 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12763 int jx = player->jx, jy = player->jy;
12764 boolean player_is_moving_to_valid_field =
12765 (!player_is_snapping &&
12766 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12767 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12768 boolean player_can_fall_down = canFallDown(player);
12770 if (player_can_fall_down &&
12771 !player_is_moving_to_valid_field)
12772 player->programmed_action = MV_DOWN;
12776 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12778 return CheckGravityMovement(player);
12780 if (player->gravity && !player->programmed_action)
12782 int jx = player->jx, jy = player->jy;
12783 boolean field_under_player_is_free =
12784 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12785 boolean player_is_standing_on_valid_field =
12786 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12787 (IS_WALKABLE(Tile[jx][jy]) &&
12788 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12790 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12791 player->programmed_action = MV_DOWN;
12796 MovePlayerOneStep()
12797 -----------------------------------------------------------------------------
12798 dx, dy: direction (non-diagonal) to try to move the player to
12799 real_dx, real_dy: direction as read from input device (can be diagonal)
12802 boolean MovePlayerOneStep(struct PlayerInfo *player,
12803 int dx, int dy, int real_dx, int real_dy)
12805 int jx = player->jx, jy = player->jy;
12806 int new_jx = jx + dx, new_jy = jy + dy;
12808 boolean player_can_move = !player->cannot_move;
12810 if (!player->active || (!dx && !dy))
12811 return MP_NO_ACTION;
12813 player->MovDir = (dx < 0 ? MV_LEFT :
12814 dx > 0 ? MV_RIGHT :
12816 dy > 0 ? MV_DOWN : MV_NONE);
12818 if (!IN_LEV_FIELD(new_jx, new_jy))
12819 return MP_NO_ACTION;
12821 if (!player_can_move)
12823 if (player->MovPos == 0)
12825 player->is_moving = FALSE;
12826 player->is_digging = FALSE;
12827 player->is_collecting = FALSE;
12828 player->is_snapping = FALSE;
12829 player->is_pushing = FALSE;
12833 if (!network.enabled && game.centered_player_nr == -1 &&
12834 !AllPlayersInSight(player, new_jx, new_jy))
12835 return MP_NO_ACTION;
12837 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12838 if (can_move != MP_MOVING)
12841 // check if DigField() has caused relocation of the player
12842 if (player->jx != jx || player->jy != jy)
12843 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12845 StorePlayer[jx][jy] = 0;
12846 player->last_jx = jx;
12847 player->last_jy = jy;
12848 player->jx = new_jx;
12849 player->jy = new_jy;
12850 StorePlayer[new_jx][new_jy] = player->element_nr;
12852 if (player->move_delay_value_next != -1)
12854 player->move_delay_value = player->move_delay_value_next;
12855 player->move_delay_value_next = -1;
12859 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12861 player->step_counter++;
12863 PlayerVisit[jx][jy] = FrameCounter;
12865 player->is_moving = TRUE;
12868 // should better be called in MovePlayer(), but this breaks some tapes
12869 ScrollPlayer(player, SCROLL_INIT);
12875 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12877 int jx = player->jx, jy = player->jy;
12878 int old_jx = jx, old_jy = jy;
12879 int moved = MP_NO_ACTION;
12881 if (!player->active)
12886 if (player->MovPos == 0)
12888 player->is_moving = FALSE;
12889 player->is_digging = FALSE;
12890 player->is_collecting = FALSE;
12891 player->is_snapping = FALSE;
12892 player->is_pushing = FALSE;
12898 if (player->move_delay > 0)
12901 player->move_delay = -1; // set to "uninitialized" value
12903 // store if player is automatically moved to next field
12904 player->is_auto_moving = (player->programmed_action != MV_NONE);
12906 // remove the last programmed player action
12907 player->programmed_action = 0;
12909 if (player->MovPos)
12911 // should only happen if pre-1.2 tape recordings are played
12912 // this is only for backward compatibility
12914 int original_move_delay_value = player->move_delay_value;
12917 Debug("game:playing:MovePlayer",
12918 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12922 // scroll remaining steps with finest movement resolution
12923 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12925 while (player->MovPos)
12927 ScrollPlayer(player, SCROLL_GO_ON);
12928 ScrollScreen(NULL, SCROLL_GO_ON);
12930 AdvanceFrameAndPlayerCounters(player->index_nr);
12933 BackToFront_WithFrameDelay(0);
12936 player->move_delay_value = original_move_delay_value;
12939 player->is_active = FALSE;
12941 if (player->last_move_dir & MV_HORIZONTAL)
12943 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12944 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12948 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12949 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12952 if (!moved && !player->is_active)
12954 player->is_moving = FALSE;
12955 player->is_digging = FALSE;
12956 player->is_collecting = FALSE;
12957 player->is_snapping = FALSE;
12958 player->is_pushing = FALSE;
12964 if (moved & MP_MOVING && !ScreenMovPos &&
12965 (player->index_nr == game.centered_player_nr ||
12966 game.centered_player_nr == -1))
12968 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12970 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12972 // actual player has left the screen -- scroll in that direction
12973 if (jx != old_jx) // player has moved horizontally
12974 scroll_x += (jx - old_jx);
12975 else // player has moved vertically
12976 scroll_y += (jy - old_jy);
12980 int offset_raw = game.scroll_delay_value;
12982 if (jx != old_jx) // player has moved horizontally
12984 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12985 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12986 int new_scroll_x = jx - MIDPOSX + offset_x;
12988 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12989 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12990 scroll_x = new_scroll_x;
12992 // don't scroll over playfield boundaries
12993 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12995 // don't scroll more than one field at a time
12996 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12998 // don't scroll against the player's moving direction
12999 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13000 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13001 scroll_x = old_scroll_x;
13003 else // player has moved vertically
13005 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13006 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13007 int new_scroll_y = jy - MIDPOSY + offset_y;
13009 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13010 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13011 scroll_y = new_scroll_y;
13013 // don't scroll over playfield boundaries
13014 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13016 // don't scroll more than one field at a time
13017 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13019 // don't scroll against the player's moving direction
13020 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13021 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13022 scroll_y = old_scroll_y;
13026 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13028 if (!network.enabled && game.centered_player_nr == -1 &&
13029 !AllPlayersInVisibleScreen())
13031 scroll_x = old_scroll_x;
13032 scroll_y = old_scroll_y;
13036 ScrollScreen(player, SCROLL_INIT);
13037 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13042 player->StepFrame = 0;
13044 if (moved & MP_MOVING)
13046 if (old_jx != jx && old_jy == jy)
13047 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13048 else if (old_jx == jx && old_jy != jy)
13049 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13051 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13053 player->last_move_dir = player->MovDir;
13054 player->is_moving = TRUE;
13055 player->is_snapping = FALSE;
13056 player->is_switching = FALSE;
13057 player->is_dropping = FALSE;
13058 player->is_dropping_pressed = FALSE;
13059 player->drop_pressed_delay = 0;
13062 // should better be called here than above, but this breaks some tapes
13063 ScrollPlayer(player, SCROLL_INIT);
13068 CheckGravityMovementWhenNotMoving(player);
13070 player->is_moving = FALSE;
13072 /* at this point, the player is allowed to move, but cannot move right now
13073 (e.g. because of something blocking the way) -- ensure that the player
13074 is also allowed to move in the next frame (in old versions before 3.1.1,
13075 the player was forced to wait again for eight frames before next try) */
13077 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13078 player->move_delay = 0; // allow direct movement in the next frame
13081 if (player->move_delay == -1) // not yet initialized by DigField()
13082 player->move_delay = player->move_delay_value;
13084 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13086 TestIfPlayerTouchesBadThing(jx, jy);
13087 TestIfPlayerTouchesCustomElement(jx, jy);
13090 if (!player->active)
13091 RemovePlayer(player);
13096 void ScrollPlayer(struct PlayerInfo *player, int mode)
13098 int jx = player->jx, jy = player->jy;
13099 int last_jx = player->last_jx, last_jy = player->last_jy;
13100 int move_stepsize = TILEX / player->move_delay_value;
13102 if (!player->active)
13105 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13108 if (mode == SCROLL_INIT)
13110 player->actual_frame_counter = FrameCounter;
13111 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13113 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13114 Tile[last_jx][last_jy] == EL_EMPTY)
13116 int last_field_block_delay = 0; // start with no blocking at all
13117 int block_delay_adjustment = player->block_delay_adjustment;
13119 // if player blocks last field, add delay for exactly one move
13120 if (player->block_last_field)
13122 last_field_block_delay += player->move_delay_value;
13124 // when blocking enabled, prevent moving up despite gravity
13125 if (player->gravity && player->MovDir == MV_UP)
13126 block_delay_adjustment = -1;
13129 // add block delay adjustment (also possible when not blocking)
13130 last_field_block_delay += block_delay_adjustment;
13132 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13133 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13136 if (player->MovPos != 0) // player has not yet reached destination
13139 else if (!FrameReached(&player->actual_frame_counter, 1))
13142 if (player->MovPos != 0)
13144 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13145 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13147 // before DrawPlayer() to draw correct player graphic for this case
13148 if (player->MovPos == 0)
13149 CheckGravityMovement(player);
13152 if (player->MovPos == 0) // player reached destination field
13154 if (player->move_delay_reset_counter > 0)
13156 player->move_delay_reset_counter--;
13158 if (player->move_delay_reset_counter == 0)
13160 // continue with normal speed after quickly moving through gate
13161 HALVE_PLAYER_SPEED(player);
13163 // be able to make the next move without delay
13164 player->move_delay = 0;
13168 player->last_jx = jx;
13169 player->last_jy = jy;
13171 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13172 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13173 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13174 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13175 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13176 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13177 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13178 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13180 ExitPlayer(player);
13182 if (game.players_still_needed == 0 &&
13183 (game.friends_still_needed == 0 ||
13184 IS_SP_ELEMENT(Tile[jx][jy])))
13188 // this breaks one level: "machine", level 000
13190 int move_direction = player->MovDir;
13191 int enter_side = MV_DIR_OPPOSITE(move_direction);
13192 int leave_side = move_direction;
13193 int old_jx = last_jx;
13194 int old_jy = last_jy;
13195 int old_element = Tile[old_jx][old_jy];
13196 int new_element = Tile[jx][jy];
13198 if (IS_CUSTOM_ELEMENT(old_element))
13199 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13201 player->index_bit, leave_side);
13203 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13204 CE_PLAYER_LEAVES_X,
13205 player->index_bit, leave_side);
13207 if (IS_CUSTOM_ELEMENT(new_element))
13208 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13209 player->index_bit, enter_side);
13211 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13212 CE_PLAYER_ENTERS_X,
13213 player->index_bit, enter_side);
13215 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13216 CE_MOVE_OF_X, move_direction);
13219 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13221 TestIfPlayerTouchesBadThing(jx, jy);
13222 TestIfPlayerTouchesCustomElement(jx, jy);
13224 /* needed because pushed element has not yet reached its destination,
13225 so it would trigger a change event at its previous field location */
13226 if (!player->is_pushing)
13227 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13229 if (level.finish_dig_collect &&
13230 (player->is_digging || player->is_collecting))
13232 int last_element = player->last_removed_element;
13233 int move_direction = player->MovDir;
13234 int enter_side = MV_DIR_OPPOSITE(move_direction);
13235 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13236 CE_PLAYER_COLLECTS_X);
13238 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13239 player->index_bit, enter_side);
13241 player->last_removed_element = EL_UNDEFINED;
13244 if (!player->active)
13245 RemovePlayer(player);
13248 if (level.use_step_counter)
13258 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13259 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13261 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13263 DisplayGameControlValues();
13265 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13266 for (i = 0; i < MAX_PLAYERS; i++)
13267 KillPlayer(&stored_player[i]);
13269 else if (game.no_time_limit && !game.all_players_gone)
13271 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13273 DisplayGameControlValues();
13277 if (tape.single_step && tape.recording && !tape.pausing &&
13278 !player->programmed_action)
13279 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13281 if (!player->programmed_action)
13282 CheckSaveEngineSnapshot(player);
13286 void ScrollScreen(struct PlayerInfo *player, int mode)
13288 static unsigned int screen_frame_counter = 0;
13290 if (mode == SCROLL_INIT)
13292 // set scrolling step size according to actual player's moving speed
13293 ScrollStepSize = TILEX / player->move_delay_value;
13295 screen_frame_counter = FrameCounter;
13296 ScreenMovDir = player->MovDir;
13297 ScreenMovPos = player->MovPos;
13298 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13301 else if (!FrameReached(&screen_frame_counter, 1))
13306 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13307 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13308 redraw_mask |= REDRAW_FIELD;
13311 ScreenMovDir = MV_NONE;
13314 void CheckNextToConditions(int x, int y)
13316 int element = Tile[x][y];
13318 if (IS_PLAYER(x, y))
13319 TestIfPlayerNextToCustomElement(x, y);
13321 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13322 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13323 TestIfElementNextToCustomElement(x, y);
13326 void TestIfPlayerNextToCustomElement(int x, int y)
13328 static int xy[4][2] =
13335 static int trigger_sides[4][2] =
13337 // center side border side
13338 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13339 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13340 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13341 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13345 if (!IS_PLAYER(x, y))
13348 struct PlayerInfo *player = PLAYERINFO(x, y);
13350 if (player->is_moving)
13353 for (i = 0; i < NUM_DIRECTIONS; i++)
13355 int xx = x + xy[i][0];
13356 int yy = y + xy[i][1];
13357 int border_side = trigger_sides[i][1];
13358 int border_element;
13360 if (!IN_LEV_FIELD(xx, yy))
13363 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13364 continue; // center and border element not connected
13366 border_element = Tile[xx][yy];
13368 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13369 player->index_bit, border_side);
13370 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13371 CE_PLAYER_NEXT_TO_X,
13372 player->index_bit, border_side);
13374 /* use player element that is initially defined in the level playfield,
13375 not the player element that corresponds to the runtime player number
13376 (example: a level that contains EL_PLAYER_3 as the only player would
13377 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13379 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13380 CE_NEXT_TO_X, border_side);
13384 void TestIfPlayerTouchesCustomElement(int x, int y)
13386 static int xy[4][2] =
13393 static int trigger_sides[4][2] =
13395 // center side border side
13396 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13397 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13398 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13399 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13401 static int touch_dir[4] =
13403 MV_LEFT | MV_RIGHT,
13408 int center_element = Tile[x][y]; // should always be non-moving!
13411 for (i = 0; i < NUM_DIRECTIONS; i++)
13413 int xx = x + xy[i][0];
13414 int yy = y + xy[i][1];
13415 int center_side = trigger_sides[i][0];
13416 int border_side = trigger_sides[i][1];
13417 int border_element;
13419 if (!IN_LEV_FIELD(xx, yy))
13422 if (IS_PLAYER(x, y)) // player found at center element
13424 struct PlayerInfo *player = PLAYERINFO(x, y);
13426 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13427 border_element = Tile[xx][yy]; // may be moving!
13428 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13429 border_element = Tile[xx][yy];
13430 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13431 border_element = MovingOrBlocked2Element(xx, yy);
13433 continue; // center and border element do not touch
13435 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13436 player->index_bit, border_side);
13437 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13438 CE_PLAYER_TOUCHES_X,
13439 player->index_bit, border_side);
13442 /* use player element that is initially defined in the level playfield,
13443 not the player element that corresponds to the runtime player number
13444 (example: a level that contains EL_PLAYER_3 as the only player would
13445 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13446 int player_element = PLAYERINFO(x, y)->initial_element;
13448 CheckElementChangeBySide(xx, yy, border_element, player_element,
13449 CE_TOUCHING_X, border_side);
13452 else if (IS_PLAYER(xx, yy)) // player found at border element
13454 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13456 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13458 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13459 continue; // center and border element do not touch
13462 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13463 player->index_bit, center_side);
13464 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13465 CE_PLAYER_TOUCHES_X,
13466 player->index_bit, center_side);
13469 /* use player element that is initially defined in the level playfield,
13470 not the player element that corresponds to the runtime player number
13471 (example: a level that contains EL_PLAYER_3 as the only player would
13472 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13473 int player_element = PLAYERINFO(xx, yy)->initial_element;
13475 CheckElementChangeBySide(x, y, center_element, player_element,
13476 CE_TOUCHING_X, center_side);
13484 void TestIfElementNextToCustomElement(int x, int y)
13486 static int xy[4][2] =
13493 static int trigger_sides[4][2] =
13495 // center side border side
13496 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13497 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13498 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13499 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13501 int center_element = Tile[x][y]; // should always be non-moving!
13504 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13507 for (i = 0; i < NUM_DIRECTIONS; i++)
13509 int xx = x + xy[i][0];
13510 int yy = y + xy[i][1];
13511 int border_side = trigger_sides[i][1];
13512 int border_element;
13514 if (!IN_LEV_FIELD(xx, yy))
13517 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13518 continue; // center and border element not connected
13520 border_element = Tile[xx][yy];
13522 // check for change of center element (but change it only once)
13523 if (CheckElementChangeBySide(x, y, center_element, border_element,
13524 CE_NEXT_TO_X, border_side))
13529 void TestIfElementTouchesCustomElement(int x, int y)
13531 static int xy[4][2] =
13538 static int trigger_sides[4][2] =
13540 // center side border side
13541 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13542 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13543 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13544 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13546 static int touch_dir[4] =
13548 MV_LEFT | MV_RIGHT,
13553 boolean change_center_element = FALSE;
13554 int center_element = Tile[x][y]; // should always be non-moving!
13555 int border_element_old[NUM_DIRECTIONS];
13558 for (i = 0; i < NUM_DIRECTIONS; i++)
13560 int xx = x + xy[i][0];
13561 int yy = y + xy[i][1];
13562 int border_element;
13564 border_element_old[i] = -1;
13566 if (!IN_LEV_FIELD(xx, yy))
13569 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13570 border_element = Tile[xx][yy]; // may be moving!
13571 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13572 border_element = Tile[xx][yy];
13573 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13574 border_element = MovingOrBlocked2Element(xx, yy);
13576 continue; // center and border element do not touch
13578 border_element_old[i] = border_element;
13581 for (i = 0; i < NUM_DIRECTIONS; i++)
13583 int xx = x + xy[i][0];
13584 int yy = y + xy[i][1];
13585 int center_side = trigger_sides[i][0];
13586 int border_element = border_element_old[i];
13588 if (border_element == -1)
13591 // check for change of border element
13592 CheckElementChangeBySide(xx, yy, border_element, center_element,
13593 CE_TOUCHING_X, center_side);
13595 // (center element cannot be player, so we dont have to check this here)
13598 for (i = 0; i < NUM_DIRECTIONS; i++)
13600 int xx = x + xy[i][0];
13601 int yy = y + xy[i][1];
13602 int border_side = trigger_sides[i][1];
13603 int border_element = border_element_old[i];
13605 if (border_element == -1)
13608 // check for change of center element (but change it only once)
13609 if (!change_center_element)
13610 change_center_element =
13611 CheckElementChangeBySide(x, y, center_element, border_element,
13612 CE_TOUCHING_X, border_side);
13614 if (IS_PLAYER(xx, yy))
13616 /* use player element that is initially defined in the level playfield,
13617 not the player element that corresponds to the runtime player number
13618 (example: a level that contains EL_PLAYER_3 as the only player would
13619 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13620 int player_element = PLAYERINFO(xx, yy)->initial_element;
13622 CheckElementChangeBySide(x, y, center_element, player_element,
13623 CE_TOUCHING_X, border_side);
13628 void TestIfElementHitsCustomElement(int x, int y, int direction)
13630 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13631 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13632 int hitx = x + dx, hity = y + dy;
13633 int hitting_element = Tile[x][y];
13634 int touched_element;
13636 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13639 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13640 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13642 if (IN_LEV_FIELD(hitx, hity))
13644 int opposite_direction = MV_DIR_OPPOSITE(direction);
13645 int hitting_side = direction;
13646 int touched_side = opposite_direction;
13647 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13648 MovDir[hitx][hity] != direction ||
13649 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13655 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13656 CE_HITTING_X, touched_side);
13658 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13659 CE_HIT_BY_X, hitting_side);
13661 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13662 CE_HIT_BY_SOMETHING, opposite_direction);
13664 if (IS_PLAYER(hitx, hity))
13666 /* use player element that is initially defined in the level playfield,
13667 not the player element that corresponds to the runtime player number
13668 (example: a level that contains EL_PLAYER_3 as the only player would
13669 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13670 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13672 CheckElementChangeBySide(x, y, hitting_element, player_element,
13673 CE_HITTING_X, touched_side);
13678 // "hitting something" is also true when hitting the playfield border
13679 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13680 CE_HITTING_SOMETHING, direction);
13683 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13685 int i, kill_x = -1, kill_y = -1;
13687 int bad_element = -1;
13688 static int test_xy[4][2] =
13695 static int test_dir[4] =
13703 for (i = 0; i < NUM_DIRECTIONS; i++)
13705 int test_x, test_y, test_move_dir, test_element;
13707 test_x = good_x + test_xy[i][0];
13708 test_y = good_y + test_xy[i][1];
13710 if (!IN_LEV_FIELD(test_x, test_y))
13714 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13716 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13718 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13719 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13721 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13722 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13726 bad_element = test_element;
13732 if (kill_x != -1 || kill_y != -1)
13734 if (IS_PLAYER(good_x, good_y))
13736 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13738 if (player->shield_deadly_time_left > 0 &&
13739 !IS_INDESTRUCTIBLE(bad_element))
13740 Bang(kill_x, kill_y);
13741 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13742 KillPlayer(player);
13745 Bang(good_x, good_y);
13749 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13751 int i, kill_x = -1, kill_y = -1;
13752 int bad_element = Tile[bad_x][bad_y];
13753 static int test_xy[4][2] =
13760 static int touch_dir[4] =
13762 MV_LEFT | MV_RIGHT,
13767 static int test_dir[4] =
13775 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13778 for (i = 0; i < NUM_DIRECTIONS; i++)
13780 int test_x, test_y, test_move_dir, test_element;
13782 test_x = bad_x + test_xy[i][0];
13783 test_y = bad_y + test_xy[i][1];
13785 if (!IN_LEV_FIELD(test_x, test_y))
13789 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13791 test_element = Tile[test_x][test_y];
13793 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13794 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13796 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13797 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13799 // good thing is player or penguin that does not move away
13800 if (IS_PLAYER(test_x, test_y))
13802 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13804 if (bad_element == EL_ROBOT && player->is_moving)
13805 continue; // robot does not kill player if he is moving
13807 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13809 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13810 continue; // center and border element do not touch
13818 else if (test_element == EL_PENGUIN)
13828 if (kill_x != -1 || kill_y != -1)
13830 if (IS_PLAYER(kill_x, kill_y))
13832 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13834 if (player->shield_deadly_time_left > 0 &&
13835 !IS_INDESTRUCTIBLE(bad_element))
13836 Bang(bad_x, bad_y);
13837 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13838 KillPlayer(player);
13841 Bang(kill_x, kill_y);
13845 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13847 int bad_element = Tile[bad_x][bad_y];
13848 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13849 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13850 int test_x = bad_x + dx, test_y = bad_y + dy;
13851 int test_move_dir, test_element;
13852 int kill_x = -1, kill_y = -1;
13854 if (!IN_LEV_FIELD(test_x, test_y))
13858 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13860 test_element = Tile[test_x][test_y];
13862 if (test_move_dir != bad_move_dir)
13864 // good thing can be player or penguin that does not move away
13865 if (IS_PLAYER(test_x, test_y))
13867 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13869 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13870 player as being hit when he is moving towards the bad thing, because
13871 the "get hit by" condition would be lost after the player stops) */
13872 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13873 return; // player moves away from bad thing
13878 else if (test_element == EL_PENGUIN)
13885 if (kill_x != -1 || kill_y != -1)
13887 if (IS_PLAYER(kill_x, kill_y))
13889 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13891 if (player->shield_deadly_time_left > 0 &&
13892 !IS_INDESTRUCTIBLE(bad_element))
13893 Bang(bad_x, bad_y);
13894 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13895 KillPlayer(player);
13898 Bang(kill_x, kill_y);
13902 void TestIfPlayerTouchesBadThing(int x, int y)
13904 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13907 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13909 TestIfGoodThingHitsBadThing(x, y, move_dir);
13912 void TestIfBadThingTouchesPlayer(int x, int y)
13914 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13917 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13919 TestIfBadThingHitsGoodThing(x, y, move_dir);
13922 void TestIfFriendTouchesBadThing(int x, int y)
13924 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13927 void TestIfBadThingTouchesFriend(int x, int y)
13929 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13932 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13934 int i, kill_x = bad_x, kill_y = bad_y;
13935 static int xy[4][2] =
13943 for (i = 0; i < NUM_DIRECTIONS; i++)
13947 x = bad_x + xy[i][0];
13948 y = bad_y + xy[i][1];
13949 if (!IN_LEV_FIELD(x, y))
13952 element = Tile[x][y];
13953 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13954 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13962 if (kill_x != bad_x || kill_y != bad_y)
13963 Bang(bad_x, bad_y);
13966 void KillPlayer(struct PlayerInfo *player)
13968 int jx = player->jx, jy = player->jy;
13970 if (!player->active)
13974 Debug("game:playing:KillPlayer",
13975 "0: killed == %d, active == %d, reanimated == %d",
13976 player->killed, player->active, player->reanimated);
13979 /* the following code was introduced to prevent an infinite loop when calling
13981 -> CheckTriggeredElementChangeExt()
13982 -> ExecuteCustomElementAction()
13984 -> (infinitely repeating the above sequence of function calls)
13985 which occurs when killing the player while having a CE with the setting
13986 "kill player X when explosion of <player X>"; the solution using a new
13987 field "player->killed" was chosen for backwards compatibility, although
13988 clever use of the fields "player->active" etc. would probably also work */
13990 if (player->killed)
13994 player->killed = TRUE;
13996 // remove accessible field at the player's position
13997 Tile[jx][jy] = EL_EMPTY;
13999 // deactivate shield (else Bang()/Explode() would not work right)
14000 player->shield_normal_time_left = 0;
14001 player->shield_deadly_time_left = 0;
14004 Debug("game:playing:KillPlayer",
14005 "1: killed == %d, active == %d, reanimated == %d",
14006 player->killed, player->active, player->reanimated);
14012 Debug("game:playing:KillPlayer",
14013 "2: killed == %d, active == %d, reanimated == %d",
14014 player->killed, player->active, player->reanimated);
14017 if (player->reanimated) // killed player may have been reanimated
14018 player->killed = player->reanimated = FALSE;
14020 BuryPlayer(player);
14023 static void KillPlayerUnlessEnemyProtected(int x, int y)
14025 if (!PLAYER_ENEMY_PROTECTED(x, y))
14026 KillPlayer(PLAYERINFO(x, y));
14029 static void KillPlayerUnlessExplosionProtected(int x, int y)
14031 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14032 KillPlayer(PLAYERINFO(x, y));
14035 void BuryPlayer(struct PlayerInfo *player)
14037 int jx = player->jx, jy = player->jy;
14039 if (!player->active)
14042 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14043 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14045 RemovePlayer(player);
14047 player->buried = TRUE;
14049 if (game.all_players_gone)
14050 game.GameOver = TRUE;
14053 void RemovePlayer(struct PlayerInfo *player)
14055 int jx = player->jx, jy = player->jy;
14056 int i, found = FALSE;
14058 player->present = FALSE;
14059 player->active = FALSE;
14061 // required for some CE actions (even if the player is not active anymore)
14062 player->MovPos = 0;
14064 if (!ExplodeField[jx][jy])
14065 StorePlayer[jx][jy] = 0;
14067 if (player->is_moving)
14068 TEST_DrawLevelField(player->last_jx, player->last_jy);
14070 for (i = 0; i < MAX_PLAYERS; i++)
14071 if (stored_player[i].active)
14076 game.all_players_gone = TRUE;
14077 game.GameOver = TRUE;
14080 game.exit_x = game.robot_wheel_x = jx;
14081 game.exit_y = game.robot_wheel_y = jy;
14084 void ExitPlayer(struct PlayerInfo *player)
14086 DrawPlayer(player); // needed here only to cleanup last field
14087 RemovePlayer(player);
14089 if (game.players_still_needed > 0)
14090 game.players_still_needed--;
14093 static void SetFieldForSnapping(int x, int y, int element, int direction,
14094 int player_index_bit)
14096 struct ElementInfo *ei = &element_info[element];
14097 int direction_bit = MV_DIR_TO_BIT(direction);
14098 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14099 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14100 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14102 Tile[x][y] = EL_ELEMENT_SNAPPING;
14103 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14104 MovDir[x][y] = direction;
14105 Store[x][y] = element;
14106 Store2[x][y] = player_index_bit;
14108 ResetGfxAnimation(x, y);
14110 GfxElement[x][y] = element;
14111 GfxAction[x][y] = action;
14112 GfxDir[x][y] = direction;
14113 GfxFrame[x][y] = -1;
14116 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14117 int player_index_bit)
14119 TestIfElementTouchesCustomElement(x, y); // for empty space
14121 if (level.finish_dig_collect)
14123 int dig_side = MV_DIR_OPPOSITE(direction);
14124 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14125 CE_PLAYER_COLLECTS_X);
14127 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14128 player_index_bit, dig_side);
14129 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14130 player_index_bit, dig_side);
14135 =============================================================================
14136 checkDiagonalPushing()
14137 -----------------------------------------------------------------------------
14138 check if diagonal input device direction results in pushing of object
14139 (by checking if the alternative direction is walkable, diggable, ...)
14140 =============================================================================
14143 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14144 int x, int y, int real_dx, int real_dy)
14146 int jx, jy, dx, dy, xx, yy;
14148 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14151 // diagonal direction: check alternative direction
14156 xx = jx + (dx == 0 ? real_dx : 0);
14157 yy = jy + (dy == 0 ? real_dy : 0);
14159 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14163 =============================================================================
14165 -----------------------------------------------------------------------------
14166 x, y: field next to player (non-diagonal) to try to dig to
14167 real_dx, real_dy: direction as read from input device (can be diagonal)
14168 =============================================================================
14171 static int DigField(struct PlayerInfo *player,
14172 int oldx, int oldy, int x, int y,
14173 int real_dx, int real_dy, int mode)
14175 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14176 boolean player_was_pushing = player->is_pushing;
14177 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14178 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14179 int jx = oldx, jy = oldy;
14180 int dx = x - jx, dy = y - jy;
14181 int nextx = x + dx, nexty = y + dy;
14182 int move_direction = (dx == -1 ? MV_LEFT :
14183 dx == +1 ? MV_RIGHT :
14185 dy == +1 ? MV_DOWN : MV_NONE);
14186 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14187 int dig_side = MV_DIR_OPPOSITE(move_direction);
14188 int old_element = Tile[jx][jy];
14189 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14192 if (is_player) // function can also be called by EL_PENGUIN
14194 if (player->MovPos == 0)
14196 player->is_digging = FALSE;
14197 player->is_collecting = FALSE;
14200 if (player->MovPos == 0) // last pushing move finished
14201 player->is_pushing = FALSE;
14203 if (mode == DF_NO_PUSH) // player just stopped pushing
14205 player->is_switching = FALSE;
14206 player->push_delay = -1;
14208 return MP_NO_ACTION;
14211 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14212 old_element = Back[jx][jy];
14214 // in case of element dropped at player position, check background
14215 else if (Back[jx][jy] != EL_EMPTY &&
14216 game.engine_version >= VERSION_IDENT(2,2,0,0))
14217 old_element = Back[jx][jy];
14219 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14220 return MP_NO_ACTION; // field has no opening in this direction
14222 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14223 return MP_NO_ACTION; // field has no opening in this direction
14225 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14229 Tile[jx][jy] = player->artwork_element;
14230 InitMovingField(jx, jy, MV_DOWN);
14231 Store[jx][jy] = EL_ACID;
14232 ContinueMoving(jx, jy);
14233 BuryPlayer(player);
14235 return MP_DONT_RUN_INTO;
14238 if (player_can_move && DONT_RUN_INTO(element))
14240 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14242 return MP_DONT_RUN_INTO;
14245 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14246 return MP_NO_ACTION;
14248 collect_count = element_info[element].collect_count_initial;
14250 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14251 return MP_NO_ACTION;
14253 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14254 player_can_move = player_can_move_or_snap;
14256 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14257 game.engine_version >= VERSION_IDENT(2,2,0,0))
14259 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14260 player->index_bit, dig_side);
14261 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14262 player->index_bit, dig_side);
14264 if (element == EL_DC_LANDMINE)
14267 if (Tile[x][y] != element) // field changed by snapping
14270 return MP_NO_ACTION;
14273 if (player->gravity && is_player && !player->is_auto_moving &&
14274 canFallDown(player) && move_direction != MV_DOWN &&
14275 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14276 return MP_NO_ACTION; // player cannot walk here due to gravity
14278 if (player_can_move &&
14279 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14281 int sound_element = SND_ELEMENT(element);
14282 int sound_action = ACTION_WALKING;
14284 if (IS_RND_GATE(element))
14286 if (!player->key[RND_GATE_NR(element)])
14287 return MP_NO_ACTION;
14289 else if (IS_RND_GATE_GRAY(element))
14291 if (!player->key[RND_GATE_GRAY_NR(element)])
14292 return MP_NO_ACTION;
14294 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14296 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14297 return MP_NO_ACTION;
14299 else if (element == EL_EXIT_OPEN ||
14300 element == EL_EM_EXIT_OPEN ||
14301 element == EL_EM_EXIT_OPENING ||
14302 element == EL_STEEL_EXIT_OPEN ||
14303 element == EL_EM_STEEL_EXIT_OPEN ||
14304 element == EL_EM_STEEL_EXIT_OPENING ||
14305 element == EL_SP_EXIT_OPEN ||
14306 element == EL_SP_EXIT_OPENING)
14308 sound_action = ACTION_PASSING; // player is passing exit
14310 else if (element == EL_EMPTY)
14312 sound_action = ACTION_MOVING; // nothing to walk on
14315 // play sound from background or player, whatever is available
14316 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14317 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14319 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14321 else if (player_can_move &&
14322 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14324 if (!ACCESS_FROM(element, opposite_direction))
14325 return MP_NO_ACTION; // field not accessible from this direction
14327 if (CAN_MOVE(element)) // only fixed elements can be passed!
14328 return MP_NO_ACTION;
14330 if (IS_EM_GATE(element))
14332 if (!player->key[EM_GATE_NR(element)])
14333 return MP_NO_ACTION;
14335 else if (IS_EM_GATE_GRAY(element))
14337 if (!player->key[EM_GATE_GRAY_NR(element)])
14338 return MP_NO_ACTION;
14340 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14342 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14343 return MP_NO_ACTION;
14345 else if (IS_EMC_GATE(element))
14347 if (!player->key[EMC_GATE_NR(element)])
14348 return MP_NO_ACTION;
14350 else if (IS_EMC_GATE_GRAY(element))
14352 if (!player->key[EMC_GATE_GRAY_NR(element)])
14353 return MP_NO_ACTION;
14355 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14357 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14358 return MP_NO_ACTION;
14360 else if (element == EL_DC_GATE_WHITE ||
14361 element == EL_DC_GATE_WHITE_GRAY ||
14362 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14364 if (player->num_white_keys == 0)
14365 return MP_NO_ACTION;
14367 player->num_white_keys--;
14369 else if (IS_SP_PORT(element))
14371 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14372 element == EL_SP_GRAVITY_PORT_RIGHT ||
14373 element == EL_SP_GRAVITY_PORT_UP ||
14374 element == EL_SP_GRAVITY_PORT_DOWN)
14375 player->gravity = !player->gravity;
14376 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14377 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14378 element == EL_SP_GRAVITY_ON_PORT_UP ||
14379 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14380 player->gravity = TRUE;
14381 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14382 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14383 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14384 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14385 player->gravity = FALSE;
14388 // automatically move to the next field with double speed
14389 player->programmed_action = move_direction;
14391 if (player->move_delay_reset_counter == 0)
14393 player->move_delay_reset_counter = 2; // two double speed steps
14395 DOUBLE_PLAYER_SPEED(player);
14398 PlayLevelSoundAction(x, y, ACTION_PASSING);
14400 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14404 if (mode != DF_SNAP)
14406 GfxElement[x][y] = GFX_ELEMENT(element);
14407 player->is_digging = TRUE;
14410 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14412 // use old behaviour for old levels (digging)
14413 if (!level.finish_dig_collect)
14415 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14416 player->index_bit, dig_side);
14418 // if digging triggered player relocation, finish digging tile
14419 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14420 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14423 if (mode == DF_SNAP)
14425 if (level.block_snap_field)
14426 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14428 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14430 // use old behaviour for old levels (snapping)
14431 if (!level.finish_dig_collect)
14432 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14433 player->index_bit, dig_side);
14436 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14440 if (is_player && mode != DF_SNAP)
14442 GfxElement[x][y] = element;
14443 player->is_collecting = TRUE;
14446 if (element == EL_SPEED_PILL)
14448 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14450 else if (element == EL_EXTRA_TIME && level.time > 0)
14452 TimeLeft += level.extra_time;
14454 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14456 DisplayGameControlValues();
14458 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14460 player->shield_normal_time_left += level.shield_normal_time;
14461 if (element == EL_SHIELD_DEADLY)
14462 player->shield_deadly_time_left += level.shield_deadly_time;
14464 else if (element == EL_DYNAMITE ||
14465 element == EL_EM_DYNAMITE ||
14466 element == EL_SP_DISK_RED)
14468 if (player->inventory_size < MAX_INVENTORY_SIZE)
14469 player->inventory_element[player->inventory_size++] = element;
14471 DrawGameDoorValues();
14473 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14475 player->dynabomb_count++;
14476 player->dynabombs_left++;
14478 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14480 player->dynabomb_size++;
14482 else if (element == EL_DYNABOMB_INCREASE_POWER)
14484 player->dynabomb_xl = TRUE;
14486 else if (IS_KEY(element))
14488 player->key[KEY_NR(element)] = TRUE;
14490 DrawGameDoorValues();
14492 else if (element == EL_DC_KEY_WHITE)
14494 player->num_white_keys++;
14496 // display white keys?
14497 // DrawGameDoorValues();
14499 else if (IS_ENVELOPE(element))
14501 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14503 if (!wait_for_snapping)
14504 player->show_envelope = element;
14506 else if (element == EL_EMC_LENSES)
14508 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14510 RedrawAllInvisibleElementsForLenses();
14512 else if (element == EL_EMC_MAGNIFIER)
14514 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14516 RedrawAllInvisibleElementsForMagnifier();
14518 else if (IS_DROPPABLE(element) ||
14519 IS_THROWABLE(element)) // can be collected and dropped
14523 if (collect_count == 0)
14524 player->inventory_infinite_element = element;
14526 for (i = 0; i < collect_count; i++)
14527 if (player->inventory_size < MAX_INVENTORY_SIZE)
14528 player->inventory_element[player->inventory_size++] = element;
14530 DrawGameDoorValues();
14532 else if (collect_count > 0)
14534 game.gems_still_needed -= collect_count;
14535 if (game.gems_still_needed < 0)
14536 game.gems_still_needed = 0;
14538 game.snapshot.collected_item = TRUE;
14540 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14542 DisplayGameControlValues();
14545 RaiseScoreElement(element);
14546 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14548 // use old behaviour for old levels (collecting)
14549 if (!level.finish_dig_collect && is_player)
14551 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14552 player->index_bit, dig_side);
14554 // if collecting triggered player relocation, finish collecting tile
14555 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14556 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14559 if (mode == DF_SNAP)
14561 if (level.block_snap_field)
14562 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14564 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14566 // use old behaviour for old levels (snapping)
14567 if (!level.finish_dig_collect)
14568 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14569 player->index_bit, dig_side);
14572 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14574 if (mode == DF_SNAP && element != EL_BD_ROCK)
14575 return MP_NO_ACTION;
14577 if (CAN_FALL(element) && dy)
14578 return MP_NO_ACTION;
14580 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14581 !(element == EL_SPRING && level.use_spring_bug))
14582 return MP_NO_ACTION;
14584 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14585 ((move_direction & MV_VERTICAL &&
14586 ((element_info[element].move_pattern & MV_LEFT &&
14587 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14588 (element_info[element].move_pattern & MV_RIGHT &&
14589 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14590 (move_direction & MV_HORIZONTAL &&
14591 ((element_info[element].move_pattern & MV_UP &&
14592 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14593 (element_info[element].move_pattern & MV_DOWN &&
14594 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14595 return MP_NO_ACTION;
14597 // do not push elements already moving away faster than player
14598 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14599 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14600 return MP_NO_ACTION;
14602 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14604 if (player->push_delay_value == -1 || !player_was_pushing)
14605 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14607 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14609 if (player->push_delay_value == -1)
14610 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14612 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14614 if (!player->is_pushing)
14615 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14618 player->is_pushing = TRUE;
14619 player->is_active = TRUE;
14621 if (!(IN_LEV_FIELD(nextx, nexty) &&
14622 (IS_FREE(nextx, nexty) ||
14623 (IS_SB_ELEMENT(element) &&
14624 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14625 (IS_CUSTOM_ELEMENT(element) &&
14626 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14627 return MP_NO_ACTION;
14629 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14630 return MP_NO_ACTION;
14632 if (player->push_delay == -1) // new pushing; restart delay
14633 player->push_delay = 0;
14635 if (player->push_delay < player->push_delay_value &&
14636 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14637 element != EL_SPRING && element != EL_BALLOON)
14639 // make sure that there is no move delay before next try to push
14640 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14641 player->move_delay = 0;
14643 return MP_NO_ACTION;
14646 if (IS_CUSTOM_ELEMENT(element) &&
14647 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14649 if (!DigFieldByCE(nextx, nexty, element))
14650 return MP_NO_ACTION;
14653 if (IS_SB_ELEMENT(element))
14655 boolean sokoban_task_solved = FALSE;
14657 if (element == EL_SOKOBAN_FIELD_FULL)
14659 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14661 IncrementSokobanFieldsNeeded();
14662 IncrementSokobanObjectsNeeded();
14665 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14667 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14669 DecrementSokobanFieldsNeeded();
14670 DecrementSokobanObjectsNeeded();
14672 // sokoban object was pushed from empty field to sokoban field
14673 if (Back[x][y] == EL_EMPTY)
14674 sokoban_task_solved = TRUE;
14677 Tile[x][y] = EL_SOKOBAN_OBJECT;
14679 if (Back[x][y] == Back[nextx][nexty])
14680 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14681 else if (Back[x][y] != 0)
14682 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14685 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14688 if (sokoban_task_solved &&
14689 game.sokoban_fields_still_needed == 0 &&
14690 game.sokoban_objects_still_needed == 0 &&
14691 level.auto_exit_sokoban)
14693 game.players_still_needed = 0;
14697 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14701 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14703 InitMovingField(x, y, move_direction);
14704 GfxAction[x][y] = ACTION_PUSHING;
14706 if (mode == DF_SNAP)
14707 ContinueMoving(x, y);
14709 MovPos[x][y] = (dx != 0 ? dx : dy);
14711 Pushed[x][y] = TRUE;
14712 Pushed[nextx][nexty] = TRUE;
14714 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14715 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14717 player->push_delay_value = -1; // get new value later
14719 // check for element change _after_ element has been pushed
14720 if (game.use_change_when_pushing_bug)
14722 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14723 player->index_bit, dig_side);
14724 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14725 player->index_bit, dig_side);
14728 else if (IS_SWITCHABLE(element))
14730 if (PLAYER_SWITCHING(player, x, y))
14732 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14733 player->index_bit, dig_side);
14738 player->is_switching = TRUE;
14739 player->switch_x = x;
14740 player->switch_y = y;
14742 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14744 if (element == EL_ROBOT_WHEEL)
14746 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14748 game.robot_wheel_x = x;
14749 game.robot_wheel_y = y;
14750 game.robot_wheel_active = TRUE;
14752 TEST_DrawLevelField(x, y);
14754 else if (element == EL_SP_TERMINAL)
14758 SCAN_PLAYFIELD(xx, yy)
14760 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14764 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14766 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14768 ResetGfxAnimation(xx, yy);
14769 TEST_DrawLevelField(xx, yy);
14773 else if (IS_BELT_SWITCH(element))
14775 ToggleBeltSwitch(x, y);
14777 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14778 element == EL_SWITCHGATE_SWITCH_DOWN ||
14779 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14780 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14782 ToggleSwitchgateSwitch(x, y);
14784 else if (element == EL_LIGHT_SWITCH ||
14785 element == EL_LIGHT_SWITCH_ACTIVE)
14787 ToggleLightSwitch(x, y);
14789 else if (element == EL_TIMEGATE_SWITCH ||
14790 element == EL_DC_TIMEGATE_SWITCH)
14792 ActivateTimegateSwitch(x, y);
14794 else if (element == EL_BALLOON_SWITCH_LEFT ||
14795 element == EL_BALLOON_SWITCH_RIGHT ||
14796 element == EL_BALLOON_SWITCH_UP ||
14797 element == EL_BALLOON_SWITCH_DOWN ||
14798 element == EL_BALLOON_SWITCH_NONE ||
14799 element == EL_BALLOON_SWITCH_ANY)
14801 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14802 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14803 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14804 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14805 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14808 else if (element == EL_LAMP)
14810 Tile[x][y] = EL_LAMP_ACTIVE;
14811 game.lights_still_needed--;
14813 ResetGfxAnimation(x, y);
14814 TEST_DrawLevelField(x, y);
14816 else if (element == EL_TIME_ORB_FULL)
14818 Tile[x][y] = EL_TIME_ORB_EMPTY;
14820 if (level.time > 0 || level.use_time_orb_bug)
14822 TimeLeft += level.time_orb_time;
14823 game.no_time_limit = FALSE;
14825 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14827 DisplayGameControlValues();
14830 ResetGfxAnimation(x, y);
14831 TEST_DrawLevelField(x, y);
14833 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14834 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14838 game.ball_active = !game.ball_active;
14840 SCAN_PLAYFIELD(xx, yy)
14842 int e = Tile[xx][yy];
14844 if (game.ball_active)
14846 if (e == EL_EMC_MAGIC_BALL)
14847 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14848 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14849 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14853 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14854 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14855 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14856 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14861 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14862 player->index_bit, dig_side);
14864 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14865 player->index_bit, dig_side);
14867 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14868 player->index_bit, dig_side);
14874 if (!PLAYER_SWITCHING(player, x, y))
14876 player->is_switching = TRUE;
14877 player->switch_x = x;
14878 player->switch_y = y;
14880 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14881 player->index_bit, dig_side);
14882 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14883 player->index_bit, dig_side);
14885 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14886 player->index_bit, dig_side);
14887 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14888 player->index_bit, dig_side);
14891 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14892 player->index_bit, dig_side);
14893 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14894 player->index_bit, dig_side);
14896 return MP_NO_ACTION;
14899 player->push_delay = -1;
14901 if (is_player) // function can also be called by EL_PENGUIN
14903 if (Tile[x][y] != element) // really digged/collected something
14905 player->is_collecting = !player->is_digging;
14906 player->is_active = TRUE;
14908 player->last_removed_element = element;
14915 static boolean DigFieldByCE(int x, int y, int digging_element)
14917 int element = Tile[x][y];
14919 if (!IS_FREE(x, y))
14921 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14922 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14925 // no element can dig solid indestructible elements
14926 if (IS_INDESTRUCTIBLE(element) &&
14927 !IS_DIGGABLE(element) &&
14928 !IS_COLLECTIBLE(element))
14931 if (AmoebaNr[x][y] &&
14932 (element == EL_AMOEBA_FULL ||
14933 element == EL_BD_AMOEBA ||
14934 element == EL_AMOEBA_GROWING))
14936 AmoebaCnt[AmoebaNr[x][y]]--;
14937 AmoebaCnt2[AmoebaNr[x][y]]--;
14940 if (IS_MOVING(x, y))
14941 RemoveMovingField(x, y);
14945 TEST_DrawLevelField(x, y);
14948 // if digged element was about to explode, prevent the explosion
14949 ExplodeField[x][y] = EX_TYPE_NONE;
14951 PlayLevelSoundAction(x, y, action);
14954 Store[x][y] = EL_EMPTY;
14956 // this makes it possible to leave the removed element again
14957 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14958 Store[x][y] = element;
14963 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14965 int jx = player->jx, jy = player->jy;
14966 int x = jx + dx, y = jy + dy;
14967 int snap_direction = (dx == -1 ? MV_LEFT :
14968 dx == +1 ? MV_RIGHT :
14970 dy == +1 ? MV_DOWN : MV_NONE);
14971 boolean can_continue_snapping = (level.continuous_snapping &&
14972 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14974 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14977 if (!player->active || !IN_LEV_FIELD(x, y))
14985 if (player->MovPos == 0)
14986 player->is_pushing = FALSE;
14988 player->is_snapping = FALSE;
14990 if (player->MovPos == 0)
14992 player->is_moving = FALSE;
14993 player->is_digging = FALSE;
14994 player->is_collecting = FALSE;
15000 // prevent snapping with already pressed snap key when not allowed
15001 if (player->is_snapping && !can_continue_snapping)
15004 player->MovDir = snap_direction;
15006 if (player->MovPos == 0)
15008 player->is_moving = FALSE;
15009 player->is_digging = FALSE;
15010 player->is_collecting = FALSE;
15013 player->is_dropping = FALSE;
15014 player->is_dropping_pressed = FALSE;
15015 player->drop_pressed_delay = 0;
15017 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15020 player->is_snapping = TRUE;
15021 player->is_active = TRUE;
15023 if (player->MovPos == 0)
15025 player->is_moving = FALSE;
15026 player->is_digging = FALSE;
15027 player->is_collecting = FALSE;
15030 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15031 TEST_DrawLevelField(player->last_jx, player->last_jy);
15033 TEST_DrawLevelField(x, y);
15038 static boolean DropElement(struct PlayerInfo *player)
15040 int old_element, new_element;
15041 int dropx = player->jx, dropy = player->jy;
15042 int drop_direction = player->MovDir;
15043 int drop_side = drop_direction;
15044 int drop_element = get_next_dropped_element(player);
15046 /* do not drop an element on top of another element; when holding drop key
15047 pressed without moving, dropped element must move away before the next
15048 element can be dropped (this is especially important if the next element
15049 is dynamite, which can be placed on background for historical reasons) */
15050 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15053 if (IS_THROWABLE(drop_element))
15055 dropx += GET_DX_FROM_DIR(drop_direction);
15056 dropy += GET_DY_FROM_DIR(drop_direction);
15058 if (!IN_LEV_FIELD(dropx, dropy))
15062 old_element = Tile[dropx][dropy]; // old element at dropping position
15063 new_element = drop_element; // default: no change when dropping
15065 // check if player is active, not moving and ready to drop
15066 if (!player->active || player->MovPos || player->drop_delay > 0)
15069 // check if player has anything that can be dropped
15070 if (new_element == EL_UNDEFINED)
15073 // only set if player has anything that can be dropped
15074 player->is_dropping_pressed = TRUE;
15076 // check if drop key was pressed long enough for EM style dynamite
15077 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15080 // check if anything can be dropped at the current position
15081 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15084 // collected custom elements can only be dropped on empty fields
15085 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15088 if (old_element != EL_EMPTY)
15089 Back[dropx][dropy] = old_element; // store old element on this field
15091 ResetGfxAnimation(dropx, dropy);
15092 ResetRandomAnimationValue(dropx, dropy);
15094 if (player->inventory_size > 0 ||
15095 player->inventory_infinite_element != EL_UNDEFINED)
15097 if (player->inventory_size > 0)
15099 player->inventory_size--;
15101 DrawGameDoorValues();
15103 if (new_element == EL_DYNAMITE)
15104 new_element = EL_DYNAMITE_ACTIVE;
15105 else if (new_element == EL_EM_DYNAMITE)
15106 new_element = EL_EM_DYNAMITE_ACTIVE;
15107 else if (new_element == EL_SP_DISK_RED)
15108 new_element = EL_SP_DISK_RED_ACTIVE;
15111 Tile[dropx][dropy] = new_element;
15113 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15114 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15115 el2img(Tile[dropx][dropy]), 0);
15117 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15119 // needed if previous element just changed to "empty" in the last frame
15120 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15122 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15123 player->index_bit, drop_side);
15124 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15126 player->index_bit, drop_side);
15128 TestIfElementTouchesCustomElement(dropx, dropy);
15130 else // player is dropping a dyna bomb
15132 player->dynabombs_left--;
15134 Tile[dropx][dropy] = new_element;
15136 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15137 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15138 el2img(Tile[dropx][dropy]), 0);
15140 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15143 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15144 InitField_WithBug1(dropx, dropy, FALSE);
15146 new_element = Tile[dropx][dropy]; // element might have changed
15148 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15149 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15151 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15152 MovDir[dropx][dropy] = drop_direction;
15154 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15156 // do not cause impact style collision by dropping elements that can fall
15157 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15160 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15161 player->is_dropping = TRUE;
15163 player->drop_pressed_delay = 0;
15164 player->is_dropping_pressed = FALSE;
15166 player->drop_x = dropx;
15167 player->drop_y = dropy;
15172 // ----------------------------------------------------------------------------
15173 // game sound playing functions
15174 // ----------------------------------------------------------------------------
15176 static int *loop_sound_frame = NULL;
15177 static int *loop_sound_volume = NULL;
15179 void InitPlayLevelSound(void)
15181 int num_sounds = getSoundListSize();
15183 checked_free(loop_sound_frame);
15184 checked_free(loop_sound_volume);
15186 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15187 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15190 static void PlayLevelSound(int x, int y, int nr)
15192 int sx = SCREENX(x), sy = SCREENY(y);
15193 int volume, stereo_position;
15194 int max_distance = 8;
15195 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15197 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15198 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15201 if (!IN_LEV_FIELD(x, y) ||
15202 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15203 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15206 volume = SOUND_MAX_VOLUME;
15208 if (!IN_SCR_FIELD(sx, sy))
15210 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15211 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15213 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15216 stereo_position = (SOUND_MAX_LEFT +
15217 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15218 (SCR_FIELDX + 2 * max_distance));
15220 if (IS_LOOP_SOUND(nr))
15222 /* This assures that quieter loop sounds do not overwrite louder ones,
15223 while restarting sound volume comparison with each new game frame. */
15225 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15228 loop_sound_volume[nr] = volume;
15229 loop_sound_frame[nr] = FrameCounter;
15232 PlaySoundExt(nr, volume, stereo_position, type);
15235 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15237 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15238 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15239 y < LEVELY(BY1) ? LEVELY(BY1) :
15240 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15244 static void PlayLevelSoundAction(int x, int y, int action)
15246 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15249 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15251 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15253 if (sound_effect != SND_UNDEFINED)
15254 PlayLevelSound(x, y, sound_effect);
15257 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15260 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15262 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15263 PlayLevelSound(x, y, sound_effect);
15266 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15268 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15270 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15271 PlayLevelSound(x, y, sound_effect);
15274 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15276 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15278 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15279 StopSound(sound_effect);
15282 static int getLevelMusicNr(void)
15284 if (levelset.music[level_nr] != MUS_UNDEFINED)
15285 return levelset.music[level_nr]; // from config file
15287 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15290 static void FadeLevelSounds(void)
15295 static void FadeLevelMusic(void)
15297 int music_nr = getLevelMusicNr();
15298 char *curr_music = getCurrentlyPlayingMusicFilename();
15299 char *next_music = getMusicInfoEntryFilename(music_nr);
15301 if (!strEqual(curr_music, next_music))
15305 void FadeLevelSoundsAndMusic(void)
15311 static void PlayLevelMusic(void)
15313 int music_nr = getLevelMusicNr();
15314 char *curr_music = getCurrentlyPlayingMusicFilename();
15315 char *next_music = getMusicInfoEntryFilename(music_nr);
15317 if (!strEqual(curr_music, next_music))
15318 PlayMusicLoop(music_nr);
15321 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15323 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15325 int x = xx - offset;
15326 int y = yy - offset;
15331 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15335 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15339 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15343 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15347 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15351 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15355 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15358 case SOUND_android_clone:
15359 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15362 case SOUND_android_move:
15363 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15367 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15371 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15375 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15378 case SOUND_eater_eat:
15379 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15383 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15386 case SOUND_collect:
15387 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15390 case SOUND_diamond:
15391 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15395 // !!! CHECK THIS !!!
15397 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15399 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15403 case SOUND_wonderfall:
15404 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15408 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15412 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15416 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15420 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15424 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15428 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15432 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15436 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15439 case SOUND_exit_open:
15440 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15443 case SOUND_exit_leave:
15444 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15447 case SOUND_dynamite:
15448 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15452 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15456 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15460 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15464 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15468 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15472 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15476 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15481 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15483 int element = map_element_SP_to_RND(element_sp);
15484 int action = map_action_SP_to_RND(action_sp);
15485 int offset = (setup.sp_show_border_elements ? 0 : 1);
15486 int x = xx - offset;
15487 int y = yy - offset;
15489 PlayLevelSoundElementAction(x, y, element, action);
15492 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15494 int element = map_element_MM_to_RND(element_mm);
15495 int action = map_action_MM_to_RND(action_mm);
15497 int x = xx - offset;
15498 int y = yy - offset;
15500 if (!IS_MM_ELEMENT(element))
15501 element = EL_MM_DEFAULT;
15503 PlayLevelSoundElementAction(x, y, element, action);
15506 void PlaySound_MM(int sound_mm)
15508 int sound = map_sound_MM_to_RND(sound_mm);
15510 if (sound == SND_UNDEFINED)
15516 void PlaySoundLoop_MM(int sound_mm)
15518 int sound = map_sound_MM_to_RND(sound_mm);
15520 if (sound == SND_UNDEFINED)
15523 PlaySoundLoop(sound);
15526 void StopSound_MM(int sound_mm)
15528 int sound = map_sound_MM_to_RND(sound_mm);
15530 if (sound == SND_UNDEFINED)
15536 void RaiseScore(int value)
15538 game.score += value;
15540 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15542 DisplayGameControlValues();
15545 void RaiseScoreElement(int element)
15550 case EL_BD_DIAMOND:
15551 case EL_EMERALD_YELLOW:
15552 case EL_EMERALD_RED:
15553 case EL_EMERALD_PURPLE:
15554 case EL_SP_INFOTRON:
15555 RaiseScore(level.score[SC_EMERALD]);
15558 RaiseScore(level.score[SC_DIAMOND]);
15561 RaiseScore(level.score[SC_CRYSTAL]);
15564 RaiseScore(level.score[SC_PEARL]);
15567 case EL_BD_BUTTERFLY:
15568 case EL_SP_ELECTRON:
15569 RaiseScore(level.score[SC_BUG]);
15572 case EL_BD_FIREFLY:
15573 case EL_SP_SNIKSNAK:
15574 RaiseScore(level.score[SC_SPACESHIP]);
15577 case EL_DARK_YAMYAM:
15578 RaiseScore(level.score[SC_YAMYAM]);
15581 RaiseScore(level.score[SC_ROBOT]);
15584 RaiseScore(level.score[SC_PACMAN]);
15587 RaiseScore(level.score[SC_NUT]);
15590 case EL_EM_DYNAMITE:
15591 case EL_SP_DISK_RED:
15592 case EL_DYNABOMB_INCREASE_NUMBER:
15593 case EL_DYNABOMB_INCREASE_SIZE:
15594 case EL_DYNABOMB_INCREASE_POWER:
15595 RaiseScore(level.score[SC_DYNAMITE]);
15597 case EL_SHIELD_NORMAL:
15598 case EL_SHIELD_DEADLY:
15599 RaiseScore(level.score[SC_SHIELD]);
15601 case EL_EXTRA_TIME:
15602 RaiseScore(level.extra_time_score);
15616 case EL_DC_KEY_WHITE:
15617 RaiseScore(level.score[SC_KEY]);
15620 RaiseScore(element_info[element].collect_score);
15625 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15627 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15631 // prevent short reactivation of overlay buttons while closing door
15632 SetOverlayActive(FALSE);
15634 // door may still be open due to skipped or envelope style request
15635 CloseDoor(DOOR_CLOSE_1);
15638 if (network.enabled)
15639 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15643 FadeSkipNextFadeIn();
15645 SetGameStatus(GAME_MODE_MAIN);
15650 else // continue playing the game
15652 if (tape.playing && tape.deactivate_display)
15653 TapeDeactivateDisplayOff(TRUE);
15655 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15657 if (tape.playing && tape.deactivate_display)
15658 TapeDeactivateDisplayOn();
15662 void RequestQuitGame(boolean escape_key_pressed)
15664 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15665 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15666 level_editor_test_game);
15667 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15670 RequestQuitGameExt(skip_request, quick_quit,
15671 "Do you really want to quit the game?");
15674 void RequestRestartGame(char *message)
15676 game.restart_game_message = NULL;
15678 boolean has_started_game = hasStartedNetworkGame();
15679 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15681 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15683 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15687 // needed in case of envelope request to close game panel
15688 CloseDoor(DOOR_CLOSE_1);
15690 SetGameStatus(GAME_MODE_MAIN);
15696 void CheckGameOver(void)
15698 static boolean last_game_over = FALSE;
15699 static int game_over_delay = 0;
15700 int game_over_delay_value = 50;
15701 boolean game_over = checkGameFailed();
15703 // do not handle game over if request dialog is already active
15704 if (game.request_active)
15707 // do not ask to play again if game was never actually played
15708 if (!game.GamePlayed)
15713 last_game_over = FALSE;
15714 game_over_delay = game_over_delay_value;
15719 if (game_over_delay > 0)
15726 if (last_game_over != game_over)
15727 game.restart_game_message = (hasStartedNetworkGame() ?
15728 "Game over! Play it again?" :
15731 last_game_over = game_over;
15734 boolean checkGameSolved(void)
15736 // set for all game engines if level was solved
15737 return game.LevelSolved_GameEnd;
15740 boolean checkGameFailed(void)
15742 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15743 return (game_em.game_over && !game_em.level_solved);
15744 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15745 return (game_sp.game_over && !game_sp.level_solved);
15746 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15747 return (game_mm.game_over && !game_mm.level_solved);
15748 else // GAME_ENGINE_TYPE_RND
15749 return (game.GameOver && !game.LevelSolved);
15752 boolean checkGameEnded(void)
15754 return (checkGameSolved() || checkGameFailed());
15758 // ----------------------------------------------------------------------------
15759 // random generator functions
15760 // ----------------------------------------------------------------------------
15762 unsigned int InitEngineRandom_RND(int seed)
15764 game.num_random_calls = 0;
15766 return InitEngineRandom(seed);
15769 unsigned int RND(int max)
15773 game.num_random_calls++;
15775 return GetEngineRandom(max);
15782 // ----------------------------------------------------------------------------
15783 // game engine snapshot handling functions
15784 // ----------------------------------------------------------------------------
15786 struct EngineSnapshotInfo
15788 // runtime values for custom element collect score
15789 int collect_score[NUM_CUSTOM_ELEMENTS];
15791 // runtime values for group element choice position
15792 int choice_pos[NUM_GROUP_ELEMENTS];
15794 // runtime values for belt position animations
15795 int belt_graphic[4][NUM_BELT_PARTS];
15796 int belt_anim_mode[4][NUM_BELT_PARTS];
15799 static struct EngineSnapshotInfo engine_snapshot_rnd;
15800 static char *snapshot_level_identifier = NULL;
15801 static int snapshot_level_nr = -1;
15803 static void SaveEngineSnapshotValues_RND(void)
15805 static int belt_base_active_element[4] =
15807 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15808 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15809 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15810 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15814 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15816 int element = EL_CUSTOM_START + i;
15818 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15821 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15823 int element = EL_GROUP_START + i;
15825 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15828 for (i = 0; i < 4; i++)
15830 for (j = 0; j < NUM_BELT_PARTS; j++)
15832 int element = belt_base_active_element[i] + j;
15833 int graphic = el2img(element);
15834 int anim_mode = graphic_info[graphic].anim_mode;
15836 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15837 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15842 static void LoadEngineSnapshotValues_RND(void)
15844 unsigned int num_random_calls = game.num_random_calls;
15847 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15849 int element = EL_CUSTOM_START + i;
15851 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15854 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15856 int element = EL_GROUP_START + i;
15858 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15861 for (i = 0; i < 4; i++)
15863 for (j = 0; j < NUM_BELT_PARTS; j++)
15865 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15866 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15868 graphic_info[graphic].anim_mode = anim_mode;
15872 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15874 InitRND(tape.random_seed);
15875 for (i = 0; i < num_random_calls; i++)
15879 if (game.num_random_calls != num_random_calls)
15881 Error("number of random calls out of sync");
15882 Error("number of random calls should be %d", num_random_calls);
15883 Error("number of random calls is %d", game.num_random_calls);
15885 Fail("this should not happen -- please debug");
15889 void FreeEngineSnapshotSingle(void)
15891 FreeSnapshotSingle();
15893 setString(&snapshot_level_identifier, NULL);
15894 snapshot_level_nr = -1;
15897 void FreeEngineSnapshotList(void)
15899 FreeSnapshotList();
15902 static ListNode *SaveEngineSnapshotBuffers(void)
15904 ListNode *buffers = NULL;
15906 // copy some special values to a structure better suited for the snapshot
15908 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15909 SaveEngineSnapshotValues_RND();
15910 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15911 SaveEngineSnapshotValues_EM();
15912 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15913 SaveEngineSnapshotValues_SP(&buffers);
15914 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15915 SaveEngineSnapshotValues_MM(&buffers);
15917 // save values stored in special snapshot structure
15919 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15920 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15921 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15922 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15923 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15924 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15925 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15928 // save further RND engine values
15930 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15931 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15934 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15935 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15936 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15937 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15940 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15951 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15959 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15962 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15966 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15976 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15979 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15980 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15985 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15989 ListNode *node = engine_snapshot_list_rnd;
15992 while (node != NULL)
15994 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15999 Debug("game:playing:SaveEngineSnapshotBuffers",
16000 "size of engine snapshot: %d bytes", num_bytes);
16006 void SaveEngineSnapshotSingle(void)
16008 ListNode *buffers = SaveEngineSnapshotBuffers();
16010 // finally save all snapshot buffers to single snapshot
16011 SaveSnapshotSingle(buffers);
16013 // save level identification information
16014 setString(&snapshot_level_identifier, leveldir_current->identifier);
16015 snapshot_level_nr = level_nr;
16018 boolean CheckSaveEngineSnapshotToList(void)
16020 boolean save_snapshot =
16021 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16022 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16023 game.snapshot.changed_action) ||
16024 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16025 game.snapshot.collected_item));
16027 game.snapshot.changed_action = FALSE;
16028 game.snapshot.collected_item = FALSE;
16029 game.snapshot.save_snapshot = save_snapshot;
16031 return save_snapshot;
16034 void SaveEngineSnapshotToList(void)
16036 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16040 ListNode *buffers = SaveEngineSnapshotBuffers();
16042 // finally save all snapshot buffers to snapshot list
16043 SaveSnapshotToList(buffers);
16046 void SaveEngineSnapshotToListInitial(void)
16048 FreeEngineSnapshotList();
16050 SaveEngineSnapshotToList();
16053 static void LoadEngineSnapshotValues(void)
16055 // restore special values from snapshot structure
16057 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16058 LoadEngineSnapshotValues_RND();
16059 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16060 LoadEngineSnapshotValues_EM();
16061 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16062 LoadEngineSnapshotValues_SP();
16063 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16064 LoadEngineSnapshotValues_MM();
16067 void LoadEngineSnapshotSingle(void)
16069 LoadSnapshotSingle();
16071 LoadEngineSnapshotValues();
16074 static void LoadEngineSnapshot_Undo(int steps)
16076 LoadSnapshotFromList_Older(steps);
16078 LoadEngineSnapshotValues();
16081 static void LoadEngineSnapshot_Redo(int steps)
16083 LoadSnapshotFromList_Newer(steps);
16085 LoadEngineSnapshotValues();
16088 boolean CheckEngineSnapshotSingle(void)
16090 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16091 snapshot_level_nr == level_nr);
16094 boolean CheckEngineSnapshotList(void)
16096 return CheckSnapshotList();
16100 // ---------- new game button stuff -------------------------------------------
16107 boolean *setup_value;
16108 boolean allowed_on_tape;
16109 boolean is_touch_button;
16111 } gamebutton_info[NUM_GAME_BUTTONS] =
16114 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16115 GAME_CTRL_ID_STOP, NULL,
16116 TRUE, FALSE, "stop game"
16119 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16120 GAME_CTRL_ID_PAUSE, NULL,
16121 TRUE, FALSE, "pause game"
16124 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16125 GAME_CTRL_ID_PLAY, NULL,
16126 TRUE, FALSE, "play game"
16129 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16130 GAME_CTRL_ID_UNDO, NULL,
16131 TRUE, FALSE, "undo step"
16134 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16135 GAME_CTRL_ID_REDO, NULL,
16136 TRUE, FALSE, "redo step"
16139 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16140 GAME_CTRL_ID_SAVE, NULL,
16141 TRUE, FALSE, "save game"
16144 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16145 GAME_CTRL_ID_PAUSE2, NULL,
16146 TRUE, FALSE, "pause game"
16149 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16150 GAME_CTRL_ID_LOAD, NULL,
16151 TRUE, FALSE, "load game"
16154 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16155 GAME_CTRL_ID_PANEL_STOP, NULL,
16156 FALSE, FALSE, "stop game"
16159 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16160 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16161 FALSE, FALSE, "pause game"
16164 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16165 GAME_CTRL_ID_PANEL_PLAY, NULL,
16166 FALSE, FALSE, "play game"
16169 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16170 GAME_CTRL_ID_TOUCH_STOP, NULL,
16171 FALSE, TRUE, "stop game"
16174 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16175 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16176 FALSE, TRUE, "pause game"
16179 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16180 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16181 TRUE, FALSE, "background music on/off"
16184 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16185 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16186 TRUE, FALSE, "sound loops on/off"
16189 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16190 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16191 TRUE, FALSE, "normal sounds on/off"
16194 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16195 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16196 FALSE, FALSE, "background music on/off"
16199 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16200 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16201 FALSE, FALSE, "sound loops on/off"
16204 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16205 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16206 FALSE, FALSE, "normal sounds on/off"
16210 void CreateGameButtons(void)
16214 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16216 int graphic = gamebutton_info[i].graphic;
16217 struct GraphicInfo *gfx = &graphic_info[graphic];
16218 struct XY *pos = gamebutton_info[i].pos;
16219 struct GadgetInfo *gi;
16222 unsigned int event_mask;
16223 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16224 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16225 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16226 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16227 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16228 int gd_x = gfx->src_x;
16229 int gd_y = gfx->src_y;
16230 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16231 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16232 int gd_xa = gfx->src_x + gfx->active_xoffset;
16233 int gd_ya = gfx->src_y + gfx->active_yoffset;
16234 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16235 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16236 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16237 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16240 if (gfx->bitmap == NULL)
16242 game_gadget[id] = NULL;
16247 if (id == GAME_CTRL_ID_STOP ||
16248 id == GAME_CTRL_ID_PANEL_STOP ||
16249 id == GAME_CTRL_ID_TOUCH_STOP ||
16250 id == GAME_CTRL_ID_PLAY ||
16251 id == GAME_CTRL_ID_PANEL_PLAY ||
16252 id == GAME_CTRL_ID_SAVE ||
16253 id == GAME_CTRL_ID_LOAD)
16255 button_type = GD_TYPE_NORMAL_BUTTON;
16257 event_mask = GD_EVENT_RELEASED;
16259 else if (id == GAME_CTRL_ID_UNDO ||
16260 id == GAME_CTRL_ID_REDO)
16262 button_type = GD_TYPE_NORMAL_BUTTON;
16264 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16268 button_type = GD_TYPE_CHECK_BUTTON;
16269 checked = (gamebutton_info[i].setup_value != NULL ?
16270 *gamebutton_info[i].setup_value : FALSE);
16271 event_mask = GD_EVENT_PRESSED;
16274 gi = CreateGadget(GDI_CUSTOM_ID, id,
16275 GDI_IMAGE_ID, graphic,
16276 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16279 GDI_WIDTH, gfx->width,
16280 GDI_HEIGHT, gfx->height,
16281 GDI_TYPE, button_type,
16282 GDI_STATE, GD_BUTTON_UNPRESSED,
16283 GDI_CHECKED, checked,
16284 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16285 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16286 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16287 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16288 GDI_DIRECT_DRAW, FALSE,
16289 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16290 GDI_EVENT_MASK, event_mask,
16291 GDI_CALLBACK_ACTION, HandleGameButtons,
16295 Fail("cannot create gadget");
16297 game_gadget[id] = gi;
16301 void FreeGameButtons(void)
16305 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16306 FreeGadget(game_gadget[i]);
16309 static void UnmapGameButtonsAtSamePosition(int id)
16313 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16315 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16316 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16317 UnmapGadget(game_gadget[i]);
16320 static void UnmapGameButtonsAtSamePosition_All(void)
16322 if (setup.show_load_save_buttons)
16324 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16325 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16326 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16328 else if (setup.show_undo_redo_buttons)
16330 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16331 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16332 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16336 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16337 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16338 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16340 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16341 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16342 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16346 void MapLoadSaveButtons(void)
16348 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16349 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16351 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16352 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16355 void MapUndoRedoButtons(void)
16357 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16358 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16360 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16361 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16364 void ModifyPauseButtons(void)
16368 GAME_CTRL_ID_PAUSE,
16369 GAME_CTRL_ID_PAUSE2,
16370 GAME_CTRL_ID_PANEL_PAUSE,
16371 GAME_CTRL_ID_TOUCH_PAUSE,
16376 for (i = 0; ids[i] > -1; i++)
16377 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16380 static void MapGameButtonsExt(boolean on_tape)
16384 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16385 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16386 MapGadget(game_gadget[i]);
16388 UnmapGameButtonsAtSamePosition_All();
16390 RedrawGameButtons();
16393 static void UnmapGameButtonsExt(boolean on_tape)
16397 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16398 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16399 UnmapGadget(game_gadget[i]);
16402 static void RedrawGameButtonsExt(boolean on_tape)
16406 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16407 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16408 RedrawGadget(game_gadget[i]);
16411 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16416 gi->checked = state;
16419 static void RedrawSoundButtonGadget(int id)
16421 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16422 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16423 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16424 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16425 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16426 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16429 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16430 RedrawGadget(game_gadget[id2]);
16433 void MapGameButtons(void)
16435 MapGameButtonsExt(FALSE);
16438 void UnmapGameButtons(void)
16440 UnmapGameButtonsExt(FALSE);
16443 void RedrawGameButtons(void)
16445 RedrawGameButtonsExt(FALSE);
16448 void MapGameButtonsOnTape(void)
16450 MapGameButtonsExt(TRUE);
16453 void UnmapGameButtonsOnTape(void)
16455 UnmapGameButtonsExt(TRUE);
16458 void RedrawGameButtonsOnTape(void)
16460 RedrawGameButtonsExt(TRUE);
16463 static void GameUndoRedoExt(void)
16465 ClearPlayerAction();
16467 tape.pausing = TRUE;
16470 UpdateAndDisplayGameControlValues();
16472 DrawCompleteVideoDisplay();
16473 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16474 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16475 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16477 ModifyPauseButtons();
16482 static void GameUndo(int steps)
16484 if (!CheckEngineSnapshotList())
16487 int tape_property_bits = tape.property_bits;
16489 LoadEngineSnapshot_Undo(steps);
16491 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16496 static void GameRedo(int steps)
16498 if (!CheckEngineSnapshotList())
16501 int tape_property_bits = tape.property_bits;
16503 LoadEngineSnapshot_Redo(steps);
16505 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16510 static void HandleGameButtonsExt(int id, int button)
16512 static boolean game_undo_executed = FALSE;
16513 int steps = BUTTON_STEPSIZE(button);
16514 boolean handle_game_buttons =
16515 (game_status == GAME_MODE_PLAYING ||
16516 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16518 if (!handle_game_buttons)
16523 case GAME_CTRL_ID_STOP:
16524 case GAME_CTRL_ID_PANEL_STOP:
16525 case GAME_CTRL_ID_TOUCH_STOP:
16526 if (game_status == GAME_MODE_MAIN)
16532 RequestQuitGame(FALSE);
16536 case GAME_CTRL_ID_PAUSE:
16537 case GAME_CTRL_ID_PAUSE2:
16538 case GAME_CTRL_ID_PANEL_PAUSE:
16539 case GAME_CTRL_ID_TOUCH_PAUSE:
16540 if (network.enabled && game_status == GAME_MODE_PLAYING)
16543 SendToServer_ContinuePlaying();
16545 SendToServer_PausePlaying();
16548 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16550 game_undo_executed = FALSE;
16554 case GAME_CTRL_ID_PLAY:
16555 case GAME_CTRL_ID_PANEL_PLAY:
16556 if (game_status == GAME_MODE_MAIN)
16558 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16560 else if (tape.pausing)
16562 if (network.enabled)
16563 SendToServer_ContinuePlaying();
16565 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16569 case GAME_CTRL_ID_UNDO:
16570 // Important: When using "save snapshot when collecting an item" mode,
16571 // load last (current) snapshot for first "undo" after pressing "pause"
16572 // (else the last-but-one snapshot would be loaded, because the snapshot
16573 // pointer already points to the last snapshot when pressing "pause",
16574 // which is fine for "every step/move" mode, but not for "every collect")
16575 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16576 !game_undo_executed)
16579 game_undo_executed = TRUE;
16584 case GAME_CTRL_ID_REDO:
16588 case GAME_CTRL_ID_SAVE:
16592 case GAME_CTRL_ID_LOAD:
16596 case SOUND_CTRL_ID_MUSIC:
16597 case SOUND_CTRL_ID_PANEL_MUSIC:
16598 if (setup.sound_music)
16600 setup.sound_music = FALSE;
16604 else if (audio.music_available)
16606 setup.sound = setup.sound_music = TRUE;
16608 SetAudioMode(setup.sound);
16610 if (game_status == GAME_MODE_PLAYING)
16614 RedrawSoundButtonGadget(id);
16618 case SOUND_CTRL_ID_LOOPS:
16619 case SOUND_CTRL_ID_PANEL_LOOPS:
16620 if (setup.sound_loops)
16621 setup.sound_loops = FALSE;
16622 else if (audio.loops_available)
16624 setup.sound = setup.sound_loops = TRUE;
16626 SetAudioMode(setup.sound);
16629 RedrawSoundButtonGadget(id);
16633 case SOUND_CTRL_ID_SIMPLE:
16634 case SOUND_CTRL_ID_PANEL_SIMPLE:
16635 if (setup.sound_simple)
16636 setup.sound_simple = FALSE;
16637 else if (audio.sound_available)
16639 setup.sound = setup.sound_simple = TRUE;
16641 SetAudioMode(setup.sound);
16644 RedrawSoundButtonGadget(id);
16653 static void HandleGameButtons(struct GadgetInfo *gi)
16655 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16658 void HandleSoundButtonKeys(Key key)
16660 if (key == setup.shortcut.sound_simple)
16661 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16662 else if (key == setup.shortcut.sound_loops)
16663 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16664 else if (key == setup.shortcut.sound_music)
16665 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);