1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2036 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2039 static void InitField_WithBug1(int x, int y, boolean init_game)
2041 InitField(x, y, init_game);
2043 // not needed to call InitMovDir() -- already done by InitField()!
2044 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2045 CAN_MOVE(Tile[x][y]))
2049 static void InitField_WithBug2(int x, int y, boolean init_game)
2051 int old_element = Tile[x][y];
2053 InitField(x, y, init_game);
2055 // not needed to call InitMovDir() -- already done by InitField()!
2056 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2057 CAN_MOVE(old_element) &&
2058 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2061 /* this case is in fact a combination of not less than three bugs:
2062 first, it calls InitMovDir() for elements that can move, although this is
2063 already done by InitField(); then, it checks the element that was at this
2064 field _before_ the call to InitField() (which can change it); lastly, it
2065 was not called for "mole with direction" elements, which were treated as
2066 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2070 static int get_key_element_from_nr(int key_nr)
2072 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2073 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2074 EL_EM_KEY_1 : EL_KEY_1);
2076 return key_base_element + key_nr;
2079 static int get_next_dropped_element(struct PlayerInfo *player)
2081 return (player->inventory_size > 0 ?
2082 player->inventory_element[player->inventory_size - 1] :
2083 player->inventory_infinite_element != EL_UNDEFINED ?
2084 player->inventory_infinite_element :
2085 player->dynabombs_left > 0 ?
2086 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2090 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2092 // pos >= 0: get element from bottom of the stack;
2093 // pos < 0: get element from top of the stack
2097 int min_inventory_size = -pos;
2098 int inventory_pos = player->inventory_size - min_inventory_size;
2099 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2101 return (player->inventory_size >= min_inventory_size ?
2102 player->inventory_element[inventory_pos] :
2103 player->inventory_infinite_element != EL_UNDEFINED ?
2104 player->inventory_infinite_element :
2105 player->dynabombs_left >= min_dynabombs_left ?
2106 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2111 int min_dynabombs_left = pos + 1;
2112 int min_inventory_size = pos + 1 - player->dynabombs_left;
2113 int inventory_pos = pos - player->dynabombs_left;
2115 return (player->inventory_infinite_element != EL_UNDEFINED ?
2116 player->inventory_infinite_element :
2117 player->dynabombs_left >= min_dynabombs_left ?
2118 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 player->inventory_size >= min_inventory_size ?
2120 player->inventory_element[inventory_pos] :
2125 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2127 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2128 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2131 if (gpo1->sort_priority != gpo2->sort_priority)
2132 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2134 compare_result = gpo1->nr - gpo2->nr;
2136 return compare_result;
2139 int getPlayerInventorySize(int player_nr)
2141 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2142 return game_em.ply[player_nr]->dynamite;
2143 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2144 return game_sp.red_disk_count;
2146 return stored_player[player_nr].inventory_size;
2149 static void InitGameControlValues(void)
2153 for (i = 0; game_panel_controls[i].nr != -1; i++)
2155 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2156 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2157 struct TextPosInfo *pos = gpc->pos;
2159 int type = gpc->type;
2163 Error("'game_panel_controls' structure corrupted at %d", i);
2165 Fail("this should not happen -- please debug");
2168 // force update of game controls after initialization
2169 gpc->value = gpc->last_value = -1;
2170 gpc->frame = gpc->last_frame = -1;
2171 gpc->gfx_frame = -1;
2173 // determine panel value width for later calculation of alignment
2174 if (type == TYPE_INTEGER || type == TYPE_STRING)
2176 pos->width = pos->size * getFontWidth(pos->font);
2177 pos->height = getFontHeight(pos->font);
2179 else if (type == TYPE_ELEMENT)
2181 pos->width = pos->size;
2182 pos->height = pos->size;
2185 // fill structure for game panel draw order
2187 gpo->sort_priority = pos->sort_priority;
2190 // sort game panel controls according to sort_priority and control number
2191 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2192 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2195 static void UpdatePlayfieldElementCount(void)
2197 boolean use_element_count = FALSE;
2200 // first check if it is needed at all to calculate playfield element count
2201 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2202 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2203 use_element_count = TRUE;
2205 if (!use_element_count)
2208 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2209 element_info[i].element_count = 0;
2211 SCAN_PLAYFIELD(x, y)
2213 element_info[Tile[x][y]].element_count++;
2216 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2217 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2218 if (IS_IN_GROUP(j, i))
2219 element_info[EL_GROUP_START + i].element_count +=
2220 element_info[j].element_count;
2223 static void UpdateGameControlValues(void)
2226 int time = (game.LevelSolved ?
2227 game.LevelSolved_CountingTime :
2228 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2230 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2231 game_sp.time_played :
2232 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 game_mm.energy_left :
2234 game.no_time_limit ? TimePlayed : TimeLeft);
2235 int score = (game.LevelSolved ?
2236 game.LevelSolved_CountingScore :
2237 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->score :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2244 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2245 game_em.lev->gems_needed :
2246 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2247 game_sp.infotrons_still_needed :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 game_mm.kettles_still_needed :
2250 game.gems_still_needed);
2251 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2252 game_em.lev->gems_needed > 0 :
2253 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2254 game_sp.infotrons_still_needed > 0 :
2255 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2256 game_mm.kettles_still_needed > 0 ||
2257 game_mm.lights_still_needed > 0 :
2258 game.gems_still_needed > 0 ||
2259 game.sokoban_fields_still_needed > 0 ||
2260 game.sokoban_objects_still_needed > 0 ||
2261 game.lights_still_needed > 0);
2262 int health = (game.LevelSolved ?
2263 game.LevelSolved_CountingHealth :
2264 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2265 MM_HEALTH(game_mm.laser_overload_value) :
2267 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2269 UpdatePlayfieldElementCount();
2271 // update game panel control values
2273 // used instead of "level_nr" (for network games)
2274 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2275 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2277 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2278 for (i = 0; i < MAX_NUM_KEYS; i++)
2279 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2280 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2281 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2283 if (game.centered_player_nr == -1)
2285 for (i = 0; i < MAX_PLAYERS; i++)
2287 // only one player in Supaplex game engine
2288 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2291 for (k = 0; k < MAX_NUM_KEYS; k++)
2293 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2295 if (game_em.ply[i]->keys & (1 << k))
2296 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2297 get_key_element_from_nr(k);
2299 else if (stored_player[i].key[k])
2300 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2301 get_key_element_from_nr(k);
2304 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2305 getPlayerInventorySize(i);
2307 if (stored_player[i].num_white_keys > 0)
2308 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2311 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2312 stored_player[i].num_white_keys;
2317 int player_nr = game.centered_player_nr;
2319 for (k = 0; k < MAX_NUM_KEYS; k++)
2321 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2323 if (game_em.ply[player_nr]->keys & (1 << k))
2324 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2325 get_key_element_from_nr(k);
2327 else if (stored_player[player_nr].key[k])
2328 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2329 get_key_element_from_nr(k);
2332 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2333 getPlayerInventorySize(player_nr);
2335 if (stored_player[player_nr].num_white_keys > 0)
2336 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2338 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2339 stored_player[player_nr].num_white_keys;
2342 // re-arrange keys on game panel, if needed or if defined by style settings
2343 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2345 int nr = GAME_PANEL_KEY_1 + i;
2346 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2347 struct TextPosInfo *pos = gpc->pos;
2349 // skip check if key is not in the player's inventory
2350 if (gpc->value == EL_EMPTY)
2353 // check if keys should be arranged on panel from left to right
2354 if (pos->style == STYLE_LEFTMOST_POSITION)
2356 // check previous key positions (left from current key)
2357 for (k = 0; k < i; k++)
2359 int nr_new = GAME_PANEL_KEY_1 + k;
2361 if (game_panel_controls[nr_new].value == EL_EMPTY)
2363 game_panel_controls[nr_new].value = gpc->value;
2364 gpc->value = EL_EMPTY;
2371 // check if "undefined" keys can be placed at some other position
2372 if (pos->x == -1 && pos->y == -1)
2374 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2376 // 1st try: display key at the same position as normal or EM keys
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2383 // 2nd try: display key at the next free position in the key panel
2384 for (k = 0; k < STD_NUM_KEYS; k++)
2386 nr_new = GAME_PANEL_KEY_1 + k;
2388 if (game_panel_controls[nr_new].value == EL_EMPTY)
2390 game_panel_controls[nr_new].value = gpc->value;
2399 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2401 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2402 get_inventory_element_from_pos(local_player, i);
2403 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2404 get_inventory_element_from_pos(local_player, -i - 1);
2407 game_panel_controls[GAME_PANEL_SCORE].value = score;
2408 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2410 game_panel_controls[GAME_PANEL_TIME].value = time;
2412 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2413 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2414 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2416 if (level.time == 0)
2417 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2419 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2421 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2422 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2424 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2427 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2429 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2430 local_player->shield_normal_time_left;
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2432 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2434 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2435 local_player->shield_deadly_time_left;
2437 game_panel_controls[GAME_PANEL_EXIT].value =
2438 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2440 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2441 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2442 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2443 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2444 EL_EMC_MAGIC_BALL_SWITCH);
2446 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2447 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2448 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2449 game.light_time_left;
2451 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2452 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2453 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2454 game.timegate_time_left;
2456 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2457 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2459 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2460 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2461 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2462 game.lenses_time_left;
2464 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2465 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2466 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2467 game.magnify_time_left;
2469 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2470 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2471 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2472 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2473 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2474 EL_BALLOON_SWITCH_NONE);
2476 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2477 local_player->dynabomb_count;
2478 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2479 local_player->dynabomb_size;
2480 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2481 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2483 game_panel_controls[GAME_PANEL_PENGUINS].value =
2484 game.friends_still_needed;
2486 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2487 game.sokoban_objects_still_needed;
2488 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2489 game.sokoban_fields_still_needed;
2491 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2492 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2494 for (i = 0; i < NUM_BELTS; i++)
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2497 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2498 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2499 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2500 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2503 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2504 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2505 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2506 game.magic_wall_time_left;
2508 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2509 local_player->gravity;
2511 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2512 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2514 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2515 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2516 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2517 game.panel.element[i].id : EL_UNDEFINED);
2519 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2520 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2521 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2522 element_info[game.panel.element_count[i].id].element_count : 0);
2524 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2525 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2526 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2527 element_info[game.panel.ce_score[i].id].collect_score : 0);
2529 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2530 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2531 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2532 element_info[game.panel.ce_score_element[i].id].collect_score :
2535 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2536 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2537 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2539 // update game panel control frames
2541 for (i = 0; game_panel_controls[i].nr != -1; i++)
2543 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2545 if (gpc->type == TYPE_ELEMENT)
2547 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2549 int last_anim_random_frame = gfx.anim_random_frame;
2550 int element = gpc->value;
2551 int graphic = el2panelimg(element);
2552 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2553 sync_random_frame : INIT_GFX_RANDOM());
2555 if (gpc->value != gpc->last_value)
2558 gpc->gfx_random = init_gfx_random;
2564 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2565 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2566 gpc->gfx_random = init_gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2570 gfx.anim_random_frame = gpc->gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2573 gpc->gfx_frame = element_info[element].collect_score;
2575 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = last_anim_random_frame;
2581 else if (gpc->type == TYPE_GRAPHIC)
2583 if (gpc->graphic != IMG_UNDEFINED)
2585 int last_anim_random_frame = gfx.anim_random_frame;
2586 int graphic = gpc->graphic;
2587 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2588 sync_random_frame : INIT_GFX_RANDOM());
2590 if (gpc->value != gpc->last_value)
2593 gpc->gfx_random = init_gfx_random;
2599 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2600 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2601 gpc->gfx_random = init_gfx_random;
2604 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2605 gfx.anim_random_frame = gpc->gfx_random;
2607 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2609 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2610 gfx.anim_random_frame = last_anim_random_frame;
2616 static void DisplayGameControlValues(void)
2618 boolean redraw_panel = FALSE;
2621 for (i = 0; game_panel_controls[i].nr != -1; i++)
2623 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2625 if (PANEL_DEACTIVATED(gpc->pos))
2628 if (gpc->value == gpc->last_value &&
2629 gpc->frame == gpc->last_frame)
2632 redraw_panel = TRUE;
2638 // copy default game door content to main double buffer
2640 // !!! CHECK AGAIN !!!
2641 SetPanelBackground();
2642 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2643 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2645 // redraw game control buttons
2646 RedrawGameButtons();
2648 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2650 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2652 int nr = game_panel_order[i].nr;
2653 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2654 struct TextPosInfo *pos = gpc->pos;
2655 int type = gpc->type;
2656 int value = gpc->value;
2657 int frame = gpc->frame;
2658 int size = pos->size;
2659 int font = pos->font;
2660 boolean draw_masked = pos->draw_masked;
2661 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2663 if (PANEL_DEACTIVATED(pos))
2666 if (pos->class == get_hash_from_key("extra_panel_items") &&
2667 !setup.prefer_extra_panel_items)
2670 gpc->last_value = value;
2671 gpc->last_frame = frame;
2673 if (type == TYPE_INTEGER)
2675 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2676 nr == GAME_PANEL_TIME)
2678 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2680 if (use_dynamic_size) // use dynamic number of digits
2682 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2683 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2684 int size2 = size1 + 1;
2685 int font1 = pos->font;
2686 int font2 = pos->font_alt;
2688 size = (value < value_change ? size1 : size2);
2689 font = (value < value_change ? font1 : font2);
2693 // correct text size if "digits" is zero or less
2695 size = strlen(int2str(value, size));
2697 // dynamically correct text alignment
2698 pos->width = size * getFontWidth(font);
2700 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2701 int2str(value, size), font, mask_mode);
2703 else if (type == TYPE_ELEMENT)
2705 int element, graphic;
2709 int dst_x = PANEL_XPOS(pos);
2710 int dst_y = PANEL_YPOS(pos);
2712 if (value != EL_UNDEFINED && value != EL_EMPTY)
2715 graphic = el2panelimg(value);
2718 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2719 element, EL_NAME(element), size);
2722 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2725 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2728 width = graphic_info[graphic].width * size / TILESIZE;
2729 height = graphic_info[graphic].height * size / TILESIZE;
2732 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2735 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2739 else if (type == TYPE_GRAPHIC)
2741 int graphic = gpc->graphic;
2742 int graphic_active = gpc->graphic_active;
2746 int dst_x = PANEL_XPOS(pos);
2747 int dst_y = PANEL_YPOS(pos);
2748 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2749 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2751 if (graphic != IMG_UNDEFINED && !skip)
2753 if (pos->style == STYLE_REVERSE)
2754 value = 100 - value;
2756 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2758 if (pos->direction & MV_HORIZONTAL)
2760 width = graphic_info[graphic_active].width * value / 100;
2761 height = graphic_info[graphic_active].height;
2763 if (pos->direction == MV_LEFT)
2765 src_x += graphic_info[graphic_active].width - width;
2766 dst_x += graphic_info[graphic_active].width - width;
2771 width = graphic_info[graphic_active].width;
2772 height = graphic_info[graphic_active].height * value / 100;
2774 if (pos->direction == MV_UP)
2776 src_y += graphic_info[graphic_active].height - height;
2777 dst_y += graphic_info[graphic_active].height - height;
2782 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2785 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2788 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2790 if (pos->direction & MV_HORIZONTAL)
2792 if (pos->direction == MV_RIGHT)
2799 dst_x = PANEL_XPOS(pos);
2802 width = graphic_info[graphic].width - width;
2806 if (pos->direction == MV_DOWN)
2813 dst_y = PANEL_YPOS(pos);
2816 height = graphic_info[graphic].height - height;
2820 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2823 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2827 else if (type == TYPE_STRING)
2829 boolean active = (value != 0);
2830 char *state_normal = "off";
2831 char *state_active = "on";
2832 char *state = (active ? state_active : state_normal);
2833 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2834 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2835 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2836 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2838 if (nr == GAME_PANEL_GRAVITY_STATE)
2840 int font1 = pos->font; // (used for normal state)
2841 int font2 = pos->font_alt; // (used for active state)
2843 font = (active ? font2 : font1);
2852 // don't truncate output if "chars" is zero or less
2855 // dynamically correct text alignment
2856 pos->width = size * getFontWidth(font);
2859 s_cut = getStringCopyN(s, size);
2861 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2862 s_cut, font, mask_mode);
2868 redraw_mask |= REDRAW_DOOR_1;
2871 SetGameStatus(GAME_MODE_PLAYING);
2874 void UpdateAndDisplayGameControlValues(void)
2876 if (tape.deactivate_display)
2879 UpdateGameControlValues();
2880 DisplayGameControlValues();
2883 void UpdateGameDoorValues(void)
2885 UpdateGameControlValues();
2888 void DrawGameDoorValues(void)
2890 DisplayGameControlValues();
2894 // ============================================================================
2896 // ----------------------------------------------------------------------------
2897 // initialize game engine due to level / tape version number
2898 // ============================================================================
2900 static void InitGameEngine(void)
2902 int i, j, k, l, x, y;
2904 // set game engine from tape file when re-playing, else from level file
2905 game.engine_version = (tape.playing ? tape.engine_version :
2906 level.game_version);
2908 // set single or multi-player game mode (needed for re-playing tapes)
2909 game.team_mode = setup.team_mode;
2913 int num_players = 0;
2915 for (i = 0; i < MAX_PLAYERS; i++)
2916 if (tape.player_participates[i])
2919 // multi-player tapes contain input data for more than one player
2920 game.team_mode = (num_players > 1);
2924 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2925 level.game_version);
2926 Debug("game:init:level", " tape.file_version == %06d",
2928 Debug("game:init:level", " tape.game_version == %06d",
2930 Debug("game:init:level", " tape.engine_version == %06d",
2931 tape.engine_version);
2932 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2933 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2936 // --------------------------------------------------------------------------
2937 // set flags for bugs and changes according to active game engine version
2938 // --------------------------------------------------------------------------
2942 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2944 Bug was introduced in version:
2947 Bug was fixed in version:
2951 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2952 but the property "can fall" was missing, which caused some levels to be
2953 unsolvable. This was fixed in version 4.2.0.0.
2955 Affected levels/tapes:
2956 An example for a tape that was fixed by this bugfix is tape 029 from the
2957 level set "rnd_sam_bateman".
2958 The wrong behaviour will still be used for all levels or tapes that were
2959 created/recorded with it. An example for this is tape 023 from the level
2960 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2963 boolean use_amoeba_dropping_cannot_fall_bug =
2964 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2965 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2967 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2968 tape.game_version < VERSION_IDENT(4,2,0,0)));
2971 Summary of bugfix/change:
2972 Fixed move speed of elements entering or leaving magic wall.
2974 Fixed/changed in version:
2978 Before 2.0.1, move speed of elements entering or leaving magic wall was
2979 twice as fast as it is now.
2980 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2982 Affected levels/tapes:
2983 The first condition is generally needed for all levels/tapes before version
2984 2.0.1, which might use the old behaviour before it was changed; known tapes
2985 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2986 The second condition is an exception from the above case and is needed for
2987 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2988 above, but before it was known that this change would break tapes like the
2989 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2990 although the engine version while recording maybe was before 2.0.1. There
2991 are a lot of tapes that are affected by this exception, like tape 006 from
2992 the level set "rnd_conor_mancone".
2995 boolean use_old_move_stepsize_for_magic_wall =
2996 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2998 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2999 tape.game_version < VERSION_IDENT(4,2,0,0)));
3002 Summary of bugfix/change:
3003 Fixed handling for custom elements that change when pushed by the player.
3005 Fixed/changed in version:
3009 Before 3.1.0, custom elements that "change when pushing" changed directly
3010 after the player started pushing them (until then handled in "DigField()").
3011 Since 3.1.0, these custom elements are not changed until the "pushing"
3012 move of the element is finished (now handled in "ContinueMoving()").
3014 Affected levels/tapes:
3015 The first condition is generally needed for all levels/tapes before version
3016 3.1.0, which might use the old behaviour before it was changed; known tapes
3017 that are affected are some tapes from the level set "Walpurgis Gardens" by
3019 The second condition is an exception from the above case and is needed for
3020 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3021 above (including some development versions of 3.1.0), but before it was
3022 known that this change would break tapes like the above and was fixed in
3023 3.1.1, so that the changed behaviour was active although the engine version
3024 while recording maybe was before 3.1.0. There is at least one tape that is
3025 affected by this exception, which is the tape for the one-level set "Bug
3026 Machine" by Juergen Bonhagen.
3029 game.use_change_when_pushing_bug =
3030 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3032 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3033 tape.game_version < VERSION_IDENT(3,1,1,0)));
3036 Summary of bugfix/change:
3037 Fixed handling for blocking the field the player leaves when moving.
3039 Fixed/changed in version:
3043 Before 3.1.1, when "block last field when moving" was enabled, the field
3044 the player is leaving when moving was blocked for the time of the move,
3045 and was directly unblocked afterwards. This resulted in the last field
3046 being blocked for exactly one less than the number of frames of one player
3047 move. Additionally, even when blocking was disabled, the last field was
3048 blocked for exactly one frame.
3049 Since 3.1.1, due to changes in player movement handling, the last field
3050 is not blocked at all when blocking is disabled. When blocking is enabled,
3051 the last field is blocked for exactly the number of frames of one player
3052 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3053 last field is blocked for exactly one more than the number of frames of
3056 Affected levels/tapes:
3057 (!!! yet to be determined -- probably many !!!)
3060 game.use_block_last_field_bug =
3061 (game.engine_version < VERSION_IDENT(3,1,1,0));
3063 /* various special flags and settings for native Emerald Mine game engine */
3065 game_em.use_single_button =
3066 (game.engine_version > VERSION_IDENT(4,0,0,2));
3068 game_em.use_snap_key_bug =
3069 (game.engine_version < VERSION_IDENT(4,0,1,0));
3071 game_em.use_random_bug =
3072 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3074 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3076 game_em.use_old_explosions = use_old_em_engine;
3077 game_em.use_old_android = use_old_em_engine;
3078 game_em.use_old_push_elements = use_old_em_engine;
3079 game_em.use_old_push_into_acid = use_old_em_engine;
3081 game_em.use_wrap_around = !use_old_em_engine;
3083 // --------------------------------------------------------------------------
3085 // set maximal allowed number of custom element changes per game frame
3086 game.max_num_changes_per_frame = 1;
3088 // default scan direction: scan playfield from top/left to bottom/right
3089 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3091 // dynamically adjust element properties according to game engine version
3092 InitElementPropertiesEngine(game.engine_version);
3094 // ---------- initialize special element properties -------------------------
3096 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3097 if (use_amoeba_dropping_cannot_fall_bug)
3098 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3100 // ---------- initialize player's initial move delay ------------------------
3102 // dynamically adjust player properties according to level information
3103 for (i = 0; i < MAX_PLAYERS; i++)
3104 game.initial_move_delay_value[i] =
3105 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3107 // dynamically adjust player properties according to game engine version
3108 for (i = 0; i < MAX_PLAYERS; i++)
3109 game.initial_move_delay[i] =
3110 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3111 game.initial_move_delay_value[i] : 0);
3113 // ---------- initialize player's initial push delay ------------------------
3115 // dynamically adjust player properties according to game engine version
3116 game.initial_push_delay_value =
3117 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3119 // ---------- initialize changing elements ----------------------------------
3121 // initialize changing elements information
3122 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3124 struct ElementInfo *ei = &element_info[i];
3126 // this pointer might have been changed in the level editor
3127 ei->change = &ei->change_page[0];
3129 if (!IS_CUSTOM_ELEMENT(i))
3131 ei->change->target_element = EL_EMPTY_SPACE;
3132 ei->change->delay_fixed = 0;
3133 ei->change->delay_random = 0;
3134 ei->change->delay_frames = 1;
3137 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3139 ei->has_change_event[j] = FALSE;
3141 ei->event_page_nr[j] = 0;
3142 ei->event_page[j] = &ei->change_page[0];
3146 // add changing elements from pre-defined list
3147 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3149 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3150 struct ElementInfo *ei = &element_info[ch_delay->element];
3152 ei->change->target_element = ch_delay->target_element;
3153 ei->change->delay_fixed = ch_delay->change_delay;
3155 ei->change->pre_change_function = ch_delay->pre_change_function;
3156 ei->change->change_function = ch_delay->change_function;
3157 ei->change->post_change_function = ch_delay->post_change_function;
3159 ei->change->can_change = TRUE;
3160 ei->change->can_change_or_has_action = TRUE;
3162 ei->has_change_event[CE_DELAY] = TRUE;
3164 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3165 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3168 // ---------- initialize internal run-time variables ------------------------
3170 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3172 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3174 for (j = 0; j < ei->num_change_pages; j++)
3176 ei->change_page[j].can_change_or_has_action =
3177 (ei->change_page[j].can_change |
3178 ei->change_page[j].has_action);
3182 // add change events from custom element configuration
3183 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3185 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3187 for (j = 0; j < ei->num_change_pages; j++)
3189 if (!ei->change_page[j].can_change_or_has_action)
3192 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3194 // only add event page for the first page found with this event
3195 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3197 ei->has_change_event[k] = TRUE;
3199 ei->event_page_nr[k] = j;
3200 ei->event_page[k] = &ei->change_page[j];
3206 // ---------- initialize reference elements in change conditions ------------
3208 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3210 int element = EL_CUSTOM_START + i;
3211 struct ElementInfo *ei = &element_info[element];
3213 for (j = 0; j < ei->num_change_pages; j++)
3215 int trigger_element = ei->change_page[j].initial_trigger_element;
3217 if (trigger_element >= EL_PREV_CE_8 &&
3218 trigger_element <= EL_NEXT_CE_8)
3219 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3221 ei->change_page[j].trigger_element = trigger_element;
3225 // ---------- initialize run-time trigger player and element ----------------
3227 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3229 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3231 for (j = 0; j < ei->num_change_pages; j++)
3233 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3234 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3235 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3236 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3237 ei->change_page[j].actual_trigger_ce_value = 0;
3238 ei->change_page[j].actual_trigger_ce_score = 0;
3242 // ---------- initialize trigger events -------------------------------------
3244 // initialize trigger events information
3245 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3246 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3247 trigger_events[i][j] = FALSE;
3249 // add trigger events from element change event properties
3250 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3252 struct ElementInfo *ei = &element_info[i];
3254 for (j = 0; j < ei->num_change_pages; j++)
3256 if (!ei->change_page[j].can_change_or_has_action)
3259 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3261 int trigger_element = ei->change_page[j].trigger_element;
3263 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3265 if (ei->change_page[j].has_event[k])
3267 if (IS_GROUP_ELEMENT(trigger_element))
3269 struct ElementGroupInfo *group =
3270 element_info[trigger_element].group;
3272 for (l = 0; l < group->num_elements_resolved; l++)
3273 trigger_events[group->element_resolved[l]][k] = TRUE;
3275 else if (trigger_element == EL_ANY_ELEMENT)
3276 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3277 trigger_events[l][k] = TRUE;
3279 trigger_events[trigger_element][k] = TRUE;
3286 // ---------- initialize push delay -----------------------------------------
3288 // initialize push delay values to default
3289 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3291 if (!IS_CUSTOM_ELEMENT(i))
3293 // set default push delay values (corrected since version 3.0.7-1)
3294 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3296 element_info[i].push_delay_fixed = 2;
3297 element_info[i].push_delay_random = 8;
3301 element_info[i].push_delay_fixed = 8;
3302 element_info[i].push_delay_random = 8;
3307 // set push delay value for certain elements from pre-defined list
3308 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3310 int e = push_delay_list[i].element;
3312 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3313 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3316 // set push delay value for Supaplex elements for newer engine versions
3317 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3319 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3321 if (IS_SP_ELEMENT(i))
3323 // set SP push delay to just enough to push under a falling zonk
3324 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3326 element_info[i].push_delay_fixed = delay;
3327 element_info[i].push_delay_random = 0;
3332 // ---------- initialize move stepsize --------------------------------------
3334 // initialize move stepsize values to default
3335 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3336 if (!IS_CUSTOM_ELEMENT(i))
3337 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3339 // set move stepsize value for certain elements from pre-defined list
3340 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3342 int e = move_stepsize_list[i].element;
3344 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3346 // set move stepsize value for certain elements for older engine versions
3347 if (use_old_move_stepsize_for_magic_wall)
3349 if (e == EL_MAGIC_WALL_FILLING ||
3350 e == EL_MAGIC_WALL_EMPTYING ||
3351 e == EL_BD_MAGIC_WALL_FILLING ||
3352 e == EL_BD_MAGIC_WALL_EMPTYING)
3353 element_info[e].move_stepsize *= 2;
3357 // ---------- initialize collect score --------------------------------------
3359 // initialize collect score values for custom elements from initial value
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3361 if (IS_CUSTOM_ELEMENT(i))
3362 element_info[i].collect_score = element_info[i].collect_score_initial;
3364 // ---------- initialize collect count --------------------------------------
3366 // initialize collect count values for non-custom elements
3367 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3368 if (!IS_CUSTOM_ELEMENT(i))
3369 element_info[i].collect_count_initial = 0;
3371 // add collect count values for all elements from pre-defined list
3372 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3373 element_info[collect_count_list[i].element].collect_count_initial =
3374 collect_count_list[i].count;
3376 // ---------- initialize access direction -----------------------------------
3378 // initialize access direction values to default (access from every side)
3379 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (!IS_CUSTOM_ELEMENT(i))
3381 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3383 // set access direction value for certain elements from pre-defined list
3384 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3385 element_info[access_direction_list[i].element].access_direction =
3386 access_direction_list[i].direction;
3388 // ---------- initialize explosion content ----------------------------------
3389 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3391 if (IS_CUSTOM_ELEMENT(i))
3394 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3396 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3398 element_info[i].content.e[x][y] =
3399 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3400 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3401 i == EL_PLAYER_3 ? EL_EMERALD :
3402 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3403 i == EL_MOLE ? EL_EMERALD_RED :
3404 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3405 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3406 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3407 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3408 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3409 i == EL_WALL_EMERALD ? EL_EMERALD :
3410 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3411 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3412 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3413 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3414 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3415 i == EL_WALL_PEARL ? EL_PEARL :
3416 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3421 // ---------- initialize recursion detection --------------------------------
3422 recursion_loop_depth = 0;
3423 recursion_loop_detected = FALSE;
3424 recursion_loop_element = EL_UNDEFINED;
3426 // ---------- initialize graphics engine ------------------------------------
3427 game.scroll_delay_value =
3428 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3429 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3430 !setup.forced_scroll_delay ? 0 :
3431 setup.scroll_delay ? setup.scroll_delay_value : 0);
3432 game.scroll_delay_value =
3433 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3435 // ---------- initialize game engine snapshots ------------------------------
3436 for (i = 0; i < MAX_PLAYERS; i++)
3437 game.snapshot.last_action[i] = 0;
3438 game.snapshot.changed_action = FALSE;
3439 game.snapshot.collected_item = FALSE;
3440 game.snapshot.mode =
3441 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3442 SNAPSHOT_MODE_EVERY_STEP :
3443 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3444 SNAPSHOT_MODE_EVERY_MOVE :
3445 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3446 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3447 game.snapshot.save_snapshot = FALSE;
3449 // ---------- initialize level time for Supaplex engine ---------------------
3450 // Supaplex levels with time limit currently unsupported -- should be added
3451 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3454 // ---------- initialize flags for handling game actions --------------------
3456 // set flags for game actions to default values
3457 game.use_key_actions = TRUE;
3458 game.use_mouse_actions = FALSE;
3460 // when using Mirror Magic game engine, handle mouse events only
3461 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3463 game.use_key_actions = FALSE;
3464 game.use_mouse_actions = TRUE;
3467 // check for custom elements with mouse click events
3468 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3470 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3472 int element = EL_CUSTOM_START + i;
3474 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3475 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3476 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3477 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3478 game.use_mouse_actions = TRUE;
3483 static int get_num_special_action(int element, int action_first,
3486 int num_special_action = 0;
3489 for (i = action_first; i <= action_last; i++)
3491 boolean found = FALSE;
3493 for (j = 0; j < NUM_DIRECTIONS; j++)
3494 if (el_act_dir2img(element, i, j) !=
3495 el_act_dir2img(element, ACTION_DEFAULT, j))
3499 num_special_action++;
3504 return num_special_action;
3508 // ============================================================================
3510 // ----------------------------------------------------------------------------
3511 // initialize and start new game
3512 // ============================================================================
3514 #if DEBUG_INIT_PLAYER
3515 static void DebugPrintPlayerStatus(char *message)
3522 Debug("game:init:player", "%s:", message);
3524 for (i = 0; i < MAX_PLAYERS; i++)
3526 struct PlayerInfo *player = &stored_player[i];
3528 Debug("game:init:player",
3529 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3533 player->connected_locally,
3534 player->connected_network,
3536 (local_player == player ? " (local player)" : ""));
3543 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3544 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3545 int fade_mask = REDRAW_FIELD;
3547 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3548 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3549 int initial_move_dir = MV_DOWN;
3552 // required here to update video display before fading (FIX THIS)
3553 DrawMaskedBorder(REDRAW_DOOR_2);
3555 if (!game.restart_level)
3556 CloseDoor(DOOR_CLOSE_1);
3558 SetGameStatus(GAME_MODE_PLAYING);
3560 if (level_editor_test_game)
3561 FadeSkipNextFadeOut();
3563 FadeSetEnterScreen();
3566 fade_mask = REDRAW_ALL;
3568 FadeLevelSoundsAndMusic();
3570 ExpireSoundLoops(TRUE);
3574 if (level_editor_test_game)
3575 FadeSkipNextFadeIn();
3577 // needed if different viewport properties defined for playing
3578 ChangeViewportPropertiesIfNeeded();
3582 DrawCompleteVideoDisplay();
3584 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3587 InitGameControlValues();
3591 // initialize tape actions from game when recording tape
3592 tape.use_key_actions = game.use_key_actions;
3593 tape.use_mouse_actions = game.use_mouse_actions;
3595 // initialize visible playfield size when recording tape (for team mode)
3596 tape.scr_fieldx = SCR_FIELDX;
3597 tape.scr_fieldy = SCR_FIELDY;
3600 // don't play tapes over network
3601 network_playing = (network.enabled && !tape.playing);
3603 for (i = 0; i < MAX_PLAYERS; i++)
3605 struct PlayerInfo *player = &stored_player[i];
3607 player->index_nr = i;
3608 player->index_bit = (1 << i);
3609 player->element_nr = EL_PLAYER_1 + i;
3611 player->present = FALSE;
3612 player->active = FALSE;
3613 player->mapped = FALSE;
3615 player->killed = FALSE;
3616 player->reanimated = FALSE;
3617 player->buried = FALSE;
3620 player->effective_action = 0;
3621 player->programmed_action = 0;
3622 player->snap_action = 0;
3624 player->mouse_action.lx = 0;
3625 player->mouse_action.ly = 0;
3626 player->mouse_action.button = 0;
3627 player->mouse_action.button_hint = 0;
3629 player->effective_mouse_action.lx = 0;
3630 player->effective_mouse_action.ly = 0;
3631 player->effective_mouse_action.button = 0;
3632 player->effective_mouse_action.button_hint = 0;
3634 for (j = 0; j < MAX_NUM_KEYS; j++)
3635 player->key[j] = FALSE;
3637 player->num_white_keys = 0;
3639 player->dynabomb_count = 0;
3640 player->dynabomb_size = 1;
3641 player->dynabombs_left = 0;
3642 player->dynabomb_xl = FALSE;
3644 player->MovDir = initial_move_dir;
3647 player->GfxDir = initial_move_dir;
3648 player->GfxAction = ACTION_DEFAULT;
3650 player->StepFrame = 0;
3652 player->initial_element = player->element_nr;
3653 player->artwork_element =
3654 (level.use_artwork_element[i] ? level.artwork_element[i] :
3655 player->element_nr);
3656 player->use_murphy = FALSE;
3658 player->block_last_field = FALSE; // initialized in InitPlayerField()
3659 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3661 player->gravity = level.initial_player_gravity[i];
3663 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3665 player->actual_frame_counter = 0;
3667 player->step_counter = 0;
3669 player->last_move_dir = initial_move_dir;
3671 player->is_active = FALSE;
3673 player->is_waiting = FALSE;
3674 player->is_moving = FALSE;
3675 player->is_auto_moving = FALSE;
3676 player->is_digging = FALSE;
3677 player->is_snapping = FALSE;
3678 player->is_collecting = FALSE;
3679 player->is_pushing = FALSE;
3680 player->is_switching = FALSE;
3681 player->is_dropping = FALSE;
3682 player->is_dropping_pressed = FALSE;
3684 player->is_bored = FALSE;
3685 player->is_sleeping = FALSE;
3687 player->was_waiting = TRUE;
3688 player->was_moving = FALSE;
3689 player->was_snapping = FALSE;
3690 player->was_dropping = FALSE;
3692 player->force_dropping = FALSE;
3694 player->frame_counter_bored = -1;
3695 player->frame_counter_sleeping = -1;
3697 player->anim_delay_counter = 0;
3698 player->post_delay_counter = 0;
3700 player->dir_waiting = initial_move_dir;
3701 player->action_waiting = ACTION_DEFAULT;
3702 player->last_action_waiting = ACTION_DEFAULT;
3703 player->special_action_bored = ACTION_DEFAULT;
3704 player->special_action_sleeping = ACTION_DEFAULT;
3706 player->switch_x = -1;
3707 player->switch_y = -1;
3709 player->drop_x = -1;
3710 player->drop_y = -1;
3712 player->show_envelope = 0;
3714 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3716 player->push_delay = -1; // initialized when pushing starts
3717 player->push_delay_value = game.initial_push_delay_value;
3719 player->drop_delay = 0;
3720 player->drop_pressed_delay = 0;
3722 player->last_jx = -1;
3723 player->last_jy = -1;
3727 player->shield_normal_time_left = 0;
3728 player->shield_deadly_time_left = 0;
3730 player->last_removed_element = EL_UNDEFINED;
3732 player->inventory_infinite_element = EL_UNDEFINED;
3733 player->inventory_size = 0;
3735 if (level.use_initial_inventory[i])
3737 for (j = 0; j < level.initial_inventory_size[i]; j++)
3739 int element = level.initial_inventory_content[i][j];
3740 int collect_count = element_info[element].collect_count_initial;
3743 if (!IS_CUSTOM_ELEMENT(element))
3746 if (collect_count == 0)
3747 player->inventory_infinite_element = element;
3749 for (k = 0; k < collect_count; k++)
3750 if (player->inventory_size < MAX_INVENTORY_SIZE)
3751 player->inventory_element[player->inventory_size++] = element;
3755 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3756 SnapField(player, 0, 0);
3758 map_player_action[i] = i;
3761 network_player_action_received = FALSE;
3763 // initial null action
3764 if (network_playing)
3765 SendToServer_MovePlayer(MV_NONE);
3770 TimeLeft = level.time;
3773 ScreenMovDir = MV_NONE;
3777 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3779 game.robot_wheel_x = -1;
3780 game.robot_wheel_y = -1;
3785 game.all_players_gone = FALSE;
3787 game.LevelSolved = FALSE;
3788 game.GameOver = FALSE;
3790 game.GamePlayed = !tape.playing;
3792 game.LevelSolved_GameWon = FALSE;
3793 game.LevelSolved_GameEnd = FALSE;
3794 game.LevelSolved_SaveTape = FALSE;
3795 game.LevelSolved_SaveScore = FALSE;
3797 game.LevelSolved_CountingTime = 0;
3798 game.LevelSolved_CountingScore = 0;
3799 game.LevelSolved_CountingHealth = 0;
3801 game.panel.active = TRUE;
3803 game.no_time_limit = (level.time == 0);
3805 game.yamyam_content_nr = 0;
3806 game.robot_wheel_active = FALSE;
3807 game.magic_wall_active = FALSE;
3808 game.magic_wall_time_left = 0;
3809 game.light_time_left = 0;
3810 game.timegate_time_left = 0;
3811 game.switchgate_pos = 0;
3812 game.wind_direction = level.wind_direction_initial;
3814 game.time_final = 0;
3815 game.score_time_final = 0;
3818 game.score_final = 0;
3820 game.health = MAX_HEALTH;
3821 game.health_final = MAX_HEALTH;
3823 game.gems_still_needed = level.gems_needed;
3824 game.sokoban_fields_still_needed = 0;
3825 game.sokoban_objects_still_needed = 0;
3826 game.lights_still_needed = 0;
3827 game.players_still_needed = 0;
3828 game.friends_still_needed = 0;
3830 game.lenses_time_left = 0;
3831 game.magnify_time_left = 0;
3833 game.ball_active = level.ball_active_initial;
3834 game.ball_content_nr = 0;
3836 game.explosions_delayed = TRUE;
3838 game.envelope_active = FALSE;
3840 for (i = 0; i < NUM_BELTS; i++)
3842 game.belt_dir[i] = MV_NONE;
3843 game.belt_dir_nr[i] = 3; // not moving, next moving left
3846 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3847 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3849 #if DEBUG_INIT_PLAYER
3850 DebugPrintPlayerStatus("Player status at level initialization");
3853 SCAN_PLAYFIELD(x, y)
3855 Tile[x][y] = Last[x][y] = level.field[x][y];
3856 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3857 ChangeDelay[x][y] = 0;
3858 ChangePage[x][y] = -1;
3859 CustomValue[x][y] = 0; // initialized in InitField()
3860 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3862 WasJustMoving[x][y] = 0;
3863 WasJustFalling[x][y] = 0;
3864 CheckCollision[x][y] = 0;
3865 CheckImpact[x][y] = 0;
3867 Pushed[x][y] = FALSE;
3869 ChangeCount[x][y] = 0;
3870 ChangeEvent[x][y] = -1;
3872 ExplodePhase[x][y] = 0;
3873 ExplodeDelay[x][y] = 0;
3874 ExplodeField[x][y] = EX_TYPE_NONE;
3876 RunnerVisit[x][y] = 0;
3877 PlayerVisit[x][y] = 0;
3880 GfxRandom[x][y] = INIT_GFX_RANDOM();
3881 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3882 GfxElement[x][y] = EL_UNDEFINED;
3883 GfxAction[x][y] = ACTION_DEFAULT;
3884 GfxDir[x][y] = MV_NONE;
3885 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3888 SCAN_PLAYFIELD(x, y)
3890 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3892 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3895 InitField(x, y, TRUE);
3897 ResetGfxAnimation(x, y);
3902 for (i = 0; i < MAX_PLAYERS; i++)
3904 struct PlayerInfo *player = &stored_player[i];
3906 // set number of special actions for bored and sleeping animation
3907 player->num_special_action_bored =
3908 get_num_special_action(player->artwork_element,
3909 ACTION_BORING_1, ACTION_BORING_LAST);
3910 player->num_special_action_sleeping =
3911 get_num_special_action(player->artwork_element,
3912 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3915 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3916 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3918 // initialize type of slippery elements
3919 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3921 if (!IS_CUSTOM_ELEMENT(i))
3923 // default: elements slip down either to the left or right randomly
3924 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3926 // SP style elements prefer to slip down on the left side
3927 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3928 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3930 // BD style elements prefer to slip down on the left side
3931 if (game.emulation == EMU_BOULDERDASH)
3932 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3936 // initialize explosion and ignition delay
3937 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3939 if (!IS_CUSTOM_ELEMENT(i))
3942 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3943 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3944 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3945 int last_phase = (num_phase + 1) * delay;
3946 int half_phase = (num_phase / 2) * delay;
3948 element_info[i].explosion_delay = last_phase - 1;
3949 element_info[i].ignition_delay = half_phase;
3951 if (i == EL_BLACK_ORB)
3952 element_info[i].ignition_delay = 1;
3956 // correct non-moving belts to start moving left
3957 for (i = 0; i < NUM_BELTS; i++)
3958 if (game.belt_dir[i] == MV_NONE)
3959 game.belt_dir_nr[i] = 3; // not moving, next moving left
3961 #if USE_NEW_PLAYER_ASSIGNMENTS
3962 // use preferred player also in local single-player mode
3963 if (!network.enabled && !game.team_mode)
3965 int new_index_nr = setup.network_player_nr;
3967 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3969 for (i = 0; i < MAX_PLAYERS; i++)
3970 stored_player[i].connected_locally = FALSE;
3972 stored_player[new_index_nr].connected_locally = TRUE;
3976 for (i = 0; i < MAX_PLAYERS; i++)
3978 stored_player[i].connected = FALSE;
3980 // in network game mode, the local player might not be the first player
3981 if (stored_player[i].connected_locally)
3982 local_player = &stored_player[i];
3985 if (!network.enabled)
3986 local_player->connected = TRUE;
3990 for (i = 0; i < MAX_PLAYERS; i++)
3991 stored_player[i].connected = tape.player_participates[i];
3993 else if (network.enabled)
3995 // add team mode players connected over the network (needed for correct
3996 // assignment of player figures from level to locally playing players)
3998 for (i = 0; i < MAX_PLAYERS; i++)
3999 if (stored_player[i].connected_network)
4000 stored_player[i].connected = TRUE;
4002 else if (game.team_mode)
4004 // try to guess locally connected team mode players (needed for correct
4005 // assignment of player figures from level to locally playing players)
4007 for (i = 0; i < MAX_PLAYERS; i++)
4008 if (setup.input[i].use_joystick ||
4009 setup.input[i].key.left != KSYM_UNDEFINED)
4010 stored_player[i].connected = TRUE;
4013 #if DEBUG_INIT_PLAYER
4014 DebugPrintPlayerStatus("Player status after level initialization");
4017 #if DEBUG_INIT_PLAYER
4018 Debug("game:init:player", "Reassigning players ...");
4021 // check if any connected player was not found in playfield
4022 for (i = 0; i < MAX_PLAYERS; i++)
4024 struct PlayerInfo *player = &stored_player[i];
4026 if (player->connected && !player->present)
4028 struct PlayerInfo *field_player = NULL;
4030 #if DEBUG_INIT_PLAYER
4031 Debug("game:init:player",
4032 "- looking for field player for player %d ...", i + 1);
4035 // assign first free player found that is present in the playfield
4037 // first try: look for unmapped playfield player that is not connected
4038 for (j = 0; j < MAX_PLAYERS; j++)
4039 if (field_player == NULL &&
4040 stored_player[j].present &&
4041 !stored_player[j].mapped &&
4042 !stored_player[j].connected)
4043 field_player = &stored_player[j];
4045 // second try: look for *any* unmapped playfield player
4046 for (j = 0; j < MAX_PLAYERS; j++)
4047 if (field_player == NULL &&
4048 stored_player[j].present &&
4049 !stored_player[j].mapped)
4050 field_player = &stored_player[j];
4052 if (field_player != NULL)
4054 int jx = field_player->jx, jy = field_player->jy;
4056 #if DEBUG_INIT_PLAYER
4057 Debug("game:init:player", "- found player %d",
4058 field_player->index_nr + 1);
4061 player->present = FALSE;
4062 player->active = FALSE;
4064 field_player->present = TRUE;
4065 field_player->active = TRUE;
4068 player->initial_element = field_player->initial_element;
4069 player->artwork_element = field_player->artwork_element;
4071 player->block_last_field = field_player->block_last_field;
4072 player->block_delay_adjustment = field_player->block_delay_adjustment;
4075 StorePlayer[jx][jy] = field_player->element_nr;
4077 field_player->jx = field_player->last_jx = jx;
4078 field_player->jy = field_player->last_jy = jy;
4080 if (local_player == player)
4081 local_player = field_player;
4083 map_player_action[field_player->index_nr] = i;
4085 field_player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 Debug("game:init:player", "- map_player_action[%d] == %d",
4089 field_player->index_nr + 1, i + 1);
4094 if (player->connected && player->present)
4095 player->mapped = TRUE;
4098 #if DEBUG_INIT_PLAYER
4099 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4104 // check if any connected player was not found in playfield
4105 for (i = 0; i < MAX_PLAYERS; i++)
4107 struct PlayerInfo *player = &stored_player[i];
4109 if (player->connected && !player->present)
4111 for (j = 0; j < MAX_PLAYERS; j++)
4113 struct PlayerInfo *field_player = &stored_player[j];
4114 int jx = field_player->jx, jy = field_player->jy;
4116 // assign first free player found that is present in the playfield
4117 if (field_player->present && !field_player->connected)
4119 player->present = TRUE;
4120 player->active = TRUE;
4122 field_player->present = FALSE;
4123 field_player->active = FALSE;
4125 player->initial_element = field_player->initial_element;
4126 player->artwork_element = field_player->artwork_element;
4128 player->block_last_field = field_player->block_last_field;
4129 player->block_delay_adjustment = field_player->block_delay_adjustment;
4131 StorePlayer[jx][jy] = player->element_nr;
4133 player->jx = player->last_jx = jx;
4134 player->jy = player->last_jy = jy;
4144 Debug("game:init:player", "local_player->present == %d",
4145 local_player->present);
4148 // set focus to local player for network games, else to all players
4149 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4150 game.centered_player_nr_next = game.centered_player_nr;
4151 game.set_centered_player = FALSE;
4152 game.set_centered_player_wrap = FALSE;
4154 if (network_playing && tape.recording)
4156 // store client dependent player focus when recording network games
4157 tape.centered_player_nr_next = game.centered_player_nr_next;
4158 tape.set_centered_player = TRUE;
4163 // when playing a tape, eliminate all players who do not participate
4165 #if USE_NEW_PLAYER_ASSIGNMENTS
4167 if (!game.team_mode)
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 if (stored_player[i].active &&
4172 !tape.player_participates[map_player_action[i]])
4174 struct PlayerInfo *player = &stored_player[i];
4175 int jx = player->jx, jy = player->jy;
4177 #if DEBUG_INIT_PLAYER
4178 Debug("game:init:player", "Removing player %d at (%d, %d)",
4182 player->active = FALSE;
4183 StorePlayer[jx][jy] = 0;
4184 Tile[jx][jy] = EL_EMPTY;
4191 for (i = 0; i < MAX_PLAYERS; i++)
4193 if (stored_player[i].active &&
4194 !tape.player_participates[i])
4196 struct PlayerInfo *player = &stored_player[i];
4197 int jx = player->jx, jy = player->jy;
4199 player->active = FALSE;
4200 StorePlayer[jx][jy] = 0;
4201 Tile[jx][jy] = EL_EMPTY;
4206 else if (!network.enabled && !game.team_mode) // && !tape.playing
4208 // when in single player mode, eliminate all but the local player
4210 for (i = 0; i < MAX_PLAYERS; i++)
4212 struct PlayerInfo *player = &stored_player[i];
4214 if (player->active && player != local_player)
4216 int jx = player->jx, jy = player->jy;
4218 player->active = FALSE;
4219 player->present = FALSE;
4221 StorePlayer[jx][jy] = 0;
4222 Tile[jx][jy] = EL_EMPTY;
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].active)
4229 game.players_still_needed++;
4231 if (level.solved_by_one_player)
4232 game.players_still_needed = 1;
4234 // when recording the game, store which players take part in the game
4237 #if USE_NEW_PLAYER_ASSIGNMENTS
4238 for (i = 0; i < MAX_PLAYERS; i++)
4239 if (stored_player[i].connected)
4240 tape.player_participates[i] = TRUE;
4242 for (i = 0; i < MAX_PLAYERS; i++)
4243 if (stored_player[i].active)
4244 tape.player_participates[i] = TRUE;
4248 #if DEBUG_INIT_PLAYER
4249 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4252 if (BorderElement == EL_EMPTY)
4255 SBX_Right = lev_fieldx - SCR_FIELDX;
4257 SBY_Lower = lev_fieldy - SCR_FIELDY;
4262 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4264 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4267 if (full_lev_fieldx <= SCR_FIELDX)
4268 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4269 if (full_lev_fieldy <= SCR_FIELDY)
4270 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4272 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4274 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4277 // if local player not found, look for custom element that might create
4278 // the player (make some assumptions about the right custom element)
4279 if (!local_player->present)
4281 int start_x = 0, start_y = 0;
4282 int found_rating = 0;
4283 int found_element = EL_UNDEFINED;
4284 int player_nr = local_player->index_nr;
4286 SCAN_PLAYFIELD(x, y)
4288 int element = Tile[x][y];
4293 if (level.use_start_element[player_nr] &&
4294 level.start_element[player_nr] == element &&
4301 found_element = element;
4304 if (!IS_CUSTOM_ELEMENT(element))
4307 if (CAN_CHANGE(element))
4309 for (i = 0; i < element_info[element].num_change_pages; i++)
4311 // check for player created from custom element as single target
4312 content = element_info[element].change_page[i].target_element;
4313 is_player = IS_PLAYER_ELEMENT(content);
4315 if (is_player && (found_rating < 3 ||
4316 (found_rating == 3 && element < found_element)))
4322 found_element = element;
4327 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4329 // check for player created from custom element as explosion content
4330 content = element_info[element].content.e[xx][yy];
4331 is_player = IS_PLAYER_ELEMENT(content);
4333 if (is_player && (found_rating < 2 ||
4334 (found_rating == 2 && element < found_element)))
4336 start_x = x + xx - 1;
4337 start_y = y + yy - 1;
4340 found_element = element;
4343 if (!CAN_CHANGE(element))
4346 for (i = 0; i < element_info[element].num_change_pages; i++)
4348 // check for player created from custom element as extended target
4350 element_info[element].change_page[i].target_content.e[xx][yy];
4352 is_player = IS_PLAYER_ELEMENT(content);
4354 if (is_player && (found_rating < 1 ||
4355 (found_rating == 1 && element < found_element)))
4357 start_x = x + xx - 1;
4358 start_y = y + yy - 1;
4361 found_element = element;
4367 scroll_x = SCROLL_POSITION_X(start_x);
4368 scroll_y = SCROLL_POSITION_Y(start_y);
4372 scroll_x = SCROLL_POSITION_X(local_player->jx);
4373 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4376 // !!! FIX THIS (START) !!!
4377 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4379 InitGameEngine_EM();
4381 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4383 InitGameEngine_SP();
4385 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4387 InitGameEngine_MM();
4391 DrawLevel(REDRAW_FIELD);
4394 // after drawing the level, correct some elements
4395 if (game.timegate_time_left == 0)
4396 CloseAllOpenTimegates();
4399 // blit playfield from scroll buffer to normal back buffer for fading in
4400 BlitScreenToBitmap(backbuffer);
4401 // !!! FIX THIS (END) !!!
4403 DrawMaskedBorder(fade_mask);
4408 // full screen redraw is required at this point in the following cases:
4409 // - special editor door undrawn when game was started from level editor
4410 // - drawing area (playfield) was changed and has to be removed completely
4411 redraw_mask = REDRAW_ALL;
4415 if (!game.restart_level)
4417 // copy default game door content to main double buffer
4419 // !!! CHECK AGAIN !!!
4420 SetPanelBackground();
4421 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4422 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4425 SetPanelBackground();
4426 SetDrawBackgroundMask(REDRAW_DOOR_1);
4428 UpdateAndDisplayGameControlValues();
4430 if (!game.restart_level)
4436 CreateGameButtons();
4441 // copy actual game door content to door double buffer for OpenDoor()
4442 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4444 OpenDoor(DOOR_OPEN_ALL);
4446 KeyboardAutoRepeatOffUnlessAutoplay();
4448 #if DEBUG_INIT_PLAYER
4449 DebugPrintPlayerStatus("Player status (final)");
4458 if (!game.restart_level && !tape.playing)
4460 LevelStats_incPlayed(level_nr);
4462 SaveLevelSetup_SeriesInfo();
4465 game.restart_level = FALSE;
4466 game.restart_game_message = NULL;
4468 game.request_active = FALSE;
4469 game.request_active_or_moving = FALSE;
4471 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4472 InitGameActions_MM();
4474 SaveEngineSnapshotToListInitial();
4476 if (!game.restart_level)
4478 PlaySound(SND_GAME_STARTING);
4480 if (setup.sound_music)
4484 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4487 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4488 int actual_player_x, int actual_player_y)
4490 // this is used for non-R'n'D game engines to update certain engine values
4492 // needed to determine if sounds are played within the visible screen area
4493 scroll_x = actual_scroll_x;
4494 scroll_y = actual_scroll_y;
4496 // needed to get player position for "follow finger" playing input method
4497 local_player->jx = actual_player_x;
4498 local_player->jy = actual_player_y;
4501 void InitMovDir(int x, int y)
4503 int i, element = Tile[x][y];
4504 static int xy[4][2] =
4511 static int direction[3][4] =
4513 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4514 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4515 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4524 Tile[x][y] = EL_BUG;
4525 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4528 case EL_SPACESHIP_RIGHT:
4529 case EL_SPACESHIP_UP:
4530 case EL_SPACESHIP_LEFT:
4531 case EL_SPACESHIP_DOWN:
4532 Tile[x][y] = EL_SPACESHIP;
4533 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4536 case EL_BD_BUTTERFLY_RIGHT:
4537 case EL_BD_BUTTERFLY_UP:
4538 case EL_BD_BUTTERFLY_LEFT:
4539 case EL_BD_BUTTERFLY_DOWN:
4540 Tile[x][y] = EL_BD_BUTTERFLY;
4541 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4544 case EL_BD_FIREFLY_RIGHT:
4545 case EL_BD_FIREFLY_UP:
4546 case EL_BD_FIREFLY_LEFT:
4547 case EL_BD_FIREFLY_DOWN:
4548 Tile[x][y] = EL_BD_FIREFLY;
4549 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4552 case EL_PACMAN_RIGHT:
4554 case EL_PACMAN_LEFT:
4555 case EL_PACMAN_DOWN:
4556 Tile[x][y] = EL_PACMAN;
4557 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4560 case EL_YAMYAM_LEFT:
4561 case EL_YAMYAM_RIGHT:
4563 case EL_YAMYAM_DOWN:
4564 Tile[x][y] = EL_YAMYAM;
4565 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4568 case EL_SP_SNIKSNAK:
4569 MovDir[x][y] = MV_UP;
4572 case EL_SP_ELECTRON:
4573 MovDir[x][y] = MV_LEFT;
4580 Tile[x][y] = EL_MOLE;
4581 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4584 case EL_SPRING_LEFT:
4585 case EL_SPRING_RIGHT:
4586 Tile[x][y] = EL_SPRING;
4587 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4591 if (IS_CUSTOM_ELEMENT(element))
4593 struct ElementInfo *ei = &element_info[element];
4594 int move_direction_initial = ei->move_direction_initial;
4595 int move_pattern = ei->move_pattern;
4597 if (move_direction_initial == MV_START_PREVIOUS)
4599 if (MovDir[x][y] != MV_NONE)
4602 move_direction_initial = MV_START_AUTOMATIC;
4605 if (move_direction_initial == MV_START_RANDOM)
4606 MovDir[x][y] = 1 << RND(4);
4607 else if (move_direction_initial & MV_ANY_DIRECTION)
4608 MovDir[x][y] = move_direction_initial;
4609 else if (move_pattern == MV_ALL_DIRECTIONS ||
4610 move_pattern == MV_TURNING_LEFT ||
4611 move_pattern == MV_TURNING_RIGHT ||
4612 move_pattern == MV_TURNING_LEFT_RIGHT ||
4613 move_pattern == MV_TURNING_RIGHT_LEFT ||
4614 move_pattern == MV_TURNING_RANDOM)
4615 MovDir[x][y] = 1 << RND(4);
4616 else if (move_pattern == MV_HORIZONTAL)
4617 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4618 else if (move_pattern == MV_VERTICAL)
4619 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4620 else if (move_pattern & MV_ANY_DIRECTION)
4621 MovDir[x][y] = element_info[element].move_pattern;
4622 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4623 move_pattern == MV_ALONG_RIGHT_SIDE)
4625 // use random direction as default start direction
4626 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4627 MovDir[x][y] = 1 << RND(4);
4629 for (i = 0; i < NUM_DIRECTIONS; i++)
4631 int x1 = x + xy[i][0];
4632 int y1 = y + xy[i][1];
4634 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4636 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4637 MovDir[x][y] = direction[0][i];
4639 MovDir[x][y] = direction[1][i];
4648 MovDir[x][y] = 1 << RND(4);
4650 if (element != EL_BUG &&
4651 element != EL_SPACESHIP &&
4652 element != EL_BD_BUTTERFLY &&
4653 element != EL_BD_FIREFLY)
4656 for (i = 0; i < NUM_DIRECTIONS; i++)
4658 int x1 = x + xy[i][0];
4659 int y1 = y + xy[i][1];
4661 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4663 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4665 MovDir[x][y] = direction[0][i];
4668 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4669 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4671 MovDir[x][y] = direction[1][i];
4680 GfxDir[x][y] = MovDir[x][y];
4683 void InitAmoebaNr(int x, int y)
4686 int group_nr = AmoebaNeighbourNr(x, y);
4690 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4692 if (AmoebaCnt[i] == 0)
4700 AmoebaNr[x][y] = group_nr;
4701 AmoebaCnt[group_nr]++;
4702 AmoebaCnt2[group_nr]++;
4705 static void LevelSolved_SetFinalGameValues(void)
4707 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4708 game.score_time_final = (level.use_step_counter ? TimePlayed :
4709 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4711 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4712 game_em.lev->score :
4713 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4717 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4718 MM_HEALTH(game_mm.laser_overload_value) :
4721 game.LevelSolved_CountingTime = game.time_final;
4722 game.LevelSolved_CountingScore = game.score_final;
4723 game.LevelSolved_CountingHealth = game.health_final;
4726 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4728 game.LevelSolved_CountingTime = time;
4729 game.LevelSolved_CountingScore = score;
4730 game.LevelSolved_CountingHealth = health;
4732 game_panel_controls[GAME_PANEL_TIME].value = time;
4733 game_panel_controls[GAME_PANEL_SCORE].value = score;
4734 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4736 DisplayGameControlValues();
4739 static void LevelSolved(void)
4741 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4742 game.players_still_needed > 0)
4745 game.LevelSolved = TRUE;
4746 game.GameOver = TRUE;
4748 // needed here to display correct panel values while player walks into exit
4749 LevelSolved_SetFinalGameValues();
4754 static int time_count_steps;
4755 static int time, time_final;
4756 static float score, score_final; // needed for time score < 10 for 10 seconds
4757 static int health, health_final;
4758 static int game_over_delay_1 = 0;
4759 static int game_over_delay_2 = 0;
4760 static int game_over_delay_3 = 0;
4761 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4762 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4764 if (!game.LevelSolved_GameWon)
4768 // do not start end game actions before the player stops moving (to exit)
4769 if (local_player->active && local_player->MovPos)
4772 // calculate final game values after player finished walking into exit
4773 LevelSolved_SetFinalGameValues();
4775 game.LevelSolved_GameWon = TRUE;
4776 game.LevelSolved_SaveTape = tape.recording;
4777 game.LevelSolved_SaveScore = !tape.playing;
4781 LevelStats_incSolved(level_nr);
4783 SaveLevelSetup_SeriesInfo();
4786 if (tape.auto_play) // tape might already be stopped here
4787 tape.auto_play_level_solved = TRUE;
4791 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4792 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4793 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4795 time = time_final = game.time_final;
4796 score = score_final = game.score_final;
4797 health = health_final = game.health_final;
4799 // update game panel values before (delayed) counting of score (if any)
4800 LevelSolved_DisplayFinalGameValues(time, score, health);
4802 // if level has time score defined, calculate new final game values
4805 int time_final_max = 999;
4806 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4807 int time_frames = 0;
4808 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4809 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4814 time_frames = time_frames_left;
4816 else if (game.no_time_limit && TimePlayed < time_final_max)
4818 time_final = time_final_max;
4819 time_frames = time_frames_final_max - time_frames_played;
4822 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4824 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4826 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4829 score_final += health * time_score;
4832 game.score_final = score_final;
4833 game.health_final = health_final;
4836 // if not counting score after game, immediately update game panel values
4837 if (level_editor_test_game || !setup.count_score_after_game)
4840 score = score_final;
4842 LevelSolved_DisplayFinalGameValues(time, score, health);
4845 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4847 // check if last player has left the level
4848 if (game.exit_x >= 0 &&
4851 int x = game.exit_x;
4852 int y = game.exit_y;
4853 int element = Tile[x][y];
4855 // close exit door after last player
4856 if ((game.all_players_gone &&
4857 (element == EL_EXIT_OPEN ||
4858 element == EL_SP_EXIT_OPEN ||
4859 element == EL_STEEL_EXIT_OPEN)) ||
4860 element == EL_EM_EXIT_OPEN ||
4861 element == EL_EM_STEEL_EXIT_OPEN)
4865 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4866 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4867 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4868 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4869 EL_EM_STEEL_EXIT_CLOSING);
4871 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4874 // player disappears
4875 DrawLevelField(x, y);
4878 for (i = 0; i < MAX_PLAYERS; i++)
4880 struct PlayerInfo *player = &stored_player[i];
4882 if (player->present)
4884 RemovePlayer(player);
4886 // player disappears
4887 DrawLevelField(player->jx, player->jy);
4892 PlaySound(SND_GAME_WINNING);
4895 if (setup.count_score_after_game)
4897 if (time != time_final)
4899 if (game_over_delay_1 > 0)
4901 game_over_delay_1--;
4906 int time_to_go = ABS(time_final - time);
4907 int time_count_dir = (time < time_final ? +1 : -1);
4909 if (time_to_go < time_count_steps)
4910 time_count_steps = 1;
4912 time += time_count_steps * time_count_dir;
4913 score += time_count_steps * time_score;
4915 // set final score to correct rounding differences after counting score
4916 if (time == time_final)
4917 score = score_final;
4919 LevelSolved_DisplayFinalGameValues(time, score, health);
4921 if (time == time_final)
4922 StopSound(SND_GAME_LEVELTIME_BONUS);
4923 else if (setup.sound_loops)
4924 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4926 PlaySound(SND_GAME_LEVELTIME_BONUS);
4931 if (health != health_final)
4933 if (game_over_delay_2 > 0)
4935 game_over_delay_2--;
4940 int health_count_dir = (health < health_final ? +1 : -1);
4942 health += health_count_dir;
4943 score += time_score;
4945 LevelSolved_DisplayFinalGameValues(time, score, health);
4947 if (health == health_final)
4948 StopSound(SND_GAME_LEVELTIME_BONUS);
4949 else if (setup.sound_loops)
4950 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4952 PlaySound(SND_GAME_LEVELTIME_BONUS);
4958 game.panel.active = FALSE;
4960 if (game_over_delay_3 > 0)
4962 game_over_delay_3--;
4972 // used instead of "level_nr" (needed for network games)
4973 int last_level_nr = levelset.level_nr;
4974 boolean tape_saved = FALSE;
4976 game.LevelSolved_GameEnd = TRUE;
4978 if (game.LevelSolved_SaveTape)
4980 // make sure that request dialog to save tape does not open door again
4981 if (!global.use_envelope_request)
4982 CloseDoor(DOOR_CLOSE_1);
4985 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
4987 // set unique basename for score tape (also saved in high score table)
4988 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4991 // if no tape is to be saved, close both doors simultaneously
4992 CloseDoor(DOOR_CLOSE_ALL);
4994 if (level_editor_test_game)
4996 SetGameStatus(GAME_MODE_MAIN);
5003 if (!game.LevelSolved_SaveScore)
5005 SetGameStatus(GAME_MODE_MAIN);
5012 if (level_nr == leveldir_current->handicap_level)
5014 leveldir_current->handicap_level++;
5016 SaveLevelSetup_SeriesInfo();
5019 // save score and score tape before potentially erasing tape below
5020 NewHighScore(last_level_nr, tape_saved);
5022 if (setup.increment_levels &&
5023 level_nr < leveldir_current->last_level &&
5026 level_nr++; // advance to next level
5027 TapeErase(); // start with empty tape
5029 if (setup.auto_play_next_level)
5031 LoadLevel(level_nr);
5033 SaveLevelSetup_SeriesInfo();
5037 if (scores.last_added >= 0 && setup.show_scores_after_game)
5039 SetGameStatus(GAME_MODE_SCORES);
5041 DrawHallOfFame(last_level_nr);
5043 else if (setup.auto_play_next_level && setup.increment_levels &&
5044 last_level_nr < leveldir_current->last_level &&
5047 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5051 SetGameStatus(GAME_MODE_MAIN);
5057 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5058 boolean one_score_entry_per_name)
5062 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5065 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5067 struct ScoreEntry *entry = &list->entry[i];
5068 boolean score_is_better = (new_entry->score > entry->score);
5069 boolean score_is_equal = (new_entry->score == entry->score);
5070 boolean time_is_better = (new_entry->time < entry->time);
5071 boolean time_is_equal = (new_entry->time == entry->time);
5072 boolean better_by_score = (score_is_better ||
5073 (score_is_equal && time_is_better));
5074 boolean better_by_time = (time_is_better ||
5075 (time_is_equal && score_is_better));
5076 boolean is_better = (level.rate_time_over_score ? better_by_time :
5078 boolean entry_is_empty = (entry->score == 0 &&
5081 // prevent adding server score entries if also existing in local score file
5082 // (special case: historic score entries have an empty tape basename entry)
5083 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5084 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5087 if (is_better || entry_is_empty)
5089 // player has made it to the hall of fame
5091 if (i < MAX_SCORE_ENTRIES - 1)
5093 int m = MAX_SCORE_ENTRIES - 1;
5096 if (one_score_entry_per_name)
5098 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5099 if (strEqual(list->entry[l].name, new_entry->name))
5102 if (m == i) // player's new highscore overwrites his old one
5106 for (l = m; l > i; l--)
5107 list->entry[l] = list->entry[l - 1];
5112 *entry = *new_entry;
5116 else if (one_score_entry_per_name &&
5117 strEqual(entry->name, new_entry->name))
5119 // player already in high score list with better score or time
5128 void NewHighScore(int level_nr, boolean tape_saved)
5130 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5131 boolean one_per_name = FALSE;
5133 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5134 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5136 new_entry.score = game.score_final;
5137 new_entry.time = game.score_time_final;
5139 LoadScore(level_nr);
5141 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5143 if (scores.last_added < 0)
5146 SaveScore(level_nr);
5148 // store last added local score entry (before merging server scores)
5149 scores.last_added_local = scores.last_added;
5151 if (!game.LevelSolved_SaveTape)
5154 SaveScoreTape(level_nr);
5156 if (setup.ask_for_using_api_server)
5158 setup.use_api_server =
5159 Request("Upload your score and tape to the high score server?", REQ_ASK);
5161 if (!setup.use_api_server)
5162 Request("Not using high score server! Use setup menu to enable again!",
5165 runtime.use_api_server = setup.use_api_server;
5167 // after asking for using API server once, do not ask again
5168 setup.ask_for_using_api_server = FALSE;
5170 SaveSetup_ServerSetup();
5173 SaveServerScore(level_nr, tape_saved);
5176 void MergeServerScore(void)
5178 struct ScoreEntry last_added_entry;
5179 boolean one_per_name = FALSE;
5182 if (scores.last_added >= 0)
5183 last_added_entry = scores.entry[scores.last_added];
5185 for (i = 0; i < server_scores.num_entries; i++)
5187 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5189 if (pos >= 0 && pos <= scores.last_added)
5190 scores.last_added++;
5193 if (scores.last_added >= MAX_SCORE_ENTRIES)
5195 scores.last_added = MAX_SCORE_ENTRIES - 1;
5196 scores.force_last_added = TRUE;
5198 scores.entry[scores.last_added] = last_added_entry;
5202 static int getElementMoveStepsizeExt(int x, int y, int direction)
5204 int element = Tile[x][y];
5205 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5206 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5207 int horiz_move = (dx != 0);
5208 int sign = (horiz_move ? dx : dy);
5209 int step = sign * element_info[element].move_stepsize;
5211 // special values for move stepsize for spring and things on conveyor belt
5214 if (CAN_FALL(element) &&
5215 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5216 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5217 else if (element == EL_SPRING)
5218 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5224 static int getElementMoveStepsize(int x, int y)
5226 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5229 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5231 if (player->GfxAction != action || player->GfxDir != dir)
5233 player->GfxAction = action;
5234 player->GfxDir = dir;
5236 player->StepFrame = 0;
5240 static void ResetGfxFrame(int x, int y)
5242 // profiling showed that "autotest" spends 10~20% of its time in this function
5243 if (DrawingDeactivatedField())
5246 int element = Tile[x][y];
5247 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5249 if (graphic_info[graphic].anim_global_sync)
5250 GfxFrame[x][y] = FrameCounter;
5251 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5252 GfxFrame[x][y] = CustomValue[x][y];
5253 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5254 GfxFrame[x][y] = element_info[element].collect_score;
5255 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5256 GfxFrame[x][y] = ChangeDelay[x][y];
5259 static void ResetGfxAnimation(int x, int y)
5261 GfxAction[x][y] = ACTION_DEFAULT;
5262 GfxDir[x][y] = MovDir[x][y];
5265 ResetGfxFrame(x, y);
5268 static void ResetRandomAnimationValue(int x, int y)
5270 GfxRandom[x][y] = INIT_GFX_RANDOM();
5273 static void InitMovingField(int x, int y, int direction)
5275 int element = Tile[x][y];
5276 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5277 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5280 boolean is_moving_before, is_moving_after;
5282 // check if element was/is moving or being moved before/after mode change
5283 is_moving_before = (WasJustMoving[x][y] != 0);
5284 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5286 // reset animation only for moving elements which change direction of moving
5287 // or which just started or stopped moving
5288 // (else CEs with property "can move" / "not moving" are reset each frame)
5289 if (is_moving_before != is_moving_after ||
5290 direction != MovDir[x][y])
5291 ResetGfxAnimation(x, y);
5293 MovDir[x][y] = direction;
5294 GfxDir[x][y] = direction;
5296 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5297 direction == MV_DOWN && CAN_FALL(element) ?
5298 ACTION_FALLING : ACTION_MOVING);
5300 // this is needed for CEs with property "can move" / "not moving"
5302 if (is_moving_after)
5304 if (Tile[newx][newy] == EL_EMPTY)
5305 Tile[newx][newy] = EL_BLOCKED;
5307 MovDir[newx][newy] = MovDir[x][y];
5309 CustomValue[newx][newy] = CustomValue[x][y];
5311 GfxFrame[newx][newy] = GfxFrame[x][y];
5312 GfxRandom[newx][newy] = GfxRandom[x][y];
5313 GfxAction[newx][newy] = GfxAction[x][y];
5314 GfxDir[newx][newy] = GfxDir[x][y];
5318 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5320 int direction = MovDir[x][y];
5321 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5322 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5328 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5330 int oldx = x, oldy = y;
5331 int direction = MovDir[x][y];
5333 if (direction == MV_LEFT)
5335 else if (direction == MV_RIGHT)
5337 else if (direction == MV_UP)
5339 else if (direction == MV_DOWN)
5342 *comes_from_x = oldx;
5343 *comes_from_y = oldy;
5346 static int MovingOrBlocked2Element(int x, int y)
5348 int element = Tile[x][y];
5350 if (element == EL_BLOCKED)
5354 Blocked2Moving(x, y, &oldx, &oldy);
5355 return Tile[oldx][oldy];
5361 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5363 // like MovingOrBlocked2Element(), but if element is moving
5364 // and (x,y) is the field the moving element is just leaving,
5365 // return EL_BLOCKED instead of the element value
5366 int element = Tile[x][y];
5368 if (IS_MOVING(x, y))
5370 if (element == EL_BLOCKED)
5374 Blocked2Moving(x, y, &oldx, &oldy);
5375 return Tile[oldx][oldy];
5384 static void RemoveField(int x, int y)
5386 Tile[x][y] = EL_EMPTY;
5392 CustomValue[x][y] = 0;
5395 ChangeDelay[x][y] = 0;
5396 ChangePage[x][y] = -1;
5397 Pushed[x][y] = FALSE;
5399 GfxElement[x][y] = EL_UNDEFINED;
5400 GfxAction[x][y] = ACTION_DEFAULT;
5401 GfxDir[x][y] = MV_NONE;
5404 static void RemoveMovingField(int x, int y)
5406 int oldx = x, oldy = y, newx = x, newy = y;
5407 int element = Tile[x][y];
5408 int next_element = EL_UNDEFINED;
5410 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5413 if (IS_MOVING(x, y))
5415 Moving2Blocked(x, y, &newx, &newy);
5417 if (Tile[newx][newy] != EL_BLOCKED)
5419 // element is moving, but target field is not free (blocked), but
5420 // already occupied by something different (example: acid pool);
5421 // in this case, only remove the moving field, but not the target
5423 RemoveField(oldx, oldy);
5425 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5427 TEST_DrawLevelField(oldx, oldy);
5432 else if (element == EL_BLOCKED)
5434 Blocked2Moving(x, y, &oldx, &oldy);
5435 if (!IS_MOVING(oldx, oldy))
5439 if (element == EL_BLOCKED &&
5440 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5441 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5442 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5443 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5444 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5445 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5446 next_element = get_next_element(Tile[oldx][oldy]);
5448 RemoveField(oldx, oldy);
5449 RemoveField(newx, newy);
5451 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5453 if (next_element != EL_UNDEFINED)
5454 Tile[oldx][oldy] = next_element;
5456 TEST_DrawLevelField(oldx, oldy);
5457 TEST_DrawLevelField(newx, newy);
5460 void DrawDynamite(int x, int y)
5462 int sx = SCREENX(x), sy = SCREENY(y);
5463 int graphic = el2img(Tile[x][y]);
5466 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5469 if (IS_WALKABLE_INSIDE(Back[x][y]))
5473 DrawLevelElement(x, y, Back[x][y]);
5474 else if (Store[x][y])
5475 DrawLevelElement(x, y, Store[x][y]);
5476 else if (game.use_masked_elements)
5477 DrawLevelElement(x, y, EL_EMPTY);
5479 frame = getGraphicAnimationFrameXY(graphic, x, y);
5481 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5482 DrawGraphicThruMask(sx, sy, graphic, frame);
5484 DrawGraphic(sx, sy, graphic, frame);
5487 static void CheckDynamite(int x, int y)
5489 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5493 if (MovDelay[x][y] != 0)
5496 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5502 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5507 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5509 boolean num_checked_players = 0;
5512 for (i = 0; i < MAX_PLAYERS; i++)
5514 if (stored_player[i].active)
5516 int sx = stored_player[i].jx;
5517 int sy = stored_player[i].jy;
5519 if (num_checked_players == 0)
5526 *sx1 = MIN(*sx1, sx);
5527 *sy1 = MIN(*sy1, sy);
5528 *sx2 = MAX(*sx2, sx);
5529 *sy2 = MAX(*sy2, sy);
5532 num_checked_players++;
5537 static boolean checkIfAllPlayersFitToScreen_RND(void)
5539 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5541 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5543 return (sx2 - sx1 < SCR_FIELDX &&
5544 sy2 - sy1 < SCR_FIELDY);
5547 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5549 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5551 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5553 *sx = (sx1 + sx2) / 2;
5554 *sy = (sy1 + sy2) / 2;
5557 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5558 boolean center_screen, boolean quick_relocation)
5560 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5561 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5562 boolean no_delay = (tape.warp_forward);
5563 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5564 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5565 int new_scroll_x, new_scroll_y;
5567 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5569 // case 1: quick relocation inside visible screen (without scrolling)
5576 if (!level.shifted_relocation || center_screen)
5578 // relocation _with_ centering of screen
5580 new_scroll_x = SCROLL_POSITION_X(x);
5581 new_scroll_y = SCROLL_POSITION_Y(y);
5585 // relocation _without_ centering of screen
5587 int center_scroll_x = SCROLL_POSITION_X(old_x);
5588 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5589 int offset_x = x + (scroll_x - center_scroll_x);
5590 int offset_y = y + (scroll_y - center_scroll_y);
5592 // for new screen position, apply previous offset to center position
5593 new_scroll_x = SCROLL_POSITION_X(offset_x);
5594 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5597 if (quick_relocation)
5599 // case 2: quick relocation (redraw without visible scrolling)
5601 scroll_x = new_scroll_x;
5602 scroll_y = new_scroll_y;
5609 // case 3: visible relocation (with scrolling to new position)
5611 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5613 SetVideoFrameDelay(wait_delay_value);
5615 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5617 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5618 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5620 if (dx == 0 && dy == 0) // no scrolling needed at all
5626 // set values for horizontal/vertical screen scrolling (half tile size)
5627 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5628 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5629 int pos_x = dx * TILEX / 2;
5630 int pos_y = dy * TILEY / 2;
5631 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5632 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5634 ScrollLevel(dx, dy);
5637 // scroll in two steps of half tile size to make things smoother
5638 BlitScreenToBitmapExt_RND(window, fx, fy);
5640 // scroll second step to align at full tile size
5641 BlitScreenToBitmap(window);
5647 SetVideoFrameDelay(frame_delay_value_old);
5650 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5652 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5653 int player_nr = GET_PLAYER_NR(el_player);
5654 struct PlayerInfo *player = &stored_player[player_nr];
5655 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5656 boolean no_delay = (tape.warp_forward);
5657 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5658 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5659 int old_jx = player->jx;
5660 int old_jy = player->jy;
5661 int old_element = Tile[old_jx][old_jy];
5662 int element = Tile[jx][jy];
5663 boolean player_relocated = (old_jx != jx || old_jy != jy);
5665 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5666 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5667 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5668 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5669 int leave_side_horiz = move_dir_horiz;
5670 int leave_side_vert = move_dir_vert;
5671 int enter_side = enter_side_horiz | enter_side_vert;
5672 int leave_side = leave_side_horiz | leave_side_vert;
5674 if (player->buried) // do not reanimate dead player
5677 if (!player_relocated) // no need to relocate the player
5680 if (IS_PLAYER(jx, jy)) // player already placed at new position
5682 RemoveField(jx, jy); // temporarily remove newly placed player
5683 DrawLevelField(jx, jy);
5686 if (player->present)
5688 while (player->MovPos)
5690 ScrollPlayer(player, SCROLL_GO_ON);
5691 ScrollScreen(NULL, SCROLL_GO_ON);
5693 AdvanceFrameAndPlayerCounters(player->index_nr);
5697 BackToFront_WithFrameDelay(wait_delay_value);
5700 DrawPlayer(player); // needed here only to cleanup last field
5701 DrawLevelField(player->jx, player->jy); // remove player graphic
5703 player->is_moving = FALSE;
5706 if (IS_CUSTOM_ELEMENT(old_element))
5707 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5709 player->index_bit, leave_side);
5711 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5713 player->index_bit, leave_side);
5715 Tile[jx][jy] = el_player;
5716 InitPlayerField(jx, jy, el_player, TRUE);
5718 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5719 possible that the relocation target field did not contain a player element,
5720 but a walkable element, to which the new player was relocated -- in this
5721 case, restore that (already initialized!) element on the player field */
5722 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5724 Tile[jx][jy] = element; // restore previously existing element
5727 // only visually relocate centered player
5728 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5729 FALSE, level.instant_relocation);
5731 TestIfPlayerTouchesBadThing(jx, jy);
5732 TestIfPlayerTouchesCustomElement(jx, jy);
5734 if (IS_CUSTOM_ELEMENT(element))
5735 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5736 player->index_bit, enter_side);
5738 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5739 player->index_bit, enter_side);
5741 if (player->is_switching)
5743 /* ensure that relocation while still switching an element does not cause
5744 a new element to be treated as also switched directly after relocation
5745 (this is important for teleporter switches that teleport the player to
5746 a place where another teleporter switch is in the same direction, which
5747 would then incorrectly be treated as immediately switched before the
5748 direction key that caused the switch was released) */
5750 player->switch_x += jx - old_jx;
5751 player->switch_y += jy - old_jy;
5755 static void Explode(int ex, int ey, int phase, int mode)
5761 // !!! eliminate this variable !!!
5762 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5764 if (game.explosions_delayed)
5766 ExplodeField[ex][ey] = mode;
5770 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5772 int center_element = Tile[ex][ey];
5773 int artwork_element, explosion_element; // set these values later
5775 // remove things displayed in background while burning dynamite
5776 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5779 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5781 // put moving element to center field (and let it explode there)
5782 center_element = MovingOrBlocked2Element(ex, ey);
5783 RemoveMovingField(ex, ey);
5784 Tile[ex][ey] = center_element;
5787 // now "center_element" is finally determined -- set related values now
5788 artwork_element = center_element; // for custom player artwork
5789 explosion_element = center_element; // for custom player artwork
5791 if (IS_PLAYER(ex, ey))
5793 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5795 artwork_element = stored_player[player_nr].artwork_element;
5797 if (level.use_explosion_element[player_nr])
5799 explosion_element = level.explosion_element[player_nr];
5800 artwork_element = explosion_element;
5804 if (mode == EX_TYPE_NORMAL ||
5805 mode == EX_TYPE_CENTER ||
5806 mode == EX_TYPE_CROSS)
5807 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5809 last_phase = element_info[explosion_element].explosion_delay + 1;
5811 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5813 int xx = x - ex + 1;
5814 int yy = y - ey + 1;
5817 if (!IN_LEV_FIELD(x, y) ||
5818 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5819 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5822 element = Tile[x][y];
5824 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5826 element = MovingOrBlocked2Element(x, y);
5828 if (!IS_EXPLOSION_PROOF(element))
5829 RemoveMovingField(x, y);
5832 // indestructible elements can only explode in center (but not flames)
5833 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5834 mode == EX_TYPE_BORDER)) ||
5835 element == EL_FLAMES)
5838 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5839 behaviour, for example when touching a yamyam that explodes to rocks
5840 with active deadly shield, a rock is created under the player !!! */
5841 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5843 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5844 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5845 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5847 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5850 if (IS_ACTIVE_BOMB(element))
5852 // re-activate things under the bomb like gate or penguin
5853 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5860 // save walkable background elements while explosion on same tile
5861 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5862 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5863 Back[x][y] = element;
5865 // ignite explodable elements reached by other explosion
5866 if (element == EL_EXPLOSION)
5867 element = Store2[x][y];
5869 if (AmoebaNr[x][y] &&
5870 (element == EL_AMOEBA_FULL ||
5871 element == EL_BD_AMOEBA ||
5872 element == EL_AMOEBA_GROWING))
5874 AmoebaCnt[AmoebaNr[x][y]]--;
5875 AmoebaCnt2[AmoebaNr[x][y]]--;
5880 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5882 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5884 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5886 if (PLAYERINFO(ex, ey)->use_murphy)
5887 Store[x][y] = EL_EMPTY;
5890 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5891 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5892 else if (IS_PLAYER_ELEMENT(center_element))
5893 Store[x][y] = EL_EMPTY;
5894 else if (center_element == EL_YAMYAM)
5895 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5896 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5897 Store[x][y] = element_info[center_element].content.e[xx][yy];
5899 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5900 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5901 // otherwise) -- FIX THIS !!!
5902 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5903 Store[x][y] = element_info[element].content.e[1][1];
5905 else if (!CAN_EXPLODE(element))
5906 Store[x][y] = element_info[element].content.e[1][1];
5909 Store[x][y] = EL_EMPTY;
5911 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5912 center_element == EL_AMOEBA_TO_DIAMOND)
5913 Store2[x][y] = element;
5915 Tile[x][y] = EL_EXPLOSION;
5916 GfxElement[x][y] = artwork_element;
5918 ExplodePhase[x][y] = 1;
5919 ExplodeDelay[x][y] = last_phase;
5924 if (center_element == EL_YAMYAM)
5925 game.yamyam_content_nr =
5926 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5938 GfxFrame[x][y] = 0; // restart explosion animation
5940 last_phase = ExplodeDelay[x][y];
5942 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5944 // this can happen if the player leaves an explosion just in time
5945 if (GfxElement[x][y] == EL_UNDEFINED)
5946 GfxElement[x][y] = EL_EMPTY;
5948 border_element = Store2[x][y];
5949 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5950 border_element = StorePlayer[x][y];
5952 if (phase == element_info[border_element].ignition_delay ||
5953 phase == last_phase)
5955 boolean border_explosion = FALSE;
5957 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5958 !PLAYER_EXPLOSION_PROTECTED(x, y))
5960 KillPlayerUnlessExplosionProtected(x, y);
5961 border_explosion = TRUE;
5963 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5965 Tile[x][y] = Store2[x][y];
5968 border_explosion = TRUE;
5970 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5972 AmoebaToDiamond(x, y);
5974 border_explosion = TRUE;
5977 // if an element just explodes due to another explosion (chain-reaction),
5978 // do not immediately end the new explosion when it was the last frame of
5979 // the explosion (as it would be done in the following "if"-statement!)
5980 if (border_explosion && phase == last_phase)
5984 if (phase == last_phase)
5988 element = Tile[x][y] = Store[x][y];
5989 Store[x][y] = Store2[x][y] = 0;
5990 GfxElement[x][y] = EL_UNDEFINED;
5992 // player can escape from explosions and might therefore be still alive
5993 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5994 element <= EL_PLAYER_IS_EXPLODING_4)
5996 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5997 int explosion_element = EL_PLAYER_1 + player_nr;
5998 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5999 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6001 if (level.use_explosion_element[player_nr])
6002 explosion_element = level.explosion_element[player_nr];
6004 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6005 element_info[explosion_element].content.e[xx][yy]);
6008 // restore probably existing indestructible background element
6009 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6010 element = Tile[x][y] = Back[x][y];
6013 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6014 GfxDir[x][y] = MV_NONE;
6015 ChangeDelay[x][y] = 0;
6016 ChangePage[x][y] = -1;
6018 CustomValue[x][y] = 0;
6020 InitField_WithBug2(x, y, FALSE);
6022 TEST_DrawLevelField(x, y);
6024 TestIfElementTouchesCustomElement(x, y);
6026 if (GFX_CRUMBLED(element))
6027 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6029 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6030 StorePlayer[x][y] = 0;
6032 if (IS_PLAYER_ELEMENT(element))
6033 RelocatePlayer(x, y, element);
6035 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6037 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6038 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6041 TEST_DrawLevelFieldCrumbled(x, y);
6043 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6045 DrawLevelElement(x, y, Back[x][y]);
6046 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6048 else if (IS_WALKABLE_UNDER(Back[x][y]))
6050 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6051 DrawLevelElementThruMask(x, y, Back[x][y]);
6053 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6054 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6058 static void DynaExplode(int ex, int ey)
6061 int dynabomb_element = Tile[ex][ey];
6062 int dynabomb_size = 1;
6063 boolean dynabomb_xl = FALSE;
6064 struct PlayerInfo *player;
6065 static int xy[4][2] =
6073 if (IS_ACTIVE_BOMB(dynabomb_element))
6075 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6076 dynabomb_size = player->dynabomb_size;
6077 dynabomb_xl = player->dynabomb_xl;
6078 player->dynabombs_left++;
6081 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6083 for (i = 0; i < NUM_DIRECTIONS; i++)
6085 for (j = 1; j <= dynabomb_size; j++)
6087 int x = ex + j * xy[i][0];
6088 int y = ey + j * xy[i][1];
6091 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6094 element = Tile[x][y];
6096 // do not restart explosions of fields with active bombs
6097 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6100 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6102 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6103 !IS_DIGGABLE(element) && !dynabomb_xl)
6109 void Bang(int x, int y)
6111 int element = MovingOrBlocked2Element(x, y);
6112 int explosion_type = EX_TYPE_NORMAL;
6114 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6116 struct PlayerInfo *player = PLAYERINFO(x, y);
6118 element = Tile[x][y] = player->initial_element;
6120 if (level.use_explosion_element[player->index_nr])
6122 int explosion_element = level.explosion_element[player->index_nr];
6124 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6125 explosion_type = EX_TYPE_CROSS;
6126 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6127 explosion_type = EX_TYPE_CENTER;
6135 case EL_BD_BUTTERFLY:
6138 case EL_DARK_YAMYAM:
6142 RaiseScoreElement(element);
6145 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6146 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6147 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6148 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6149 case EL_DYNABOMB_INCREASE_NUMBER:
6150 case EL_DYNABOMB_INCREASE_SIZE:
6151 case EL_DYNABOMB_INCREASE_POWER:
6152 explosion_type = EX_TYPE_DYNA;
6155 case EL_DC_LANDMINE:
6156 explosion_type = EX_TYPE_CENTER;
6161 case EL_LAMP_ACTIVE:
6162 case EL_AMOEBA_TO_DIAMOND:
6163 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6164 explosion_type = EX_TYPE_CENTER;
6168 if (element_info[element].explosion_type == EXPLODES_CROSS)
6169 explosion_type = EX_TYPE_CROSS;
6170 else if (element_info[element].explosion_type == EXPLODES_1X1)
6171 explosion_type = EX_TYPE_CENTER;
6175 if (explosion_type == EX_TYPE_DYNA)
6178 Explode(x, y, EX_PHASE_START, explosion_type);
6180 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6183 static void SplashAcid(int x, int y)
6185 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6186 (!IN_LEV_FIELD(x - 1, y - 2) ||
6187 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6188 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6190 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6191 (!IN_LEV_FIELD(x + 1, y - 2) ||
6192 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6193 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6195 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6198 static void InitBeltMovement(void)
6200 static int belt_base_element[4] =
6202 EL_CONVEYOR_BELT_1_LEFT,
6203 EL_CONVEYOR_BELT_2_LEFT,
6204 EL_CONVEYOR_BELT_3_LEFT,
6205 EL_CONVEYOR_BELT_4_LEFT
6207 static int belt_base_active_element[4] =
6209 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6210 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6211 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6212 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6217 // set frame order for belt animation graphic according to belt direction
6218 for (i = 0; i < NUM_BELTS; i++)
6222 for (j = 0; j < NUM_BELT_PARTS; j++)
6224 int element = belt_base_active_element[belt_nr] + j;
6225 int graphic_1 = el2img(element);
6226 int graphic_2 = el2panelimg(element);
6228 if (game.belt_dir[i] == MV_LEFT)
6230 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6231 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6235 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6236 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6241 SCAN_PLAYFIELD(x, y)
6243 int element = Tile[x][y];
6245 for (i = 0; i < NUM_BELTS; i++)
6247 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6249 int e_belt_nr = getBeltNrFromBeltElement(element);
6252 if (e_belt_nr == belt_nr)
6254 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6256 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6263 static void ToggleBeltSwitch(int x, int y)
6265 static int belt_base_element[4] =
6267 EL_CONVEYOR_BELT_1_LEFT,
6268 EL_CONVEYOR_BELT_2_LEFT,
6269 EL_CONVEYOR_BELT_3_LEFT,
6270 EL_CONVEYOR_BELT_4_LEFT
6272 static int belt_base_active_element[4] =
6274 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6275 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6276 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6277 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6279 static int belt_base_switch_element[4] =
6281 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6282 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6283 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6284 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6286 static int belt_move_dir[4] =
6294 int element = Tile[x][y];
6295 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6296 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6297 int belt_dir = belt_move_dir[belt_dir_nr];
6300 if (!IS_BELT_SWITCH(element))
6303 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6304 game.belt_dir[belt_nr] = belt_dir;
6306 if (belt_dir_nr == 3)
6309 // set frame order for belt animation graphic according to belt direction
6310 for (i = 0; i < NUM_BELT_PARTS; i++)
6312 int element = belt_base_active_element[belt_nr] + i;
6313 int graphic_1 = el2img(element);
6314 int graphic_2 = el2panelimg(element);
6316 if (belt_dir == MV_LEFT)
6318 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6319 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6323 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6324 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6328 SCAN_PLAYFIELD(xx, yy)
6330 int element = Tile[xx][yy];
6332 if (IS_BELT_SWITCH(element))
6334 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6336 if (e_belt_nr == belt_nr)
6338 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6339 TEST_DrawLevelField(xx, yy);
6342 else if (IS_BELT(element) && belt_dir != MV_NONE)
6344 int e_belt_nr = getBeltNrFromBeltElement(element);
6346 if (e_belt_nr == belt_nr)
6348 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6350 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6351 TEST_DrawLevelField(xx, yy);
6354 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6356 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6358 if (e_belt_nr == belt_nr)
6360 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6362 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6363 TEST_DrawLevelField(xx, yy);
6369 static void ToggleSwitchgateSwitch(int x, int y)
6373 game.switchgate_pos = !game.switchgate_pos;
6375 SCAN_PLAYFIELD(xx, yy)
6377 int element = Tile[xx][yy];
6379 if (element == EL_SWITCHGATE_SWITCH_UP)
6381 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6382 TEST_DrawLevelField(xx, yy);
6384 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6386 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6387 TEST_DrawLevelField(xx, yy);
6389 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6391 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6392 TEST_DrawLevelField(xx, yy);
6394 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6396 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6397 TEST_DrawLevelField(xx, yy);
6399 else if (element == EL_SWITCHGATE_OPEN ||
6400 element == EL_SWITCHGATE_OPENING)
6402 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6404 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6406 else if (element == EL_SWITCHGATE_CLOSED ||
6407 element == EL_SWITCHGATE_CLOSING)
6409 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6411 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6416 static int getInvisibleActiveFromInvisibleElement(int element)
6418 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6419 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6420 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6424 static int getInvisibleFromInvisibleActiveElement(int element)
6426 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6427 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6428 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6432 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6436 SCAN_PLAYFIELD(x, y)
6438 int element = Tile[x][y];
6440 if (element == EL_LIGHT_SWITCH &&
6441 game.light_time_left > 0)
6443 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6444 TEST_DrawLevelField(x, y);
6446 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6447 game.light_time_left == 0)
6449 Tile[x][y] = EL_LIGHT_SWITCH;
6450 TEST_DrawLevelField(x, y);
6452 else if (element == EL_EMC_DRIPPER &&
6453 game.light_time_left > 0)
6455 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6456 TEST_DrawLevelField(x, y);
6458 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6459 game.light_time_left == 0)
6461 Tile[x][y] = EL_EMC_DRIPPER;
6462 TEST_DrawLevelField(x, y);
6464 else if (element == EL_INVISIBLE_STEELWALL ||
6465 element == EL_INVISIBLE_WALL ||
6466 element == EL_INVISIBLE_SAND)
6468 if (game.light_time_left > 0)
6469 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6471 TEST_DrawLevelField(x, y);
6473 // uncrumble neighbour fields, if needed
6474 if (element == EL_INVISIBLE_SAND)
6475 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6477 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6478 element == EL_INVISIBLE_WALL_ACTIVE ||
6479 element == EL_INVISIBLE_SAND_ACTIVE)
6481 if (game.light_time_left == 0)
6482 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6484 TEST_DrawLevelField(x, y);
6486 // re-crumble neighbour fields, if needed
6487 if (element == EL_INVISIBLE_SAND)
6488 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6493 static void RedrawAllInvisibleElementsForLenses(void)
6497 SCAN_PLAYFIELD(x, y)
6499 int element = Tile[x][y];
6501 if (element == EL_EMC_DRIPPER &&
6502 game.lenses_time_left > 0)
6504 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6505 TEST_DrawLevelField(x, y);
6507 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6508 game.lenses_time_left == 0)
6510 Tile[x][y] = EL_EMC_DRIPPER;
6511 TEST_DrawLevelField(x, y);
6513 else if (element == EL_INVISIBLE_STEELWALL ||
6514 element == EL_INVISIBLE_WALL ||
6515 element == EL_INVISIBLE_SAND)
6517 if (game.lenses_time_left > 0)
6518 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6520 TEST_DrawLevelField(x, y);
6522 // uncrumble neighbour fields, if needed
6523 if (element == EL_INVISIBLE_SAND)
6524 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6526 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6527 element == EL_INVISIBLE_WALL_ACTIVE ||
6528 element == EL_INVISIBLE_SAND_ACTIVE)
6530 if (game.lenses_time_left == 0)
6531 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6533 TEST_DrawLevelField(x, y);
6535 // re-crumble neighbour fields, if needed
6536 if (element == EL_INVISIBLE_SAND)
6537 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6542 static void RedrawAllInvisibleElementsForMagnifier(void)
6546 SCAN_PLAYFIELD(x, y)
6548 int element = Tile[x][y];
6550 if (element == EL_EMC_FAKE_GRASS &&
6551 game.magnify_time_left > 0)
6553 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6554 TEST_DrawLevelField(x, y);
6556 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6557 game.magnify_time_left == 0)
6559 Tile[x][y] = EL_EMC_FAKE_GRASS;
6560 TEST_DrawLevelField(x, y);
6562 else if (IS_GATE_GRAY(element) &&
6563 game.magnify_time_left > 0)
6565 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6566 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6567 IS_EM_GATE_GRAY(element) ?
6568 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6569 IS_EMC_GATE_GRAY(element) ?
6570 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6571 IS_DC_GATE_GRAY(element) ?
6572 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6574 TEST_DrawLevelField(x, y);
6576 else if (IS_GATE_GRAY_ACTIVE(element) &&
6577 game.magnify_time_left == 0)
6579 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6580 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6581 IS_EM_GATE_GRAY_ACTIVE(element) ?
6582 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6583 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6584 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6585 IS_DC_GATE_GRAY_ACTIVE(element) ?
6586 EL_DC_GATE_WHITE_GRAY :
6588 TEST_DrawLevelField(x, y);
6593 static void ToggleLightSwitch(int x, int y)
6595 int element = Tile[x][y];
6597 game.light_time_left =
6598 (element == EL_LIGHT_SWITCH ?
6599 level.time_light * FRAMES_PER_SECOND : 0);
6601 RedrawAllLightSwitchesAndInvisibleElements();
6604 static void ActivateTimegateSwitch(int x, int y)
6608 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6610 SCAN_PLAYFIELD(xx, yy)
6612 int element = Tile[xx][yy];
6614 if (element == EL_TIMEGATE_CLOSED ||
6615 element == EL_TIMEGATE_CLOSING)
6617 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6618 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6622 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6624 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6625 TEST_DrawLevelField(xx, yy);
6631 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6632 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6635 static void Impact(int x, int y)
6637 boolean last_line = (y == lev_fieldy - 1);
6638 boolean object_hit = FALSE;
6639 boolean impact = (last_line || object_hit);
6640 int element = Tile[x][y];
6641 int smashed = EL_STEELWALL;
6643 if (!last_line) // check if element below was hit
6645 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6648 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6649 MovDir[x][y + 1] != MV_DOWN ||
6650 MovPos[x][y + 1] <= TILEY / 2));
6652 // do not smash moving elements that left the smashed field in time
6653 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6654 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6657 #if USE_QUICKSAND_IMPACT_BUGFIX
6658 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6660 RemoveMovingField(x, y + 1);
6661 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6662 Tile[x][y + 2] = EL_ROCK;
6663 TEST_DrawLevelField(x, y + 2);
6668 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6670 RemoveMovingField(x, y + 1);
6671 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6672 Tile[x][y + 2] = EL_ROCK;
6673 TEST_DrawLevelField(x, y + 2);
6680 smashed = MovingOrBlocked2Element(x, y + 1);
6682 impact = (last_line || object_hit);
6685 if (!last_line && smashed == EL_ACID) // element falls into acid
6687 SplashAcid(x, y + 1);
6691 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6692 // only reset graphic animation if graphic really changes after impact
6694 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6696 ResetGfxAnimation(x, y);
6697 TEST_DrawLevelField(x, y);
6700 if (impact && CAN_EXPLODE_IMPACT(element))
6705 else if (impact && element == EL_PEARL &&
6706 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6708 ResetGfxAnimation(x, y);
6710 Tile[x][y] = EL_PEARL_BREAKING;
6711 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6714 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6716 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6721 if (impact && element == EL_AMOEBA_DROP)
6723 if (object_hit && IS_PLAYER(x, y + 1))
6724 KillPlayerUnlessEnemyProtected(x, y + 1);
6725 else if (object_hit && smashed == EL_PENGUIN)
6729 Tile[x][y] = EL_AMOEBA_GROWING;
6730 Store[x][y] = EL_AMOEBA_WET;
6732 ResetRandomAnimationValue(x, y);
6737 if (object_hit) // check which object was hit
6739 if ((CAN_PASS_MAGIC_WALL(element) &&
6740 (smashed == EL_MAGIC_WALL ||
6741 smashed == EL_BD_MAGIC_WALL)) ||
6742 (CAN_PASS_DC_MAGIC_WALL(element) &&
6743 smashed == EL_DC_MAGIC_WALL))
6746 int activated_magic_wall =
6747 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6748 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6749 EL_DC_MAGIC_WALL_ACTIVE);
6751 // activate magic wall / mill
6752 SCAN_PLAYFIELD(xx, yy)
6754 if (Tile[xx][yy] == smashed)
6755 Tile[xx][yy] = activated_magic_wall;
6758 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6759 game.magic_wall_active = TRUE;
6761 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6762 SND_MAGIC_WALL_ACTIVATING :
6763 smashed == EL_BD_MAGIC_WALL ?
6764 SND_BD_MAGIC_WALL_ACTIVATING :
6765 SND_DC_MAGIC_WALL_ACTIVATING));
6768 if (IS_PLAYER(x, y + 1))
6770 if (CAN_SMASH_PLAYER(element))
6772 KillPlayerUnlessEnemyProtected(x, y + 1);
6776 else if (smashed == EL_PENGUIN)
6778 if (CAN_SMASH_PLAYER(element))
6784 else if (element == EL_BD_DIAMOND)
6786 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6792 else if (((element == EL_SP_INFOTRON ||
6793 element == EL_SP_ZONK) &&
6794 (smashed == EL_SP_SNIKSNAK ||
6795 smashed == EL_SP_ELECTRON ||
6796 smashed == EL_SP_DISK_ORANGE)) ||
6797 (element == EL_SP_INFOTRON &&
6798 smashed == EL_SP_DISK_YELLOW))
6803 else if (CAN_SMASH_EVERYTHING(element))
6805 if (IS_CLASSIC_ENEMY(smashed) ||
6806 CAN_EXPLODE_SMASHED(smashed))
6811 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6813 if (smashed == EL_LAMP ||
6814 smashed == EL_LAMP_ACTIVE)
6819 else if (smashed == EL_NUT)
6821 Tile[x][y + 1] = EL_NUT_BREAKING;
6822 PlayLevelSound(x, y, SND_NUT_BREAKING);
6823 RaiseScoreElement(EL_NUT);
6826 else if (smashed == EL_PEARL)
6828 ResetGfxAnimation(x, y);
6830 Tile[x][y + 1] = EL_PEARL_BREAKING;
6831 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6834 else if (smashed == EL_DIAMOND)
6836 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6837 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6840 else if (IS_BELT_SWITCH(smashed))
6842 ToggleBeltSwitch(x, y + 1);
6844 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6845 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6846 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6847 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6849 ToggleSwitchgateSwitch(x, y + 1);
6851 else if (smashed == EL_LIGHT_SWITCH ||
6852 smashed == EL_LIGHT_SWITCH_ACTIVE)
6854 ToggleLightSwitch(x, y + 1);
6858 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6860 CheckElementChangeBySide(x, y + 1, smashed, element,
6861 CE_SWITCHED, CH_SIDE_TOP);
6862 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6868 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6873 // play sound of magic wall / mill
6875 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6876 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6877 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6879 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6880 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6881 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6882 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6883 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6884 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6889 // play sound of object that hits the ground
6890 if (last_line || object_hit)
6891 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6894 static void TurnRoundExt(int x, int y)
6906 { 0, 0 }, { 0, 0 }, { 0, 0 },
6911 int left, right, back;
6915 { MV_DOWN, MV_UP, MV_RIGHT },
6916 { MV_UP, MV_DOWN, MV_LEFT },
6918 { MV_LEFT, MV_RIGHT, MV_DOWN },
6922 { MV_RIGHT, MV_LEFT, MV_UP }
6925 int element = Tile[x][y];
6926 int move_pattern = element_info[element].move_pattern;
6928 int old_move_dir = MovDir[x][y];
6929 int left_dir = turn[old_move_dir].left;
6930 int right_dir = turn[old_move_dir].right;
6931 int back_dir = turn[old_move_dir].back;
6933 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6934 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6935 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6936 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6938 int left_x = x + left_dx, left_y = y + left_dy;
6939 int right_x = x + right_dx, right_y = y + right_dy;
6940 int move_x = x + move_dx, move_y = y + move_dy;
6944 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6946 TestIfBadThingTouchesOtherBadThing(x, y);
6948 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6949 MovDir[x][y] = right_dir;
6950 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6951 MovDir[x][y] = left_dir;
6953 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6955 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6958 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6960 TestIfBadThingTouchesOtherBadThing(x, y);
6962 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6963 MovDir[x][y] = left_dir;
6964 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6965 MovDir[x][y] = right_dir;
6967 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6969 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6972 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6974 TestIfBadThingTouchesOtherBadThing(x, y);
6976 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6977 MovDir[x][y] = left_dir;
6978 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6979 MovDir[x][y] = right_dir;
6981 if (MovDir[x][y] != old_move_dir)
6984 else if (element == EL_YAMYAM)
6986 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6987 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6989 if (can_turn_left && can_turn_right)
6990 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6991 else if (can_turn_left)
6992 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6993 else if (can_turn_right)
6994 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6996 MovDir[x][y] = back_dir;
6998 MovDelay[x][y] = 16 + 16 * RND(3);
7000 else if (element == EL_DARK_YAMYAM)
7002 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7004 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7007 if (can_turn_left && can_turn_right)
7008 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7009 else if (can_turn_left)
7010 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7011 else if (can_turn_right)
7012 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7014 MovDir[x][y] = back_dir;
7016 MovDelay[x][y] = 16 + 16 * RND(3);
7018 else if (element == EL_PACMAN)
7020 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7021 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7023 if (can_turn_left && can_turn_right)
7024 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7025 else if (can_turn_left)
7026 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7027 else if (can_turn_right)
7028 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7030 MovDir[x][y] = back_dir;
7032 MovDelay[x][y] = 6 + RND(40);
7034 else if (element == EL_PIG)
7036 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7037 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7038 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7039 boolean should_turn_left, should_turn_right, should_move_on;
7041 int rnd = RND(rnd_value);
7043 should_turn_left = (can_turn_left &&
7045 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7046 y + back_dy + left_dy)));
7047 should_turn_right = (can_turn_right &&
7049 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7050 y + back_dy + right_dy)));
7051 should_move_on = (can_move_on &&
7054 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7055 y + move_dy + left_dy) ||
7056 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7057 y + move_dy + right_dy)));
7059 if (should_turn_left || should_turn_right || should_move_on)
7061 if (should_turn_left && should_turn_right && should_move_on)
7062 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7063 rnd < 2 * rnd_value / 3 ? right_dir :
7065 else if (should_turn_left && should_turn_right)
7066 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7067 else if (should_turn_left && should_move_on)
7068 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7069 else if (should_turn_right && should_move_on)
7070 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7071 else if (should_turn_left)
7072 MovDir[x][y] = left_dir;
7073 else if (should_turn_right)
7074 MovDir[x][y] = right_dir;
7075 else if (should_move_on)
7076 MovDir[x][y] = old_move_dir;
7078 else if (can_move_on && rnd > rnd_value / 8)
7079 MovDir[x][y] = old_move_dir;
7080 else if (can_turn_left && can_turn_right)
7081 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7082 else if (can_turn_left && rnd > rnd_value / 8)
7083 MovDir[x][y] = left_dir;
7084 else if (can_turn_right && rnd > rnd_value/8)
7085 MovDir[x][y] = right_dir;
7087 MovDir[x][y] = back_dir;
7089 xx = x + move_xy[MovDir[x][y]].dx;
7090 yy = y + move_xy[MovDir[x][y]].dy;
7092 if (!IN_LEV_FIELD(xx, yy) ||
7093 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7094 MovDir[x][y] = old_move_dir;
7098 else if (element == EL_DRAGON)
7100 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7101 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7102 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7104 int rnd = RND(rnd_value);
7106 if (can_move_on && rnd > rnd_value / 8)
7107 MovDir[x][y] = old_move_dir;
7108 else if (can_turn_left && can_turn_right)
7109 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7110 else if (can_turn_left && rnd > rnd_value / 8)
7111 MovDir[x][y] = left_dir;
7112 else if (can_turn_right && rnd > rnd_value / 8)
7113 MovDir[x][y] = right_dir;
7115 MovDir[x][y] = back_dir;
7117 xx = x + move_xy[MovDir[x][y]].dx;
7118 yy = y + move_xy[MovDir[x][y]].dy;
7120 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7121 MovDir[x][y] = old_move_dir;
7125 else if (element == EL_MOLE)
7127 boolean can_move_on =
7128 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7129 IS_AMOEBOID(Tile[move_x][move_y]) ||
7130 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7133 boolean can_turn_left =
7134 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7135 IS_AMOEBOID(Tile[left_x][left_y])));
7137 boolean can_turn_right =
7138 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7139 IS_AMOEBOID(Tile[right_x][right_y])));
7141 if (can_turn_left && can_turn_right)
7142 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7143 else if (can_turn_left)
7144 MovDir[x][y] = left_dir;
7146 MovDir[x][y] = right_dir;
7149 if (MovDir[x][y] != old_move_dir)
7152 else if (element == EL_BALLOON)
7154 MovDir[x][y] = game.wind_direction;
7157 else if (element == EL_SPRING)
7159 if (MovDir[x][y] & MV_HORIZONTAL)
7161 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7162 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7164 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7165 ResetGfxAnimation(move_x, move_y);
7166 TEST_DrawLevelField(move_x, move_y);
7168 MovDir[x][y] = back_dir;
7170 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7171 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7172 MovDir[x][y] = MV_NONE;
7177 else if (element == EL_ROBOT ||
7178 element == EL_SATELLITE ||
7179 element == EL_PENGUIN ||
7180 element == EL_EMC_ANDROID)
7182 int attr_x = -1, attr_y = -1;
7184 if (game.all_players_gone)
7186 attr_x = game.exit_x;
7187 attr_y = game.exit_y;
7193 for (i = 0; i < MAX_PLAYERS; i++)
7195 struct PlayerInfo *player = &stored_player[i];
7196 int jx = player->jx, jy = player->jy;
7198 if (!player->active)
7202 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7210 if (element == EL_ROBOT &&
7211 game.robot_wheel_x >= 0 &&
7212 game.robot_wheel_y >= 0 &&
7213 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7214 game.engine_version < VERSION_IDENT(3,1,0,0)))
7216 attr_x = game.robot_wheel_x;
7217 attr_y = game.robot_wheel_y;
7220 if (element == EL_PENGUIN)
7223 static int xy[4][2] =
7231 for (i = 0; i < NUM_DIRECTIONS; i++)
7233 int ex = x + xy[i][0];
7234 int ey = y + xy[i][1];
7236 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7237 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7238 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7239 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7248 MovDir[x][y] = MV_NONE;
7250 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7251 else if (attr_x > x)
7252 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7254 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7255 else if (attr_y > y)
7256 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7258 if (element == EL_ROBOT)
7262 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7263 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7264 Moving2Blocked(x, y, &newx, &newy);
7266 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7267 MovDelay[x][y] = 8 + 8 * !RND(3);
7269 MovDelay[x][y] = 16;
7271 else if (element == EL_PENGUIN)
7277 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7279 boolean first_horiz = RND(2);
7280 int new_move_dir = MovDir[x][y];
7283 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7284 Moving2Blocked(x, y, &newx, &newy);
7286 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7290 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7291 Moving2Blocked(x, y, &newx, &newy);
7293 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7296 MovDir[x][y] = old_move_dir;
7300 else if (element == EL_SATELLITE)
7306 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7308 boolean first_horiz = RND(2);
7309 int new_move_dir = MovDir[x][y];
7312 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7313 Moving2Blocked(x, y, &newx, &newy);
7315 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7319 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7320 Moving2Blocked(x, y, &newx, &newy);
7322 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7325 MovDir[x][y] = old_move_dir;
7329 else if (element == EL_EMC_ANDROID)
7331 static int check_pos[16] =
7333 -1, // 0 => (invalid)
7336 -1, // 3 => (invalid)
7338 0, // 5 => MV_LEFT | MV_UP
7339 2, // 6 => MV_RIGHT | MV_UP
7340 -1, // 7 => (invalid)
7342 6, // 9 => MV_LEFT | MV_DOWN
7343 4, // 10 => MV_RIGHT | MV_DOWN
7344 -1, // 11 => (invalid)
7345 -1, // 12 => (invalid)
7346 -1, // 13 => (invalid)
7347 -1, // 14 => (invalid)
7348 -1, // 15 => (invalid)
7356 { -1, -1, MV_LEFT | MV_UP },
7358 { +1, -1, MV_RIGHT | MV_UP },
7359 { +1, 0, MV_RIGHT },
7360 { +1, +1, MV_RIGHT | MV_DOWN },
7362 { -1, +1, MV_LEFT | MV_DOWN },
7365 int start_pos, check_order;
7366 boolean can_clone = FALSE;
7369 // check if there is any free field around current position
7370 for (i = 0; i < 8; i++)
7372 int newx = x + check_xy[i].dx;
7373 int newy = y + check_xy[i].dy;
7375 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7383 if (can_clone) // randomly find an element to clone
7387 start_pos = check_pos[RND(8)];
7388 check_order = (RND(2) ? -1 : +1);
7390 for (i = 0; i < 8; i++)
7392 int pos_raw = start_pos + i * check_order;
7393 int pos = (pos_raw + 8) % 8;
7394 int newx = x + check_xy[pos].dx;
7395 int newy = y + check_xy[pos].dy;
7397 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7399 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7400 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7402 Store[x][y] = Tile[newx][newy];
7411 if (can_clone) // randomly find a direction to move
7415 start_pos = check_pos[RND(8)];
7416 check_order = (RND(2) ? -1 : +1);
7418 for (i = 0; i < 8; i++)
7420 int pos_raw = start_pos + i * check_order;
7421 int pos = (pos_raw + 8) % 8;
7422 int newx = x + check_xy[pos].dx;
7423 int newy = y + check_xy[pos].dy;
7424 int new_move_dir = check_xy[pos].dir;
7426 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7428 MovDir[x][y] = new_move_dir;
7429 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7438 if (can_clone) // cloning and moving successful
7441 // cannot clone -- try to move towards player
7443 start_pos = check_pos[MovDir[x][y] & 0x0f];
7444 check_order = (RND(2) ? -1 : +1);
7446 for (i = 0; i < 3; i++)
7448 // first check start_pos, then previous/next or (next/previous) pos
7449 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7450 int pos = (pos_raw + 8) % 8;
7451 int newx = x + check_xy[pos].dx;
7452 int newy = y + check_xy[pos].dy;
7453 int new_move_dir = check_xy[pos].dir;
7455 if (IS_PLAYER(newx, newy))
7458 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7460 MovDir[x][y] = new_move_dir;
7461 MovDelay[x][y] = level.android_move_time * 8 + 1;
7468 else if (move_pattern == MV_TURNING_LEFT ||
7469 move_pattern == MV_TURNING_RIGHT ||
7470 move_pattern == MV_TURNING_LEFT_RIGHT ||
7471 move_pattern == MV_TURNING_RIGHT_LEFT ||
7472 move_pattern == MV_TURNING_RANDOM ||
7473 move_pattern == MV_ALL_DIRECTIONS)
7475 boolean can_turn_left =
7476 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7477 boolean can_turn_right =
7478 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7480 if (element_info[element].move_stepsize == 0) // "not moving"
7483 if (move_pattern == MV_TURNING_LEFT)
7484 MovDir[x][y] = left_dir;
7485 else if (move_pattern == MV_TURNING_RIGHT)
7486 MovDir[x][y] = right_dir;
7487 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7488 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7489 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7490 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7491 else if (move_pattern == MV_TURNING_RANDOM)
7492 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7493 can_turn_right && !can_turn_left ? right_dir :
7494 RND(2) ? left_dir : right_dir);
7495 else if (can_turn_left && can_turn_right)
7496 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7497 else if (can_turn_left)
7498 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7499 else if (can_turn_right)
7500 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7502 MovDir[x][y] = back_dir;
7504 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7506 else if (move_pattern == MV_HORIZONTAL ||
7507 move_pattern == MV_VERTICAL)
7509 if (move_pattern & old_move_dir)
7510 MovDir[x][y] = back_dir;
7511 else if (move_pattern == MV_HORIZONTAL)
7512 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7513 else if (move_pattern == MV_VERTICAL)
7514 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7516 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7518 else if (move_pattern & MV_ANY_DIRECTION)
7520 MovDir[x][y] = move_pattern;
7521 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7523 else if (move_pattern & MV_WIND_DIRECTION)
7525 MovDir[x][y] = game.wind_direction;
7526 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7528 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7530 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7531 MovDir[x][y] = left_dir;
7532 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7533 MovDir[x][y] = right_dir;
7535 if (MovDir[x][y] != old_move_dir)
7536 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7538 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7540 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7541 MovDir[x][y] = right_dir;
7542 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7543 MovDir[x][y] = left_dir;
7545 if (MovDir[x][y] != old_move_dir)
7546 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7548 else if (move_pattern == MV_TOWARDS_PLAYER ||
7549 move_pattern == MV_AWAY_FROM_PLAYER)
7551 int attr_x = -1, attr_y = -1;
7553 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7555 if (game.all_players_gone)
7557 attr_x = game.exit_x;
7558 attr_y = game.exit_y;
7564 for (i = 0; i < MAX_PLAYERS; i++)
7566 struct PlayerInfo *player = &stored_player[i];
7567 int jx = player->jx, jy = player->jy;
7569 if (!player->active)
7573 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7581 MovDir[x][y] = MV_NONE;
7583 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7584 else if (attr_x > x)
7585 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7587 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7588 else if (attr_y > y)
7589 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7591 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7593 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7595 boolean first_horiz = RND(2);
7596 int new_move_dir = MovDir[x][y];
7598 if (element_info[element].move_stepsize == 0) // "not moving"
7600 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7601 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7607 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7608 Moving2Blocked(x, y, &newx, &newy);
7610 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7614 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7615 Moving2Blocked(x, y, &newx, &newy);
7617 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7620 MovDir[x][y] = old_move_dir;
7623 else if (move_pattern == MV_WHEN_PUSHED ||
7624 move_pattern == MV_WHEN_DROPPED)
7626 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7627 MovDir[x][y] = MV_NONE;
7631 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7633 static int test_xy[7][2] =
7643 static int test_dir[7] =
7653 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7654 int move_preference = -1000000; // start with very low preference
7655 int new_move_dir = MV_NONE;
7656 int start_test = RND(4);
7659 for (i = 0; i < NUM_DIRECTIONS; i++)
7661 int move_dir = test_dir[start_test + i];
7662 int move_dir_preference;
7664 xx = x + test_xy[start_test + i][0];
7665 yy = y + test_xy[start_test + i][1];
7667 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7668 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7670 new_move_dir = move_dir;
7675 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7678 move_dir_preference = -1 * RunnerVisit[xx][yy];
7679 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7680 move_dir_preference = PlayerVisit[xx][yy];
7682 if (move_dir_preference > move_preference)
7684 // prefer field that has not been visited for the longest time
7685 move_preference = move_dir_preference;
7686 new_move_dir = move_dir;
7688 else if (move_dir_preference == move_preference &&
7689 move_dir == old_move_dir)
7691 // prefer last direction when all directions are preferred equally
7692 move_preference = move_dir_preference;
7693 new_move_dir = move_dir;
7697 MovDir[x][y] = new_move_dir;
7698 if (old_move_dir != new_move_dir)
7699 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7703 static void TurnRound(int x, int y)
7705 int direction = MovDir[x][y];
7709 GfxDir[x][y] = MovDir[x][y];
7711 if (direction != MovDir[x][y])
7715 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7717 ResetGfxFrame(x, y);
7720 static boolean JustBeingPushed(int x, int y)
7724 for (i = 0; i < MAX_PLAYERS; i++)
7726 struct PlayerInfo *player = &stored_player[i];
7728 if (player->active && player->is_pushing && player->MovPos)
7730 int next_jx = player->jx + (player->jx - player->last_jx);
7731 int next_jy = player->jy + (player->jy - player->last_jy);
7733 if (x == next_jx && y == next_jy)
7741 static void StartMoving(int x, int y)
7743 boolean started_moving = FALSE; // some elements can fall _and_ move
7744 int element = Tile[x][y];
7749 if (MovDelay[x][y] == 0)
7750 GfxAction[x][y] = ACTION_DEFAULT;
7752 if (CAN_FALL(element) && y < lev_fieldy - 1)
7754 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7755 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7756 if (JustBeingPushed(x, y))
7759 if (element == EL_QUICKSAND_FULL)
7761 if (IS_FREE(x, y + 1))
7763 InitMovingField(x, y, MV_DOWN);
7764 started_moving = TRUE;
7766 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7767 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7768 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7769 Store[x][y] = EL_ROCK;
7771 Store[x][y] = EL_ROCK;
7774 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7776 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7778 if (!MovDelay[x][y])
7780 MovDelay[x][y] = TILEY + 1;
7782 ResetGfxAnimation(x, y);
7783 ResetGfxAnimation(x, y + 1);
7788 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7789 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7796 Tile[x][y] = EL_QUICKSAND_EMPTY;
7797 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7798 Store[x][y + 1] = Store[x][y];
7801 PlayLevelSoundAction(x, y, ACTION_FILLING);
7803 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7805 if (!MovDelay[x][y])
7807 MovDelay[x][y] = TILEY + 1;
7809 ResetGfxAnimation(x, y);
7810 ResetGfxAnimation(x, y + 1);
7815 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7816 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7823 Tile[x][y] = EL_QUICKSAND_EMPTY;
7824 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7825 Store[x][y + 1] = Store[x][y];
7828 PlayLevelSoundAction(x, y, ACTION_FILLING);
7831 else if (element == EL_QUICKSAND_FAST_FULL)
7833 if (IS_FREE(x, y + 1))
7835 InitMovingField(x, y, MV_DOWN);
7836 started_moving = TRUE;
7838 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7839 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7840 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7841 Store[x][y] = EL_ROCK;
7843 Store[x][y] = EL_ROCK;
7846 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7848 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7850 if (!MovDelay[x][y])
7852 MovDelay[x][y] = TILEY + 1;
7854 ResetGfxAnimation(x, y);
7855 ResetGfxAnimation(x, y + 1);
7860 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7861 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7868 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7869 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7870 Store[x][y + 1] = Store[x][y];
7873 PlayLevelSoundAction(x, y, ACTION_FILLING);
7875 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7877 if (!MovDelay[x][y])
7879 MovDelay[x][y] = TILEY + 1;
7881 ResetGfxAnimation(x, y);
7882 ResetGfxAnimation(x, y + 1);
7887 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7888 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7895 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7896 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7897 Store[x][y + 1] = Store[x][y];
7900 PlayLevelSoundAction(x, y, ACTION_FILLING);
7903 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7904 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7906 InitMovingField(x, y, MV_DOWN);
7907 started_moving = TRUE;
7909 Tile[x][y] = EL_QUICKSAND_FILLING;
7910 Store[x][y] = element;
7912 PlayLevelSoundAction(x, y, ACTION_FILLING);
7914 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7915 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7917 InitMovingField(x, y, MV_DOWN);
7918 started_moving = TRUE;
7920 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7921 Store[x][y] = element;
7923 PlayLevelSoundAction(x, y, ACTION_FILLING);
7925 else if (element == EL_MAGIC_WALL_FULL)
7927 if (IS_FREE(x, y + 1))
7929 InitMovingField(x, y, MV_DOWN);
7930 started_moving = TRUE;
7932 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7933 Store[x][y] = EL_CHANGED(Store[x][y]);
7935 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7937 if (!MovDelay[x][y])
7938 MovDelay[x][y] = TILEY / 4 + 1;
7947 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7948 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7949 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7953 else if (element == EL_BD_MAGIC_WALL_FULL)
7955 if (IS_FREE(x, y + 1))
7957 InitMovingField(x, y, MV_DOWN);
7958 started_moving = TRUE;
7960 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7961 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7963 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7965 if (!MovDelay[x][y])
7966 MovDelay[x][y] = TILEY / 4 + 1;
7975 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7976 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7977 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7981 else if (element == EL_DC_MAGIC_WALL_FULL)
7983 if (IS_FREE(x, y + 1))
7985 InitMovingField(x, y, MV_DOWN);
7986 started_moving = TRUE;
7988 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7989 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7991 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7993 if (!MovDelay[x][y])
7994 MovDelay[x][y] = TILEY / 4 + 1;
8003 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8004 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8005 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8009 else if ((CAN_PASS_MAGIC_WALL(element) &&
8010 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8011 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8012 (CAN_PASS_DC_MAGIC_WALL(element) &&
8013 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8016 InitMovingField(x, y, MV_DOWN);
8017 started_moving = TRUE;
8020 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8021 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8022 EL_DC_MAGIC_WALL_FILLING);
8023 Store[x][y] = element;
8025 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8027 SplashAcid(x, y + 1);
8029 InitMovingField(x, y, MV_DOWN);
8030 started_moving = TRUE;
8032 Store[x][y] = EL_ACID;
8035 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8036 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8037 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8038 CAN_FALL(element) && WasJustFalling[x][y] &&
8039 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8041 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8042 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8043 (Tile[x][y + 1] == EL_BLOCKED)))
8045 /* this is needed for a special case not covered by calling "Impact()"
8046 from "ContinueMoving()": if an element moves to a tile directly below
8047 another element which was just falling on that tile (which was empty
8048 in the previous frame), the falling element above would just stop
8049 instead of smashing the element below (in previous version, the above
8050 element was just checked for "moving" instead of "falling", resulting
8051 in incorrect smashes caused by horizontal movement of the above
8052 element; also, the case of the player being the element to smash was
8053 simply not covered here... :-/ ) */
8055 CheckCollision[x][y] = 0;
8056 CheckImpact[x][y] = 0;
8060 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8062 if (MovDir[x][y] == MV_NONE)
8064 InitMovingField(x, y, MV_DOWN);
8065 started_moving = TRUE;
8068 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8070 if (WasJustFalling[x][y]) // prevent animation from being restarted
8071 MovDir[x][y] = MV_DOWN;
8073 InitMovingField(x, y, MV_DOWN);
8074 started_moving = TRUE;
8076 else if (element == EL_AMOEBA_DROP)
8078 Tile[x][y] = EL_AMOEBA_GROWING;
8079 Store[x][y] = EL_AMOEBA_WET;
8081 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8082 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8083 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8084 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8086 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8087 (IS_FREE(x - 1, y + 1) ||
8088 Tile[x - 1][y + 1] == EL_ACID));
8089 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8090 (IS_FREE(x + 1, y + 1) ||
8091 Tile[x + 1][y + 1] == EL_ACID));
8092 boolean can_fall_any = (can_fall_left || can_fall_right);
8093 boolean can_fall_both = (can_fall_left && can_fall_right);
8094 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8096 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8098 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8099 can_fall_right = FALSE;
8100 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8101 can_fall_left = FALSE;
8102 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8103 can_fall_right = FALSE;
8104 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8105 can_fall_left = FALSE;
8107 can_fall_any = (can_fall_left || can_fall_right);
8108 can_fall_both = FALSE;
8113 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8114 can_fall_right = FALSE; // slip down on left side
8116 can_fall_left = !(can_fall_right = RND(2));
8118 can_fall_both = FALSE;
8123 // if not determined otherwise, prefer left side for slipping down
8124 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8125 started_moving = TRUE;
8128 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8130 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8131 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8132 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8133 int belt_dir = game.belt_dir[belt_nr];
8135 if ((belt_dir == MV_LEFT && left_is_free) ||
8136 (belt_dir == MV_RIGHT && right_is_free))
8138 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8140 InitMovingField(x, y, belt_dir);
8141 started_moving = TRUE;
8143 Pushed[x][y] = TRUE;
8144 Pushed[nextx][y] = TRUE;
8146 GfxAction[x][y] = ACTION_DEFAULT;
8150 MovDir[x][y] = 0; // if element was moving, stop it
8155 // not "else if" because of elements that can fall and move (EL_SPRING)
8156 if (CAN_MOVE(element) && !started_moving)
8158 int move_pattern = element_info[element].move_pattern;
8161 Moving2Blocked(x, y, &newx, &newy);
8163 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8166 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8167 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8169 WasJustMoving[x][y] = 0;
8170 CheckCollision[x][y] = 0;
8172 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8174 if (Tile[x][y] != element) // element has changed
8178 if (!MovDelay[x][y]) // start new movement phase
8180 // all objects that can change their move direction after each step
8181 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8183 if (element != EL_YAMYAM &&
8184 element != EL_DARK_YAMYAM &&
8185 element != EL_PACMAN &&
8186 !(move_pattern & MV_ANY_DIRECTION) &&
8187 move_pattern != MV_TURNING_LEFT &&
8188 move_pattern != MV_TURNING_RIGHT &&
8189 move_pattern != MV_TURNING_LEFT_RIGHT &&
8190 move_pattern != MV_TURNING_RIGHT_LEFT &&
8191 move_pattern != MV_TURNING_RANDOM)
8195 if (MovDelay[x][y] && (element == EL_BUG ||
8196 element == EL_SPACESHIP ||
8197 element == EL_SP_SNIKSNAK ||
8198 element == EL_SP_ELECTRON ||
8199 element == EL_MOLE))
8200 TEST_DrawLevelField(x, y);
8204 if (MovDelay[x][y]) // wait some time before next movement
8208 if (element == EL_ROBOT ||
8209 element == EL_YAMYAM ||
8210 element == EL_DARK_YAMYAM)
8212 DrawLevelElementAnimationIfNeeded(x, y, element);
8213 PlayLevelSoundAction(x, y, ACTION_WAITING);
8215 else if (element == EL_SP_ELECTRON)
8216 DrawLevelElementAnimationIfNeeded(x, y, element);
8217 else if (element == EL_DRAGON)
8220 int dir = MovDir[x][y];
8221 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8222 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8223 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8224 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8225 dir == MV_UP ? IMG_FLAMES_1_UP :
8226 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8227 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8229 GfxAction[x][y] = ACTION_ATTACKING;
8231 if (IS_PLAYER(x, y))
8232 DrawPlayerField(x, y);
8234 TEST_DrawLevelField(x, y);
8236 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8238 for (i = 1; i <= 3; i++)
8240 int xx = x + i * dx;
8241 int yy = y + i * dy;
8242 int sx = SCREENX(xx);
8243 int sy = SCREENY(yy);
8244 int flame_graphic = graphic + (i - 1);
8246 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8251 int flamed = MovingOrBlocked2Element(xx, yy);
8253 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8256 RemoveMovingField(xx, yy);
8258 ChangeDelay[xx][yy] = 0;
8260 Tile[xx][yy] = EL_FLAMES;
8262 if (IN_SCR_FIELD(sx, sy))
8264 TEST_DrawLevelFieldCrumbled(xx, yy);
8265 DrawGraphic(sx, sy, flame_graphic, frame);
8270 if (Tile[xx][yy] == EL_FLAMES)
8271 Tile[xx][yy] = EL_EMPTY;
8272 TEST_DrawLevelField(xx, yy);
8277 if (MovDelay[x][y]) // element still has to wait some time
8279 PlayLevelSoundAction(x, y, ACTION_WAITING);
8285 // now make next step
8287 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8289 if (DONT_COLLIDE_WITH(element) &&
8290 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8291 !PLAYER_ENEMY_PROTECTED(newx, newy))
8293 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8298 else if (CAN_MOVE_INTO_ACID(element) &&
8299 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8300 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8301 (MovDir[x][y] == MV_DOWN ||
8302 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8304 SplashAcid(newx, newy);
8305 Store[x][y] = EL_ACID;
8307 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8309 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8310 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8311 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8312 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8315 TEST_DrawLevelField(x, y);
8317 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8318 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8319 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8321 game.friends_still_needed--;
8322 if (!game.friends_still_needed &&
8324 game.all_players_gone)
8329 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8331 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8332 TEST_DrawLevelField(newx, newy);
8334 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8336 else if (!IS_FREE(newx, newy))
8338 GfxAction[x][y] = ACTION_WAITING;
8340 if (IS_PLAYER(x, y))
8341 DrawPlayerField(x, y);
8343 TEST_DrawLevelField(x, y);
8348 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8350 if (IS_FOOD_PIG(Tile[newx][newy]))
8352 if (IS_MOVING(newx, newy))
8353 RemoveMovingField(newx, newy);
8356 Tile[newx][newy] = EL_EMPTY;
8357 TEST_DrawLevelField(newx, newy);
8360 PlayLevelSound(x, y, SND_PIG_DIGGING);
8362 else if (!IS_FREE(newx, newy))
8364 if (IS_PLAYER(x, y))
8365 DrawPlayerField(x, y);
8367 TEST_DrawLevelField(x, y);
8372 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8374 if (Store[x][y] != EL_EMPTY)
8376 boolean can_clone = FALSE;
8379 // check if element to clone is still there
8380 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8382 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8390 // cannot clone or target field not free anymore -- do not clone
8391 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8392 Store[x][y] = EL_EMPTY;
8395 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8397 if (IS_MV_DIAGONAL(MovDir[x][y]))
8399 int diagonal_move_dir = MovDir[x][y];
8400 int stored = Store[x][y];
8401 int change_delay = 8;
8404 // android is moving diagonally
8406 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8408 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8409 GfxElement[x][y] = EL_EMC_ANDROID;
8410 GfxAction[x][y] = ACTION_SHRINKING;
8411 GfxDir[x][y] = diagonal_move_dir;
8412 ChangeDelay[x][y] = change_delay;
8414 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8417 DrawLevelGraphicAnimation(x, y, graphic);
8418 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8420 if (Tile[newx][newy] == EL_ACID)
8422 SplashAcid(newx, newy);
8427 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8429 Store[newx][newy] = EL_EMC_ANDROID;
8430 GfxElement[newx][newy] = EL_EMC_ANDROID;
8431 GfxAction[newx][newy] = ACTION_GROWING;
8432 GfxDir[newx][newy] = diagonal_move_dir;
8433 ChangeDelay[newx][newy] = change_delay;
8435 graphic = el_act_dir2img(GfxElement[newx][newy],
8436 GfxAction[newx][newy], GfxDir[newx][newy]);
8438 DrawLevelGraphicAnimation(newx, newy, graphic);
8439 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8445 Tile[newx][newy] = EL_EMPTY;
8446 TEST_DrawLevelField(newx, newy);
8448 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8451 else if (!IS_FREE(newx, newy))
8456 else if (IS_CUSTOM_ELEMENT(element) &&
8457 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8459 if (!DigFieldByCE(newx, newy, element))
8462 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8464 RunnerVisit[x][y] = FrameCounter;
8465 PlayerVisit[x][y] /= 8; // expire player visit path
8468 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8470 if (!IS_FREE(newx, newy))
8472 if (IS_PLAYER(x, y))
8473 DrawPlayerField(x, y);
8475 TEST_DrawLevelField(x, y);
8481 boolean wanna_flame = !RND(10);
8482 int dx = newx - x, dy = newy - y;
8483 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8484 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8485 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8486 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8487 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8488 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8491 IS_CLASSIC_ENEMY(element1) ||
8492 IS_CLASSIC_ENEMY(element2)) &&
8493 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8494 element1 != EL_FLAMES && element2 != EL_FLAMES)
8496 ResetGfxAnimation(x, y);
8497 GfxAction[x][y] = ACTION_ATTACKING;
8499 if (IS_PLAYER(x, y))
8500 DrawPlayerField(x, y);
8502 TEST_DrawLevelField(x, y);
8504 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8506 MovDelay[x][y] = 50;
8508 Tile[newx][newy] = EL_FLAMES;
8509 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8510 Tile[newx1][newy1] = EL_FLAMES;
8511 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8512 Tile[newx2][newy2] = EL_FLAMES;
8518 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8519 Tile[newx][newy] == EL_DIAMOND)
8521 if (IS_MOVING(newx, newy))
8522 RemoveMovingField(newx, newy);
8525 Tile[newx][newy] = EL_EMPTY;
8526 TEST_DrawLevelField(newx, newy);
8529 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8531 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8532 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8534 if (AmoebaNr[newx][newy])
8536 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8537 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8538 Tile[newx][newy] == EL_BD_AMOEBA)
8539 AmoebaCnt[AmoebaNr[newx][newy]]--;
8542 if (IS_MOVING(newx, newy))
8544 RemoveMovingField(newx, newy);
8548 Tile[newx][newy] = EL_EMPTY;
8549 TEST_DrawLevelField(newx, newy);
8552 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8554 else if ((element == EL_PACMAN || element == EL_MOLE)
8555 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8557 if (AmoebaNr[newx][newy])
8559 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8560 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8561 Tile[newx][newy] == EL_BD_AMOEBA)
8562 AmoebaCnt[AmoebaNr[newx][newy]]--;
8565 if (element == EL_MOLE)
8567 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8568 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8570 ResetGfxAnimation(x, y);
8571 GfxAction[x][y] = ACTION_DIGGING;
8572 TEST_DrawLevelField(x, y);
8574 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8576 return; // wait for shrinking amoeba
8578 else // element == EL_PACMAN
8580 Tile[newx][newy] = EL_EMPTY;
8581 TEST_DrawLevelField(newx, newy);
8582 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8585 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8586 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8587 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8589 // wait for shrinking amoeba to completely disappear
8592 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8594 // object was running against a wall
8598 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8599 DrawLevelElementAnimation(x, y, element);
8601 if (DONT_TOUCH(element))
8602 TestIfBadThingTouchesPlayer(x, y);
8607 InitMovingField(x, y, MovDir[x][y]);
8609 PlayLevelSoundAction(x, y, ACTION_MOVING);
8613 ContinueMoving(x, y);
8616 void ContinueMoving(int x, int y)
8618 int element = Tile[x][y];
8619 struct ElementInfo *ei = &element_info[element];
8620 int direction = MovDir[x][y];
8621 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8622 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8623 int newx = x + dx, newy = y + dy;
8624 int stored = Store[x][y];
8625 int stored_new = Store[newx][newy];
8626 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8627 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8628 boolean last_line = (newy == lev_fieldy - 1);
8629 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8631 if (pushed_by_player) // special case: moving object pushed by player
8633 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8635 else if (use_step_delay) // special case: moving object has step delay
8637 if (!MovDelay[x][y])
8638 MovPos[x][y] += getElementMoveStepsize(x, y);
8643 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8647 TEST_DrawLevelField(x, y);
8649 return; // element is still waiting
8652 else // normal case: generically moving object
8654 MovPos[x][y] += getElementMoveStepsize(x, y);
8657 if (ABS(MovPos[x][y]) < TILEX)
8659 TEST_DrawLevelField(x, y);
8661 return; // element is still moving
8664 // element reached destination field
8666 Tile[x][y] = EL_EMPTY;
8667 Tile[newx][newy] = element;
8668 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8670 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8672 element = Tile[newx][newy] = EL_ACID;
8674 else if (element == EL_MOLE)
8676 Tile[x][y] = EL_SAND;
8678 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8680 else if (element == EL_QUICKSAND_FILLING)
8682 element = Tile[newx][newy] = get_next_element(element);
8683 Store[newx][newy] = Store[x][y];
8685 else if (element == EL_QUICKSAND_EMPTYING)
8687 Tile[x][y] = get_next_element(element);
8688 element = Tile[newx][newy] = Store[x][y];
8690 else if (element == EL_QUICKSAND_FAST_FILLING)
8692 element = Tile[newx][newy] = get_next_element(element);
8693 Store[newx][newy] = Store[x][y];
8695 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8697 Tile[x][y] = get_next_element(element);
8698 element = Tile[newx][newy] = Store[x][y];
8700 else if (element == EL_MAGIC_WALL_FILLING)
8702 element = Tile[newx][newy] = get_next_element(element);
8703 if (!game.magic_wall_active)
8704 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8705 Store[newx][newy] = Store[x][y];
8707 else if (element == EL_MAGIC_WALL_EMPTYING)
8709 Tile[x][y] = get_next_element(element);
8710 if (!game.magic_wall_active)
8711 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8712 element = Tile[newx][newy] = Store[x][y];
8714 InitField(newx, newy, FALSE);
8716 else if (element == EL_BD_MAGIC_WALL_FILLING)
8718 element = Tile[newx][newy] = get_next_element(element);
8719 if (!game.magic_wall_active)
8720 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8721 Store[newx][newy] = Store[x][y];
8723 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8725 Tile[x][y] = get_next_element(element);
8726 if (!game.magic_wall_active)
8727 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8728 element = Tile[newx][newy] = Store[x][y];
8730 InitField(newx, newy, FALSE);
8732 else if (element == EL_DC_MAGIC_WALL_FILLING)
8734 element = Tile[newx][newy] = get_next_element(element);
8735 if (!game.magic_wall_active)
8736 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8737 Store[newx][newy] = Store[x][y];
8739 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8741 Tile[x][y] = get_next_element(element);
8742 if (!game.magic_wall_active)
8743 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8744 element = Tile[newx][newy] = Store[x][y];
8746 InitField(newx, newy, FALSE);
8748 else if (element == EL_AMOEBA_DROPPING)
8750 Tile[x][y] = get_next_element(element);
8751 element = Tile[newx][newy] = Store[x][y];
8753 else if (element == EL_SOKOBAN_OBJECT)
8756 Tile[x][y] = Back[x][y];
8758 if (Back[newx][newy])
8759 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8761 Back[x][y] = Back[newx][newy] = 0;
8764 Store[x][y] = EL_EMPTY;
8769 MovDelay[newx][newy] = 0;
8771 if (CAN_CHANGE_OR_HAS_ACTION(element))
8773 // copy element change control values to new field
8774 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8775 ChangePage[newx][newy] = ChangePage[x][y];
8776 ChangeCount[newx][newy] = ChangeCount[x][y];
8777 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8780 CustomValue[newx][newy] = CustomValue[x][y];
8782 ChangeDelay[x][y] = 0;
8783 ChangePage[x][y] = -1;
8784 ChangeCount[x][y] = 0;
8785 ChangeEvent[x][y] = -1;
8787 CustomValue[x][y] = 0;
8789 // copy animation control values to new field
8790 GfxFrame[newx][newy] = GfxFrame[x][y];
8791 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8792 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8793 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8795 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8797 // some elements can leave other elements behind after moving
8798 if (ei->move_leave_element != EL_EMPTY &&
8799 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8800 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8802 int move_leave_element = ei->move_leave_element;
8804 // this makes it possible to leave the removed element again
8805 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8806 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8808 Tile[x][y] = move_leave_element;
8810 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8811 MovDir[x][y] = direction;
8813 InitField(x, y, FALSE);
8815 if (GFX_CRUMBLED(Tile[x][y]))
8816 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8818 if (IS_PLAYER_ELEMENT(move_leave_element))
8819 RelocatePlayer(x, y, move_leave_element);
8822 // do this after checking for left-behind element
8823 ResetGfxAnimation(x, y); // reset animation values for old field
8825 if (!CAN_MOVE(element) ||
8826 (CAN_FALL(element) && direction == MV_DOWN &&
8827 (element == EL_SPRING ||
8828 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8829 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8830 GfxDir[x][y] = MovDir[newx][newy] = 0;
8832 TEST_DrawLevelField(x, y);
8833 TEST_DrawLevelField(newx, newy);
8835 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8837 // prevent pushed element from moving on in pushed direction
8838 if (pushed_by_player && CAN_MOVE(element) &&
8839 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8840 !(element_info[element].move_pattern & direction))
8841 TurnRound(newx, newy);
8843 // prevent elements on conveyor belt from moving on in last direction
8844 if (pushed_by_conveyor && CAN_FALL(element) &&
8845 direction & MV_HORIZONTAL)
8846 MovDir[newx][newy] = 0;
8848 if (!pushed_by_player)
8850 int nextx = newx + dx, nexty = newy + dy;
8851 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8853 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8855 if (CAN_FALL(element) && direction == MV_DOWN)
8856 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8858 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8859 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8861 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8862 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8865 if (DONT_TOUCH(element)) // object may be nasty to player or others
8867 TestIfBadThingTouchesPlayer(newx, newy);
8868 TestIfBadThingTouchesFriend(newx, newy);
8870 if (!IS_CUSTOM_ELEMENT(element))
8871 TestIfBadThingTouchesOtherBadThing(newx, newy);
8873 else if (element == EL_PENGUIN)
8874 TestIfFriendTouchesBadThing(newx, newy);
8876 if (DONT_GET_HIT_BY(element))
8878 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8881 // give the player one last chance (one more frame) to move away
8882 if (CAN_FALL(element) && direction == MV_DOWN &&
8883 (last_line || (!IS_FREE(x, newy + 1) &&
8884 (!IS_PLAYER(x, newy + 1) ||
8885 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8888 if (pushed_by_player && !game.use_change_when_pushing_bug)
8890 int push_side = MV_DIR_OPPOSITE(direction);
8891 struct PlayerInfo *player = PLAYERINFO(x, y);
8893 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8894 player->index_bit, push_side);
8895 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8896 player->index_bit, push_side);
8899 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8900 MovDelay[newx][newy] = 1;
8902 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8904 TestIfElementTouchesCustomElement(x, y); // empty or new element
8905 TestIfElementHitsCustomElement(newx, newy, direction);
8906 TestIfPlayerTouchesCustomElement(newx, newy);
8907 TestIfElementTouchesCustomElement(newx, newy);
8909 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8910 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8911 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8912 MV_DIR_OPPOSITE(direction));
8915 int AmoebaNeighbourNr(int ax, int ay)
8918 int element = Tile[ax][ay];
8920 static int xy[4][2] =
8928 for (i = 0; i < NUM_DIRECTIONS; i++)
8930 int x = ax + xy[i][0];
8931 int y = ay + xy[i][1];
8933 if (!IN_LEV_FIELD(x, y))
8936 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8937 group_nr = AmoebaNr[x][y];
8943 static void AmoebaMerge(int ax, int ay)
8945 int i, x, y, xx, yy;
8946 int new_group_nr = AmoebaNr[ax][ay];
8947 static int xy[4][2] =
8955 if (new_group_nr == 0)
8958 for (i = 0; i < NUM_DIRECTIONS; i++)
8963 if (!IN_LEV_FIELD(x, y))
8966 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8967 Tile[x][y] == EL_BD_AMOEBA ||
8968 Tile[x][y] == EL_AMOEBA_DEAD) &&
8969 AmoebaNr[x][y] != new_group_nr)
8971 int old_group_nr = AmoebaNr[x][y];
8973 if (old_group_nr == 0)
8976 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8977 AmoebaCnt[old_group_nr] = 0;
8978 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8979 AmoebaCnt2[old_group_nr] = 0;
8981 SCAN_PLAYFIELD(xx, yy)
8983 if (AmoebaNr[xx][yy] == old_group_nr)
8984 AmoebaNr[xx][yy] = new_group_nr;
8990 void AmoebaToDiamond(int ax, int ay)
8994 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8996 int group_nr = AmoebaNr[ax][ay];
9001 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9002 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9008 SCAN_PLAYFIELD(x, y)
9010 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9013 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9017 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9018 SND_AMOEBA_TURNING_TO_GEM :
9019 SND_AMOEBA_TURNING_TO_ROCK));
9024 static int xy[4][2] =
9032 for (i = 0; i < NUM_DIRECTIONS; i++)
9037 if (!IN_LEV_FIELD(x, y))
9040 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9042 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9043 SND_AMOEBA_TURNING_TO_GEM :
9044 SND_AMOEBA_TURNING_TO_ROCK));
9051 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9054 int group_nr = AmoebaNr[ax][ay];
9055 boolean done = FALSE;
9060 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9061 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9067 SCAN_PLAYFIELD(x, y)
9069 if (AmoebaNr[x][y] == group_nr &&
9070 (Tile[x][y] == EL_AMOEBA_DEAD ||
9071 Tile[x][y] == EL_BD_AMOEBA ||
9072 Tile[x][y] == EL_AMOEBA_GROWING))
9075 Tile[x][y] = new_element;
9076 InitField(x, y, FALSE);
9077 TEST_DrawLevelField(x, y);
9083 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9084 SND_BD_AMOEBA_TURNING_TO_ROCK :
9085 SND_BD_AMOEBA_TURNING_TO_GEM));
9088 static void AmoebaGrowing(int x, int y)
9090 static unsigned int sound_delay = 0;
9091 static unsigned int sound_delay_value = 0;
9093 if (!MovDelay[x][y]) // start new growing cycle
9097 if (DelayReached(&sound_delay, sound_delay_value))
9099 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9100 sound_delay_value = 30;
9104 if (MovDelay[x][y]) // wait some time before growing bigger
9107 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9109 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9110 6 - MovDelay[x][y]);
9112 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9115 if (!MovDelay[x][y])
9117 Tile[x][y] = Store[x][y];
9119 TEST_DrawLevelField(x, y);
9124 static void AmoebaShrinking(int x, int y)
9126 static unsigned int sound_delay = 0;
9127 static unsigned int sound_delay_value = 0;
9129 if (!MovDelay[x][y]) // start new shrinking cycle
9133 if (DelayReached(&sound_delay, sound_delay_value))
9134 sound_delay_value = 30;
9137 if (MovDelay[x][y]) // wait some time before shrinking
9140 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9142 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9143 6 - MovDelay[x][y]);
9145 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9148 if (!MovDelay[x][y])
9150 Tile[x][y] = EL_EMPTY;
9151 TEST_DrawLevelField(x, y);
9153 // don't let mole enter this field in this cycle;
9154 // (give priority to objects falling to this field from above)
9160 static void AmoebaReproduce(int ax, int ay)
9163 int element = Tile[ax][ay];
9164 int graphic = el2img(element);
9165 int newax = ax, neway = ay;
9166 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9167 static int xy[4][2] =
9175 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9177 Tile[ax][ay] = EL_AMOEBA_DEAD;
9178 TEST_DrawLevelField(ax, ay);
9182 if (IS_ANIMATED(graphic))
9183 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9185 if (!MovDelay[ax][ay]) // start making new amoeba field
9186 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9188 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9191 if (MovDelay[ax][ay])
9195 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9198 int x = ax + xy[start][0];
9199 int y = ay + xy[start][1];
9201 if (!IN_LEV_FIELD(x, y))
9204 if (IS_FREE(x, y) ||
9205 CAN_GROW_INTO(Tile[x][y]) ||
9206 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9207 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9213 if (newax == ax && neway == ay)
9216 else // normal or "filled" (BD style) amoeba
9219 boolean waiting_for_player = FALSE;
9221 for (i = 0; i < NUM_DIRECTIONS; i++)
9223 int j = (start + i) % 4;
9224 int x = ax + xy[j][0];
9225 int y = ay + xy[j][1];
9227 if (!IN_LEV_FIELD(x, y))
9230 if (IS_FREE(x, y) ||
9231 CAN_GROW_INTO(Tile[x][y]) ||
9232 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9233 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9239 else if (IS_PLAYER(x, y))
9240 waiting_for_player = TRUE;
9243 if (newax == ax && neway == ay) // amoeba cannot grow
9245 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9247 Tile[ax][ay] = EL_AMOEBA_DEAD;
9248 TEST_DrawLevelField(ax, ay);
9249 AmoebaCnt[AmoebaNr[ax][ay]]--;
9251 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9253 if (element == EL_AMOEBA_FULL)
9254 AmoebaToDiamond(ax, ay);
9255 else if (element == EL_BD_AMOEBA)
9256 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9261 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9263 // amoeba gets larger by growing in some direction
9265 int new_group_nr = AmoebaNr[ax][ay];
9268 if (new_group_nr == 0)
9270 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9272 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9278 AmoebaNr[newax][neway] = new_group_nr;
9279 AmoebaCnt[new_group_nr]++;
9280 AmoebaCnt2[new_group_nr]++;
9282 // if amoeba touches other amoeba(s) after growing, unify them
9283 AmoebaMerge(newax, neway);
9285 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9287 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9293 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9294 (neway == lev_fieldy - 1 && newax != ax))
9296 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9297 Store[newax][neway] = element;
9299 else if (neway == ay || element == EL_EMC_DRIPPER)
9301 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9303 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9307 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9308 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9309 Store[ax][ay] = EL_AMOEBA_DROP;
9310 ContinueMoving(ax, ay);
9314 TEST_DrawLevelField(newax, neway);
9317 static void Life(int ax, int ay)
9321 int element = Tile[ax][ay];
9322 int graphic = el2img(element);
9323 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9325 boolean changed = FALSE;
9327 if (IS_ANIMATED(graphic))
9328 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9333 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9334 MovDelay[ax][ay] = life_time;
9336 if (MovDelay[ax][ay]) // wait some time before next cycle
9339 if (MovDelay[ax][ay])
9343 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9345 int xx = ax+x1, yy = ay+y1;
9346 int old_element = Tile[xx][yy];
9347 int num_neighbours = 0;
9349 if (!IN_LEV_FIELD(xx, yy))
9352 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9354 int x = xx+x2, y = yy+y2;
9356 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9359 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9360 boolean is_neighbour = FALSE;
9362 if (level.use_life_bugs)
9364 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9365 (IS_FREE(x, y) && Stop[x][y]));
9368 (Last[x][y] == element || is_player_cell);
9374 boolean is_free = FALSE;
9376 if (level.use_life_bugs)
9377 is_free = (IS_FREE(xx, yy));
9379 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9381 if (xx == ax && yy == ay) // field in the middle
9383 if (num_neighbours < life_parameter[0] ||
9384 num_neighbours > life_parameter[1])
9386 Tile[xx][yy] = EL_EMPTY;
9387 if (Tile[xx][yy] != old_element)
9388 TEST_DrawLevelField(xx, yy);
9389 Stop[xx][yy] = TRUE;
9393 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9394 { // free border field
9395 if (num_neighbours >= life_parameter[2] &&
9396 num_neighbours <= life_parameter[3])
9398 Tile[xx][yy] = element;
9399 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9400 if (Tile[xx][yy] != old_element)
9401 TEST_DrawLevelField(xx, yy);
9402 Stop[xx][yy] = TRUE;
9409 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9410 SND_GAME_OF_LIFE_GROWING);
9413 static void InitRobotWheel(int x, int y)
9415 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9418 static void RunRobotWheel(int x, int y)
9420 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9423 static void StopRobotWheel(int x, int y)
9425 if (game.robot_wheel_x == x &&
9426 game.robot_wheel_y == y)
9428 game.robot_wheel_x = -1;
9429 game.robot_wheel_y = -1;
9430 game.robot_wheel_active = FALSE;
9434 static void InitTimegateWheel(int x, int y)
9436 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9439 static void RunTimegateWheel(int x, int y)
9441 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9444 static void InitMagicBallDelay(int x, int y)
9446 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9449 static void ActivateMagicBall(int bx, int by)
9453 if (level.ball_random)
9455 int pos_border = RND(8); // select one of the eight border elements
9456 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9457 int xx = pos_content % 3;
9458 int yy = pos_content / 3;
9463 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9464 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9468 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9470 int xx = x - bx + 1;
9471 int yy = y - by + 1;
9473 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9474 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9478 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9481 static void CheckExit(int x, int y)
9483 if (game.gems_still_needed > 0 ||
9484 game.sokoban_fields_still_needed > 0 ||
9485 game.sokoban_objects_still_needed > 0 ||
9486 game.lights_still_needed > 0)
9488 int element = Tile[x][y];
9489 int graphic = el2img(element);
9491 if (IS_ANIMATED(graphic))
9492 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9497 // do not re-open exit door closed after last player
9498 if (game.all_players_gone)
9501 Tile[x][y] = EL_EXIT_OPENING;
9503 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9506 static void CheckExitEM(int x, int y)
9508 if (game.gems_still_needed > 0 ||
9509 game.sokoban_fields_still_needed > 0 ||
9510 game.sokoban_objects_still_needed > 0 ||
9511 game.lights_still_needed > 0)
9513 int element = Tile[x][y];
9514 int graphic = el2img(element);
9516 if (IS_ANIMATED(graphic))
9517 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9522 // do not re-open exit door closed after last player
9523 if (game.all_players_gone)
9526 Tile[x][y] = EL_EM_EXIT_OPENING;
9528 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9531 static void CheckExitSteel(int x, int y)
9533 if (game.gems_still_needed > 0 ||
9534 game.sokoban_fields_still_needed > 0 ||
9535 game.sokoban_objects_still_needed > 0 ||
9536 game.lights_still_needed > 0)
9538 int element = Tile[x][y];
9539 int graphic = el2img(element);
9541 if (IS_ANIMATED(graphic))
9542 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9547 // do not re-open exit door closed after last player
9548 if (game.all_players_gone)
9551 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9553 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9556 static void CheckExitSteelEM(int x, int y)
9558 if (game.gems_still_needed > 0 ||
9559 game.sokoban_fields_still_needed > 0 ||
9560 game.sokoban_objects_still_needed > 0 ||
9561 game.lights_still_needed > 0)
9563 int element = Tile[x][y];
9564 int graphic = el2img(element);
9566 if (IS_ANIMATED(graphic))
9567 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9572 // do not re-open exit door closed after last player
9573 if (game.all_players_gone)
9576 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9578 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9581 static void CheckExitSP(int x, int y)
9583 if (game.gems_still_needed > 0)
9585 int element = Tile[x][y];
9586 int graphic = el2img(element);
9588 if (IS_ANIMATED(graphic))
9589 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9594 // do not re-open exit door closed after last player
9595 if (game.all_players_gone)
9598 Tile[x][y] = EL_SP_EXIT_OPENING;
9600 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9603 static void CloseAllOpenTimegates(void)
9607 SCAN_PLAYFIELD(x, y)
9609 int element = Tile[x][y];
9611 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9613 Tile[x][y] = EL_TIMEGATE_CLOSING;
9615 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9620 static void DrawTwinkleOnField(int x, int y)
9622 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9625 if (Tile[x][y] == EL_BD_DIAMOND)
9628 if (MovDelay[x][y] == 0) // next animation frame
9629 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9631 if (MovDelay[x][y] != 0) // wait some time before next frame
9635 DrawLevelElementAnimation(x, y, Tile[x][y]);
9637 if (MovDelay[x][y] != 0)
9639 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9640 10 - MovDelay[x][y]);
9642 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9647 static void MauerWaechst(int x, int y)
9651 if (!MovDelay[x][y]) // next animation frame
9652 MovDelay[x][y] = 3 * delay;
9654 if (MovDelay[x][y]) // wait some time before next frame
9658 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9660 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9661 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9663 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9666 if (!MovDelay[x][y])
9668 if (MovDir[x][y] == MV_LEFT)
9670 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9671 TEST_DrawLevelField(x - 1, y);
9673 else if (MovDir[x][y] == MV_RIGHT)
9675 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9676 TEST_DrawLevelField(x + 1, y);
9678 else if (MovDir[x][y] == MV_UP)
9680 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9681 TEST_DrawLevelField(x, y - 1);
9685 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9686 TEST_DrawLevelField(x, y + 1);
9689 Tile[x][y] = Store[x][y];
9691 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9692 TEST_DrawLevelField(x, y);
9697 static void MauerAbleger(int ax, int ay)
9699 int element = Tile[ax][ay];
9700 int graphic = el2img(element);
9701 boolean oben_frei = FALSE, unten_frei = FALSE;
9702 boolean links_frei = FALSE, rechts_frei = FALSE;
9703 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9704 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9705 boolean new_wall = FALSE;
9707 if (IS_ANIMATED(graphic))
9708 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9710 if (!MovDelay[ax][ay]) // start building new wall
9711 MovDelay[ax][ay] = 6;
9713 if (MovDelay[ax][ay]) // wait some time before building new wall
9716 if (MovDelay[ax][ay])
9720 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9722 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9724 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9726 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9729 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9730 element == EL_EXPANDABLE_WALL_ANY)
9734 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9735 Store[ax][ay-1] = element;
9736 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9737 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9738 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9739 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9744 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9745 Store[ax][ay+1] = element;
9746 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9747 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9748 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9749 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9754 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9755 element == EL_EXPANDABLE_WALL_ANY ||
9756 element == EL_EXPANDABLE_WALL ||
9757 element == EL_BD_EXPANDABLE_WALL)
9761 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9762 Store[ax-1][ay] = element;
9763 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9764 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9765 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9766 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9772 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9773 Store[ax+1][ay] = element;
9774 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9775 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9776 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9777 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9782 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9783 TEST_DrawLevelField(ax, ay);
9785 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9787 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9788 unten_massiv = TRUE;
9789 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9790 links_massiv = TRUE;
9791 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9792 rechts_massiv = TRUE;
9794 if (((oben_massiv && unten_massiv) ||
9795 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9796 element == EL_EXPANDABLE_WALL) &&
9797 ((links_massiv && rechts_massiv) ||
9798 element == EL_EXPANDABLE_WALL_VERTICAL))
9799 Tile[ax][ay] = EL_WALL;
9802 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9805 static void MauerAblegerStahl(int ax, int ay)
9807 int element = Tile[ax][ay];
9808 int graphic = el2img(element);
9809 boolean oben_frei = FALSE, unten_frei = FALSE;
9810 boolean links_frei = FALSE, rechts_frei = FALSE;
9811 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9812 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9813 boolean new_wall = FALSE;
9815 if (IS_ANIMATED(graphic))
9816 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9818 if (!MovDelay[ax][ay]) // start building new wall
9819 MovDelay[ax][ay] = 6;
9821 if (MovDelay[ax][ay]) // wait some time before building new wall
9824 if (MovDelay[ax][ay])
9828 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9830 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9832 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9834 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9837 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9838 element == EL_EXPANDABLE_STEELWALL_ANY)
9842 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9843 Store[ax][ay-1] = element;
9844 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9845 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9846 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9847 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9852 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9853 Store[ax][ay+1] = element;
9854 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9855 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9856 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9857 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9862 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9863 element == EL_EXPANDABLE_STEELWALL_ANY)
9867 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9868 Store[ax-1][ay] = element;
9869 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9870 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9871 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9872 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9878 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9879 Store[ax+1][ay] = element;
9880 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9881 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9882 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9883 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9888 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9890 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9891 unten_massiv = TRUE;
9892 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9893 links_massiv = TRUE;
9894 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9895 rechts_massiv = TRUE;
9897 if (((oben_massiv && unten_massiv) ||
9898 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9899 ((links_massiv && rechts_massiv) ||
9900 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9901 Tile[ax][ay] = EL_STEELWALL;
9904 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9907 static void CheckForDragon(int x, int y)
9910 boolean dragon_found = FALSE;
9911 static int xy[4][2] =
9919 for (i = 0; i < NUM_DIRECTIONS; i++)
9921 for (j = 0; j < 4; j++)
9923 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9925 if (IN_LEV_FIELD(xx, yy) &&
9926 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9928 if (Tile[xx][yy] == EL_DRAGON)
9929 dragon_found = TRUE;
9938 for (i = 0; i < NUM_DIRECTIONS; i++)
9940 for (j = 0; j < 3; j++)
9942 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9944 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9946 Tile[xx][yy] = EL_EMPTY;
9947 TEST_DrawLevelField(xx, yy);
9956 static void InitBuggyBase(int x, int y)
9958 int element = Tile[x][y];
9959 int activating_delay = FRAMES_PER_SECOND / 4;
9962 (element == EL_SP_BUGGY_BASE ?
9963 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9964 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9966 element == EL_SP_BUGGY_BASE_ACTIVE ?
9967 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9970 static void WarnBuggyBase(int x, int y)
9973 static int xy[4][2] =
9981 for (i = 0; i < NUM_DIRECTIONS; i++)
9983 int xx = x + xy[i][0];
9984 int yy = y + xy[i][1];
9986 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9988 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9995 static void InitTrap(int x, int y)
9997 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10000 static void ActivateTrap(int x, int y)
10002 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10005 static void ChangeActiveTrap(int x, int y)
10007 int graphic = IMG_TRAP_ACTIVE;
10009 // if new animation frame was drawn, correct crumbled sand border
10010 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10011 TEST_DrawLevelFieldCrumbled(x, y);
10014 static int getSpecialActionElement(int element, int number, int base_element)
10016 return (element != EL_EMPTY ? element :
10017 number != -1 ? base_element + number - 1 :
10021 static int getModifiedActionNumber(int value_old, int operator, int operand,
10022 int value_min, int value_max)
10024 int value_new = (operator == CA_MODE_SET ? operand :
10025 operator == CA_MODE_ADD ? value_old + operand :
10026 operator == CA_MODE_SUBTRACT ? value_old - operand :
10027 operator == CA_MODE_MULTIPLY ? value_old * operand :
10028 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10029 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10032 return (value_new < value_min ? value_min :
10033 value_new > value_max ? value_max :
10037 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10039 struct ElementInfo *ei = &element_info[element];
10040 struct ElementChangeInfo *change = &ei->change_page[page];
10041 int target_element = change->target_element;
10042 int action_type = change->action_type;
10043 int action_mode = change->action_mode;
10044 int action_arg = change->action_arg;
10045 int action_element = change->action_element;
10048 if (!change->has_action)
10051 // ---------- determine action paramater values -----------------------------
10053 int level_time_value =
10054 (level.time > 0 ? TimeLeft :
10057 int action_arg_element_raw =
10058 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10059 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10060 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10061 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10062 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10063 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10064 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10066 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10068 int action_arg_direction =
10069 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10070 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10071 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10072 change->actual_trigger_side :
10073 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10074 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10077 int action_arg_number_min =
10078 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10081 int action_arg_number_max =
10082 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10083 action_type == CA_SET_LEVEL_GEMS ? 999 :
10084 action_type == CA_SET_LEVEL_TIME ? 9999 :
10085 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10086 action_type == CA_SET_CE_VALUE ? 9999 :
10087 action_type == CA_SET_CE_SCORE ? 9999 :
10090 int action_arg_number_reset =
10091 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10092 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10093 action_type == CA_SET_LEVEL_TIME ? level.time :
10094 action_type == CA_SET_LEVEL_SCORE ? 0 :
10095 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10096 action_type == CA_SET_CE_SCORE ? 0 :
10099 int action_arg_number =
10100 (action_arg <= CA_ARG_MAX ? action_arg :
10101 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10102 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10103 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10104 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10105 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10106 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10107 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10108 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10109 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10110 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10111 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10112 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10113 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10114 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10115 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10116 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10117 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10118 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10119 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10120 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10121 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10124 int action_arg_number_old =
10125 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10126 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10127 action_type == CA_SET_LEVEL_SCORE ? game.score :
10128 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10129 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10132 int action_arg_number_new =
10133 getModifiedActionNumber(action_arg_number_old,
10134 action_mode, action_arg_number,
10135 action_arg_number_min, action_arg_number_max);
10137 int trigger_player_bits =
10138 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10139 change->actual_trigger_player_bits : change->trigger_player);
10141 int action_arg_player_bits =
10142 (action_arg >= CA_ARG_PLAYER_1 &&
10143 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10144 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10145 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10148 // ---------- execute action -----------------------------------------------
10150 switch (action_type)
10157 // ---------- level actions ----------------------------------------------
10159 case CA_RESTART_LEVEL:
10161 game.restart_level = TRUE;
10166 case CA_SHOW_ENVELOPE:
10168 int element = getSpecialActionElement(action_arg_element,
10169 action_arg_number, EL_ENVELOPE_1);
10171 if (IS_ENVELOPE(element))
10172 local_player->show_envelope = element;
10177 case CA_SET_LEVEL_TIME:
10179 if (level.time > 0) // only modify limited time value
10181 TimeLeft = action_arg_number_new;
10183 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10185 DisplayGameControlValues();
10187 if (!TimeLeft && setup.time_limit)
10188 for (i = 0; i < MAX_PLAYERS; i++)
10189 KillPlayer(&stored_player[i]);
10195 case CA_SET_LEVEL_SCORE:
10197 game.score = action_arg_number_new;
10199 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10201 DisplayGameControlValues();
10206 case CA_SET_LEVEL_GEMS:
10208 game.gems_still_needed = action_arg_number_new;
10210 game.snapshot.collected_item = TRUE;
10212 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10214 DisplayGameControlValues();
10219 case CA_SET_LEVEL_WIND:
10221 game.wind_direction = action_arg_direction;
10226 case CA_SET_LEVEL_RANDOM_SEED:
10228 // ensure that setting a new random seed while playing is predictable
10229 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10234 // ---------- player actions ---------------------------------------------
10236 case CA_MOVE_PLAYER:
10237 case CA_MOVE_PLAYER_NEW:
10239 // automatically move to the next field in specified direction
10240 for (i = 0; i < MAX_PLAYERS; i++)
10241 if (trigger_player_bits & (1 << i))
10242 if (action_type == CA_MOVE_PLAYER ||
10243 stored_player[i].MovPos == 0)
10244 stored_player[i].programmed_action = action_arg_direction;
10249 case CA_EXIT_PLAYER:
10251 for (i = 0; i < MAX_PLAYERS; i++)
10252 if (action_arg_player_bits & (1 << i))
10253 ExitPlayer(&stored_player[i]);
10255 if (game.players_still_needed == 0)
10261 case CA_KILL_PLAYER:
10263 for (i = 0; i < MAX_PLAYERS; i++)
10264 if (action_arg_player_bits & (1 << i))
10265 KillPlayer(&stored_player[i]);
10270 case CA_SET_PLAYER_KEYS:
10272 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10273 int element = getSpecialActionElement(action_arg_element,
10274 action_arg_number, EL_KEY_1);
10276 if (IS_KEY(element))
10278 for (i = 0; i < MAX_PLAYERS; i++)
10280 if (trigger_player_bits & (1 << i))
10282 stored_player[i].key[KEY_NR(element)] = key_state;
10284 DrawGameDoorValues();
10292 case CA_SET_PLAYER_SPEED:
10294 for (i = 0; i < MAX_PLAYERS; i++)
10296 if (trigger_player_bits & (1 << i))
10298 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10300 if (action_arg == CA_ARG_SPEED_FASTER &&
10301 stored_player[i].cannot_move)
10303 action_arg_number = STEPSIZE_VERY_SLOW;
10305 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10306 action_arg == CA_ARG_SPEED_FASTER)
10308 action_arg_number = 2;
10309 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10312 else if (action_arg == CA_ARG_NUMBER_RESET)
10314 action_arg_number = level.initial_player_stepsize[i];
10318 getModifiedActionNumber(move_stepsize,
10321 action_arg_number_min,
10322 action_arg_number_max);
10324 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10331 case CA_SET_PLAYER_SHIELD:
10333 for (i = 0; i < MAX_PLAYERS; i++)
10335 if (trigger_player_bits & (1 << i))
10337 if (action_arg == CA_ARG_SHIELD_OFF)
10339 stored_player[i].shield_normal_time_left = 0;
10340 stored_player[i].shield_deadly_time_left = 0;
10342 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10344 stored_player[i].shield_normal_time_left = 999999;
10346 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10348 stored_player[i].shield_normal_time_left = 999999;
10349 stored_player[i].shield_deadly_time_left = 999999;
10357 case CA_SET_PLAYER_GRAVITY:
10359 for (i = 0; i < MAX_PLAYERS; i++)
10361 if (trigger_player_bits & (1 << i))
10363 stored_player[i].gravity =
10364 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10365 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10366 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10367 stored_player[i].gravity);
10374 case CA_SET_PLAYER_ARTWORK:
10376 for (i = 0; i < MAX_PLAYERS; i++)
10378 if (trigger_player_bits & (1 << i))
10380 int artwork_element = action_arg_element;
10382 if (action_arg == CA_ARG_ELEMENT_RESET)
10384 (level.use_artwork_element[i] ? level.artwork_element[i] :
10385 stored_player[i].element_nr);
10387 if (stored_player[i].artwork_element != artwork_element)
10388 stored_player[i].Frame = 0;
10390 stored_player[i].artwork_element = artwork_element;
10392 SetPlayerWaiting(&stored_player[i], FALSE);
10394 // set number of special actions for bored and sleeping animation
10395 stored_player[i].num_special_action_bored =
10396 get_num_special_action(artwork_element,
10397 ACTION_BORING_1, ACTION_BORING_LAST);
10398 stored_player[i].num_special_action_sleeping =
10399 get_num_special_action(artwork_element,
10400 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10407 case CA_SET_PLAYER_INVENTORY:
10409 for (i = 0; i < MAX_PLAYERS; i++)
10411 struct PlayerInfo *player = &stored_player[i];
10414 if (trigger_player_bits & (1 << i))
10416 int inventory_element = action_arg_element;
10418 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10419 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10420 action_arg == CA_ARG_ELEMENT_ACTION)
10422 int element = inventory_element;
10423 int collect_count = element_info[element].collect_count_initial;
10425 if (!IS_CUSTOM_ELEMENT(element))
10428 if (collect_count == 0)
10429 player->inventory_infinite_element = element;
10431 for (k = 0; k < collect_count; k++)
10432 if (player->inventory_size < MAX_INVENTORY_SIZE)
10433 player->inventory_element[player->inventory_size++] =
10436 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10437 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10438 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10440 if (player->inventory_infinite_element != EL_UNDEFINED &&
10441 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10442 action_arg_element_raw))
10443 player->inventory_infinite_element = EL_UNDEFINED;
10445 for (k = 0, j = 0; j < player->inventory_size; j++)
10447 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10448 action_arg_element_raw))
10449 player->inventory_element[k++] = player->inventory_element[j];
10452 player->inventory_size = k;
10454 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10456 if (player->inventory_size > 0)
10458 for (j = 0; j < player->inventory_size - 1; j++)
10459 player->inventory_element[j] = player->inventory_element[j + 1];
10461 player->inventory_size--;
10464 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10466 if (player->inventory_size > 0)
10467 player->inventory_size--;
10469 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10471 player->inventory_infinite_element = EL_UNDEFINED;
10472 player->inventory_size = 0;
10474 else if (action_arg == CA_ARG_INVENTORY_RESET)
10476 player->inventory_infinite_element = EL_UNDEFINED;
10477 player->inventory_size = 0;
10479 if (level.use_initial_inventory[i])
10481 for (j = 0; j < level.initial_inventory_size[i]; j++)
10483 int element = level.initial_inventory_content[i][j];
10484 int collect_count = element_info[element].collect_count_initial;
10486 if (!IS_CUSTOM_ELEMENT(element))
10489 if (collect_count == 0)
10490 player->inventory_infinite_element = element;
10492 for (k = 0; k < collect_count; k++)
10493 if (player->inventory_size < MAX_INVENTORY_SIZE)
10494 player->inventory_element[player->inventory_size++] =
10505 // ---------- CE actions -------------------------------------------------
10507 case CA_SET_CE_VALUE:
10509 int last_ce_value = CustomValue[x][y];
10511 CustomValue[x][y] = action_arg_number_new;
10513 if (CustomValue[x][y] != last_ce_value)
10515 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10516 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10518 if (CustomValue[x][y] == 0)
10520 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10521 ChangeCount[x][y] = 0; // allow at least one more change
10523 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10524 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10531 case CA_SET_CE_SCORE:
10533 int last_ce_score = ei->collect_score;
10535 ei->collect_score = action_arg_number_new;
10537 if (ei->collect_score != last_ce_score)
10539 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10540 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10542 if (ei->collect_score == 0)
10546 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10547 ChangeCount[x][y] = 0; // allow at least one more change
10549 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10550 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10553 This is a very special case that seems to be a mixture between
10554 CheckElementChange() and CheckTriggeredElementChange(): while
10555 the first one only affects single elements that are triggered
10556 directly, the second one affects multiple elements in the playfield
10557 that are triggered indirectly by another element. This is a third
10558 case: Changing the CE score always affects multiple identical CEs,
10559 so every affected CE must be checked, not only the single CE for
10560 which the CE score was changed in the first place (as every instance
10561 of that CE shares the same CE score, and therefore also can change)!
10563 SCAN_PLAYFIELD(xx, yy)
10565 if (Tile[xx][yy] == element)
10566 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10567 CE_SCORE_GETS_ZERO);
10575 case CA_SET_CE_ARTWORK:
10577 int artwork_element = action_arg_element;
10578 boolean reset_frame = FALSE;
10581 if (action_arg == CA_ARG_ELEMENT_RESET)
10582 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10585 if (ei->gfx_element != artwork_element)
10586 reset_frame = TRUE;
10588 ei->gfx_element = artwork_element;
10590 SCAN_PLAYFIELD(xx, yy)
10592 if (Tile[xx][yy] == element)
10596 ResetGfxAnimation(xx, yy);
10597 ResetRandomAnimationValue(xx, yy);
10600 TEST_DrawLevelField(xx, yy);
10607 // ---------- engine actions ---------------------------------------------
10609 case CA_SET_ENGINE_SCAN_MODE:
10611 InitPlayfieldScanMode(action_arg);
10621 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10623 int old_element = Tile[x][y];
10624 int new_element = GetElementFromGroupElement(element);
10625 int previous_move_direction = MovDir[x][y];
10626 int last_ce_value = CustomValue[x][y];
10627 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10628 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10629 boolean add_player_onto_element = (new_element_is_player &&
10630 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10631 IS_WALKABLE(old_element));
10633 if (!add_player_onto_element)
10635 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10636 RemoveMovingField(x, y);
10640 Tile[x][y] = new_element;
10642 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10643 MovDir[x][y] = previous_move_direction;
10645 if (element_info[new_element].use_last_ce_value)
10646 CustomValue[x][y] = last_ce_value;
10648 InitField_WithBug1(x, y, FALSE);
10650 new_element = Tile[x][y]; // element may have changed
10652 ResetGfxAnimation(x, y);
10653 ResetRandomAnimationValue(x, y);
10655 TEST_DrawLevelField(x, y);
10657 if (GFX_CRUMBLED(new_element))
10658 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10661 // check if element under the player changes from accessible to unaccessible
10662 // (needed for special case of dropping element which then changes)
10663 // (must be checked after creating new element for walkable group elements)
10664 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10665 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10672 // "ChangeCount" not set yet to allow "entered by player" change one time
10673 if (new_element_is_player)
10674 RelocatePlayer(x, y, new_element);
10677 ChangeCount[x][y]++; // count number of changes in the same frame
10679 TestIfBadThingTouchesPlayer(x, y);
10680 TestIfPlayerTouchesCustomElement(x, y);
10681 TestIfElementTouchesCustomElement(x, y);
10684 static void CreateField(int x, int y, int element)
10686 CreateFieldExt(x, y, element, FALSE);
10689 static void CreateElementFromChange(int x, int y, int element)
10691 element = GET_VALID_RUNTIME_ELEMENT(element);
10693 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10695 int old_element = Tile[x][y];
10697 // prevent changed element from moving in same engine frame
10698 // unless both old and new element can either fall or move
10699 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10700 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10704 CreateFieldExt(x, y, element, TRUE);
10707 static boolean ChangeElement(int x, int y, int element, int page)
10709 struct ElementInfo *ei = &element_info[element];
10710 struct ElementChangeInfo *change = &ei->change_page[page];
10711 int ce_value = CustomValue[x][y];
10712 int ce_score = ei->collect_score;
10713 int target_element;
10714 int old_element = Tile[x][y];
10716 // always use default change event to prevent running into a loop
10717 if (ChangeEvent[x][y] == -1)
10718 ChangeEvent[x][y] = CE_DELAY;
10720 if (ChangeEvent[x][y] == CE_DELAY)
10722 // reset actual trigger element, trigger player and action element
10723 change->actual_trigger_element = EL_EMPTY;
10724 change->actual_trigger_player = EL_EMPTY;
10725 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10726 change->actual_trigger_side = CH_SIDE_NONE;
10727 change->actual_trigger_ce_value = 0;
10728 change->actual_trigger_ce_score = 0;
10731 // do not change elements more than a specified maximum number of changes
10732 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10735 ChangeCount[x][y]++; // count number of changes in the same frame
10737 if (change->explode)
10744 if (change->use_target_content)
10746 boolean complete_replace = TRUE;
10747 boolean can_replace[3][3];
10750 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10753 boolean is_walkable;
10754 boolean is_diggable;
10755 boolean is_collectible;
10756 boolean is_removable;
10757 boolean is_destructible;
10758 int ex = x + xx - 1;
10759 int ey = y + yy - 1;
10760 int content_element = change->target_content.e[xx][yy];
10763 can_replace[xx][yy] = TRUE;
10765 if (ex == x && ey == y) // do not check changing element itself
10768 if (content_element == EL_EMPTY_SPACE)
10770 can_replace[xx][yy] = FALSE; // do not replace border with space
10775 if (!IN_LEV_FIELD(ex, ey))
10777 can_replace[xx][yy] = FALSE;
10778 complete_replace = FALSE;
10785 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10786 e = MovingOrBlocked2Element(ex, ey);
10788 is_empty = (IS_FREE(ex, ey) ||
10789 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10791 is_walkable = (is_empty || IS_WALKABLE(e));
10792 is_diggable = (is_empty || IS_DIGGABLE(e));
10793 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10794 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10795 is_removable = (is_diggable || is_collectible);
10797 can_replace[xx][yy] =
10798 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10799 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10800 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10801 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10802 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10803 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10804 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10806 if (!can_replace[xx][yy])
10807 complete_replace = FALSE;
10810 if (!change->only_if_complete || complete_replace)
10812 boolean something_has_changed = FALSE;
10814 if (change->only_if_complete && change->use_random_replace &&
10815 RND(100) < change->random_percentage)
10818 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10820 int ex = x + xx - 1;
10821 int ey = y + yy - 1;
10822 int content_element;
10824 if (can_replace[xx][yy] && (!change->use_random_replace ||
10825 RND(100) < change->random_percentage))
10827 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10828 RemoveMovingField(ex, ey);
10830 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10832 content_element = change->target_content.e[xx][yy];
10833 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10834 ce_value, ce_score);
10836 CreateElementFromChange(ex, ey, target_element);
10838 something_has_changed = TRUE;
10840 // for symmetry reasons, freeze newly created border elements
10841 if (ex != x || ey != y)
10842 Stop[ex][ey] = TRUE; // no more moving in this frame
10846 if (something_has_changed)
10848 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10849 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10855 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10856 ce_value, ce_score);
10858 if (element == EL_DIAGONAL_GROWING ||
10859 element == EL_DIAGONAL_SHRINKING)
10861 target_element = Store[x][y];
10863 Store[x][y] = EL_EMPTY;
10866 // special case: element changes to player (and may be kept if walkable)
10867 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10868 CreateElementFromChange(x, y, EL_EMPTY);
10870 CreateElementFromChange(x, y, target_element);
10872 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10873 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10876 // this uses direct change before indirect change
10877 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10882 static void HandleElementChange(int x, int y, int page)
10884 int element = MovingOrBlocked2Element(x, y);
10885 struct ElementInfo *ei = &element_info[element];
10886 struct ElementChangeInfo *change = &ei->change_page[page];
10887 boolean handle_action_before_change = FALSE;
10890 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10891 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10893 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10894 x, y, element, element_info[element].token_name);
10895 Debug("game:playing:HandleElementChange", "This should never happen!");
10899 // this can happen with classic bombs on walkable, changing elements
10900 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10905 if (ChangeDelay[x][y] == 0) // initialize element change
10907 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10909 if (change->can_change)
10911 // !!! not clear why graphic animation should be reset at all here !!!
10912 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10913 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10916 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10918 When using an animation frame delay of 1 (this only happens with
10919 "sp_zonk.moving.left/right" in the classic graphics), the default
10920 (non-moving) animation shows wrong animation frames (while the
10921 moving animation, like "sp_zonk.moving.left/right", is correct,
10922 so this graphical bug never shows up with the classic graphics).
10923 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10924 be drawn instead of the correct frames 0,1,2,3. This is caused by
10925 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10926 an element change: First when the change delay ("ChangeDelay[][]")
10927 counter has reached zero after decrementing, then a second time in
10928 the next frame (after "GfxFrame[][]" was already incremented) when
10929 "ChangeDelay[][]" is reset to the initial delay value again.
10931 This causes frame 0 to be drawn twice, while the last frame won't
10932 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10934 As some animations may already be cleverly designed around this bug
10935 (at least the "Snake Bite" snake tail animation does this), it cannot
10936 simply be fixed here without breaking such existing animations.
10937 Unfortunately, it cannot easily be detected if a graphics set was
10938 designed "before" or "after" the bug was fixed. As a workaround,
10939 a new graphics set option "game.graphics_engine_version" was added
10940 to be able to specify the game's major release version for which the
10941 graphics set was designed, which can then be used to decide if the
10942 bugfix should be used (version 4 and above) or not (version 3 or
10943 below, or if no version was specified at all, as with old sets).
10945 (The wrong/fixed animation frames can be tested with the test level set
10946 "test_gfxframe" and level "000", which contains a specially prepared
10947 custom element at level position (x/y) == (11/9) which uses the zonk
10948 animation mentioned above. Using "game.graphics_engine_version: 4"
10949 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10950 This can also be seen from the debug output for this test element.)
10953 // when a custom element is about to change (for example by change delay),
10954 // do not reset graphic animation when the custom element is moving
10955 if (game.graphics_engine_version < 4 &&
10958 ResetGfxAnimation(x, y);
10959 ResetRandomAnimationValue(x, y);
10962 if (change->pre_change_function)
10963 change->pre_change_function(x, y);
10967 ChangeDelay[x][y]--;
10969 if (ChangeDelay[x][y] != 0) // continue element change
10971 if (change->can_change)
10973 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10975 if (IS_ANIMATED(graphic))
10976 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10978 if (change->change_function)
10979 change->change_function(x, y);
10982 else // finish element change
10984 if (ChangePage[x][y] != -1) // remember page from delayed change
10986 page = ChangePage[x][y];
10987 ChangePage[x][y] = -1;
10989 change = &ei->change_page[page];
10992 if (IS_MOVING(x, y)) // never change a running system ;-)
10994 ChangeDelay[x][y] = 1; // try change after next move step
10995 ChangePage[x][y] = page; // remember page to use for change
11000 // special case: set new level random seed before changing element
11001 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11002 handle_action_before_change = TRUE;
11004 if (change->has_action && handle_action_before_change)
11005 ExecuteCustomElementAction(x, y, element, page);
11007 if (change->can_change)
11009 if (ChangeElement(x, y, element, page))
11011 if (change->post_change_function)
11012 change->post_change_function(x, y);
11016 if (change->has_action && !handle_action_before_change)
11017 ExecuteCustomElementAction(x, y, element, page);
11021 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11022 int trigger_element,
11024 int trigger_player,
11028 boolean change_done_any = FALSE;
11029 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11032 if (!(trigger_events[trigger_element][trigger_event]))
11035 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11037 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11039 int element = EL_CUSTOM_START + i;
11040 boolean change_done = FALSE;
11043 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11044 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11047 for (p = 0; p < element_info[element].num_change_pages; p++)
11049 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11051 if (change->can_change_or_has_action &&
11052 change->has_event[trigger_event] &&
11053 change->trigger_side & trigger_side &&
11054 change->trigger_player & trigger_player &&
11055 change->trigger_page & trigger_page_bits &&
11056 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11058 change->actual_trigger_element = trigger_element;
11059 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11060 change->actual_trigger_player_bits = trigger_player;
11061 change->actual_trigger_side = trigger_side;
11062 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11063 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11065 if ((change->can_change && !change_done) || change->has_action)
11069 SCAN_PLAYFIELD(x, y)
11071 if (Tile[x][y] == element)
11073 if (change->can_change && !change_done)
11075 // if element already changed in this frame, not only prevent
11076 // another element change (checked in ChangeElement()), but
11077 // also prevent additional element actions for this element
11079 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11080 !level.use_action_after_change_bug)
11083 ChangeDelay[x][y] = 1;
11084 ChangeEvent[x][y] = trigger_event;
11086 HandleElementChange(x, y, p);
11088 else if (change->has_action)
11090 // if element already changed in this frame, not only prevent
11091 // another element change (checked in ChangeElement()), but
11092 // also prevent additional element actions for this element
11094 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11095 !level.use_action_after_change_bug)
11098 ExecuteCustomElementAction(x, y, element, p);
11099 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11104 if (change->can_change)
11106 change_done = TRUE;
11107 change_done_any = TRUE;
11114 RECURSION_LOOP_DETECTION_END();
11116 return change_done_any;
11119 static boolean CheckElementChangeExt(int x, int y,
11121 int trigger_element,
11123 int trigger_player,
11126 boolean change_done = FALSE;
11129 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11130 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11133 if (Tile[x][y] == EL_BLOCKED)
11135 Blocked2Moving(x, y, &x, &y);
11136 element = Tile[x][y];
11139 // check if element has already changed or is about to change after moving
11140 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11141 Tile[x][y] != element) ||
11143 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11144 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11145 ChangePage[x][y] != -1)))
11148 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11150 for (p = 0; p < element_info[element].num_change_pages; p++)
11152 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11154 /* check trigger element for all events where the element that is checked
11155 for changing interacts with a directly adjacent element -- this is
11156 different to element changes that affect other elements to change on the
11157 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11158 boolean check_trigger_element =
11159 (trigger_event == CE_NEXT_TO_X ||
11160 trigger_event == CE_TOUCHING_X ||
11161 trigger_event == CE_HITTING_X ||
11162 trigger_event == CE_HIT_BY_X ||
11163 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11165 if (change->can_change_or_has_action &&
11166 change->has_event[trigger_event] &&
11167 change->trigger_side & trigger_side &&
11168 change->trigger_player & trigger_player &&
11169 (!check_trigger_element ||
11170 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11172 change->actual_trigger_element = trigger_element;
11173 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11174 change->actual_trigger_player_bits = trigger_player;
11175 change->actual_trigger_side = trigger_side;
11176 change->actual_trigger_ce_value = CustomValue[x][y];
11177 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11179 // special case: trigger element not at (x,y) position for some events
11180 if (check_trigger_element)
11192 { 0, 0 }, { 0, 0 }, { 0, 0 },
11196 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11197 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11199 change->actual_trigger_ce_value = CustomValue[xx][yy];
11200 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11203 if (change->can_change && !change_done)
11205 ChangeDelay[x][y] = 1;
11206 ChangeEvent[x][y] = trigger_event;
11208 HandleElementChange(x, y, p);
11210 change_done = TRUE;
11212 else if (change->has_action)
11214 ExecuteCustomElementAction(x, y, element, p);
11215 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11220 RECURSION_LOOP_DETECTION_END();
11222 return change_done;
11225 static void PlayPlayerSound(struct PlayerInfo *player)
11227 int jx = player->jx, jy = player->jy;
11228 int sound_element = player->artwork_element;
11229 int last_action = player->last_action_waiting;
11230 int action = player->action_waiting;
11232 if (player->is_waiting)
11234 if (action != last_action)
11235 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11237 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11241 if (action != last_action)
11242 StopSound(element_info[sound_element].sound[last_action]);
11244 if (last_action == ACTION_SLEEPING)
11245 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11249 static void PlayAllPlayersSound(void)
11253 for (i = 0; i < MAX_PLAYERS; i++)
11254 if (stored_player[i].active)
11255 PlayPlayerSound(&stored_player[i]);
11258 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11260 boolean last_waiting = player->is_waiting;
11261 int move_dir = player->MovDir;
11263 player->dir_waiting = move_dir;
11264 player->last_action_waiting = player->action_waiting;
11268 if (!last_waiting) // not waiting -> waiting
11270 player->is_waiting = TRUE;
11272 player->frame_counter_bored =
11274 game.player_boring_delay_fixed +
11275 GetSimpleRandom(game.player_boring_delay_random);
11276 player->frame_counter_sleeping =
11278 game.player_sleeping_delay_fixed +
11279 GetSimpleRandom(game.player_sleeping_delay_random);
11281 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11284 if (game.player_sleeping_delay_fixed +
11285 game.player_sleeping_delay_random > 0 &&
11286 player->anim_delay_counter == 0 &&
11287 player->post_delay_counter == 0 &&
11288 FrameCounter >= player->frame_counter_sleeping)
11289 player->is_sleeping = TRUE;
11290 else if (game.player_boring_delay_fixed +
11291 game.player_boring_delay_random > 0 &&
11292 FrameCounter >= player->frame_counter_bored)
11293 player->is_bored = TRUE;
11295 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11296 player->is_bored ? ACTION_BORING :
11299 if (player->is_sleeping && player->use_murphy)
11301 // special case for sleeping Murphy when leaning against non-free tile
11303 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11304 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11305 !IS_MOVING(player->jx - 1, player->jy)))
11306 move_dir = MV_LEFT;
11307 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11308 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11309 !IS_MOVING(player->jx + 1, player->jy)))
11310 move_dir = MV_RIGHT;
11312 player->is_sleeping = FALSE;
11314 player->dir_waiting = move_dir;
11317 if (player->is_sleeping)
11319 if (player->num_special_action_sleeping > 0)
11321 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11323 int last_special_action = player->special_action_sleeping;
11324 int num_special_action = player->num_special_action_sleeping;
11325 int special_action =
11326 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11327 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11328 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11329 last_special_action + 1 : ACTION_SLEEPING);
11330 int special_graphic =
11331 el_act_dir2img(player->artwork_element, special_action, move_dir);
11333 player->anim_delay_counter =
11334 graphic_info[special_graphic].anim_delay_fixed +
11335 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11336 player->post_delay_counter =
11337 graphic_info[special_graphic].post_delay_fixed +
11338 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11340 player->special_action_sleeping = special_action;
11343 if (player->anim_delay_counter > 0)
11345 player->action_waiting = player->special_action_sleeping;
11346 player->anim_delay_counter--;
11348 else if (player->post_delay_counter > 0)
11350 player->post_delay_counter--;
11354 else if (player->is_bored)
11356 if (player->num_special_action_bored > 0)
11358 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11360 int special_action =
11361 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11362 int special_graphic =
11363 el_act_dir2img(player->artwork_element, special_action, move_dir);
11365 player->anim_delay_counter =
11366 graphic_info[special_graphic].anim_delay_fixed +
11367 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11368 player->post_delay_counter =
11369 graphic_info[special_graphic].post_delay_fixed +
11370 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11372 player->special_action_bored = special_action;
11375 if (player->anim_delay_counter > 0)
11377 player->action_waiting = player->special_action_bored;
11378 player->anim_delay_counter--;
11380 else if (player->post_delay_counter > 0)
11382 player->post_delay_counter--;
11387 else if (last_waiting) // waiting -> not waiting
11389 player->is_waiting = FALSE;
11390 player->is_bored = FALSE;
11391 player->is_sleeping = FALSE;
11393 player->frame_counter_bored = -1;
11394 player->frame_counter_sleeping = -1;
11396 player->anim_delay_counter = 0;
11397 player->post_delay_counter = 0;
11399 player->dir_waiting = player->MovDir;
11400 player->action_waiting = ACTION_DEFAULT;
11402 player->special_action_bored = ACTION_DEFAULT;
11403 player->special_action_sleeping = ACTION_DEFAULT;
11407 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11409 if ((!player->is_moving && player->was_moving) ||
11410 (player->MovPos == 0 && player->was_moving) ||
11411 (player->is_snapping && !player->was_snapping) ||
11412 (player->is_dropping && !player->was_dropping))
11414 if (!CheckSaveEngineSnapshotToList())
11417 player->was_moving = FALSE;
11418 player->was_snapping = TRUE;
11419 player->was_dropping = TRUE;
11423 if (player->is_moving)
11424 player->was_moving = TRUE;
11426 if (!player->is_snapping)
11427 player->was_snapping = FALSE;
11429 if (!player->is_dropping)
11430 player->was_dropping = FALSE;
11433 static struct MouseActionInfo mouse_action_last = { 0 };
11434 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11435 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11438 CheckSaveEngineSnapshotToList();
11440 mouse_action_last = mouse_action;
11443 static void CheckSingleStepMode(struct PlayerInfo *player)
11445 if (tape.single_step && tape.recording && !tape.pausing)
11447 // as it is called "single step mode", just return to pause mode when the
11448 // player stopped moving after one tile (or never starts moving at all)
11449 // (reverse logic needed here in case single step mode used in team mode)
11450 if (player->is_moving ||
11451 player->is_pushing ||
11452 player->is_dropping_pressed ||
11453 player->effective_mouse_action.button)
11454 game.enter_single_step_mode = FALSE;
11457 CheckSaveEngineSnapshot(player);
11460 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11462 int left = player_action & JOY_LEFT;
11463 int right = player_action & JOY_RIGHT;
11464 int up = player_action & JOY_UP;
11465 int down = player_action & JOY_DOWN;
11466 int button1 = player_action & JOY_BUTTON_1;
11467 int button2 = player_action & JOY_BUTTON_2;
11468 int dx = (left ? -1 : right ? 1 : 0);
11469 int dy = (up ? -1 : down ? 1 : 0);
11471 if (!player->active || tape.pausing)
11477 SnapField(player, dx, dy);
11481 DropElement(player);
11483 MovePlayer(player, dx, dy);
11486 CheckSingleStepMode(player);
11488 SetPlayerWaiting(player, FALSE);
11490 return player_action;
11494 // no actions for this player (no input at player's configured device)
11496 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11497 SnapField(player, 0, 0);
11498 CheckGravityMovementWhenNotMoving(player);
11500 if (player->MovPos == 0)
11501 SetPlayerWaiting(player, TRUE);
11503 if (player->MovPos == 0) // needed for tape.playing
11504 player->is_moving = FALSE;
11506 player->is_dropping = FALSE;
11507 player->is_dropping_pressed = FALSE;
11508 player->drop_pressed_delay = 0;
11510 CheckSingleStepMode(player);
11516 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11519 if (!tape.use_mouse_actions)
11522 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11523 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11524 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11527 static void SetTapeActionFromMouseAction(byte *tape_action,
11528 struct MouseActionInfo *mouse_action)
11530 if (!tape.use_mouse_actions)
11533 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11534 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11535 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11538 static void CheckLevelSolved(void)
11540 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11542 if (game_em.level_solved &&
11543 !game_em.game_over) // game won
11547 game_em.game_over = TRUE;
11549 game.all_players_gone = TRUE;
11552 if (game_em.game_over) // game lost
11553 game.all_players_gone = TRUE;
11555 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11557 if (game_sp.level_solved &&
11558 !game_sp.game_over) // game won
11562 game_sp.game_over = TRUE;
11564 game.all_players_gone = TRUE;
11567 if (game_sp.game_over) // game lost
11568 game.all_players_gone = TRUE;
11570 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11572 if (game_mm.level_solved &&
11573 !game_mm.game_over) // game won
11577 game_mm.game_over = TRUE;
11579 game.all_players_gone = TRUE;
11582 if (game_mm.game_over) // game lost
11583 game.all_players_gone = TRUE;
11587 static void CheckLevelTime(void)
11591 if (TimeFrames >= FRAMES_PER_SECOND)
11596 for (i = 0; i < MAX_PLAYERS; i++)
11598 struct PlayerInfo *player = &stored_player[i];
11600 if (SHIELD_ON(player))
11602 player->shield_normal_time_left--;
11604 if (player->shield_deadly_time_left > 0)
11605 player->shield_deadly_time_left--;
11609 if (!game.LevelSolved && !level.use_step_counter)
11617 if (TimeLeft <= 10 && setup.time_limit)
11618 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11620 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11621 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11623 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11625 if (!TimeLeft && setup.time_limit)
11627 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11628 game_em.lev->killed_out_of_time = TRUE;
11630 for (i = 0; i < MAX_PLAYERS; i++)
11631 KillPlayer(&stored_player[i]);
11634 else if (game.no_time_limit && !game.all_players_gone)
11636 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11639 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11642 if (tape.recording || tape.playing)
11643 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11646 if (tape.recording || tape.playing)
11647 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11649 UpdateAndDisplayGameControlValues();
11652 void AdvanceFrameAndPlayerCounters(int player_nr)
11656 // advance frame counters (global frame counter and time frame counter)
11660 // advance player counters (counters for move delay, move animation etc.)
11661 for (i = 0; i < MAX_PLAYERS; i++)
11663 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11664 int move_delay_value = stored_player[i].move_delay_value;
11665 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11667 if (!advance_player_counters) // not all players may be affected
11670 if (move_frames == 0) // less than one move per game frame
11672 int stepsize = TILEX / move_delay_value;
11673 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11674 int count = (stored_player[i].is_moving ?
11675 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11677 if (count % delay == 0)
11681 stored_player[i].Frame += move_frames;
11683 if (stored_player[i].MovPos != 0)
11684 stored_player[i].StepFrame += move_frames;
11686 if (stored_player[i].move_delay > 0)
11687 stored_player[i].move_delay--;
11689 // due to bugs in previous versions, counter must count up, not down
11690 if (stored_player[i].push_delay != -1)
11691 stored_player[i].push_delay++;
11693 if (stored_player[i].drop_delay > 0)
11694 stored_player[i].drop_delay--;
11696 if (stored_player[i].is_dropping_pressed)
11697 stored_player[i].drop_pressed_delay++;
11701 void StartGameActions(boolean init_network_game, boolean record_tape,
11704 unsigned int new_random_seed = InitRND(random_seed);
11707 TapeStartRecording(new_random_seed);
11709 if (init_network_game)
11711 SendToServer_LevelFile();
11712 SendToServer_StartPlaying();
11720 static void GameActionsExt(void)
11723 static unsigned int game_frame_delay = 0;
11725 unsigned int game_frame_delay_value;
11726 byte *recorded_player_action;
11727 byte summarized_player_action = 0;
11728 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11731 // detect endless loops, caused by custom element programming
11732 if (recursion_loop_detected && recursion_loop_depth == 0)
11734 char *message = getStringCat3("Internal Error! Element ",
11735 EL_NAME(recursion_loop_element),
11736 " caused endless loop! Quit the game?");
11738 Warn("element '%s' caused endless loop in game engine",
11739 EL_NAME(recursion_loop_element));
11741 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11743 recursion_loop_detected = FALSE; // if game should be continued
11750 if (game.restart_level)
11751 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11753 CheckLevelSolved();
11755 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11758 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11761 if (game_status != GAME_MODE_PLAYING) // status might have changed
11764 game_frame_delay_value =
11765 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11767 if (tape.playing && tape.warp_forward && !tape.pausing)
11768 game_frame_delay_value = 0;
11770 SetVideoFrameDelay(game_frame_delay_value);
11772 // (de)activate virtual buttons depending on current game status
11773 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11775 if (game.all_players_gone) // if no players there to be controlled anymore
11776 SetOverlayActive(FALSE);
11777 else if (!tape.playing) // if game continues after tape stopped playing
11778 SetOverlayActive(TRUE);
11783 // ---------- main game synchronization point ----------
11785 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11787 Debug("game:playing:skip", "skip == %d", skip);
11790 // ---------- main game synchronization point ----------
11792 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11796 if (network_playing && !network_player_action_received)
11798 // try to get network player actions in time
11800 // last chance to get network player actions without main loop delay
11801 HandleNetworking();
11803 // game was quit by network peer
11804 if (game_status != GAME_MODE_PLAYING)
11807 // check if network player actions still missing and game still running
11808 if (!network_player_action_received && !checkGameEnded())
11809 return; // failed to get network player actions in time
11811 // do not yet reset "network_player_action_received" (for tape.pausing)
11817 // at this point we know that we really continue executing the game
11819 network_player_action_received = FALSE;
11821 // when playing tape, read previously recorded player input from tape data
11822 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11824 local_player->effective_mouse_action = local_player->mouse_action;
11826 if (recorded_player_action != NULL)
11827 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11828 recorded_player_action);
11830 // TapePlayAction() may return NULL when toggling to "pause before death"
11834 if (tape.set_centered_player)
11836 game.centered_player_nr_next = tape.centered_player_nr_next;
11837 game.set_centered_player = TRUE;
11840 for (i = 0; i < MAX_PLAYERS; i++)
11842 summarized_player_action |= stored_player[i].action;
11844 if (!network_playing && (game.team_mode || tape.playing))
11845 stored_player[i].effective_action = stored_player[i].action;
11848 if (network_playing && !checkGameEnded())
11849 SendToServer_MovePlayer(summarized_player_action);
11851 // summarize all actions at local players mapped input device position
11852 // (this allows using different input devices in single player mode)
11853 if (!network.enabled && !game.team_mode)
11854 stored_player[map_player_action[local_player->index_nr]].effective_action =
11855 summarized_player_action;
11857 // summarize all actions at centered player in local team mode
11858 if (tape.recording &&
11859 setup.team_mode && !network.enabled &&
11860 setup.input_on_focus &&
11861 game.centered_player_nr != -1)
11863 for (i = 0; i < MAX_PLAYERS; i++)
11864 stored_player[map_player_action[i]].effective_action =
11865 (i == game.centered_player_nr ? summarized_player_action : 0);
11868 if (recorded_player_action != NULL)
11869 for (i = 0; i < MAX_PLAYERS; i++)
11870 stored_player[i].effective_action = recorded_player_action[i];
11872 for (i = 0; i < MAX_PLAYERS; i++)
11874 tape_action[i] = stored_player[i].effective_action;
11876 /* (this may happen in the RND game engine if a player was not present on
11877 the playfield on level start, but appeared later from a custom element */
11878 if (setup.team_mode &&
11881 !tape.player_participates[i])
11882 tape.player_participates[i] = TRUE;
11885 SetTapeActionFromMouseAction(tape_action,
11886 &local_player->effective_mouse_action);
11888 // only record actions from input devices, but not programmed actions
11889 if (tape.recording)
11890 TapeRecordAction(tape_action);
11892 // remember if game was played (especially after tape stopped playing)
11893 if (!tape.playing && summarized_player_action)
11894 game.GamePlayed = TRUE;
11896 #if USE_NEW_PLAYER_ASSIGNMENTS
11897 // !!! also map player actions in single player mode !!!
11898 // if (game.team_mode)
11901 byte mapped_action[MAX_PLAYERS];
11903 #if DEBUG_PLAYER_ACTIONS
11904 for (i = 0; i < MAX_PLAYERS; i++)
11905 DebugContinued("", "%d, ", stored_player[i].effective_action);
11908 for (i = 0; i < MAX_PLAYERS; i++)
11909 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11911 for (i = 0; i < MAX_PLAYERS; i++)
11912 stored_player[i].effective_action = mapped_action[i];
11914 #if DEBUG_PLAYER_ACTIONS
11915 DebugContinued("", "=> ");
11916 for (i = 0; i < MAX_PLAYERS; i++)
11917 DebugContinued("", "%d, ", stored_player[i].effective_action);
11918 DebugContinued("game:playing:player", "\n");
11921 #if DEBUG_PLAYER_ACTIONS
11924 for (i = 0; i < MAX_PLAYERS; i++)
11925 DebugContinued("", "%d, ", stored_player[i].effective_action);
11926 DebugContinued("game:playing:player", "\n");
11931 for (i = 0; i < MAX_PLAYERS; i++)
11933 // allow engine snapshot in case of changed movement attempt
11934 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11935 (stored_player[i].effective_action & KEY_MOTION))
11936 game.snapshot.changed_action = TRUE;
11938 // allow engine snapshot in case of snapping/dropping attempt
11939 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11940 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11941 game.snapshot.changed_action = TRUE;
11943 game.snapshot.last_action[i] = stored_player[i].effective_action;
11946 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11948 GameActions_EM_Main();
11950 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11952 GameActions_SP_Main();
11954 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11956 GameActions_MM_Main();
11960 GameActions_RND_Main();
11963 BlitScreenToBitmap(backbuffer);
11965 CheckLevelSolved();
11968 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11970 if (global.show_frames_per_second)
11972 static unsigned int fps_counter = 0;
11973 static int fps_frames = 0;
11974 unsigned int fps_delay_ms = Counter() - fps_counter;
11978 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11980 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11983 fps_counter = Counter();
11985 // always draw FPS to screen after FPS value was updated
11986 redraw_mask |= REDRAW_FPS;
11989 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11990 if (GetDrawDeactivationMask() == REDRAW_NONE)
11991 redraw_mask |= REDRAW_FPS;
11995 static void GameActions_CheckSaveEngineSnapshot(void)
11997 if (!game.snapshot.save_snapshot)
12000 // clear flag for saving snapshot _before_ saving snapshot
12001 game.snapshot.save_snapshot = FALSE;
12003 SaveEngineSnapshotToList();
12006 void GameActions(void)
12010 GameActions_CheckSaveEngineSnapshot();
12013 void GameActions_EM_Main(void)
12015 byte effective_action[MAX_PLAYERS];
12016 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12019 for (i = 0; i < MAX_PLAYERS; i++)
12020 effective_action[i] = stored_player[i].effective_action;
12022 GameActions_EM(effective_action, warp_mode);
12025 void GameActions_SP_Main(void)
12027 byte effective_action[MAX_PLAYERS];
12028 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12031 for (i = 0; i < MAX_PLAYERS; i++)
12032 effective_action[i] = stored_player[i].effective_action;
12034 GameActions_SP(effective_action, warp_mode);
12036 for (i = 0; i < MAX_PLAYERS; i++)
12038 if (stored_player[i].force_dropping)
12039 stored_player[i].action |= KEY_BUTTON_DROP;
12041 stored_player[i].force_dropping = FALSE;
12045 void GameActions_MM_Main(void)
12047 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12049 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12052 void GameActions_RND_Main(void)
12057 void GameActions_RND(void)
12059 static struct MouseActionInfo mouse_action_last = { 0 };
12060 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12061 int magic_wall_x = 0, magic_wall_y = 0;
12062 int i, x, y, element, graphic, last_gfx_frame;
12064 InitPlayfieldScanModeVars();
12066 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12068 SCAN_PLAYFIELD(x, y)
12070 ChangeCount[x][y] = 0;
12071 ChangeEvent[x][y] = -1;
12075 if (game.set_centered_player)
12077 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12079 // switching to "all players" only possible if all players fit to screen
12080 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12082 game.centered_player_nr_next = game.centered_player_nr;
12083 game.set_centered_player = FALSE;
12086 // do not switch focus to non-existing (or non-active) player
12087 if (game.centered_player_nr_next >= 0 &&
12088 !stored_player[game.centered_player_nr_next].active)
12090 game.centered_player_nr_next = game.centered_player_nr;
12091 game.set_centered_player = FALSE;
12095 if (game.set_centered_player &&
12096 ScreenMovPos == 0) // screen currently aligned at tile position
12100 if (game.centered_player_nr_next == -1)
12102 setScreenCenteredToAllPlayers(&sx, &sy);
12106 sx = stored_player[game.centered_player_nr_next].jx;
12107 sy = stored_player[game.centered_player_nr_next].jy;
12110 game.centered_player_nr = game.centered_player_nr_next;
12111 game.set_centered_player = FALSE;
12113 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12114 DrawGameDoorValues();
12117 // check single step mode (set flag and clear again if any player is active)
12118 game.enter_single_step_mode =
12119 (tape.single_step && tape.recording && !tape.pausing);
12121 for (i = 0; i < MAX_PLAYERS; i++)
12123 int actual_player_action = stored_player[i].effective_action;
12126 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12127 - rnd_equinox_tetrachloride 048
12128 - rnd_equinox_tetrachloride_ii 096
12129 - rnd_emanuel_schmieg 002
12130 - doctor_sloan_ww 001, 020
12132 if (stored_player[i].MovPos == 0)
12133 CheckGravityMovement(&stored_player[i]);
12136 // overwrite programmed action with tape action
12137 if (stored_player[i].programmed_action)
12138 actual_player_action = stored_player[i].programmed_action;
12140 PlayerActions(&stored_player[i], actual_player_action);
12142 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12145 // single step pause mode may already have been toggled by "ScrollPlayer()"
12146 if (game.enter_single_step_mode && !tape.pausing)
12147 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12149 ScrollScreen(NULL, SCROLL_GO_ON);
12151 /* for backwards compatibility, the following code emulates a fixed bug that
12152 occured when pushing elements (causing elements that just made their last
12153 pushing step to already (if possible) make their first falling step in the
12154 same game frame, which is bad); this code is also needed to use the famous
12155 "spring push bug" which is used in older levels and might be wanted to be
12156 used also in newer levels, but in this case the buggy pushing code is only
12157 affecting the "spring" element and no other elements */
12159 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12161 for (i = 0; i < MAX_PLAYERS; i++)
12163 struct PlayerInfo *player = &stored_player[i];
12164 int x = player->jx;
12165 int y = player->jy;
12167 if (player->active && player->is_pushing && player->is_moving &&
12169 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12170 Tile[x][y] == EL_SPRING))
12172 ContinueMoving(x, y);
12174 // continue moving after pushing (this is actually a bug)
12175 if (!IS_MOVING(x, y))
12176 Stop[x][y] = FALSE;
12181 SCAN_PLAYFIELD(x, y)
12183 Last[x][y] = Tile[x][y];
12185 ChangeCount[x][y] = 0;
12186 ChangeEvent[x][y] = -1;
12188 // this must be handled before main playfield loop
12189 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12192 if (MovDelay[x][y] <= 0)
12196 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12199 if (MovDelay[x][y] <= 0)
12201 int element = Store[x][y];
12202 int move_direction = MovDir[x][y];
12203 int player_index_bit = Store2[x][y];
12209 TEST_DrawLevelField(x, y);
12211 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12213 if (IS_ENVELOPE(element))
12214 local_player->show_envelope = element;
12219 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12221 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12223 Debug("game:playing:GameActions_RND", "This should never happen!");
12225 ChangePage[x][y] = -1;
12229 Stop[x][y] = FALSE;
12230 if (WasJustMoving[x][y] > 0)
12231 WasJustMoving[x][y]--;
12232 if (WasJustFalling[x][y] > 0)
12233 WasJustFalling[x][y]--;
12234 if (CheckCollision[x][y] > 0)
12235 CheckCollision[x][y]--;
12236 if (CheckImpact[x][y] > 0)
12237 CheckImpact[x][y]--;
12241 /* reset finished pushing action (not done in ContinueMoving() to allow
12242 continuous pushing animation for elements with zero push delay) */
12243 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12245 ResetGfxAnimation(x, y);
12246 TEST_DrawLevelField(x, y);
12250 if (IS_BLOCKED(x, y))
12254 Blocked2Moving(x, y, &oldx, &oldy);
12255 if (!IS_MOVING(oldx, oldy))
12257 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12258 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12259 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12260 Debug("game:playing:GameActions_RND", "This should never happen!");
12266 if (mouse_action.button)
12268 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12269 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12271 x = mouse_action.lx;
12272 y = mouse_action.ly;
12273 element = Tile[x][y];
12277 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12278 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12282 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12283 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12287 SCAN_PLAYFIELD(x, y)
12289 element = Tile[x][y];
12290 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12291 last_gfx_frame = GfxFrame[x][y];
12293 ResetGfxFrame(x, y);
12295 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12296 DrawLevelGraphicAnimation(x, y, graphic);
12298 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12299 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12300 ResetRandomAnimationValue(x, y);
12302 SetRandomAnimationValue(x, y);
12304 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12306 if (IS_INACTIVE(element))
12308 if (IS_ANIMATED(graphic))
12309 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12314 // this may take place after moving, so 'element' may have changed
12315 if (IS_CHANGING(x, y) &&
12316 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12318 int page = element_info[element].event_page_nr[CE_DELAY];
12320 HandleElementChange(x, y, page);
12322 element = Tile[x][y];
12323 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12326 CheckNextToConditions(x, y);
12328 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12332 element = Tile[x][y];
12333 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12335 if (IS_ANIMATED(graphic) &&
12336 !IS_MOVING(x, y) &&
12338 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12340 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12341 TEST_DrawTwinkleOnField(x, y);
12343 else if (element == EL_ACID)
12346 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12348 else if ((element == EL_EXIT_OPEN ||
12349 element == EL_EM_EXIT_OPEN ||
12350 element == EL_SP_EXIT_OPEN ||
12351 element == EL_STEEL_EXIT_OPEN ||
12352 element == EL_EM_STEEL_EXIT_OPEN ||
12353 element == EL_SP_TERMINAL ||
12354 element == EL_SP_TERMINAL_ACTIVE ||
12355 element == EL_EXTRA_TIME ||
12356 element == EL_SHIELD_NORMAL ||
12357 element == EL_SHIELD_DEADLY) &&
12358 IS_ANIMATED(graphic))
12359 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12360 else if (IS_MOVING(x, y))
12361 ContinueMoving(x, y);
12362 else if (IS_ACTIVE_BOMB(element))
12363 CheckDynamite(x, y);
12364 else if (element == EL_AMOEBA_GROWING)
12365 AmoebaGrowing(x, y);
12366 else if (element == EL_AMOEBA_SHRINKING)
12367 AmoebaShrinking(x, y);
12369 #if !USE_NEW_AMOEBA_CODE
12370 else if (IS_AMOEBALIVE(element))
12371 AmoebaReproduce(x, y);
12374 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12376 else if (element == EL_EXIT_CLOSED)
12378 else if (element == EL_EM_EXIT_CLOSED)
12380 else if (element == EL_STEEL_EXIT_CLOSED)
12381 CheckExitSteel(x, y);
12382 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12383 CheckExitSteelEM(x, y);
12384 else if (element == EL_SP_EXIT_CLOSED)
12386 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12387 element == EL_EXPANDABLE_STEELWALL_GROWING)
12388 MauerWaechst(x, y);
12389 else if (element == EL_EXPANDABLE_WALL ||
12390 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12391 element == EL_EXPANDABLE_WALL_VERTICAL ||
12392 element == EL_EXPANDABLE_WALL_ANY ||
12393 element == EL_BD_EXPANDABLE_WALL)
12394 MauerAbleger(x, y);
12395 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12396 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12397 element == EL_EXPANDABLE_STEELWALL_ANY)
12398 MauerAblegerStahl(x, y);
12399 else if (element == EL_FLAMES)
12400 CheckForDragon(x, y);
12401 else if (element == EL_EXPLOSION)
12402 ; // drawing of correct explosion animation is handled separately
12403 else if (element == EL_ELEMENT_SNAPPING ||
12404 element == EL_DIAGONAL_SHRINKING ||
12405 element == EL_DIAGONAL_GROWING)
12407 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12409 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12411 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12412 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12414 if (IS_BELT_ACTIVE(element))
12415 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12417 if (game.magic_wall_active)
12419 int jx = local_player->jx, jy = local_player->jy;
12421 // play the element sound at the position nearest to the player
12422 if ((element == EL_MAGIC_WALL_FULL ||
12423 element == EL_MAGIC_WALL_ACTIVE ||
12424 element == EL_MAGIC_WALL_EMPTYING ||
12425 element == EL_BD_MAGIC_WALL_FULL ||
12426 element == EL_BD_MAGIC_WALL_ACTIVE ||
12427 element == EL_BD_MAGIC_WALL_EMPTYING ||
12428 element == EL_DC_MAGIC_WALL_FULL ||
12429 element == EL_DC_MAGIC_WALL_ACTIVE ||
12430 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12431 ABS(x - jx) + ABS(y - jy) <
12432 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12440 #if USE_NEW_AMOEBA_CODE
12441 // new experimental amoeba growth stuff
12442 if (!(FrameCounter % 8))
12444 static unsigned int random = 1684108901;
12446 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12448 x = RND(lev_fieldx);
12449 y = RND(lev_fieldy);
12450 element = Tile[x][y];
12452 if (!IS_PLAYER(x,y) &&
12453 (element == EL_EMPTY ||
12454 CAN_GROW_INTO(element) ||
12455 element == EL_QUICKSAND_EMPTY ||
12456 element == EL_QUICKSAND_FAST_EMPTY ||
12457 element == EL_ACID_SPLASH_LEFT ||
12458 element == EL_ACID_SPLASH_RIGHT))
12460 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12461 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12462 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12463 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12464 Tile[x][y] = EL_AMOEBA_DROP;
12467 random = random * 129 + 1;
12472 game.explosions_delayed = FALSE;
12474 SCAN_PLAYFIELD(x, y)
12476 element = Tile[x][y];
12478 if (ExplodeField[x][y])
12479 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12480 else if (element == EL_EXPLOSION)
12481 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12483 ExplodeField[x][y] = EX_TYPE_NONE;
12486 game.explosions_delayed = TRUE;
12488 if (game.magic_wall_active)
12490 if (!(game.magic_wall_time_left % 4))
12492 int element = Tile[magic_wall_x][magic_wall_y];
12494 if (element == EL_BD_MAGIC_WALL_FULL ||
12495 element == EL_BD_MAGIC_WALL_ACTIVE ||
12496 element == EL_BD_MAGIC_WALL_EMPTYING)
12497 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12498 else if (element == EL_DC_MAGIC_WALL_FULL ||
12499 element == EL_DC_MAGIC_WALL_ACTIVE ||
12500 element == EL_DC_MAGIC_WALL_EMPTYING)
12501 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12503 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12506 if (game.magic_wall_time_left > 0)
12508 game.magic_wall_time_left--;
12510 if (!game.magic_wall_time_left)
12512 SCAN_PLAYFIELD(x, y)
12514 element = Tile[x][y];
12516 if (element == EL_MAGIC_WALL_ACTIVE ||
12517 element == EL_MAGIC_WALL_FULL)
12519 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12520 TEST_DrawLevelField(x, y);
12522 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12523 element == EL_BD_MAGIC_WALL_FULL)
12525 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12526 TEST_DrawLevelField(x, y);
12528 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12529 element == EL_DC_MAGIC_WALL_FULL)
12531 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12532 TEST_DrawLevelField(x, y);
12536 game.magic_wall_active = FALSE;
12541 if (game.light_time_left > 0)
12543 game.light_time_left--;
12545 if (game.light_time_left == 0)
12546 RedrawAllLightSwitchesAndInvisibleElements();
12549 if (game.timegate_time_left > 0)
12551 game.timegate_time_left--;
12553 if (game.timegate_time_left == 0)
12554 CloseAllOpenTimegates();
12557 if (game.lenses_time_left > 0)
12559 game.lenses_time_left--;
12561 if (game.lenses_time_left == 0)
12562 RedrawAllInvisibleElementsForLenses();
12565 if (game.magnify_time_left > 0)
12567 game.magnify_time_left--;
12569 if (game.magnify_time_left == 0)
12570 RedrawAllInvisibleElementsForMagnifier();
12573 for (i = 0; i < MAX_PLAYERS; i++)
12575 struct PlayerInfo *player = &stored_player[i];
12577 if (SHIELD_ON(player))
12579 if (player->shield_deadly_time_left)
12580 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12581 else if (player->shield_normal_time_left)
12582 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12586 #if USE_DELAYED_GFX_REDRAW
12587 SCAN_PLAYFIELD(x, y)
12589 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12591 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12592 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12594 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12595 DrawLevelField(x, y);
12597 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12598 DrawLevelFieldCrumbled(x, y);
12600 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12601 DrawLevelFieldCrumbledNeighbours(x, y);
12603 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12604 DrawTwinkleOnField(x, y);
12607 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12612 PlayAllPlayersSound();
12614 for (i = 0; i < MAX_PLAYERS; i++)
12616 struct PlayerInfo *player = &stored_player[i];
12618 if (player->show_envelope != 0 && (!player->active ||
12619 player->MovPos == 0))
12621 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12623 player->show_envelope = 0;
12627 // use random number generator in every frame to make it less predictable
12628 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12631 mouse_action_last = mouse_action;
12634 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12636 int min_x = x, min_y = y, max_x = x, max_y = y;
12637 int scr_fieldx = getScreenFieldSizeX();
12638 int scr_fieldy = getScreenFieldSizeY();
12641 for (i = 0; i < MAX_PLAYERS; i++)
12643 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12645 if (!stored_player[i].active || &stored_player[i] == player)
12648 min_x = MIN(min_x, jx);
12649 min_y = MIN(min_y, jy);
12650 max_x = MAX(max_x, jx);
12651 max_y = MAX(max_y, jy);
12654 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12657 static boolean AllPlayersInVisibleScreen(void)
12661 for (i = 0; i < MAX_PLAYERS; i++)
12663 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12665 if (!stored_player[i].active)
12668 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12675 void ScrollLevel(int dx, int dy)
12677 int scroll_offset = 2 * TILEX_VAR;
12680 BlitBitmap(drawto_field, drawto_field,
12681 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12682 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12683 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12684 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12685 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12686 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12690 x = (dx == 1 ? BX1 : BX2);
12691 for (y = BY1; y <= BY2; y++)
12692 DrawScreenField(x, y);
12697 y = (dy == 1 ? BY1 : BY2);
12698 for (x = BX1; x <= BX2; x++)
12699 DrawScreenField(x, y);
12702 redraw_mask |= REDRAW_FIELD;
12705 static boolean canFallDown(struct PlayerInfo *player)
12707 int jx = player->jx, jy = player->jy;
12709 return (IN_LEV_FIELD(jx, jy + 1) &&
12710 (IS_FREE(jx, jy + 1) ||
12711 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12712 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12713 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12716 static boolean canPassField(int x, int y, int move_dir)
12718 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12719 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12720 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12721 int nextx = x + dx;
12722 int nexty = y + dy;
12723 int element = Tile[x][y];
12725 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12726 !CAN_MOVE(element) &&
12727 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12728 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12729 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12732 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12734 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12735 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12736 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12740 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12741 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12742 (IS_DIGGABLE(Tile[newx][newy]) ||
12743 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12744 canPassField(newx, newy, move_dir)));
12747 static void CheckGravityMovement(struct PlayerInfo *player)
12749 if (player->gravity && !player->programmed_action)
12751 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12752 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12753 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12754 int jx = player->jx, jy = player->jy;
12755 boolean player_is_moving_to_valid_field =
12756 (!player_is_snapping &&
12757 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12758 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12759 boolean player_can_fall_down = canFallDown(player);
12761 if (player_can_fall_down &&
12762 !player_is_moving_to_valid_field)
12763 player->programmed_action = MV_DOWN;
12767 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12769 return CheckGravityMovement(player);
12771 if (player->gravity && !player->programmed_action)
12773 int jx = player->jx, jy = player->jy;
12774 boolean field_under_player_is_free =
12775 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12776 boolean player_is_standing_on_valid_field =
12777 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12778 (IS_WALKABLE(Tile[jx][jy]) &&
12779 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12781 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12782 player->programmed_action = MV_DOWN;
12787 MovePlayerOneStep()
12788 -----------------------------------------------------------------------------
12789 dx, dy: direction (non-diagonal) to try to move the player to
12790 real_dx, real_dy: direction as read from input device (can be diagonal)
12793 boolean MovePlayerOneStep(struct PlayerInfo *player,
12794 int dx, int dy, int real_dx, int real_dy)
12796 int jx = player->jx, jy = player->jy;
12797 int new_jx = jx + dx, new_jy = jy + dy;
12799 boolean player_can_move = !player->cannot_move;
12801 if (!player->active || (!dx && !dy))
12802 return MP_NO_ACTION;
12804 player->MovDir = (dx < 0 ? MV_LEFT :
12805 dx > 0 ? MV_RIGHT :
12807 dy > 0 ? MV_DOWN : MV_NONE);
12809 if (!IN_LEV_FIELD(new_jx, new_jy))
12810 return MP_NO_ACTION;
12812 if (!player_can_move)
12814 if (player->MovPos == 0)
12816 player->is_moving = FALSE;
12817 player->is_digging = FALSE;
12818 player->is_collecting = FALSE;
12819 player->is_snapping = FALSE;
12820 player->is_pushing = FALSE;
12824 if (!network.enabled && game.centered_player_nr == -1 &&
12825 !AllPlayersInSight(player, new_jx, new_jy))
12826 return MP_NO_ACTION;
12828 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12829 if (can_move != MP_MOVING)
12832 // check if DigField() has caused relocation of the player
12833 if (player->jx != jx || player->jy != jy)
12834 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12836 StorePlayer[jx][jy] = 0;
12837 player->last_jx = jx;
12838 player->last_jy = jy;
12839 player->jx = new_jx;
12840 player->jy = new_jy;
12841 StorePlayer[new_jx][new_jy] = player->element_nr;
12843 if (player->move_delay_value_next != -1)
12845 player->move_delay_value = player->move_delay_value_next;
12846 player->move_delay_value_next = -1;
12850 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12852 player->step_counter++;
12854 PlayerVisit[jx][jy] = FrameCounter;
12856 player->is_moving = TRUE;
12859 // should better be called in MovePlayer(), but this breaks some tapes
12860 ScrollPlayer(player, SCROLL_INIT);
12866 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12868 int jx = player->jx, jy = player->jy;
12869 int old_jx = jx, old_jy = jy;
12870 int moved = MP_NO_ACTION;
12872 if (!player->active)
12877 if (player->MovPos == 0)
12879 player->is_moving = FALSE;
12880 player->is_digging = FALSE;
12881 player->is_collecting = FALSE;
12882 player->is_snapping = FALSE;
12883 player->is_pushing = FALSE;
12889 if (player->move_delay > 0)
12892 player->move_delay = -1; // set to "uninitialized" value
12894 // store if player is automatically moved to next field
12895 player->is_auto_moving = (player->programmed_action != MV_NONE);
12897 // remove the last programmed player action
12898 player->programmed_action = 0;
12900 if (player->MovPos)
12902 // should only happen if pre-1.2 tape recordings are played
12903 // this is only for backward compatibility
12905 int original_move_delay_value = player->move_delay_value;
12908 Debug("game:playing:MovePlayer",
12909 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12913 // scroll remaining steps with finest movement resolution
12914 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12916 while (player->MovPos)
12918 ScrollPlayer(player, SCROLL_GO_ON);
12919 ScrollScreen(NULL, SCROLL_GO_ON);
12921 AdvanceFrameAndPlayerCounters(player->index_nr);
12924 BackToFront_WithFrameDelay(0);
12927 player->move_delay_value = original_move_delay_value;
12930 player->is_active = FALSE;
12932 if (player->last_move_dir & MV_HORIZONTAL)
12934 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12935 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12939 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12940 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12943 if (!moved && !player->is_active)
12945 player->is_moving = FALSE;
12946 player->is_digging = FALSE;
12947 player->is_collecting = FALSE;
12948 player->is_snapping = FALSE;
12949 player->is_pushing = FALSE;
12955 if (moved & MP_MOVING && !ScreenMovPos &&
12956 (player->index_nr == game.centered_player_nr ||
12957 game.centered_player_nr == -1))
12959 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12961 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12963 // actual player has left the screen -- scroll in that direction
12964 if (jx != old_jx) // player has moved horizontally
12965 scroll_x += (jx - old_jx);
12966 else // player has moved vertically
12967 scroll_y += (jy - old_jy);
12971 int offset_raw = game.scroll_delay_value;
12973 if (jx != old_jx) // player has moved horizontally
12975 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12976 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12977 int new_scroll_x = jx - MIDPOSX + offset_x;
12979 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12980 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12981 scroll_x = new_scroll_x;
12983 // don't scroll over playfield boundaries
12984 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12986 // don't scroll more than one field at a time
12987 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12989 // don't scroll against the player's moving direction
12990 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12991 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12992 scroll_x = old_scroll_x;
12994 else // player has moved vertically
12996 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12997 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12998 int new_scroll_y = jy - MIDPOSY + offset_y;
13000 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13001 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13002 scroll_y = new_scroll_y;
13004 // don't scroll over playfield boundaries
13005 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13007 // don't scroll more than one field at a time
13008 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13010 // don't scroll against the player's moving direction
13011 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13012 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13013 scroll_y = old_scroll_y;
13017 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13019 if (!network.enabled && game.centered_player_nr == -1 &&
13020 !AllPlayersInVisibleScreen())
13022 scroll_x = old_scroll_x;
13023 scroll_y = old_scroll_y;
13027 ScrollScreen(player, SCROLL_INIT);
13028 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13033 player->StepFrame = 0;
13035 if (moved & MP_MOVING)
13037 if (old_jx != jx && old_jy == jy)
13038 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13039 else if (old_jx == jx && old_jy != jy)
13040 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13042 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13044 player->last_move_dir = player->MovDir;
13045 player->is_moving = TRUE;
13046 player->is_snapping = FALSE;
13047 player->is_switching = FALSE;
13048 player->is_dropping = FALSE;
13049 player->is_dropping_pressed = FALSE;
13050 player->drop_pressed_delay = 0;
13053 // should better be called here than above, but this breaks some tapes
13054 ScrollPlayer(player, SCROLL_INIT);
13059 CheckGravityMovementWhenNotMoving(player);
13061 player->is_moving = FALSE;
13063 /* at this point, the player is allowed to move, but cannot move right now
13064 (e.g. because of something blocking the way) -- ensure that the player
13065 is also allowed to move in the next frame (in old versions before 3.1.1,
13066 the player was forced to wait again for eight frames before next try) */
13068 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13069 player->move_delay = 0; // allow direct movement in the next frame
13072 if (player->move_delay == -1) // not yet initialized by DigField()
13073 player->move_delay = player->move_delay_value;
13075 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13077 TestIfPlayerTouchesBadThing(jx, jy);
13078 TestIfPlayerTouchesCustomElement(jx, jy);
13081 if (!player->active)
13082 RemovePlayer(player);
13087 void ScrollPlayer(struct PlayerInfo *player, int mode)
13089 int jx = player->jx, jy = player->jy;
13090 int last_jx = player->last_jx, last_jy = player->last_jy;
13091 int move_stepsize = TILEX / player->move_delay_value;
13093 if (!player->active)
13096 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13099 if (mode == SCROLL_INIT)
13101 player->actual_frame_counter = FrameCounter;
13102 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13104 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13105 Tile[last_jx][last_jy] == EL_EMPTY)
13107 int last_field_block_delay = 0; // start with no blocking at all
13108 int block_delay_adjustment = player->block_delay_adjustment;
13110 // if player blocks last field, add delay for exactly one move
13111 if (player->block_last_field)
13113 last_field_block_delay += player->move_delay_value;
13115 // when blocking enabled, prevent moving up despite gravity
13116 if (player->gravity && player->MovDir == MV_UP)
13117 block_delay_adjustment = -1;
13120 // add block delay adjustment (also possible when not blocking)
13121 last_field_block_delay += block_delay_adjustment;
13123 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13124 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13127 if (player->MovPos != 0) // player has not yet reached destination
13130 else if (!FrameReached(&player->actual_frame_counter, 1))
13133 if (player->MovPos != 0)
13135 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13136 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13138 // before DrawPlayer() to draw correct player graphic for this case
13139 if (player->MovPos == 0)
13140 CheckGravityMovement(player);
13143 if (player->MovPos == 0) // player reached destination field
13145 if (player->move_delay_reset_counter > 0)
13147 player->move_delay_reset_counter--;
13149 if (player->move_delay_reset_counter == 0)
13151 // continue with normal speed after quickly moving through gate
13152 HALVE_PLAYER_SPEED(player);
13154 // be able to make the next move without delay
13155 player->move_delay = 0;
13159 player->last_jx = jx;
13160 player->last_jy = jy;
13162 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13163 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13164 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13165 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13166 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13167 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13168 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13169 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13171 ExitPlayer(player);
13173 if (game.players_still_needed == 0 &&
13174 (game.friends_still_needed == 0 ||
13175 IS_SP_ELEMENT(Tile[jx][jy])))
13179 // this breaks one level: "machine", level 000
13181 int move_direction = player->MovDir;
13182 int enter_side = MV_DIR_OPPOSITE(move_direction);
13183 int leave_side = move_direction;
13184 int old_jx = last_jx;
13185 int old_jy = last_jy;
13186 int old_element = Tile[old_jx][old_jy];
13187 int new_element = Tile[jx][jy];
13189 if (IS_CUSTOM_ELEMENT(old_element))
13190 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13192 player->index_bit, leave_side);
13194 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13195 CE_PLAYER_LEAVES_X,
13196 player->index_bit, leave_side);
13198 if (IS_CUSTOM_ELEMENT(new_element))
13199 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13200 player->index_bit, enter_side);
13202 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13203 CE_PLAYER_ENTERS_X,
13204 player->index_bit, enter_side);
13206 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13207 CE_MOVE_OF_X, move_direction);
13210 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13212 TestIfPlayerTouchesBadThing(jx, jy);
13213 TestIfPlayerTouchesCustomElement(jx, jy);
13215 /* needed because pushed element has not yet reached its destination,
13216 so it would trigger a change event at its previous field location */
13217 if (!player->is_pushing)
13218 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13220 if (level.finish_dig_collect &&
13221 (player->is_digging || player->is_collecting))
13223 int last_element = player->last_removed_element;
13224 int move_direction = player->MovDir;
13225 int enter_side = MV_DIR_OPPOSITE(move_direction);
13226 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13227 CE_PLAYER_COLLECTS_X);
13229 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13230 player->index_bit, enter_side);
13232 player->last_removed_element = EL_UNDEFINED;
13235 if (!player->active)
13236 RemovePlayer(player);
13239 if (level.use_step_counter)
13249 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13250 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13252 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13254 DisplayGameControlValues();
13256 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13257 for (i = 0; i < MAX_PLAYERS; i++)
13258 KillPlayer(&stored_player[i]);
13260 else if (game.no_time_limit && !game.all_players_gone)
13262 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13264 DisplayGameControlValues();
13268 if (tape.single_step && tape.recording && !tape.pausing &&
13269 !player->programmed_action)
13270 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13272 if (!player->programmed_action)
13273 CheckSaveEngineSnapshot(player);
13277 void ScrollScreen(struct PlayerInfo *player, int mode)
13279 static unsigned int screen_frame_counter = 0;
13281 if (mode == SCROLL_INIT)
13283 // set scrolling step size according to actual player's moving speed
13284 ScrollStepSize = TILEX / player->move_delay_value;
13286 screen_frame_counter = FrameCounter;
13287 ScreenMovDir = player->MovDir;
13288 ScreenMovPos = player->MovPos;
13289 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13292 else if (!FrameReached(&screen_frame_counter, 1))
13297 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13298 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13299 redraw_mask |= REDRAW_FIELD;
13302 ScreenMovDir = MV_NONE;
13305 void CheckNextToConditions(int x, int y)
13307 int element = Tile[x][y];
13309 if (IS_PLAYER(x, y))
13310 TestIfPlayerNextToCustomElement(x, y);
13312 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13313 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13314 TestIfElementNextToCustomElement(x, y);
13317 void TestIfPlayerNextToCustomElement(int x, int y)
13319 static int xy[4][2] =
13326 static int trigger_sides[4][2] =
13328 // center side border side
13329 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13330 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13331 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13332 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13336 if (!IS_PLAYER(x, y))
13339 struct PlayerInfo *player = PLAYERINFO(x, y);
13341 if (player->is_moving)
13344 for (i = 0; i < NUM_DIRECTIONS; i++)
13346 int xx = x + xy[i][0];
13347 int yy = y + xy[i][1];
13348 int border_side = trigger_sides[i][1];
13349 int border_element;
13351 if (!IN_LEV_FIELD(xx, yy))
13354 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13355 continue; // center and border element not connected
13357 border_element = Tile[xx][yy];
13359 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13360 player->index_bit, border_side);
13361 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13362 CE_PLAYER_NEXT_TO_X,
13363 player->index_bit, border_side);
13365 /* use player element that is initially defined in the level playfield,
13366 not the player element that corresponds to the runtime player number
13367 (example: a level that contains EL_PLAYER_3 as the only player would
13368 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13370 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13371 CE_NEXT_TO_X, border_side);
13375 void TestIfPlayerTouchesCustomElement(int x, int y)
13377 static int xy[4][2] =
13384 static int trigger_sides[4][2] =
13386 // center side border side
13387 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13388 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13389 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13390 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13392 static int touch_dir[4] =
13394 MV_LEFT | MV_RIGHT,
13399 int center_element = Tile[x][y]; // should always be non-moving!
13402 for (i = 0; i < NUM_DIRECTIONS; i++)
13404 int xx = x + xy[i][0];
13405 int yy = y + xy[i][1];
13406 int center_side = trigger_sides[i][0];
13407 int border_side = trigger_sides[i][1];
13408 int border_element;
13410 if (!IN_LEV_FIELD(xx, yy))
13413 if (IS_PLAYER(x, y)) // player found at center element
13415 struct PlayerInfo *player = PLAYERINFO(x, y);
13417 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13418 border_element = Tile[xx][yy]; // may be moving!
13419 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13420 border_element = Tile[xx][yy];
13421 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13422 border_element = MovingOrBlocked2Element(xx, yy);
13424 continue; // center and border element do not touch
13426 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13427 player->index_bit, border_side);
13428 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13429 CE_PLAYER_TOUCHES_X,
13430 player->index_bit, border_side);
13433 /* use player element that is initially defined in the level playfield,
13434 not the player element that corresponds to the runtime player number
13435 (example: a level that contains EL_PLAYER_3 as the only player would
13436 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13437 int player_element = PLAYERINFO(x, y)->initial_element;
13439 CheckElementChangeBySide(xx, yy, border_element, player_element,
13440 CE_TOUCHING_X, border_side);
13443 else if (IS_PLAYER(xx, yy)) // player found at border element
13445 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13447 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13449 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13450 continue; // center and border element do not touch
13453 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13454 player->index_bit, center_side);
13455 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13456 CE_PLAYER_TOUCHES_X,
13457 player->index_bit, center_side);
13460 /* use player element that is initially defined in the level playfield,
13461 not the player element that corresponds to the runtime player number
13462 (example: a level that contains EL_PLAYER_3 as the only player would
13463 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13464 int player_element = PLAYERINFO(xx, yy)->initial_element;
13466 CheckElementChangeBySide(x, y, center_element, player_element,
13467 CE_TOUCHING_X, center_side);
13475 void TestIfElementNextToCustomElement(int x, int y)
13477 static int xy[4][2] =
13484 static int trigger_sides[4][2] =
13486 // center side border side
13487 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13488 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13489 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13490 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13492 int center_element = Tile[x][y]; // should always be non-moving!
13495 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13498 for (i = 0; i < NUM_DIRECTIONS; i++)
13500 int xx = x + xy[i][0];
13501 int yy = y + xy[i][1];
13502 int border_side = trigger_sides[i][1];
13503 int border_element;
13505 if (!IN_LEV_FIELD(xx, yy))
13508 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13509 continue; // center and border element not connected
13511 border_element = Tile[xx][yy];
13513 // check for change of center element (but change it only once)
13514 if (CheckElementChangeBySide(x, y, center_element, border_element,
13515 CE_NEXT_TO_X, border_side))
13520 void TestIfElementTouchesCustomElement(int x, int y)
13522 static int xy[4][2] =
13529 static int trigger_sides[4][2] =
13531 // center side border side
13532 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13533 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13534 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13535 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13537 static int touch_dir[4] =
13539 MV_LEFT | MV_RIGHT,
13544 boolean change_center_element = FALSE;
13545 int center_element = Tile[x][y]; // should always be non-moving!
13546 int border_element_old[NUM_DIRECTIONS];
13549 for (i = 0; i < NUM_DIRECTIONS; i++)
13551 int xx = x + xy[i][0];
13552 int yy = y + xy[i][1];
13553 int border_element;
13555 border_element_old[i] = -1;
13557 if (!IN_LEV_FIELD(xx, yy))
13560 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13561 border_element = Tile[xx][yy]; // may be moving!
13562 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13563 border_element = Tile[xx][yy];
13564 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13565 border_element = MovingOrBlocked2Element(xx, yy);
13567 continue; // center and border element do not touch
13569 border_element_old[i] = border_element;
13572 for (i = 0; i < NUM_DIRECTIONS; i++)
13574 int xx = x + xy[i][0];
13575 int yy = y + xy[i][1];
13576 int center_side = trigger_sides[i][0];
13577 int border_element = border_element_old[i];
13579 if (border_element == -1)
13582 // check for change of border element
13583 CheckElementChangeBySide(xx, yy, border_element, center_element,
13584 CE_TOUCHING_X, center_side);
13586 // (center element cannot be player, so we dont have to check this here)
13589 for (i = 0; i < NUM_DIRECTIONS; i++)
13591 int xx = x + xy[i][0];
13592 int yy = y + xy[i][1];
13593 int border_side = trigger_sides[i][1];
13594 int border_element = border_element_old[i];
13596 if (border_element == -1)
13599 // check for change of center element (but change it only once)
13600 if (!change_center_element)
13601 change_center_element =
13602 CheckElementChangeBySide(x, y, center_element, border_element,
13603 CE_TOUCHING_X, border_side);
13605 if (IS_PLAYER(xx, yy))
13607 /* use player element that is initially defined in the level playfield,
13608 not the player element that corresponds to the runtime player number
13609 (example: a level that contains EL_PLAYER_3 as the only player would
13610 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13611 int player_element = PLAYERINFO(xx, yy)->initial_element;
13613 CheckElementChangeBySide(x, y, center_element, player_element,
13614 CE_TOUCHING_X, border_side);
13619 void TestIfElementHitsCustomElement(int x, int y, int direction)
13621 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13622 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13623 int hitx = x + dx, hity = y + dy;
13624 int hitting_element = Tile[x][y];
13625 int touched_element;
13627 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13630 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13631 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13633 if (IN_LEV_FIELD(hitx, hity))
13635 int opposite_direction = MV_DIR_OPPOSITE(direction);
13636 int hitting_side = direction;
13637 int touched_side = opposite_direction;
13638 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13639 MovDir[hitx][hity] != direction ||
13640 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13646 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13647 CE_HITTING_X, touched_side);
13649 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13650 CE_HIT_BY_X, hitting_side);
13652 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13653 CE_HIT_BY_SOMETHING, opposite_direction);
13655 if (IS_PLAYER(hitx, hity))
13657 /* use player element that is initially defined in the level playfield,
13658 not the player element that corresponds to the runtime player number
13659 (example: a level that contains EL_PLAYER_3 as the only player would
13660 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13661 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13663 CheckElementChangeBySide(x, y, hitting_element, player_element,
13664 CE_HITTING_X, touched_side);
13669 // "hitting something" is also true when hitting the playfield border
13670 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13671 CE_HITTING_SOMETHING, direction);
13674 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13676 int i, kill_x = -1, kill_y = -1;
13678 int bad_element = -1;
13679 static int test_xy[4][2] =
13686 static int test_dir[4] =
13694 for (i = 0; i < NUM_DIRECTIONS; i++)
13696 int test_x, test_y, test_move_dir, test_element;
13698 test_x = good_x + test_xy[i][0];
13699 test_y = good_y + test_xy[i][1];
13701 if (!IN_LEV_FIELD(test_x, test_y))
13705 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13707 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13709 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13710 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13712 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13713 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13717 bad_element = test_element;
13723 if (kill_x != -1 || kill_y != -1)
13725 if (IS_PLAYER(good_x, good_y))
13727 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13729 if (player->shield_deadly_time_left > 0 &&
13730 !IS_INDESTRUCTIBLE(bad_element))
13731 Bang(kill_x, kill_y);
13732 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13733 KillPlayer(player);
13736 Bang(good_x, good_y);
13740 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13742 int i, kill_x = -1, kill_y = -1;
13743 int bad_element = Tile[bad_x][bad_y];
13744 static int test_xy[4][2] =
13751 static int touch_dir[4] =
13753 MV_LEFT | MV_RIGHT,
13758 static int test_dir[4] =
13766 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13769 for (i = 0; i < NUM_DIRECTIONS; i++)
13771 int test_x, test_y, test_move_dir, test_element;
13773 test_x = bad_x + test_xy[i][0];
13774 test_y = bad_y + test_xy[i][1];
13776 if (!IN_LEV_FIELD(test_x, test_y))
13780 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13782 test_element = Tile[test_x][test_y];
13784 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13785 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13787 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13788 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13790 // good thing is player or penguin that does not move away
13791 if (IS_PLAYER(test_x, test_y))
13793 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13795 if (bad_element == EL_ROBOT && player->is_moving)
13796 continue; // robot does not kill player if he is moving
13798 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13800 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13801 continue; // center and border element do not touch
13809 else if (test_element == EL_PENGUIN)
13819 if (kill_x != -1 || kill_y != -1)
13821 if (IS_PLAYER(kill_x, kill_y))
13823 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13825 if (player->shield_deadly_time_left > 0 &&
13826 !IS_INDESTRUCTIBLE(bad_element))
13827 Bang(bad_x, bad_y);
13828 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13829 KillPlayer(player);
13832 Bang(kill_x, kill_y);
13836 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13838 int bad_element = Tile[bad_x][bad_y];
13839 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13840 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13841 int test_x = bad_x + dx, test_y = bad_y + dy;
13842 int test_move_dir, test_element;
13843 int kill_x = -1, kill_y = -1;
13845 if (!IN_LEV_FIELD(test_x, test_y))
13849 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13851 test_element = Tile[test_x][test_y];
13853 if (test_move_dir != bad_move_dir)
13855 // good thing can be player or penguin that does not move away
13856 if (IS_PLAYER(test_x, test_y))
13858 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13860 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13861 player as being hit when he is moving towards the bad thing, because
13862 the "get hit by" condition would be lost after the player stops) */
13863 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13864 return; // player moves away from bad thing
13869 else if (test_element == EL_PENGUIN)
13876 if (kill_x != -1 || kill_y != -1)
13878 if (IS_PLAYER(kill_x, kill_y))
13880 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13882 if (player->shield_deadly_time_left > 0 &&
13883 !IS_INDESTRUCTIBLE(bad_element))
13884 Bang(bad_x, bad_y);
13885 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13886 KillPlayer(player);
13889 Bang(kill_x, kill_y);
13893 void TestIfPlayerTouchesBadThing(int x, int y)
13895 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13898 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13900 TestIfGoodThingHitsBadThing(x, y, move_dir);
13903 void TestIfBadThingTouchesPlayer(int x, int y)
13905 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13908 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13910 TestIfBadThingHitsGoodThing(x, y, move_dir);
13913 void TestIfFriendTouchesBadThing(int x, int y)
13915 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13918 void TestIfBadThingTouchesFriend(int x, int y)
13920 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13923 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13925 int i, kill_x = bad_x, kill_y = bad_y;
13926 static int xy[4][2] =
13934 for (i = 0; i < NUM_DIRECTIONS; i++)
13938 x = bad_x + xy[i][0];
13939 y = bad_y + xy[i][1];
13940 if (!IN_LEV_FIELD(x, y))
13943 element = Tile[x][y];
13944 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13945 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13953 if (kill_x != bad_x || kill_y != bad_y)
13954 Bang(bad_x, bad_y);
13957 void KillPlayer(struct PlayerInfo *player)
13959 int jx = player->jx, jy = player->jy;
13961 if (!player->active)
13965 Debug("game:playing:KillPlayer",
13966 "0: killed == %d, active == %d, reanimated == %d",
13967 player->killed, player->active, player->reanimated);
13970 /* the following code was introduced to prevent an infinite loop when calling
13972 -> CheckTriggeredElementChangeExt()
13973 -> ExecuteCustomElementAction()
13975 -> (infinitely repeating the above sequence of function calls)
13976 which occurs when killing the player while having a CE with the setting
13977 "kill player X when explosion of <player X>"; the solution using a new
13978 field "player->killed" was chosen for backwards compatibility, although
13979 clever use of the fields "player->active" etc. would probably also work */
13981 if (player->killed)
13985 player->killed = TRUE;
13987 // remove accessible field at the player's position
13988 Tile[jx][jy] = EL_EMPTY;
13990 // deactivate shield (else Bang()/Explode() would not work right)
13991 player->shield_normal_time_left = 0;
13992 player->shield_deadly_time_left = 0;
13995 Debug("game:playing:KillPlayer",
13996 "1: killed == %d, active == %d, reanimated == %d",
13997 player->killed, player->active, player->reanimated);
14003 Debug("game:playing:KillPlayer",
14004 "2: killed == %d, active == %d, reanimated == %d",
14005 player->killed, player->active, player->reanimated);
14008 if (player->reanimated) // killed player may have been reanimated
14009 player->killed = player->reanimated = FALSE;
14011 BuryPlayer(player);
14014 static void KillPlayerUnlessEnemyProtected(int x, int y)
14016 if (!PLAYER_ENEMY_PROTECTED(x, y))
14017 KillPlayer(PLAYERINFO(x, y));
14020 static void KillPlayerUnlessExplosionProtected(int x, int y)
14022 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14023 KillPlayer(PLAYERINFO(x, y));
14026 void BuryPlayer(struct PlayerInfo *player)
14028 int jx = player->jx, jy = player->jy;
14030 if (!player->active)
14033 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14034 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14036 RemovePlayer(player);
14038 player->buried = TRUE;
14040 if (game.all_players_gone)
14041 game.GameOver = TRUE;
14044 void RemovePlayer(struct PlayerInfo *player)
14046 int jx = player->jx, jy = player->jy;
14047 int i, found = FALSE;
14049 player->present = FALSE;
14050 player->active = FALSE;
14052 // required for some CE actions (even if the player is not active anymore)
14053 player->MovPos = 0;
14055 if (!ExplodeField[jx][jy])
14056 StorePlayer[jx][jy] = 0;
14058 if (player->is_moving)
14059 TEST_DrawLevelField(player->last_jx, player->last_jy);
14061 for (i = 0; i < MAX_PLAYERS; i++)
14062 if (stored_player[i].active)
14067 game.all_players_gone = TRUE;
14068 game.GameOver = TRUE;
14071 game.exit_x = game.robot_wheel_x = jx;
14072 game.exit_y = game.robot_wheel_y = jy;
14075 void ExitPlayer(struct PlayerInfo *player)
14077 DrawPlayer(player); // needed here only to cleanup last field
14078 RemovePlayer(player);
14080 if (game.players_still_needed > 0)
14081 game.players_still_needed--;
14084 static void SetFieldForSnapping(int x, int y, int element, int direction,
14085 int player_index_bit)
14087 struct ElementInfo *ei = &element_info[element];
14088 int direction_bit = MV_DIR_TO_BIT(direction);
14089 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14090 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14091 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14093 Tile[x][y] = EL_ELEMENT_SNAPPING;
14094 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14095 MovDir[x][y] = direction;
14096 Store[x][y] = element;
14097 Store2[x][y] = player_index_bit;
14099 ResetGfxAnimation(x, y);
14101 GfxElement[x][y] = element;
14102 GfxAction[x][y] = action;
14103 GfxDir[x][y] = direction;
14104 GfxFrame[x][y] = -1;
14107 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14108 int player_index_bit)
14110 TestIfElementTouchesCustomElement(x, y); // for empty space
14112 if (level.finish_dig_collect)
14114 int dig_side = MV_DIR_OPPOSITE(direction);
14115 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14116 CE_PLAYER_COLLECTS_X);
14118 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14119 player_index_bit, dig_side);
14120 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14121 player_index_bit, dig_side);
14126 =============================================================================
14127 checkDiagonalPushing()
14128 -----------------------------------------------------------------------------
14129 check if diagonal input device direction results in pushing of object
14130 (by checking if the alternative direction is walkable, diggable, ...)
14131 =============================================================================
14134 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14135 int x, int y, int real_dx, int real_dy)
14137 int jx, jy, dx, dy, xx, yy;
14139 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14142 // diagonal direction: check alternative direction
14147 xx = jx + (dx == 0 ? real_dx : 0);
14148 yy = jy + (dy == 0 ? real_dy : 0);
14150 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14154 =============================================================================
14156 -----------------------------------------------------------------------------
14157 x, y: field next to player (non-diagonal) to try to dig to
14158 real_dx, real_dy: direction as read from input device (can be diagonal)
14159 =============================================================================
14162 static int DigField(struct PlayerInfo *player,
14163 int oldx, int oldy, int x, int y,
14164 int real_dx, int real_dy, int mode)
14166 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14167 boolean player_was_pushing = player->is_pushing;
14168 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14169 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14170 int jx = oldx, jy = oldy;
14171 int dx = x - jx, dy = y - jy;
14172 int nextx = x + dx, nexty = y + dy;
14173 int move_direction = (dx == -1 ? MV_LEFT :
14174 dx == +1 ? MV_RIGHT :
14176 dy == +1 ? MV_DOWN : MV_NONE);
14177 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14178 int dig_side = MV_DIR_OPPOSITE(move_direction);
14179 int old_element = Tile[jx][jy];
14180 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14183 if (is_player) // function can also be called by EL_PENGUIN
14185 if (player->MovPos == 0)
14187 player->is_digging = FALSE;
14188 player->is_collecting = FALSE;
14191 if (player->MovPos == 0) // last pushing move finished
14192 player->is_pushing = FALSE;
14194 if (mode == DF_NO_PUSH) // player just stopped pushing
14196 player->is_switching = FALSE;
14197 player->push_delay = -1;
14199 return MP_NO_ACTION;
14203 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14204 old_element = Back[jx][jy];
14206 // in case of element dropped at player position, check background
14207 else if (Back[jx][jy] != EL_EMPTY &&
14208 game.engine_version >= VERSION_IDENT(2,2,0,0))
14209 old_element = Back[jx][jy];
14211 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14212 return MP_NO_ACTION; // field has no opening in this direction
14214 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14215 return MP_NO_ACTION; // field has no opening in this direction
14217 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14221 Tile[jx][jy] = player->artwork_element;
14222 InitMovingField(jx, jy, MV_DOWN);
14223 Store[jx][jy] = EL_ACID;
14224 ContinueMoving(jx, jy);
14225 BuryPlayer(player);
14227 return MP_DONT_RUN_INTO;
14230 if (player_can_move && DONT_RUN_INTO(element))
14232 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14234 return MP_DONT_RUN_INTO;
14237 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14238 return MP_NO_ACTION;
14240 collect_count = element_info[element].collect_count_initial;
14242 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14243 return MP_NO_ACTION;
14245 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14246 player_can_move = player_can_move_or_snap;
14248 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14249 game.engine_version >= VERSION_IDENT(2,2,0,0))
14251 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14252 player->index_bit, dig_side);
14253 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14254 player->index_bit, dig_side);
14256 if (element == EL_DC_LANDMINE)
14259 if (Tile[x][y] != element) // field changed by snapping
14262 return MP_NO_ACTION;
14265 if (player->gravity && is_player && !player->is_auto_moving &&
14266 canFallDown(player) && move_direction != MV_DOWN &&
14267 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14268 return MP_NO_ACTION; // player cannot walk here due to gravity
14270 if (player_can_move &&
14271 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14273 int sound_element = SND_ELEMENT(element);
14274 int sound_action = ACTION_WALKING;
14276 if (IS_RND_GATE(element))
14278 if (!player->key[RND_GATE_NR(element)])
14279 return MP_NO_ACTION;
14281 else if (IS_RND_GATE_GRAY(element))
14283 if (!player->key[RND_GATE_GRAY_NR(element)])
14284 return MP_NO_ACTION;
14286 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14288 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14289 return MP_NO_ACTION;
14291 else if (element == EL_EXIT_OPEN ||
14292 element == EL_EM_EXIT_OPEN ||
14293 element == EL_EM_EXIT_OPENING ||
14294 element == EL_STEEL_EXIT_OPEN ||
14295 element == EL_EM_STEEL_EXIT_OPEN ||
14296 element == EL_EM_STEEL_EXIT_OPENING ||
14297 element == EL_SP_EXIT_OPEN ||
14298 element == EL_SP_EXIT_OPENING)
14300 sound_action = ACTION_PASSING; // player is passing exit
14302 else if (element == EL_EMPTY)
14304 sound_action = ACTION_MOVING; // nothing to walk on
14307 // play sound from background or player, whatever is available
14308 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14309 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14311 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14313 else if (player_can_move &&
14314 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14316 if (!ACCESS_FROM(element, opposite_direction))
14317 return MP_NO_ACTION; // field not accessible from this direction
14319 if (CAN_MOVE(element)) // only fixed elements can be passed!
14320 return MP_NO_ACTION;
14322 if (IS_EM_GATE(element))
14324 if (!player->key[EM_GATE_NR(element)])
14325 return MP_NO_ACTION;
14327 else if (IS_EM_GATE_GRAY(element))
14329 if (!player->key[EM_GATE_GRAY_NR(element)])
14330 return MP_NO_ACTION;
14332 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14334 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14335 return MP_NO_ACTION;
14337 else if (IS_EMC_GATE(element))
14339 if (!player->key[EMC_GATE_NR(element)])
14340 return MP_NO_ACTION;
14342 else if (IS_EMC_GATE_GRAY(element))
14344 if (!player->key[EMC_GATE_GRAY_NR(element)])
14345 return MP_NO_ACTION;
14347 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14349 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14350 return MP_NO_ACTION;
14352 else if (element == EL_DC_GATE_WHITE ||
14353 element == EL_DC_GATE_WHITE_GRAY ||
14354 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14356 if (player->num_white_keys == 0)
14357 return MP_NO_ACTION;
14359 player->num_white_keys--;
14361 else if (IS_SP_PORT(element))
14363 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14364 element == EL_SP_GRAVITY_PORT_RIGHT ||
14365 element == EL_SP_GRAVITY_PORT_UP ||
14366 element == EL_SP_GRAVITY_PORT_DOWN)
14367 player->gravity = !player->gravity;
14368 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14369 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14370 element == EL_SP_GRAVITY_ON_PORT_UP ||
14371 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14372 player->gravity = TRUE;
14373 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14374 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14375 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14376 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14377 player->gravity = FALSE;
14380 // automatically move to the next field with double speed
14381 player->programmed_action = move_direction;
14383 if (player->move_delay_reset_counter == 0)
14385 player->move_delay_reset_counter = 2; // two double speed steps
14387 DOUBLE_PLAYER_SPEED(player);
14390 PlayLevelSoundAction(x, y, ACTION_PASSING);
14392 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14396 if (mode != DF_SNAP)
14398 GfxElement[x][y] = GFX_ELEMENT(element);
14399 player->is_digging = TRUE;
14402 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14404 // use old behaviour for old levels (digging)
14405 if (!level.finish_dig_collect)
14407 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14408 player->index_bit, dig_side);
14410 // if digging triggered player relocation, finish digging tile
14411 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14412 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14415 if (mode == DF_SNAP)
14417 if (level.block_snap_field)
14418 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14420 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14422 // use old behaviour for old levels (snapping)
14423 if (!level.finish_dig_collect)
14424 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14425 player->index_bit, dig_side);
14428 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14432 if (is_player && mode != DF_SNAP)
14434 GfxElement[x][y] = element;
14435 player->is_collecting = TRUE;
14438 if (element == EL_SPEED_PILL)
14440 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14442 else if (element == EL_EXTRA_TIME && level.time > 0)
14444 TimeLeft += level.extra_time;
14446 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14448 DisplayGameControlValues();
14450 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14452 player->shield_normal_time_left += level.shield_normal_time;
14453 if (element == EL_SHIELD_DEADLY)
14454 player->shield_deadly_time_left += level.shield_deadly_time;
14456 else if (element == EL_DYNAMITE ||
14457 element == EL_EM_DYNAMITE ||
14458 element == EL_SP_DISK_RED)
14460 if (player->inventory_size < MAX_INVENTORY_SIZE)
14461 player->inventory_element[player->inventory_size++] = element;
14463 DrawGameDoorValues();
14465 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14467 player->dynabomb_count++;
14468 player->dynabombs_left++;
14470 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14472 player->dynabomb_size++;
14474 else if (element == EL_DYNABOMB_INCREASE_POWER)
14476 player->dynabomb_xl = TRUE;
14478 else if (IS_KEY(element))
14480 player->key[KEY_NR(element)] = TRUE;
14482 DrawGameDoorValues();
14484 else if (element == EL_DC_KEY_WHITE)
14486 player->num_white_keys++;
14488 // display white keys?
14489 // DrawGameDoorValues();
14491 else if (IS_ENVELOPE(element))
14493 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14495 if (!wait_for_snapping)
14496 player->show_envelope = element;
14498 else if (element == EL_EMC_LENSES)
14500 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14502 RedrawAllInvisibleElementsForLenses();
14504 else if (element == EL_EMC_MAGNIFIER)
14506 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14508 RedrawAllInvisibleElementsForMagnifier();
14510 else if (IS_DROPPABLE(element) ||
14511 IS_THROWABLE(element)) // can be collected and dropped
14515 if (collect_count == 0)
14516 player->inventory_infinite_element = element;
14518 for (i = 0; i < collect_count; i++)
14519 if (player->inventory_size < MAX_INVENTORY_SIZE)
14520 player->inventory_element[player->inventory_size++] = element;
14522 DrawGameDoorValues();
14524 else if (collect_count > 0)
14526 game.gems_still_needed -= collect_count;
14527 if (game.gems_still_needed < 0)
14528 game.gems_still_needed = 0;
14530 game.snapshot.collected_item = TRUE;
14532 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14534 DisplayGameControlValues();
14537 RaiseScoreElement(element);
14538 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14540 // use old behaviour for old levels (collecting)
14541 if (!level.finish_dig_collect && is_player)
14543 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14544 player->index_bit, dig_side);
14546 // if collecting triggered player relocation, finish collecting tile
14547 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14548 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14551 if (mode == DF_SNAP)
14553 if (level.block_snap_field)
14554 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14556 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14558 // use old behaviour for old levels (snapping)
14559 if (!level.finish_dig_collect)
14560 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14561 player->index_bit, dig_side);
14564 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14566 if (mode == DF_SNAP && element != EL_BD_ROCK)
14567 return MP_NO_ACTION;
14569 if (CAN_FALL(element) && dy)
14570 return MP_NO_ACTION;
14572 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14573 !(element == EL_SPRING && level.use_spring_bug))
14574 return MP_NO_ACTION;
14576 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14577 ((move_direction & MV_VERTICAL &&
14578 ((element_info[element].move_pattern & MV_LEFT &&
14579 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14580 (element_info[element].move_pattern & MV_RIGHT &&
14581 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14582 (move_direction & MV_HORIZONTAL &&
14583 ((element_info[element].move_pattern & MV_UP &&
14584 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14585 (element_info[element].move_pattern & MV_DOWN &&
14586 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14587 return MP_NO_ACTION;
14589 // do not push elements already moving away faster than player
14590 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14591 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14592 return MP_NO_ACTION;
14594 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14596 if (player->push_delay_value == -1 || !player_was_pushing)
14597 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14599 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14601 if (player->push_delay_value == -1)
14602 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14604 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14606 if (!player->is_pushing)
14607 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14610 player->is_pushing = TRUE;
14611 player->is_active = TRUE;
14613 if (!(IN_LEV_FIELD(nextx, nexty) &&
14614 (IS_FREE(nextx, nexty) ||
14615 (IS_SB_ELEMENT(element) &&
14616 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14617 (IS_CUSTOM_ELEMENT(element) &&
14618 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14619 return MP_NO_ACTION;
14621 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14622 return MP_NO_ACTION;
14624 if (player->push_delay == -1) // new pushing; restart delay
14625 player->push_delay = 0;
14627 if (player->push_delay < player->push_delay_value &&
14628 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14629 element != EL_SPRING && element != EL_BALLOON)
14631 // make sure that there is no move delay before next try to push
14632 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14633 player->move_delay = 0;
14635 return MP_NO_ACTION;
14638 if (IS_CUSTOM_ELEMENT(element) &&
14639 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14641 if (!DigFieldByCE(nextx, nexty, element))
14642 return MP_NO_ACTION;
14645 if (IS_SB_ELEMENT(element))
14647 boolean sokoban_task_solved = FALSE;
14649 if (element == EL_SOKOBAN_FIELD_FULL)
14651 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14653 IncrementSokobanFieldsNeeded();
14654 IncrementSokobanObjectsNeeded();
14657 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14659 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14661 DecrementSokobanFieldsNeeded();
14662 DecrementSokobanObjectsNeeded();
14664 // sokoban object was pushed from empty field to sokoban field
14665 if (Back[x][y] == EL_EMPTY)
14666 sokoban_task_solved = TRUE;
14669 Tile[x][y] = EL_SOKOBAN_OBJECT;
14671 if (Back[x][y] == Back[nextx][nexty])
14672 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14673 else if (Back[x][y] != 0)
14674 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14677 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14680 if (sokoban_task_solved &&
14681 game.sokoban_fields_still_needed == 0 &&
14682 game.sokoban_objects_still_needed == 0 &&
14683 level.auto_exit_sokoban)
14685 game.players_still_needed = 0;
14689 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14693 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14695 InitMovingField(x, y, move_direction);
14696 GfxAction[x][y] = ACTION_PUSHING;
14698 if (mode == DF_SNAP)
14699 ContinueMoving(x, y);
14701 MovPos[x][y] = (dx != 0 ? dx : dy);
14703 Pushed[x][y] = TRUE;
14704 Pushed[nextx][nexty] = TRUE;
14706 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14707 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14709 player->push_delay_value = -1; // get new value later
14711 // check for element change _after_ element has been pushed
14712 if (game.use_change_when_pushing_bug)
14714 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14715 player->index_bit, dig_side);
14716 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14717 player->index_bit, dig_side);
14720 else if (IS_SWITCHABLE(element))
14722 if (PLAYER_SWITCHING(player, x, y))
14724 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14725 player->index_bit, dig_side);
14730 player->is_switching = TRUE;
14731 player->switch_x = x;
14732 player->switch_y = y;
14734 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14736 if (element == EL_ROBOT_WHEEL)
14738 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14740 game.robot_wheel_x = x;
14741 game.robot_wheel_y = y;
14742 game.robot_wheel_active = TRUE;
14744 TEST_DrawLevelField(x, y);
14746 else if (element == EL_SP_TERMINAL)
14750 SCAN_PLAYFIELD(xx, yy)
14752 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14756 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14758 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14760 ResetGfxAnimation(xx, yy);
14761 TEST_DrawLevelField(xx, yy);
14765 else if (IS_BELT_SWITCH(element))
14767 ToggleBeltSwitch(x, y);
14769 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14770 element == EL_SWITCHGATE_SWITCH_DOWN ||
14771 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14772 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14774 ToggleSwitchgateSwitch(x, y);
14776 else if (element == EL_LIGHT_SWITCH ||
14777 element == EL_LIGHT_SWITCH_ACTIVE)
14779 ToggleLightSwitch(x, y);
14781 else if (element == EL_TIMEGATE_SWITCH ||
14782 element == EL_DC_TIMEGATE_SWITCH)
14784 ActivateTimegateSwitch(x, y);
14786 else if (element == EL_BALLOON_SWITCH_LEFT ||
14787 element == EL_BALLOON_SWITCH_RIGHT ||
14788 element == EL_BALLOON_SWITCH_UP ||
14789 element == EL_BALLOON_SWITCH_DOWN ||
14790 element == EL_BALLOON_SWITCH_NONE ||
14791 element == EL_BALLOON_SWITCH_ANY)
14793 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14794 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14795 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14796 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14797 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14800 else if (element == EL_LAMP)
14802 Tile[x][y] = EL_LAMP_ACTIVE;
14803 game.lights_still_needed--;
14805 ResetGfxAnimation(x, y);
14806 TEST_DrawLevelField(x, y);
14808 else if (element == EL_TIME_ORB_FULL)
14810 Tile[x][y] = EL_TIME_ORB_EMPTY;
14812 if (level.time > 0 || level.use_time_orb_bug)
14814 TimeLeft += level.time_orb_time;
14815 game.no_time_limit = FALSE;
14817 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14819 DisplayGameControlValues();
14822 ResetGfxAnimation(x, y);
14823 TEST_DrawLevelField(x, y);
14825 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14826 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14830 game.ball_active = !game.ball_active;
14832 SCAN_PLAYFIELD(xx, yy)
14834 int e = Tile[xx][yy];
14836 if (game.ball_active)
14838 if (e == EL_EMC_MAGIC_BALL)
14839 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14840 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14841 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14845 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14846 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14847 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14848 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14853 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14854 player->index_bit, dig_side);
14856 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14857 player->index_bit, dig_side);
14859 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14860 player->index_bit, dig_side);
14866 if (!PLAYER_SWITCHING(player, x, y))
14868 player->is_switching = TRUE;
14869 player->switch_x = x;
14870 player->switch_y = y;
14872 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14873 player->index_bit, dig_side);
14874 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14875 player->index_bit, dig_side);
14877 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14878 player->index_bit, dig_side);
14879 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14880 player->index_bit, dig_side);
14883 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14884 player->index_bit, dig_side);
14885 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14886 player->index_bit, dig_side);
14888 return MP_NO_ACTION;
14891 player->push_delay = -1;
14893 if (is_player) // function can also be called by EL_PENGUIN
14895 if (Tile[x][y] != element) // really digged/collected something
14897 player->is_collecting = !player->is_digging;
14898 player->is_active = TRUE;
14900 player->last_removed_element = element;
14907 static boolean DigFieldByCE(int x, int y, int digging_element)
14909 int element = Tile[x][y];
14911 if (!IS_FREE(x, y))
14913 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14914 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14917 // no element can dig solid indestructible elements
14918 if (IS_INDESTRUCTIBLE(element) &&
14919 !IS_DIGGABLE(element) &&
14920 !IS_COLLECTIBLE(element))
14923 if (AmoebaNr[x][y] &&
14924 (element == EL_AMOEBA_FULL ||
14925 element == EL_BD_AMOEBA ||
14926 element == EL_AMOEBA_GROWING))
14928 AmoebaCnt[AmoebaNr[x][y]]--;
14929 AmoebaCnt2[AmoebaNr[x][y]]--;
14932 if (IS_MOVING(x, y))
14933 RemoveMovingField(x, y);
14937 TEST_DrawLevelField(x, y);
14940 // if digged element was about to explode, prevent the explosion
14941 ExplodeField[x][y] = EX_TYPE_NONE;
14943 PlayLevelSoundAction(x, y, action);
14946 Store[x][y] = EL_EMPTY;
14948 // this makes it possible to leave the removed element again
14949 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14950 Store[x][y] = element;
14955 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14957 int jx = player->jx, jy = player->jy;
14958 int x = jx + dx, y = jy + dy;
14959 int snap_direction = (dx == -1 ? MV_LEFT :
14960 dx == +1 ? MV_RIGHT :
14962 dy == +1 ? MV_DOWN : MV_NONE);
14963 boolean can_continue_snapping = (level.continuous_snapping &&
14964 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14966 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14969 if (!player->active || !IN_LEV_FIELD(x, y))
14977 if (player->MovPos == 0)
14978 player->is_pushing = FALSE;
14980 player->is_snapping = FALSE;
14982 if (player->MovPos == 0)
14984 player->is_moving = FALSE;
14985 player->is_digging = FALSE;
14986 player->is_collecting = FALSE;
14992 // prevent snapping with already pressed snap key when not allowed
14993 if (player->is_snapping && !can_continue_snapping)
14996 player->MovDir = snap_direction;
14998 if (player->MovPos == 0)
15000 player->is_moving = FALSE;
15001 player->is_digging = FALSE;
15002 player->is_collecting = FALSE;
15005 player->is_dropping = FALSE;
15006 player->is_dropping_pressed = FALSE;
15007 player->drop_pressed_delay = 0;
15009 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15012 player->is_snapping = TRUE;
15013 player->is_active = TRUE;
15015 if (player->MovPos == 0)
15017 player->is_moving = FALSE;
15018 player->is_digging = FALSE;
15019 player->is_collecting = FALSE;
15022 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15023 TEST_DrawLevelField(player->last_jx, player->last_jy);
15025 TEST_DrawLevelField(x, y);
15030 static boolean DropElement(struct PlayerInfo *player)
15032 int old_element, new_element;
15033 int dropx = player->jx, dropy = player->jy;
15034 int drop_direction = player->MovDir;
15035 int drop_side = drop_direction;
15036 int drop_element = get_next_dropped_element(player);
15038 /* do not drop an element on top of another element; when holding drop key
15039 pressed without moving, dropped element must move away before the next
15040 element can be dropped (this is especially important if the next element
15041 is dynamite, which can be placed on background for historical reasons) */
15042 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15045 if (IS_THROWABLE(drop_element))
15047 dropx += GET_DX_FROM_DIR(drop_direction);
15048 dropy += GET_DY_FROM_DIR(drop_direction);
15050 if (!IN_LEV_FIELD(dropx, dropy))
15054 old_element = Tile[dropx][dropy]; // old element at dropping position
15055 new_element = drop_element; // default: no change when dropping
15057 // check if player is active, not moving and ready to drop
15058 if (!player->active || player->MovPos || player->drop_delay > 0)
15061 // check if player has anything that can be dropped
15062 if (new_element == EL_UNDEFINED)
15065 // only set if player has anything that can be dropped
15066 player->is_dropping_pressed = TRUE;
15068 // check if drop key was pressed long enough for EM style dynamite
15069 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15072 // check if anything can be dropped at the current position
15073 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15076 // collected custom elements can only be dropped on empty fields
15077 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15080 if (old_element != EL_EMPTY)
15081 Back[dropx][dropy] = old_element; // store old element on this field
15083 ResetGfxAnimation(dropx, dropy);
15084 ResetRandomAnimationValue(dropx, dropy);
15086 if (player->inventory_size > 0 ||
15087 player->inventory_infinite_element != EL_UNDEFINED)
15089 if (player->inventory_size > 0)
15091 player->inventory_size--;
15093 DrawGameDoorValues();
15095 if (new_element == EL_DYNAMITE)
15096 new_element = EL_DYNAMITE_ACTIVE;
15097 else if (new_element == EL_EM_DYNAMITE)
15098 new_element = EL_EM_DYNAMITE_ACTIVE;
15099 else if (new_element == EL_SP_DISK_RED)
15100 new_element = EL_SP_DISK_RED_ACTIVE;
15103 Tile[dropx][dropy] = new_element;
15105 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15106 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15107 el2img(Tile[dropx][dropy]), 0);
15109 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15111 // needed if previous element just changed to "empty" in the last frame
15112 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15114 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15115 player->index_bit, drop_side);
15116 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15118 player->index_bit, drop_side);
15120 TestIfElementTouchesCustomElement(dropx, dropy);
15122 else // player is dropping a dyna bomb
15124 player->dynabombs_left--;
15126 Tile[dropx][dropy] = new_element;
15128 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15129 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15130 el2img(Tile[dropx][dropy]), 0);
15132 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15135 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15136 InitField_WithBug1(dropx, dropy, FALSE);
15138 new_element = Tile[dropx][dropy]; // element might have changed
15140 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15141 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15143 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15144 MovDir[dropx][dropy] = drop_direction;
15146 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15148 // do not cause impact style collision by dropping elements that can fall
15149 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15152 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15153 player->is_dropping = TRUE;
15155 player->drop_pressed_delay = 0;
15156 player->is_dropping_pressed = FALSE;
15158 player->drop_x = dropx;
15159 player->drop_y = dropy;
15164 // ----------------------------------------------------------------------------
15165 // game sound playing functions
15166 // ----------------------------------------------------------------------------
15168 static int *loop_sound_frame = NULL;
15169 static int *loop_sound_volume = NULL;
15171 void InitPlayLevelSound(void)
15173 int num_sounds = getSoundListSize();
15175 checked_free(loop_sound_frame);
15176 checked_free(loop_sound_volume);
15178 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15179 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15182 static void PlayLevelSound(int x, int y, int nr)
15184 int sx = SCREENX(x), sy = SCREENY(y);
15185 int volume, stereo_position;
15186 int max_distance = 8;
15187 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15189 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15190 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15193 if (!IN_LEV_FIELD(x, y) ||
15194 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15195 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15198 volume = SOUND_MAX_VOLUME;
15200 if (!IN_SCR_FIELD(sx, sy))
15202 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15203 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15205 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15208 stereo_position = (SOUND_MAX_LEFT +
15209 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15210 (SCR_FIELDX + 2 * max_distance));
15212 if (IS_LOOP_SOUND(nr))
15214 /* This assures that quieter loop sounds do not overwrite louder ones,
15215 while restarting sound volume comparison with each new game frame. */
15217 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15220 loop_sound_volume[nr] = volume;
15221 loop_sound_frame[nr] = FrameCounter;
15224 PlaySoundExt(nr, volume, stereo_position, type);
15227 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15229 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15230 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15231 y < LEVELY(BY1) ? LEVELY(BY1) :
15232 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15236 static void PlayLevelSoundAction(int x, int y, int action)
15238 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15241 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15243 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15245 if (sound_effect != SND_UNDEFINED)
15246 PlayLevelSound(x, y, sound_effect);
15249 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15252 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15254 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15255 PlayLevelSound(x, y, sound_effect);
15258 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15260 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15262 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15263 PlayLevelSound(x, y, sound_effect);
15266 static void StopLevelSoundActionIfLoop(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 StopSound(sound_effect);
15274 static int getLevelMusicNr(void)
15276 if (levelset.music[level_nr] != MUS_UNDEFINED)
15277 return levelset.music[level_nr]; // from config file
15279 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15282 static void FadeLevelSounds(void)
15287 static void FadeLevelMusic(void)
15289 int music_nr = getLevelMusicNr();
15290 char *curr_music = getCurrentlyPlayingMusicFilename();
15291 char *next_music = getMusicInfoEntryFilename(music_nr);
15293 if (!strEqual(curr_music, next_music))
15297 void FadeLevelSoundsAndMusic(void)
15303 static void PlayLevelMusic(void)
15305 int music_nr = getLevelMusicNr();
15306 char *curr_music = getCurrentlyPlayingMusicFilename();
15307 char *next_music = getMusicInfoEntryFilename(music_nr);
15309 if (!strEqual(curr_music, next_music))
15310 PlayMusicLoop(music_nr);
15313 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15315 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15317 int x = xx - offset;
15318 int y = yy - offset;
15323 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15327 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15331 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15335 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15339 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15343 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15347 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15350 case SOUND_android_clone:
15351 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15354 case SOUND_android_move:
15355 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15359 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15363 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15367 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15370 case SOUND_eater_eat:
15371 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15375 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15378 case SOUND_collect:
15379 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15382 case SOUND_diamond:
15383 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15387 // !!! CHECK THIS !!!
15389 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15391 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15395 case SOUND_wonderfall:
15396 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15400 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15404 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15408 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15412 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15416 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15420 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15424 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15428 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15431 case SOUND_exit_open:
15432 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15435 case SOUND_exit_leave:
15436 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15439 case SOUND_dynamite:
15440 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15444 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15448 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15452 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15456 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15460 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15464 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15468 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15473 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15475 int element = map_element_SP_to_RND(element_sp);
15476 int action = map_action_SP_to_RND(action_sp);
15477 int offset = (setup.sp_show_border_elements ? 0 : 1);
15478 int x = xx - offset;
15479 int y = yy - offset;
15481 PlayLevelSoundElementAction(x, y, element, action);
15484 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15486 int element = map_element_MM_to_RND(element_mm);
15487 int action = map_action_MM_to_RND(action_mm);
15489 int x = xx - offset;
15490 int y = yy - offset;
15492 if (!IS_MM_ELEMENT(element))
15493 element = EL_MM_DEFAULT;
15495 PlayLevelSoundElementAction(x, y, element, action);
15498 void PlaySound_MM(int sound_mm)
15500 int sound = map_sound_MM_to_RND(sound_mm);
15502 if (sound == SND_UNDEFINED)
15508 void PlaySoundLoop_MM(int sound_mm)
15510 int sound = map_sound_MM_to_RND(sound_mm);
15512 if (sound == SND_UNDEFINED)
15515 PlaySoundLoop(sound);
15518 void StopSound_MM(int sound_mm)
15520 int sound = map_sound_MM_to_RND(sound_mm);
15522 if (sound == SND_UNDEFINED)
15528 void RaiseScore(int value)
15530 game.score += value;
15532 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15534 DisplayGameControlValues();
15537 void RaiseScoreElement(int element)
15542 case EL_BD_DIAMOND:
15543 case EL_EMERALD_YELLOW:
15544 case EL_EMERALD_RED:
15545 case EL_EMERALD_PURPLE:
15546 case EL_SP_INFOTRON:
15547 RaiseScore(level.score[SC_EMERALD]);
15550 RaiseScore(level.score[SC_DIAMOND]);
15553 RaiseScore(level.score[SC_CRYSTAL]);
15556 RaiseScore(level.score[SC_PEARL]);
15559 case EL_BD_BUTTERFLY:
15560 case EL_SP_ELECTRON:
15561 RaiseScore(level.score[SC_BUG]);
15564 case EL_BD_FIREFLY:
15565 case EL_SP_SNIKSNAK:
15566 RaiseScore(level.score[SC_SPACESHIP]);
15569 case EL_DARK_YAMYAM:
15570 RaiseScore(level.score[SC_YAMYAM]);
15573 RaiseScore(level.score[SC_ROBOT]);
15576 RaiseScore(level.score[SC_PACMAN]);
15579 RaiseScore(level.score[SC_NUT]);
15582 case EL_EM_DYNAMITE:
15583 case EL_SP_DISK_RED:
15584 case EL_DYNABOMB_INCREASE_NUMBER:
15585 case EL_DYNABOMB_INCREASE_SIZE:
15586 case EL_DYNABOMB_INCREASE_POWER:
15587 RaiseScore(level.score[SC_DYNAMITE]);
15589 case EL_SHIELD_NORMAL:
15590 case EL_SHIELD_DEADLY:
15591 RaiseScore(level.score[SC_SHIELD]);
15593 case EL_EXTRA_TIME:
15594 RaiseScore(level.extra_time_score);
15608 case EL_DC_KEY_WHITE:
15609 RaiseScore(level.score[SC_KEY]);
15612 RaiseScore(element_info[element].collect_score);
15617 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15619 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15623 // prevent short reactivation of overlay buttons while closing door
15624 SetOverlayActive(FALSE);
15626 // door may still be open due to skipped or envelope style request
15627 CloseDoor(DOOR_CLOSE_1);
15630 if (network.enabled)
15631 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15635 FadeSkipNextFadeIn();
15637 SetGameStatus(GAME_MODE_MAIN);
15642 else // continue playing the game
15644 if (tape.playing && tape.deactivate_display)
15645 TapeDeactivateDisplayOff(TRUE);
15647 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15649 if (tape.playing && tape.deactivate_display)
15650 TapeDeactivateDisplayOn();
15654 void RequestQuitGame(boolean escape_key_pressed)
15656 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15657 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15658 level_editor_test_game);
15659 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15662 RequestQuitGameExt(skip_request, quick_quit,
15663 "Do you really want to quit the game?");
15666 void RequestRestartGame(char *message)
15668 game.restart_game_message = NULL;
15670 boolean has_started_game = hasStartedNetworkGame();
15671 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15673 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15675 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15679 // needed in case of envelope request to close game panel
15680 CloseDoor(DOOR_CLOSE_1);
15682 SetGameStatus(GAME_MODE_MAIN);
15688 void CheckGameOver(void)
15690 static boolean last_game_over = FALSE;
15691 static int game_over_delay = 0;
15692 int game_over_delay_value = 50;
15693 boolean game_over = checkGameFailed();
15695 // do not handle game over if request dialog is already active
15696 if (game.request_active)
15699 // do not ask to play again if game was never actually played
15700 if (!game.GamePlayed)
15705 last_game_over = FALSE;
15706 game_over_delay = game_over_delay_value;
15711 if (game_over_delay > 0)
15718 if (last_game_over != game_over)
15719 game.restart_game_message = (hasStartedNetworkGame() ?
15720 "Game over! Play it again?" :
15723 last_game_over = game_over;
15726 boolean checkGameSolved(void)
15728 // set for all game engines if level was solved
15729 return game.LevelSolved_GameEnd;
15732 boolean checkGameFailed(void)
15734 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15735 return (game_em.game_over && !game_em.level_solved);
15736 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15737 return (game_sp.game_over && !game_sp.level_solved);
15738 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15739 return (game_mm.game_over && !game_mm.level_solved);
15740 else // GAME_ENGINE_TYPE_RND
15741 return (game.GameOver && !game.LevelSolved);
15744 boolean checkGameEnded(void)
15746 return (checkGameSolved() || checkGameFailed());
15750 // ----------------------------------------------------------------------------
15751 // random generator functions
15752 // ----------------------------------------------------------------------------
15754 unsigned int InitEngineRandom_RND(int seed)
15756 game.num_random_calls = 0;
15758 return InitEngineRandom(seed);
15761 unsigned int RND(int max)
15765 game.num_random_calls++;
15767 return GetEngineRandom(max);
15774 // ----------------------------------------------------------------------------
15775 // game engine snapshot handling functions
15776 // ----------------------------------------------------------------------------
15778 struct EngineSnapshotInfo
15780 // runtime values for custom element collect score
15781 int collect_score[NUM_CUSTOM_ELEMENTS];
15783 // runtime values for group element choice position
15784 int choice_pos[NUM_GROUP_ELEMENTS];
15786 // runtime values for belt position animations
15787 int belt_graphic[4][NUM_BELT_PARTS];
15788 int belt_anim_mode[4][NUM_BELT_PARTS];
15791 static struct EngineSnapshotInfo engine_snapshot_rnd;
15792 static char *snapshot_level_identifier = NULL;
15793 static int snapshot_level_nr = -1;
15795 static void SaveEngineSnapshotValues_RND(void)
15797 static int belt_base_active_element[4] =
15799 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15800 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15801 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15802 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15806 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15808 int element = EL_CUSTOM_START + i;
15810 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15813 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15815 int element = EL_GROUP_START + i;
15817 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15820 for (i = 0; i < 4; i++)
15822 for (j = 0; j < NUM_BELT_PARTS; j++)
15824 int element = belt_base_active_element[i] + j;
15825 int graphic = el2img(element);
15826 int anim_mode = graphic_info[graphic].anim_mode;
15828 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15829 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15834 static void LoadEngineSnapshotValues_RND(void)
15836 unsigned int num_random_calls = game.num_random_calls;
15839 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15841 int element = EL_CUSTOM_START + i;
15843 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15846 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15848 int element = EL_GROUP_START + i;
15850 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15853 for (i = 0; i < 4; i++)
15855 for (j = 0; j < NUM_BELT_PARTS; j++)
15857 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15858 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15860 graphic_info[graphic].anim_mode = anim_mode;
15864 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15866 InitRND(tape.random_seed);
15867 for (i = 0; i < num_random_calls; i++)
15871 if (game.num_random_calls != num_random_calls)
15873 Error("number of random calls out of sync");
15874 Error("number of random calls should be %d", num_random_calls);
15875 Error("number of random calls is %d", game.num_random_calls);
15877 Fail("this should not happen -- please debug");
15881 void FreeEngineSnapshotSingle(void)
15883 FreeSnapshotSingle();
15885 setString(&snapshot_level_identifier, NULL);
15886 snapshot_level_nr = -1;
15889 void FreeEngineSnapshotList(void)
15891 FreeSnapshotList();
15894 static ListNode *SaveEngineSnapshotBuffers(void)
15896 ListNode *buffers = NULL;
15898 // copy some special values to a structure better suited for the snapshot
15900 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15901 SaveEngineSnapshotValues_RND();
15902 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15903 SaveEngineSnapshotValues_EM();
15904 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15905 SaveEngineSnapshotValues_SP(&buffers);
15906 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15907 SaveEngineSnapshotValues_MM(&buffers);
15909 // save values stored in special snapshot structure
15911 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15912 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15913 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15914 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15915 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15916 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15917 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15918 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15920 // save further RND engine values
15922 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15923 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15924 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15926 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15927 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15928 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15929 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15930 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15932 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15933 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15934 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15936 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15938 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15939 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15941 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15942 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15943 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15944 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15945 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15946 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15947 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15948 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15949 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15950 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15951 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15952 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15953 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15954 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15955 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15956 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15957 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15958 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15960 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15961 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15963 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15964 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15965 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15968 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15970 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15972 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15974 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15975 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15981 ListNode *node = engine_snapshot_list_rnd;
15984 while (node != NULL)
15986 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15991 Debug("game:playing:SaveEngineSnapshotBuffers",
15992 "size of engine snapshot: %d bytes", num_bytes);
15998 void SaveEngineSnapshotSingle(void)
16000 ListNode *buffers = SaveEngineSnapshotBuffers();
16002 // finally save all snapshot buffers to single snapshot
16003 SaveSnapshotSingle(buffers);
16005 // save level identification information
16006 setString(&snapshot_level_identifier, leveldir_current->identifier);
16007 snapshot_level_nr = level_nr;
16010 boolean CheckSaveEngineSnapshotToList(void)
16012 boolean save_snapshot =
16013 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16014 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16015 game.snapshot.changed_action) ||
16016 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16017 game.snapshot.collected_item));
16019 game.snapshot.changed_action = FALSE;
16020 game.snapshot.collected_item = FALSE;
16021 game.snapshot.save_snapshot = save_snapshot;
16023 return save_snapshot;
16026 void SaveEngineSnapshotToList(void)
16028 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16032 ListNode *buffers = SaveEngineSnapshotBuffers();
16034 // finally save all snapshot buffers to snapshot list
16035 SaveSnapshotToList(buffers);
16038 void SaveEngineSnapshotToListInitial(void)
16040 FreeEngineSnapshotList();
16042 SaveEngineSnapshotToList();
16045 static void LoadEngineSnapshotValues(void)
16047 // restore special values from snapshot structure
16049 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16050 LoadEngineSnapshotValues_RND();
16051 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16052 LoadEngineSnapshotValues_EM();
16053 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16054 LoadEngineSnapshotValues_SP();
16055 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16056 LoadEngineSnapshotValues_MM();
16059 void LoadEngineSnapshotSingle(void)
16061 LoadSnapshotSingle();
16063 LoadEngineSnapshotValues();
16066 static void LoadEngineSnapshot_Undo(int steps)
16068 LoadSnapshotFromList_Older(steps);
16070 LoadEngineSnapshotValues();
16073 static void LoadEngineSnapshot_Redo(int steps)
16075 LoadSnapshotFromList_Newer(steps);
16077 LoadEngineSnapshotValues();
16080 boolean CheckEngineSnapshotSingle(void)
16082 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16083 snapshot_level_nr == level_nr);
16086 boolean CheckEngineSnapshotList(void)
16088 return CheckSnapshotList();
16092 // ---------- new game button stuff -------------------------------------------
16099 boolean *setup_value;
16100 boolean allowed_on_tape;
16101 boolean is_touch_button;
16103 } gamebutton_info[NUM_GAME_BUTTONS] =
16106 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16107 GAME_CTRL_ID_STOP, NULL,
16108 TRUE, FALSE, "stop game"
16111 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16112 GAME_CTRL_ID_PAUSE, NULL,
16113 TRUE, FALSE, "pause game"
16116 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16117 GAME_CTRL_ID_PLAY, NULL,
16118 TRUE, FALSE, "play game"
16121 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16122 GAME_CTRL_ID_UNDO, NULL,
16123 TRUE, FALSE, "undo step"
16126 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16127 GAME_CTRL_ID_REDO, NULL,
16128 TRUE, FALSE, "redo step"
16131 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16132 GAME_CTRL_ID_SAVE, NULL,
16133 TRUE, FALSE, "save game"
16136 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16137 GAME_CTRL_ID_PAUSE2, NULL,
16138 TRUE, FALSE, "pause game"
16141 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16142 GAME_CTRL_ID_LOAD, NULL,
16143 TRUE, FALSE, "load game"
16146 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16147 GAME_CTRL_ID_PANEL_STOP, NULL,
16148 FALSE, FALSE, "stop game"
16151 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16152 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16153 FALSE, FALSE, "pause game"
16156 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16157 GAME_CTRL_ID_PANEL_PLAY, NULL,
16158 FALSE, FALSE, "play game"
16161 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16162 GAME_CTRL_ID_TOUCH_STOP, NULL,
16163 FALSE, TRUE, "stop game"
16166 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16167 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16168 FALSE, TRUE, "pause game"
16171 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16172 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16173 TRUE, FALSE, "background music on/off"
16176 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16177 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16178 TRUE, FALSE, "sound loops on/off"
16181 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16182 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16183 TRUE, FALSE, "normal sounds on/off"
16186 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16187 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16188 FALSE, FALSE, "background music on/off"
16191 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16192 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16193 FALSE, FALSE, "sound loops on/off"
16196 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16197 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16198 FALSE, FALSE, "normal sounds on/off"
16202 void CreateGameButtons(void)
16206 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16208 int graphic = gamebutton_info[i].graphic;
16209 struct GraphicInfo *gfx = &graphic_info[graphic];
16210 struct XY *pos = gamebutton_info[i].pos;
16211 struct GadgetInfo *gi;
16214 unsigned int event_mask;
16215 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16216 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16217 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16218 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16219 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16220 int gd_x = gfx->src_x;
16221 int gd_y = gfx->src_y;
16222 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16223 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16224 int gd_xa = gfx->src_x + gfx->active_xoffset;
16225 int gd_ya = gfx->src_y + gfx->active_yoffset;
16226 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16227 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16228 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16229 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16232 if (gfx->bitmap == NULL)
16234 game_gadget[id] = NULL;
16239 if (id == GAME_CTRL_ID_STOP ||
16240 id == GAME_CTRL_ID_PANEL_STOP ||
16241 id == GAME_CTRL_ID_TOUCH_STOP ||
16242 id == GAME_CTRL_ID_PLAY ||
16243 id == GAME_CTRL_ID_PANEL_PLAY ||
16244 id == GAME_CTRL_ID_SAVE ||
16245 id == GAME_CTRL_ID_LOAD)
16247 button_type = GD_TYPE_NORMAL_BUTTON;
16249 event_mask = GD_EVENT_RELEASED;
16251 else if (id == GAME_CTRL_ID_UNDO ||
16252 id == GAME_CTRL_ID_REDO)
16254 button_type = GD_TYPE_NORMAL_BUTTON;
16256 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16260 button_type = GD_TYPE_CHECK_BUTTON;
16261 checked = (gamebutton_info[i].setup_value != NULL ?
16262 *gamebutton_info[i].setup_value : FALSE);
16263 event_mask = GD_EVENT_PRESSED;
16266 gi = CreateGadget(GDI_CUSTOM_ID, id,
16267 GDI_IMAGE_ID, graphic,
16268 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16271 GDI_WIDTH, gfx->width,
16272 GDI_HEIGHT, gfx->height,
16273 GDI_TYPE, button_type,
16274 GDI_STATE, GD_BUTTON_UNPRESSED,
16275 GDI_CHECKED, checked,
16276 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16277 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16278 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16279 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16280 GDI_DIRECT_DRAW, FALSE,
16281 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16282 GDI_EVENT_MASK, event_mask,
16283 GDI_CALLBACK_ACTION, HandleGameButtons,
16287 Fail("cannot create gadget");
16289 game_gadget[id] = gi;
16293 void FreeGameButtons(void)
16297 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16298 FreeGadget(game_gadget[i]);
16301 static void UnmapGameButtonsAtSamePosition(int id)
16305 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16307 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16308 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16309 UnmapGadget(game_gadget[i]);
16312 static void UnmapGameButtonsAtSamePosition_All(void)
16314 if (setup.show_load_save_buttons)
16316 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16317 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16318 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16320 else if (setup.show_undo_redo_buttons)
16322 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16323 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16324 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16328 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16329 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16330 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16332 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16333 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16334 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16338 void MapLoadSaveButtons(void)
16340 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16341 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16343 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16344 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16347 void MapUndoRedoButtons(void)
16349 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16350 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16352 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16353 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16356 void ModifyPauseButtons(void)
16360 GAME_CTRL_ID_PAUSE,
16361 GAME_CTRL_ID_PAUSE2,
16362 GAME_CTRL_ID_PANEL_PAUSE,
16363 GAME_CTRL_ID_TOUCH_PAUSE,
16368 for (i = 0; ids[i] > -1; i++)
16369 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16372 static void MapGameButtonsExt(boolean on_tape)
16376 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16377 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16378 MapGadget(game_gadget[i]);
16380 UnmapGameButtonsAtSamePosition_All();
16382 RedrawGameButtons();
16385 static void UnmapGameButtonsExt(boolean on_tape)
16389 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16390 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16391 UnmapGadget(game_gadget[i]);
16394 static void RedrawGameButtonsExt(boolean on_tape)
16398 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16399 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16400 RedrawGadget(game_gadget[i]);
16403 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16408 gi->checked = state;
16411 static void RedrawSoundButtonGadget(int id)
16413 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16414 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16415 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16416 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16417 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16418 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16421 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16422 RedrawGadget(game_gadget[id2]);
16425 void MapGameButtons(void)
16427 MapGameButtonsExt(FALSE);
16430 void UnmapGameButtons(void)
16432 UnmapGameButtonsExt(FALSE);
16435 void RedrawGameButtons(void)
16437 RedrawGameButtonsExt(FALSE);
16440 void MapGameButtonsOnTape(void)
16442 MapGameButtonsExt(TRUE);
16445 void UnmapGameButtonsOnTape(void)
16447 UnmapGameButtonsExt(TRUE);
16450 void RedrawGameButtonsOnTape(void)
16452 RedrawGameButtonsExt(TRUE);
16455 static void GameUndoRedoExt(void)
16457 ClearPlayerAction();
16459 tape.pausing = TRUE;
16462 UpdateAndDisplayGameControlValues();
16464 DrawCompleteVideoDisplay();
16465 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16466 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16467 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16469 ModifyPauseButtons();
16474 static void GameUndo(int steps)
16476 if (!CheckEngineSnapshotList())
16479 int tape_property_bits = tape.property_bits;
16481 LoadEngineSnapshot_Undo(steps);
16483 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16488 static void GameRedo(int steps)
16490 if (!CheckEngineSnapshotList())
16493 int tape_property_bits = tape.property_bits;
16495 LoadEngineSnapshot_Redo(steps);
16497 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16502 static void HandleGameButtonsExt(int id, int button)
16504 static boolean game_undo_executed = FALSE;
16505 int steps = BUTTON_STEPSIZE(button);
16506 boolean handle_game_buttons =
16507 (game_status == GAME_MODE_PLAYING ||
16508 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16510 if (!handle_game_buttons)
16515 case GAME_CTRL_ID_STOP:
16516 case GAME_CTRL_ID_PANEL_STOP:
16517 case GAME_CTRL_ID_TOUCH_STOP:
16518 if (game_status == GAME_MODE_MAIN)
16524 RequestQuitGame(FALSE);
16528 case GAME_CTRL_ID_PAUSE:
16529 case GAME_CTRL_ID_PAUSE2:
16530 case GAME_CTRL_ID_PANEL_PAUSE:
16531 case GAME_CTRL_ID_TOUCH_PAUSE:
16532 if (network.enabled && game_status == GAME_MODE_PLAYING)
16535 SendToServer_ContinuePlaying();
16537 SendToServer_PausePlaying();
16540 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16542 game_undo_executed = FALSE;
16546 case GAME_CTRL_ID_PLAY:
16547 case GAME_CTRL_ID_PANEL_PLAY:
16548 if (game_status == GAME_MODE_MAIN)
16550 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16552 else if (tape.pausing)
16554 if (network.enabled)
16555 SendToServer_ContinuePlaying();
16557 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16561 case GAME_CTRL_ID_UNDO:
16562 // Important: When using "save snapshot when collecting an item" mode,
16563 // load last (current) snapshot for first "undo" after pressing "pause"
16564 // (else the last-but-one snapshot would be loaded, because the snapshot
16565 // pointer already points to the last snapshot when pressing "pause",
16566 // which is fine for "every step/move" mode, but not for "every collect")
16567 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16568 !game_undo_executed)
16571 game_undo_executed = TRUE;
16576 case GAME_CTRL_ID_REDO:
16580 case GAME_CTRL_ID_SAVE:
16584 case GAME_CTRL_ID_LOAD:
16588 case SOUND_CTRL_ID_MUSIC:
16589 case SOUND_CTRL_ID_PANEL_MUSIC:
16590 if (setup.sound_music)
16592 setup.sound_music = FALSE;
16596 else if (audio.music_available)
16598 setup.sound = setup.sound_music = TRUE;
16600 SetAudioMode(setup.sound);
16602 if (game_status == GAME_MODE_PLAYING)
16606 RedrawSoundButtonGadget(id);
16610 case SOUND_CTRL_ID_LOOPS:
16611 case SOUND_CTRL_ID_PANEL_LOOPS:
16612 if (setup.sound_loops)
16613 setup.sound_loops = FALSE;
16614 else if (audio.loops_available)
16616 setup.sound = setup.sound_loops = TRUE;
16618 SetAudioMode(setup.sound);
16621 RedrawSoundButtonGadget(id);
16625 case SOUND_CTRL_ID_SIMPLE:
16626 case SOUND_CTRL_ID_PANEL_SIMPLE:
16627 if (setup.sound_simple)
16628 setup.sound_simple = FALSE;
16629 else if (audio.sound_available)
16631 setup.sound = setup.sound_simple = TRUE;
16633 SetAudioMode(setup.sound);
16636 RedrawSoundButtonGadget(id);
16645 static void HandleGameButtons(struct GadgetInfo *gi)
16647 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16650 void HandleSoundButtonKeys(Key key)
16652 if (key == setup.shortcut.sound_simple)
16653 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16654 else if (key == setup.shortcut.sound_loops)
16655 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16656 else if (key == setup.shortcut.sound_music)
16657 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);