1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2031 else if (IS_EMPTY_ELEMENT(element))
2033 GfxElementEmpty[x][y] = element;
2034 Tile[x][y] = EL_EMPTY;
2036 if (element_info[element].use_gfx_element)
2037 game.use_masked_elements = TRUE;
2044 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2047 static void InitField_WithBug1(int x, int y, boolean init_game)
2049 InitField(x, y, init_game);
2051 // not needed to call InitMovDir() -- already done by InitField()!
2052 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2053 CAN_MOVE(Tile[x][y]))
2057 static void InitField_WithBug2(int x, int y, boolean init_game)
2059 int old_element = Tile[x][y];
2061 InitField(x, y, init_game);
2063 // not needed to call InitMovDir() -- already done by InitField()!
2064 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2065 CAN_MOVE(old_element) &&
2066 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2069 /* this case is in fact a combination of not less than three bugs:
2070 first, it calls InitMovDir() for elements that can move, although this is
2071 already done by InitField(); then, it checks the element that was at this
2072 field _before_ the call to InitField() (which can change it); lastly, it
2073 was not called for "mole with direction" elements, which were treated as
2074 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2078 static int get_key_element_from_nr(int key_nr)
2080 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2081 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2082 EL_EM_KEY_1 : EL_KEY_1);
2084 return key_base_element + key_nr;
2087 static int get_next_dropped_element(struct PlayerInfo *player)
2089 return (player->inventory_size > 0 ?
2090 player->inventory_element[player->inventory_size - 1] :
2091 player->inventory_infinite_element != EL_UNDEFINED ?
2092 player->inventory_infinite_element :
2093 player->dynabombs_left > 0 ?
2094 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2098 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2100 // pos >= 0: get element from bottom of the stack;
2101 // pos < 0: get element from top of the stack
2105 int min_inventory_size = -pos;
2106 int inventory_pos = player->inventory_size - min_inventory_size;
2107 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2109 return (player->inventory_size >= min_inventory_size ?
2110 player->inventory_element[inventory_pos] :
2111 player->inventory_infinite_element != EL_UNDEFINED ?
2112 player->inventory_infinite_element :
2113 player->dynabombs_left >= min_dynabombs_left ?
2114 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 int min_dynabombs_left = pos + 1;
2120 int min_inventory_size = pos + 1 - player->dynabombs_left;
2121 int inventory_pos = pos - player->dynabombs_left;
2123 return (player->inventory_infinite_element != EL_UNDEFINED ?
2124 player->inventory_infinite_element :
2125 player->dynabombs_left >= min_dynabombs_left ?
2126 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 player->inventory_size >= min_inventory_size ?
2128 player->inventory_element[inventory_pos] :
2133 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2135 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2136 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2139 if (gpo1->sort_priority != gpo2->sort_priority)
2140 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2142 compare_result = gpo1->nr - gpo2->nr;
2144 return compare_result;
2147 int getPlayerInventorySize(int player_nr)
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2150 return game_em.ply[player_nr]->dynamite;
2151 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2152 return game_sp.red_disk_count;
2154 return stored_player[player_nr].inventory_size;
2157 static void InitGameControlValues(void)
2161 for (i = 0; game_panel_controls[i].nr != -1; i++)
2163 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2164 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2165 struct TextPosInfo *pos = gpc->pos;
2167 int type = gpc->type;
2171 Error("'game_panel_controls' structure corrupted at %d", i);
2173 Fail("this should not happen -- please debug");
2176 // force update of game controls after initialization
2177 gpc->value = gpc->last_value = -1;
2178 gpc->frame = gpc->last_frame = -1;
2179 gpc->gfx_frame = -1;
2181 // determine panel value width for later calculation of alignment
2182 if (type == TYPE_INTEGER || type == TYPE_STRING)
2184 pos->width = pos->size * getFontWidth(pos->font);
2185 pos->height = getFontHeight(pos->font);
2187 else if (type == TYPE_ELEMENT)
2189 pos->width = pos->size;
2190 pos->height = pos->size;
2193 // fill structure for game panel draw order
2195 gpo->sort_priority = pos->sort_priority;
2198 // sort game panel controls according to sort_priority and control number
2199 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2200 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2203 static void UpdatePlayfieldElementCount(void)
2205 boolean use_element_count = FALSE;
2208 // first check if it is needed at all to calculate playfield element count
2209 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2210 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2211 use_element_count = TRUE;
2213 if (!use_element_count)
2216 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2217 element_info[i].element_count = 0;
2219 SCAN_PLAYFIELD(x, y)
2221 element_info[Tile[x][y]].element_count++;
2224 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2225 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2226 if (IS_IN_GROUP(j, i))
2227 element_info[EL_GROUP_START + i].element_count +=
2228 element_info[j].element_count;
2231 static void UpdateGameControlValues(void)
2234 int time = (game.LevelSolved ?
2235 game.LevelSolved_CountingTime :
2236 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2239 game_sp.time_played :
2240 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 game_mm.energy_left :
2242 game.no_level_time_limit ? TimePlayed : TimeLeft);
2243 int score = (game.LevelSolved ?
2244 game.LevelSolved_CountingScore :
2245 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 game_em.lev->score :
2247 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2249 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2253 game_em.lev->gems_needed :
2254 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2255 game_sp.infotrons_still_needed :
2256 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2257 game_mm.kettles_still_needed :
2258 game.gems_still_needed);
2259 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2260 game_em.lev->gems_needed > 0 :
2261 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2262 game_sp.infotrons_still_needed > 0 :
2263 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2264 game_mm.kettles_still_needed > 0 ||
2265 game_mm.lights_still_needed > 0 :
2266 game.gems_still_needed > 0 ||
2267 game.sokoban_fields_still_needed > 0 ||
2268 game.sokoban_objects_still_needed > 0 ||
2269 game.lights_still_needed > 0);
2270 int health = (game.LevelSolved ?
2271 game.LevelSolved_CountingHealth :
2272 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2273 MM_HEALTH(game_mm.laser_overload_value) :
2275 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2277 UpdatePlayfieldElementCount();
2279 // update game panel control values
2281 // used instead of "level_nr" (for network games)
2282 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2283 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2285 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2286 for (i = 0; i < MAX_NUM_KEYS; i++)
2287 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2288 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2289 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2291 if (game.centered_player_nr == -1)
2293 for (i = 0; i < MAX_PLAYERS; i++)
2295 // only one player in Supaplex game engine
2296 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2299 for (k = 0; k < MAX_NUM_KEYS; k++)
2301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2303 if (game_em.ply[i]->keys & (1 << k))
2304 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2305 get_key_element_from_nr(k);
2307 else if (stored_player[i].key[k])
2308 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2309 get_key_element_from_nr(k);
2312 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2313 getPlayerInventorySize(i);
2315 if (stored_player[i].num_white_keys > 0)
2316 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2319 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2320 stored_player[i].num_white_keys;
2325 int player_nr = game.centered_player_nr;
2327 for (k = 0; k < MAX_NUM_KEYS; k++)
2329 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2331 if (game_em.ply[player_nr]->keys & (1 << k))
2332 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2333 get_key_element_from_nr(k);
2335 else if (stored_player[player_nr].key[k])
2336 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2337 get_key_element_from_nr(k);
2340 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2341 getPlayerInventorySize(player_nr);
2343 if (stored_player[player_nr].num_white_keys > 0)
2344 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2346 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2347 stored_player[player_nr].num_white_keys;
2350 // re-arrange keys on game panel, if needed or if defined by style settings
2351 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2353 int nr = GAME_PANEL_KEY_1 + i;
2354 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2355 struct TextPosInfo *pos = gpc->pos;
2357 // skip check if key is not in the player's inventory
2358 if (gpc->value == EL_EMPTY)
2361 // check if keys should be arranged on panel from left to right
2362 if (pos->style == STYLE_LEFTMOST_POSITION)
2364 // check previous key positions (left from current key)
2365 for (k = 0; k < i; k++)
2367 int nr_new = GAME_PANEL_KEY_1 + k;
2369 if (game_panel_controls[nr_new].value == EL_EMPTY)
2371 game_panel_controls[nr_new].value = gpc->value;
2372 gpc->value = EL_EMPTY;
2379 // check if "undefined" keys can be placed at some other position
2380 if (pos->x == -1 && pos->y == -1)
2382 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2384 // 1st try: display key at the same position as normal or EM keys
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2391 // 2nd try: display key at the next free position in the key panel
2392 for (k = 0; k < STD_NUM_KEYS; k++)
2394 nr_new = GAME_PANEL_KEY_1 + k;
2396 if (game_panel_controls[nr_new].value == EL_EMPTY)
2398 game_panel_controls[nr_new].value = gpc->value;
2407 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2409 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2410 get_inventory_element_from_pos(local_player, i);
2411 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2412 get_inventory_element_from_pos(local_player, -i - 1);
2415 game_panel_controls[GAME_PANEL_SCORE].value = score;
2416 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2418 game_panel_controls[GAME_PANEL_TIME].value = time;
2420 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2421 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2422 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2424 if (level.time == 0)
2425 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2427 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2429 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2430 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2432 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2434 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2435 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2437 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2438 local_player->shield_normal_time_left;
2439 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2440 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2442 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2443 local_player->shield_deadly_time_left;
2445 game_panel_controls[GAME_PANEL_EXIT].value =
2446 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2448 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2449 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2450 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2451 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2452 EL_EMC_MAGIC_BALL_SWITCH);
2454 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2455 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2456 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2457 game.light_time_left;
2459 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2460 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2461 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2462 game.timegate_time_left;
2464 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2465 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2467 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2468 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2469 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2470 game.lenses_time_left;
2472 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2473 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2474 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2475 game.magnify_time_left;
2477 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2478 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2479 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2480 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2481 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2482 EL_BALLOON_SWITCH_NONE);
2484 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2485 local_player->dynabomb_count;
2486 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2487 local_player->dynabomb_size;
2488 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2489 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2491 game_panel_controls[GAME_PANEL_PENGUINS].value =
2492 game.friends_still_needed;
2494 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2495 game.sokoban_objects_still_needed;
2496 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2497 game.sokoban_fields_still_needed;
2499 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2500 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2502 for (i = 0; i < NUM_BELTS; i++)
2504 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2505 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2506 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2507 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2508 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2511 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2512 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2513 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2514 game.magic_wall_time_left;
2516 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2517 local_player->gravity;
2519 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2520 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2522 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2523 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2524 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2525 game.panel.element[i].id : EL_UNDEFINED);
2527 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2528 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2529 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2530 element_info[game.panel.element_count[i].id].element_count : 0);
2532 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2533 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2534 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2535 element_info[game.panel.ce_score[i].id].collect_score : 0);
2537 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2538 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2539 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2540 element_info[game.panel.ce_score_element[i].id].collect_score :
2543 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2544 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2545 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2547 // update game panel control frames
2549 for (i = 0; game_panel_controls[i].nr != -1; i++)
2551 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2553 if (gpc->type == TYPE_ELEMENT)
2555 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2557 int last_anim_random_frame = gfx.anim_random_frame;
2558 int element = gpc->value;
2559 int graphic = el2panelimg(element);
2560 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2561 sync_random_frame : INIT_GFX_RANDOM());
2563 if (gpc->value != gpc->last_value)
2566 gpc->gfx_random = init_gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2573 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2574 gpc->gfx_random = init_gfx_random;
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = gpc->gfx_random;
2580 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2581 gpc->gfx_frame = element_info[element].collect_score;
2583 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2585 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2586 gfx.anim_random_frame = last_anim_random_frame;
2589 else if (gpc->type == TYPE_GRAPHIC)
2591 if (gpc->graphic != IMG_UNDEFINED)
2593 int last_anim_random_frame = gfx.anim_random_frame;
2594 int graphic = gpc->graphic;
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2596 sync_random_frame : INIT_GFX_RANDOM());
2598 if (gpc->value != gpc->last_value)
2601 gpc->gfx_random = init_gfx_random;
2607 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2608 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2609 gpc->gfx_random = init_gfx_random;
2612 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2613 gfx.anim_random_frame = gpc->gfx_random;
2615 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2617 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2618 gfx.anim_random_frame = last_anim_random_frame;
2624 static void DisplayGameControlValues(void)
2626 boolean redraw_panel = FALSE;
2629 for (i = 0; game_panel_controls[i].nr != -1; i++)
2631 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2633 if (PANEL_DEACTIVATED(gpc->pos))
2636 if (gpc->value == gpc->last_value &&
2637 gpc->frame == gpc->last_frame)
2640 redraw_panel = TRUE;
2646 // copy default game door content to main double buffer
2648 // !!! CHECK AGAIN !!!
2649 SetPanelBackground();
2650 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2651 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2653 // redraw game control buttons
2654 RedrawGameButtons();
2656 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2658 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2660 int nr = game_panel_order[i].nr;
2661 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2662 struct TextPosInfo *pos = gpc->pos;
2663 int type = gpc->type;
2664 int value = gpc->value;
2665 int frame = gpc->frame;
2666 int size = pos->size;
2667 int font = pos->font;
2668 boolean draw_masked = pos->draw_masked;
2669 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2671 if (PANEL_DEACTIVATED(pos))
2674 if (pos->class == get_hash_from_key("extra_panel_items") &&
2675 !setup.prefer_extra_panel_items)
2678 gpc->last_value = value;
2679 gpc->last_frame = frame;
2681 if (type == TYPE_INTEGER)
2683 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2684 nr == GAME_PANEL_INVENTORY_COUNT ||
2685 nr == GAME_PANEL_SCORE ||
2686 nr == GAME_PANEL_HIGHSCORE ||
2687 nr == GAME_PANEL_TIME)
2689 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2691 if (use_dynamic_size) // use dynamic number of digits
2693 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2694 nr == GAME_PANEL_INVENTORY_COUNT ||
2695 nr == GAME_PANEL_TIME ? 1000 : 100000);
2696 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2697 nr == GAME_PANEL_INVENTORY_COUNT ||
2698 nr == GAME_PANEL_TIME ? 1 : 2);
2699 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2700 nr == GAME_PANEL_INVENTORY_COUNT ||
2701 nr == GAME_PANEL_TIME ? 3 : 5);
2702 int size2 = size1 + size_add;
2703 int font1 = pos->font;
2704 int font2 = pos->font_alt;
2706 size = (value < value_change ? size1 : size2);
2707 font = (value < value_change ? font1 : font2);
2711 // correct text size if "digits" is zero or less
2713 size = strlen(int2str(value, size));
2715 // dynamically correct text alignment
2716 pos->width = size * getFontWidth(font);
2718 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2719 int2str(value, size), font, mask_mode);
2721 else if (type == TYPE_ELEMENT)
2723 int element, graphic;
2727 int dst_x = PANEL_XPOS(pos);
2728 int dst_y = PANEL_YPOS(pos);
2730 if (value != EL_UNDEFINED && value != EL_EMPTY)
2733 graphic = el2panelimg(value);
2736 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2737 element, EL_NAME(element), size);
2740 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2743 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2746 width = graphic_info[graphic].width * size / TILESIZE;
2747 height = graphic_info[graphic].height * size / TILESIZE;
2750 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2753 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2757 else if (type == TYPE_GRAPHIC)
2759 int graphic = gpc->graphic;
2760 int graphic_active = gpc->graphic_active;
2764 int dst_x = PANEL_XPOS(pos);
2765 int dst_y = PANEL_YPOS(pos);
2766 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2767 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2769 if (graphic != IMG_UNDEFINED && !skip)
2771 if (pos->style == STYLE_REVERSE)
2772 value = 100 - value;
2774 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2776 if (pos->direction & MV_HORIZONTAL)
2778 width = graphic_info[graphic_active].width * value / 100;
2779 height = graphic_info[graphic_active].height;
2781 if (pos->direction == MV_LEFT)
2783 src_x += graphic_info[graphic_active].width - width;
2784 dst_x += graphic_info[graphic_active].width - width;
2789 width = graphic_info[graphic_active].width;
2790 height = graphic_info[graphic_active].height * value / 100;
2792 if (pos->direction == MV_UP)
2794 src_y += graphic_info[graphic_active].height - height;
2795 dst_y += graphic_info[graphic_active].height - height;
2800 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2803 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2806 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2808 if (pos->direction & MV_HORIZONTAL)
2810 if (pos->direction == MV_RIGHT)
2817 dst_x = PANEL_XPOS(pos);
2820 width = graphic_info[graphic].width - width;
2824 if (pos->direction == MV_DOWN)
2831 dst_y = PANEL_YPOS(pos);
2834 height = graphic_info[graphic].height - height;
2838 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2841 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2845 else if (type == TYPE_STRING)
2847 boolean active = (value != 0);
2848 char *state_normal = "off";
2849 char *state_active = "on";
2850 char *state = (active ? state_active : state_normal);
2851 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2852 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2853 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2854 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2856 if (nr == GAME_PANEL_GRAVITY_STATE)
2858 int font1 = pos->font; // (used for normal state)
2859 int font2 = pos->font_alt; // (used for active state)
2861 font = (active ? font2 : font1);
2870 // don't truncate output if "chars" is zero or less
2873 // dynamically correct text alignment
2874 pos->width = size * getFontWidth(font);
2877 s_cut = getStringCopyN(s, size);
2879 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2880 s_cut, font, mask_mode);
2886 redraw_mask |= REDRAW_DOOR_1;
2889 SetGameStatus(GAME_MODE_PLAYING);
2892 void UpdateAndDisplayGameControlValues(void)
2894 if (tape.deactivate_display)
2897 UpdateGameControlValues();
2898 DisplayGameControlValues();
2901 void UpdateGameDoorValues(void)
2903 UpdateGameControlValues();
2906 void DrawGameDoorValues(void)
2908 DisplayGameControlValues();
2912 // ============================================================================
2914 // ----------------------------------------------------------------------------
2915 // initialize game engine due to level / tape version number
2916 // ============================================================================
2918 static void InitGameEngine(void)
2920 int i, j, k, l, x, y;
2922 // set game engine from tape file when re-playing, else from level file
2923 game.engine_version = (tape.playing ? tape.engine_version :
2924 level.game_version);
2926 // set single or multi-player game mode (needed for re-playing tapes)
2927 game.team_mode = setup.team_mode;
2931 int num_players = 0;
2933 for (i = 0; i < MAX_PLAYERS; i++)
2934 if (tape.player_participates[i])
2937 // multi-player tapes contain input data for more than one player
2938 game.team_mode = (num_players > 1);
2942 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2943 level.game_version);
2944 Debug("game:init:level", " tape.file_version == %06d",
2946 Debug("game:init:level", " tape.game_version == %06d",
2948 Debug("game:init:level", " tape.engine_version == %06d",
2949 tape.engine_version);
2950 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2951 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2954 // --------------------------------------------------------------------------
2955 // set flags for bugs and changes according to active game engine version
2956 // --------------------------------------------------------------------------
2960 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2962 Bug was introduced in version:
2965 Bug was fixed in version:
2969 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2970 but the property "can fall" was missing, which caused some levels to be
2971 unsolvable. This was fixed in version 4.2.0.0.
2973 Affected levels/tapes:
2974 An example for a tape that was fixed by this bugfix is tape 029 from the
2975 level set "rnd_sam_bateman".
2976 The wrong behaviour will still be used for all levels or tapes that were
2977 created/recorded with it. An example for this is tape 023 from the level
2978 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2981 boolean use_amoeba_dropping_cannot_fall_bug =
2982 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2983 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2985 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2986 tape.game_version < VERSION_IDENT(4,2,0,0)));
2989 Summary of bugfix/change:
2990 Fixed move speed of elements entering or leaving magic wall.
2992 Fixed/changed in version:
2996 Before 2.0.1, move speed of elements entering or leaving magic wall was
2997 twice as fast as it is now.
2998 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3000 Affected levels/tapes:
3001 The first condition is generally needed for all levels/tapes before version
3002 2.0.1, which might use the old behaviour before it was changed; known tapes
3003 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3004 The second condition is an exception from the above case and is needed for
3005 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3006 above, but before it was known that this change would break tapes like the
3007 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3008 although the engine version while recording maybe was before 2.0.1. There
3009 are a lot of tapes that are affected by this exception, like tape 006 from
3010 the level set "rnd_conor_mancone".
3013 boolean use_old_move_stepsize_for_magic_wall =
3014 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3016 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3017 tape.game_version < VERSION_IDENT(4,2,0,0)));
3020 Summary of bugfix/change:
3021 Fixed handling for custom elements that change when pushed by the player.
3023 Fixed/changed in version:
3027 Before 3.1.0, custom elements that "change when pushing" changed directly
3028 after the player started pushing them (until then handled in "DigField()").
3029 Since 3.1.0, these custom elements are not changed until the "pushing"
3030 move of the element is finished (now handled in "ContinueMoving()").
3032 Affected levels/tapes:
3033 The first condition is generally needed for all levels/tapes before version
3034 3.1.0, which might use the old behaviour before it was changed; known tapes
3035 that are affected are some tapes from the level set "Walpurgis Gardens" by
3037 The second condition is an exception from the above case and is needed for
3038 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3039 above (including some development versions of 3.1.0), but before it was
3040 known that this change would break tapes like the above and was fixed in
3041 3.1.1, so that the changed behaviour was active although the engine version
3042 while recording maybe was before 3.1.0. There is at least one tape that is
3043 affected by this exception, which is the tape for the one-level set "Bug
3044 Machine" by Juergen Bonhagen.
3047 game.use_change_when_pushing_bug =
3048 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3050 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3051 tape.game_version < VERSION_IDENT(3,1,1,0)));
3054 Summary of bugfix/change:
3055 Fixed handling for blocking the field the player leaves when moving.
3057 Fixed/changed in version:
3061 Before 3.1.1, when "block last field when moving" was enabled, the field
3062 the player is leaving when moving was blocked for the time of the move,
3063 and was directly unblocked afterwards. This resulted in the last field
3064 being blocked for exactly one less than the number of frames of one player
3065 move. Additionally, even when blocking was disabled, the last field was
3066 blocked for exactly one frame.
3067 Since 3.1.1, due to changes in player movement handling, the last field
3068 is not blocked at all when blocking is disabled. When blocking is enabled,
3069 the last field is blocked for exactly the number of frames of one player
3070 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3071 last field is blocked for exactly one more than the number of frames of
3074 Affected levels/tapes:
3075 (!!! yet to be determined -- probably many !!!)
3078 game.use_block_last_field_bug =
3079 (game.engine_version < VERSION_IDENT(3,1,1,0));
3081 /* various special flags and settings for native Emerald Mine game engine */
3083 game_em.use_single_button =
3084 (game.engine_version > VERSION_IDENT(4,0,0,2));
3086 game_em.use_snap_key_bug =
3087 (game.engine_version < VERSION_IDENT(4,0,1,0));
3089 game_em.use_random_bug =
3090 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3092 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3094 game_em.use_old_explosions = use_old_em_engine;
3095 game_em.use_old_android = use_old_em_engine;
3096 game_em.use_old_push_elements = use_old_em_engine;
3097 game_em.use_old_push_into_acid = use_old_em_engine;
3099 game_em.use_wrap_around = !use_old_em_engine;
3101 // --------------------------------------------------------------------------
3103 // set maximal allowed number of custom element changes per game frame
3104 game.max_num_changes_per_frame = 1;
3106 // default scan direction: scan playfield from top/left to bottom/right
3107 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3109 // dynamically adjust element properties according to game engine version
3110 InitElementPropertiesEngine(game.engine_version);
3112 // ---------- initialize special element properties -------------------------
3114 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3115 if (use_amoeba_dropping_cannot_fall_bug)
3116 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3118 // ---------- initialize player's initial move delay ------------------------
3120 // dynamically adjust player properties according to level information
3121 for (i = 0; i < MAX_PLAYERS; i++)
3122 game.initial_move_delay_value[i] =
3123 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3125 // dynamically adjust player properties according to game engine version
3126 for (i = 0; i < MAX_PLAYERS; i++)
3127 game.initial_move_delay[i] =
3128 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3129 game.initial_move_delay_value[i] : 0);
3131 // ---------- initialize player's initial push delay ------------------------
3133 // dynamically adjust player properties according to game engine version
3134 game.initial_push_delay_value =
3135 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3137 // ---------- initialize changing elements ----------------------------------
3139 // initialize changing elements information
3140 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3142 struct ElementInfo *ei = &element_info[i];
3144 // this pointer might have been changed in the level editor
3145 ei->change = &ei->change_page[0];
3147 if (!IS_CUSTOM_ELEMENT(i))
3149 ei->change->target_element = EL_EMPTY_SPACE;
3150 ei->change->delay_fixed = 0;
3151 ei->change->delay_random = 0;
3152 ei->change->delay_frames = 1;
3155 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3157 ei->has_change_event[j] = FALSE;
3159 ei->event_page_nr[j] = 0;
3160 ei->event_page[j] = &ei->change_page[0];
3164 // add changing elements from pre-defined list
3165 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3167 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3168 struct ElementInfo *ei = &element_info[ch_delay->element];
3170 ei->change->target_element = ch_delay->target_element;
3171 ei->change->delay_fixed = ch_delay->change_delay;
3173 ei->change->pre_change_function = ch_delay->pre_change_function;
3174 ei->change->change_function = ch_delay->change_function;
3175 ei->change->post_change_function = ch_delay->post_change_function;
3177 ei->change->can_change = TRUE;
3178 ei->change->can_change_or_has_action = TRUE;
3180 ei->has_change_event[CE_DELAY] = TRUE;
3182 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3183 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3186 // ---------- initialize internal run-time variables ------------------------
3188 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3190 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3192 for (j = 0; j < ei->num_change_pages; j++)
3194 ei->change_page[j].can_change_or_has_action =
3195 (ei->change_page[j].can_change |
3196 ei->change_page[j].has_action);
3200 // add change events from custom element configuration
3201 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3203 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3205 for (j = 0; j < ei->num_change_pages; j++)
3207 if (!ei->change_page[j].can_change_or_has_action)
3210 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3212 // only add event page for the first page found with this event
3213 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3215 ei->has_change_event[k] = TRUE;
3217 ei->event_page_nr[k] = j;
3218 ei->event_page[k] = &ei->change_page[j];
3224 // ---------- initialize reference elements in change conditions ------------
3226 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3228 int element = EL_CUSTOM_START + i;
3229 struct ElementInfo *ei = &element_info[element];
3231 for (j = 0; j < ei->num_change_pages; j++)
3233 int trigger_element = ei->change_page[j].initial_trigger_element;
3235 if (trigger_element >= EL_PREV_CE_8 &&
3236 trigger_element <= EL_NEXT_CE_8)
3237 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3239 ei->change_page[j].trigger_element = trigger_element;
3243 // ---------- initialize run-time trigger player and element ----------------
3245 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3247 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3249 for (j = 0; j < ei->num_change_pages; j++)
3251 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3252 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3253 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3254 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3255 ei->change_page[j].actual_trigger_ce_value = 0;
3256 ei->change_page[j].actual_trigger_ce_score = 0;
3260 // ---------- initialize trigger events -------------------------------------
3262 // initialize trigger events information
3263 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3264 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3265 trigger_events[i][j] = FALSE;
3267 // add trigger events from element change event properties
3268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3270 struct ElementInfo *ei = &element_info[i];
3272 for (j = 0; j < ei->num_change_pages; j++)
3274 if (!ei->change_page[j].can_change_or_has_action)
3277 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3279 int trigger_element = ei->change_page[j].trigger_element;
3281 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3283 if (ei->change_page[j].has_event[k])
3285 if (IS_GROUP_ELEMENT(trigger_element))
3287 struct ElementGroupInfo *group =
3288 element_info[trigger_element].group;
3290 for (l = 0; l < group->num_elements_resolved; l++)
3291 trigger_events[group->element_resolved[l]][k] = TRUE;
3293 else if (trigger_element == EL_ANY_ELEMENT)
3294 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3295 trigger_events[l][k] = TRUE;
3297 trigger_events[trigger_element][k] = TRUE;
3304 // ---------- initialize push delay -----------------------------------------
3306 // initialize push delay values to default
3307 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3309 if (!IS_CUSTOM_ELEMENT(i))
3311 // set default push delay values (corrected since version 3.0.7-1)
3312 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3314 element_info[i].push_delay_fixed = 2;
3315 element_info[i].push_delay_random = 8;
3319 element_info[i].push_delay_fixed = 8;
3320 element_info[i].push_delay_random = 8;
3325 // set push delay value for certain elements from pre-defined list
3326 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3328 int e = push_delay_list[i].element;
3330 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3331 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3334 // set push delay value for Supaplex elements for newer engine versions
3335 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3337 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3339 if (IS_SP_ELEMENT(i))
3341 // set SP push delay to just enough to push under a falling zonk
3342 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3344 element_info[i].push_delay_fixed = delay;
3345 element_info[i].push_delay_random = 0;
3350 // ---------- initialize move stepsize --------------------------------------
3352 // initialize move stepsize values to default
3353 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3354 if (!IS_CUSTOM_ELEMENT(i))
3355 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3357 // set move stepsize value for certain elements from pre-defined list
3358 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3360 int e = move_stepsize_list[i].element;
3362 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3364 // set move stepsize value for certain elements for older engine versions
3365 if (use_old_move_stepsize_for_magic_wall)
3367 if (e == EL_MAGIC_WALL_FILLING ||
3368 e == EL_MAGIC_WALL_EMPTYING ||
3369 e == EL_BD_MAGIC_WALL_FILLING ||
3370 e == EL_BD_MAGIC_WALL_EMPTYING)
3371 element_info[e].move_stepsize *= 2;
3375 // ---------- initialize collect score --------------------------------------
3377 // initialize collect score values for custom elements from initial value
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3379 if (IS_CUSTOM_ELEMENT(i))
3380 element_info[i].collect_score = element_info[i].collect_score_initial;
3382 // ---------- initialize collect count --------------------------------------
3384 // initialize collect count values for non-custom elements
3385 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3386 if (!IS_CUSTOM_ELEMENT(i))
3387 element_info[i].collect_count_initial = 0;
3389 // add collect count values for all elements from pre-defined list
3390 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3391 element_info[collect_count_list[i].element].collect_count_initial =
3392 collect_count_list[i].count;
3394 // ---------- initialize access direction -----------------------------------
3396 // initialize access direction values to default (access from every side)
3397 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3398 if (!IS_CUSTOM_ELEMENT(i))
3399 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3401 // set access direction value for certain elements from pre-defined list
3402 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3403 element_info[access_direction_list[i].element].access_direction =
3404 access_direction_list[i].direction;
3406 // ---------- initialize explosion content ----------------------------------
3407 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3409 if (IS_CUSTOM_ELEMENT(i))
3412 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3414 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3416 element_info[i].content.e[x][y] =
3417 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3418 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3419 i == EL_PLAYER_3 ? EL_EMERALD :
3420 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3421 i == EL_MOLE ? EL_EMERALD_RED :
3422 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3423 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3424 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3425 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3426 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3427 i == EL_WALL_EMERALD ? EL_EMERALD :
3428 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3429 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3430 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3431 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3432 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3433 i == EL_WALL_PEARL ? EL_PEARL :
3434 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3439 // ---------- initialize recursion detection --------------------------------
3440 recursion_loop_depth = 0;
3441 recursion_loop_detected = FALSE;
3442 recursion_loop_element = EL_UNDEFINED;
3444 // ---------- initialize graphics engine ------------------------------------
3445 game.scroll_delay_value =
3446 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3447 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3448 !setup.forced_scroll_delay ? 0 :
3449 setup.scroll_delay ? setup.scroll_delay_value : 0);
3450 game.scroll_delay_value =
3451 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3453 // ---------- initialize game engine snapshots ------------------------------
3454 for (i = 0; i < MAX_PLAYERS; i++)
3455 game.snapshot.last_action[i] = 0;
3456 game.snapshot.changed_action = FALSE;
3457 game.snapshot.collected_item = FALSE;
3458 game.snapshot.mode =
3459 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3460 SNAPSHOT_MODE_EVERY_STEP :
3461 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3462 SNAPSHOT_MODE_EVERY_MOVE :
3463 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3464 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3465 game.snapshot.save_snapshot = FALSE;
3467 // ---------- initialize level time for Supaplex engine ---------------------
3468 // Supaplex levels with time limit currently unsupported -- should be added
3469 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3472 // ---------- initialize flags for handling game actions --------------------
3474 // set flags for game actions to default values
3475 game.use_key_actions = TRUE;
3476 game.use_mouse_actions = FALSE;
3478 // when using Mirror Magic game engine, handle mouse events only
3479 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3481 game.use_key_actions = FALSE;
3482 game.use_mouse_actions = TRUE;
3485 // check for custom elements with mouse click events
3486 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3488 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3490 int element = EL_CUSTOM_START + i;
3492 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3493 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3494 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3495 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3496 game.use_mouse_actions = TRUE;
3501 static int get_num_special_action(int element, int action_first,
3504 int num_special_action = 0;
3507 for (i = action_first; i <= action_last; i++)
3509 boolean found = FALSE;
3511 for (j = 0; j < NUM_DIRECTIONS; j++)
3512 if (el_act_dir2img(element, i, j) !=
3513 el_act_dir2img(element, ACTION_DEFAULT, j))
3517 num_special_action++;
3522 return num_special_action;
3526 // ============================================================================
3528 // ----------------------------------------------------------------------------
3529 // initialize and start new game
3530 // ============================================================================
3532 #if DEBUG_INIT_PLAYER
3533 static void DebugPrintPlayerStatus(char *message)
3540 Debug("game:init:player", "%s:", message);
3542 for (i = 0; i < MAX_PLAYERS; i++)
3544 struct PlayerInfo *player = &stored_player[i];
3546 Debug("game:init:player",
3547 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3551 player->connected_locally,
3552 player->connected_network,
3554 (local_player == player ? " (local player)" : ""));
3561 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3562 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3563 int fade_mask = REDRAW_FIELD;
3565 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3566 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3567 int initial_move_dir = MV_DOWN;
3570 // required here to update video display before fading (FIX THIS)
3571 DrawMaskedBorder(REDRAW_DOOR_2);
3573 if (!game.restart_level)
3574 CloseDoor(DOOR_CLOSE_1);
3576 SetGameStatus(GAME_MODE_PLAYING);
3578 if (level_editor_test_game)
3579 FadeSkipNextFadeOut();
3581 FadeSetEnterScreen();
3584 fade_mask = REDRAW_ALL;
3586 FadeLevelSoundsAndMusic();
3588 ExpireSoundLoops(TRUE);
3592 if (level_editor_test_game)
3593 FadeSkipNextFadeIn();
3595 // needed if different viewport properties defined for playing
3596 ChangeViewportPropertiesIfNeeded();
3600 DrawCompleteVideoDisplay();
3602 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3605 InitGameControlValues();
3609 // initialize tape actions from game when recording tape
3610 tape.use_key_actions = game.use_key_actions;
3611 tape.use_mouse_actions = game.use_mouse_actions;
3613 // initialize visible playfield size when recording tape (for team mode)
3614 tape.scr_fieldx = SCR_FIELDX;
3615 tape.scr_fieldy = SCR_FIELDY;
3618 // don't play tapes over network
3619 network_playing = (network.enabled && !tape.playing);
3621 for (i = 0; i < MAX_PLAYERS; i++)
3623 struct PlayerInfo *player = &stored_player[i];
3625 player->index_nr = i;
3626 player->index_bit = (1 << i);
3627 player->element_nr = EL_PLAYER_1 + i;
3629 player->present = FALSE;
3630 player->active = FALSE;
3631 player->mapped = FALSE;
3633 player->killed = FALSE;
3634 player->reanimated = FALSE;
3635 player->buried = FALSE;
3638 player->effective_action = 0;
3639 player->programmed_action = 0;
3640 player->snap_action = 0;
3642 player->mouse_action.lx = 0;
3643 player->mouse_action.ly = 0;
3644 player->mouse_action.button = 0;
3645 player->mouse_action.button_hint = 0;
3647 player->effective_mouse_action.lx = 0;
3648 player->effective_mouse_action.ly = 0;
3649 player->effective_mouse_action.button = 0;
3650 player->effective_mouse_action.button_hint = 0;
3652 for (j = 0; j < MAX_NUM_KEYS; j++)
3653 player->key[j] = FALSE;
3655 player->num_white_keys = 0;
3657 player->dynabomb_count = 0;
3658 player->dynabomb_size = 1;
3659 player->dynabombs_left = 0;
3660 player->dynabomb_xl = FALSE;
3662 player->MovDir = initial_move_dir;
3665 player->GfxDir = initial_move_dir;
3666 player->GfxAction = ACTION_DEFAULT;
3668 player->StepFrame = 0;
3670 player->initial_element = player->element_nr;
3671 player->artwork_element =
3672 (level.use_artwork_element[i] ? level.artwork_element[i] :
3673 player->element_nr);
3674 player->use_murphy = FALSE;
3676 player->block_last_field = FALSE; // initialized in InitPlayerField()
3677 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3679 player->gravity = level.initial_player_gravity[i];
3681 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3683 player->actual_frame_counter = 0;
3685 player->step_counter = 0;
3687 player->last_move_dir = initial_move_dir;
3689 player->is_active = FALSE;
3691 player->is_waiting = FALSE;
3692 player->is_moving = FALSE;
3693 player->is_auto_moving = FALSE;
3694 player->is_digging = FALSE;
3695 player->is_snapping = FALSE;
3696 player->is_collecting = FALSE;
3697 player->is_pushing = FALSE;
3698 player->is_switching = FALSE;
3699 player->is_dropping = FALSE;
3700 player->is_dropping_pressed = FALSE;
3702 player->is_bored = FALSE;
3703 player->is_sleeping = FALSE;
3705 player->was_waiting = TRUE;
3706 player->was_moving = FALSE;
3707 player->was_snapping = FALSE;
3708 player->was_dropping = FALSE;
3710 player->force_dropping = FALSE;
3712 player->frame_counter_bored = -1;
3713 player->frame_counter_sleeping = -1;
3715 player->anim_delay_counter = 0;
3716 player->post_delay_counter = 0;
3718 player->dir_waiting = initial_move_dir;
3719 player->action_waiting = ACTION_DEFAULT;
3720 player->last_action_waiting = ACTION_DEFAULT;
3721 player->special_action_bored = ACTION_DEFAULT;
3722 player->special_action_sleeping = ACTION_DEFAULT;
3724 player->switch_x = -1;
3725 player->switch_y = -1;
3727 player->drop_x = -1;
3728 player->drop_y = -1;
3730 player->show_envelope = 0;
3732 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3734 player->push_delay = -1; // initialized when pushing starts
3735 player->push_delay_value = game.initial_push_delay_value;
3737 player->drop_delay = 0;
3738 player->drop_pressed_delay = 0;
3740 player->last_jx = -1;
3741 player->last_jy = -1;
3745 player->shield_normal_time_left = 0;
3746 player->shield_deadly_time_left = 0;
3748 player->last_removed_element = EL_UNDEFINED;
3750 player->inventory_infinite_element = EL_UNDEFINED;
3751 player->inventory_size = 0;
3753 if (level.use_initial_inventory[i])
3755 for (j = 0; j < level.initial_inventory_size[i]; j++)
3757 int element = level.initial_inventory_content[i][j];
3758 int collect_count = element_info[element].collect_count_initial;
3761 if (!IS_CUSTOM_ELEMENT(element))
3764 if (collect_count == 0)
3765 player->inventory_infinite_element = element;
3767 for (k = 0; k < collect_count; k++)
3768 if (player->inventory_size < MAX_INVENTORY_SIZE)
3769 player->inventory_element[player->inventory_size++] = element;
3773 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3774 SnapField(player, 0, 0);
3776 map_player_action[i] = i;
3779 network_player_action_received = FALSE;
3781 // initial null action
3782 if (network_playing)
3783 SendToServer_MovePlayer(MV_NONE);
3788 TimeLeft = level.time;
3791 ScreenMovDir = MV_NONE;
3795 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3797 game.robot_wheel_x = -1;
3798 game.robot_wheel_y = -1;
3803 game.all_players_gone = FALSE;
3805 game.LevelSolved = FALSE;
3806 game.GameOver = FALSE;
3808 game.GamePlayed = !tape.playing;
3810 game.LevelSolved_GameWon = FALSE;
3811 game.LevelSolved_GameEnd = FALSE;
3812 game.LevelSolved_SaveTape = FALSE;
3813 game.LevelSolved_SaveScore = FALSE;
3815 game.LevelSolved_CountingTime = 0;
3816 game.LevelSolved_CountingScore = 0;
3817 game.LevelSolved_CountingHealth = 0;
3819 game.panel.active = TRUE;
3821 game.no_level_time_limit = (level.time == 0);
3822 game.time_limit = (setup.time_limit);
3824 game.yamyam_content_nr = 0;
3825 game.robot_wheel_active = FALSE;
3826 game.magic_wall_active = FALSE;
3827 game.magic_wall_time_left = 0;
3828 game.light_time_left = 0;
3829 game.timegate_time_left = 0;
3830 game.switchgate_pos = 0;
3831 game.wind_direction = level.wind_direction_initial;
3833 game.time_final = 0;
3834 game.score_time_final = 0;
3837 game.score_final = 0;
3839 game.health = MAX_HEALTH;
3840 game.health_final = MAX_HEALTH;
3842 game.gems_still_needed = level.gems_needed;
3843 game.sokoban_fields_still_needed = 0;
3844 game.sokoban_objects_still_needed = 0;
3845 game.lights_still_needed = 0;
3846 game.players_still_needed = 0;
3847 game.friends_still_needed = 0;
3849 game.lenses_time_left = 0;
3850 game.magnify_time_left = 0;
3852 game.ball_active = level.ball_active_initial;
3853 game.ball_content_nr = 0;
3855 game.explosions_delayed = TRUE;
3857 game.envelope_active = FALSE;
3859 // special case: set custom artwork setting to initial value
3860 game.use_masked_elements = game.use_masked_elements_initial;
3862 for (i = 0; i < NUM_BELTS; i++)
3864 game.belt_dir[i] = MV_NONE;
3865 game.belt_dir_nr[i] = 3; // not moving, next moving left
3868 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3869 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3871 #if DEBUG_INIT_PLAYER
3872 DebugPrintPlayerStatus("Player status at level initialization");
3875 SCAN_PLAYFIELD(x, y)
3877 Tile[x][y] = Last[x][y] = level.field[x][y];
3878 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3879 ChangeDelay[x][y] = 0;
3880 ChangePage[x][y] = -1;
3881 CustomValue[x][y] = 0; // initialized in InitField()
3882 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3884 WasJustMoving[x][y] = 0;
3885 WasJustFalling[x][y] = 0;
3886 CheckCollision[x][y] = 0;
3887 CheckImpact[x][y] = 0;
3889 Pushed[x][y] = FALSE;
3891 ChangeCount[x][y] = 0;
3892 ChangeEvent[x][y] = -1;
3894 ExplodePhase[x][y] = 0;
3895 ExplodeDelay[x][y] = 0;
3896 ExplodeField[x][y] = EX_TYPE_NONE;
3898 RunnerVisit[x][y] = 0;
3899 PlayerVisit[x][y] = 0;
3902 GfxRandom[x][y] = INIT_GFX_RANDOM();
3903 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3904 GfxElement[x][y] = EL_UNDEFINED;
3905 GfxElementEmpty[x][y] = EL_EMPTY;
3906 GfxAction[x][y] = ACTION_DEFAULT;
3907 GfxDir[x][y] = MV_NONE;
3908 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3911 SCAN_PLAYFIELD(x, y)
3913 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3915 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3918 InitField(x, y, TRUE);
3920 ResetGfxAnimation(x, y);
3925 for (i = 0; i < MAX_PLAYERS; i++)
3927 struct PlayerInfo *player = &stored_player[i];
3929 // set number of special actions for bored and sleeping animation
3930 player->num_special_action_bored =
3931 get_num_special_action(player->artwork_element,
3932 ACTION_BORING_1, ACTION_BORING_LAST);
3933 player->num_special_action_sleeping =
3934 get_num_special_action(player->artwork_element,
3935 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3938 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3939 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3941 // initialize type of slippery elements
3942 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3944 if (!IS_CUSTOM_ELEMENT(i))
3946 // default: elements slip down either to the left or right randomly
3947 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3949 // SP style elements prefer to slip down on the left side
3950 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3951 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3953 // BD style elements prefer to slip down on the left side
3954 if (game.emulation == EMU_BOULDERDASH)
3955 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3959 // initialize explosion and ignition delay
3960 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3962 if (!IS_CUSTOM_ELEMENT(i))
3965 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3966 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3967 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3968 int last_phase = (num_phase + 1) * delay;
3969 int half_phase = (num_phase / 2) * delay;
3971 element_info[i].explosion_delay = last_phase - 1;
3972 element_info[i].ignition_delay = half_phase;
3974 if (i == EL_BLACK_ORB)
3975 element_info[i].ignition_delay = 1;
3979 // correct non-moving belts to start moving left
3980 for (i = 0; i < NUM_BELTS; i++)
3981 if (game.belt_dir[i] == MV_NONE)
3982 game.belt_dir_nr[i] = 3; // not moving, next moving left
3984 #if USE_NEW_PLAYER_ASSIGNMENTS
3985 // use preferred player also in local single-player mode
3986 if (!network.enabled && !game.team_mode)
3988 int new_index_nr = setup.network_player_nr;
3990 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3992 for (i = 0; i < MAX_PLAYERS; i++)
3993 stored_player[i].connected_locally = FALSE;
3995 stored_player[new_index_nr].connected_locally = TRUE;
3999 for (i = 0; i < MAX_PLAYERS; i++)
4001 stored_player[i].connected = FALSE;
4003 // in network game mode, the local player might not be the first player
4004 if (stored_player[i].connected_locally)
4005 local_player = &stored_player[i];
4008 if (!network.enabled)
4009 local_player->connected = TRUE;
4013 for (i = 0; i < MAX_PLAYERS; i++)
4014 stored_player[i].connected = tape.player_participates[i];
4016 else if (network.enabled)
4018 // add team mode players connected over the network (needed for correct
4019 // assignment of player figures from level to locally playing players)
4021 for (i = 0; i < MAX_PLAYERS; i++)
4022 if (stored_player[i].connected_network)
4023 stored_player[i].connected = TRUE;
4025 else if (game.team_mode)
4027 // try to guess locally connected team mode players (needed for correct
4028 // assignment of player figures from level to locally playing players)
4030 for (i = 0; i < MAX_PLAYERS; i++)
4031 if (setup.input[i].use_joystick ||
4032 setup.input[i].key.left != KSYM_UNDEFINED)
4033 stored_player[i].connected = TRUE;
4036 #if DEBUG_INIT_PLAYER
4037 DebugPrintPlayerStatus("Player status after level initialization");
4040 #if DEBUG_INIT_PLAYER
4041 Debug("game:init:player", "Reassigning players ...");
4044 // check if any connected player was not found in playfield
4045 for (i = 0; i < MAX_PLAYERS; i++)
4047 struct PlayerInfo *player = &stored_player[i];
4049 if (player->connected && !player->present)
4051 struct PlayerInfo *field_player = NULL;
4053 #if DEBUG_INIT_PLAYER
4054 Debug("game:init:player",
4055 "- looking for field player for player %d ...", i + 1);
4058 // assign first free player found that is present in the playfield
4060 // first try: look for unmapped playfield player that is not connected
4061 for (j = 0; j < MAX_PLAYERS; j++)
4062 if (field_player == NULL &&
4063 stored_player[j].present &&
4064 !stored_player[j].mapped &&
4065 !stored_player[j].connected)
4066 field_player = &stored_player[j];
4068 // second try: look for *any* unmapped playfield player
4069 for (j = 0; j < MAX_PLAYERS; j++)
4070 if (field_player == NULL &&
4071 stored_player[j].present &&
4072 !stored_player[j].mapped)
4073 field_player = &stored_player[j];
4075 if (field_player != NULL)
4077 int jx = field_player->jx, jy = field_player->jy;
4079 #if DEBUG_INIT_PLAYER
4080 Debug("game:init:player", "- found player %d",
4081 field_player->index_nr + 1);
4084 player->present = FALSE;
4085 player->active = FALSE;
4087 field_player->present = TRUE;
4088 field_player->active = TRUE;
4091 player->initial_element = field_player->initial_element;
4092 player->artwork_element = field_player->artwork_element;
4094 player->block_last_field = field_player->block_last_field;
4095 player->block_delay_adjustment = field_player->block_delay_adjustment;
4098 StorePlayer[jx][jy] = field_player->element_nr;
4100 field_player->jx = field_player->last_jx = jx;
4101 field_player->jy = field_player->last_jy = jy;
4103 if (local_player == player)
4104 local_player = field_player;
4106 map_player_action[field_player->index_nr] = i;
4108 field_player->mapped = TRUE;
4110 #if DEBUG_INIT_PLAYER
4111 Debug("game:init:player", "- map_player_action[%d] == %d",
4112 field_player->index_nr + 1, i + 1);
4117 if (player->connected && player->present)
4118 player->mapped = TRUE;
4121 #if DEBUG_INIT_PLAYER
4122 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4127 // check if any connected player was not found in playfield
4128 for (i = 0; i < MAX_PLAYERS; i++)
4130 struct PlayerInfo *player = &stored_player[i];
4132 if (player->connected && !player->present)
4134 for (j = 0; j < MAX_PLAYERS; j++)
4136 struct PlayerInfo *field_player = &stored_player[j];
4137 int jx = field_player->jx, jy = field_player->jy;
4139 // assign first free player found that is present in the playfield
4140 if (field_player->present && !field_player->connected)
4142 player->present = TRUE;
4143 player->active = TRUE;
4145 field_player->present = FALSE;
4146 field_player->active = FALSE;
4148 player->initial_element = field_player->initial_element;
4149 player->artwork_element = field_player->artwork_element;
4151 player->block_last_field = field_player->block_last_field;
4152 player->block_delay_adjustment = field_player->block_delay_adjustment;
4154 StorePlayer[jx][jy] = player->element_nr;
4156 player->jx = player->last_jx = jx;
4157 player->jy = player->last_jy = jy;
4167 Debug("game:init:player", "local_player->present == %d",
4168 local_player->present);
4171 // set focus to local player for network games, else to all players
4172 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4173 game.centered_player_nr_next = game.centered_player_nr;
4174 game.set_centered_player = FALSE;
4175 game.set_centered_player_wrap = FALSE;
4177 if (network_playing && tape.recording)
4179 // store client dependent player focus when recording network games
4180 tape.centered_player_nr_next = game.centered_player_nr_next;
4181 tape.set_centered_player = TRUE;
4186 // when playing a tape, eliminate all players who do not participate
4188 #if USE_NEW_PLAYER_ASSIGNMENTS
4190 if (!game.team_mode)
4192 for (i = 0; i < MAX_PLAYERS; i++)
4194 if (stored_player[i].active &&
4195 !tape.player_participates[map_player_action[i]])
4197 struct PlayerInfo *player = &stored_player[i];
4198 int jx = player->jx, jy = player->jy;
4200 #if DEBUG_INIT_PLAYER
4201 Debug("game:init:player", "Removing player %d at (%d, %d)",
4205 player->active = FALSE;
4206 StorePlayer[jx][jy] = 0;
4207 Tile[jx][jy] = EL_EMPTY;
4214 for (i = 0; i < MAX_PLAYERS; i++)
4216 if (stored_player[i].active &&
4217 !tape.player_participates[i])
4219 struct PlayerInfo *player = &stored_player[i];
4220 int jx = player->jx, jy = player->jy;
4222 player->active = FALSE;
4223 StorePlayer[jx][jy] = 0;
4224 Tile[jx][jy] = EL_EMPTY;
4229 else if (!network.enabled && !game.team_mode) // && !tape.playing
4231 // when in single player mode, eliminate all but the local player
4233 for (i = 0; i < MAX_PLAYERS; i++)
4235 struct PlayerInfo *player = &stored_player[i];
4237 if (player->active && player != local_player)
4239 int jx = player->jx, jy = player->jy;
4241 player->active = FALSE;
4242 player->present = FALSE;
4244 StorePlayer[jx][jy] = 0;
4245 Tile[jx][jy] = EL_EMPTY;
4250 for (i = 0; i < MAX_PLAYERS; i++)
4251 if (stored_player[i].active)
4252 game.players_still_needed++;
4254 if (level.solved_by_one_player)
4255 game.players_still_needed = 1;
4257 // when recording the game, store which players take part in the game
4260 #if USE_NEW_PLAYER_ASSIGNMENTS
4261 for (i = 0; i < MAX_PLAYERS; i++)
4262 if (stored_player[i].connected)
4263 tape.player_participates[i] = TRUE;
4265 for (i = 0; i < MAX_PLAYERS; i++)
4266 if (stored_player[i].active)
4267 tape.player_participates[i] = TRUE;
4271 #if DEBUG_INIT_PLAYER
4272 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4275 if (BorderElement == EL_EMPTY)
4278 SBX_Right = lev_fieldx - SCR_FIELDX;
4280 SBY_Lower = lev_fieldy - SCR_FIELDY;
4285 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4287 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4290 if (full_lev_fieldx <= SCR_FIELDX)
4291 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4292 if (full_lev_fieldy <= SCR_FIELDY)
4293 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4295 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4297 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4300 // if local player not found, look for custom element that might create
4301 // the player (make some assumptions about the right custom element)
4302 if (!local_player->present)
4304 int start_x = 0, start_y = 0;
4305 int found_rating = 0;
4306 int found_element = EL_UNDEFINED;
4307 int player_nr = local_player->index_nr;
4309 SCAN_PLAYFIELD(x, y)
4311 int element = Tile[x][y];
4316 if (level.use_start_element[player_nr] &&
4317 level.start_element[player_nr] == element &&
4324 found_element = element;
4327 if (!IS_CUSTOM_ELEMENT(element))
4330 if (CAN_CHANGE(element))
4332 for (i = 0; i < element_info[element].num_change_pages; i++)
4334 // check for player created from custom element as single target
4335 content = element_info[element].change_page[i].target_element;
4336 is_player = IS_PLAYER_ELEMENT(content);
4338 if (is_player && (found_rating < 3 ||
4339 (found_rating == 3 && element < found_element)))
4345 found_element = element;
4350 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4352 // check for player created from custom element as explosion content
4353 content = element_info[element].content.e[xx][yy];
4354 is_player = IS_PLAYER_ELEMENT(content);
4356 if (is_player && (found_rating < 2 ||
4357 (found_rating == 2 && element < found_element)))
4359 start_x = x + xx - 1;
4360 start_y = y + yy - 1;
4363 found_element = element;
4366 if (!CAN_CHANGE(element))
4369 for (i = 0; i < element_info[element].num_change_pages; i++)
4371 // check for player created from custom element as extended target
4373 element_info[element].change_page[i].target_content.e[xx][yy];
4375 is_player = IS_PLAYER_ELEMENT(content);
4377 if (is_player && (found_rating < 1 ||
4378 (found_rating == 1 && element < found_element)))
4380 start_x = x + xx - 1;
4381 start_y = y + yy - 1;
4384 found_element = element;
4390 scroll_x = SCROLL_POSITION_X(start_x);
4391 scroll_y = SCROLL_POSITION_Y(start_y);
4395 scroll_x = SCROLL_POSITION_X(local_player->jx);
4396 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4399 // !!! FIX THIS (START) !!!
4400 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4402 InitGameEngine_EM();
4404 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4406 InitGameEngine_SP();
4408 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4410 InitGameEngine_MM();
4414 DrawLevel(REDRAW_FIELD);
4417 // after drawing the level, correct some elements
4418 if (game.timegate_time_left == 0)
4419 CloseAllOpenTimegates();
4422 // blit playfield from scroll buffer to normal back buffer for fading in
4423 BlitScreenToBitmap(backbuffer);
4424 // !!! FIX THIS (END) !!!
4426 DrawMaskedBorder(fade_mask);
4431 // full screen redraw is required at this point in the following cases:
4432 // - special editor door undrawn when game was started from level editor
4433 // - drawing area (playfield) was changed and has to be removed completely
4434 redraw_mask = REDRAW_ALL;
4438 if (!game.restart_level)
4440 // copy default game door content to main double buffer
4442 // !!! CHECK AGAIN !!!
4443 SetPanelBackground();
4444 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4445 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4448 SetPanelBackground();
4449 SetDrawBackgroundMask(REDRAW_DOOR_1);
4451 UpdateAndDisplayGameControlValues();
4453 if (!game.restart_level)
4459 CreateGameButtons();
4464 // copy actual game door content to door double buffer for OpenDoor()
4465 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4467 OpenDoor(DOOR_OPEN_ALL);
4469 KeyboardAutoRepeatOffUnlessAutoplay();
4471 #if DEBUG_INIT_PLAYER
4472 DebugPrintPlayerStatus("Player status (final)");
4481 if (!game.restart_level && !tape.playing)
4483 LevelStats_incPlayed(level_nr);
4485 SaveLevelSetup_SeriesInfo();
4488 game.restart_level = FALSE;
4489 game.restart_game_message = NULL;
4491 game.request_active = FALSE;
4492 game.request_active_or_moving = FALSE;
4494 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4495 InitGameActions_MM();
4497 SaveEngineSnapshotToListInitial();
4499 if (!game.restart_level)
4501 PlaySound(SND_GAME_STARTING);
4503 if (setup.sound_music)
4507 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4510 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4511 int actual_player_x, int actual_player_y)
4513 // this is used for non-R'n'D game engines to update certain engine values
4515 // needed to determine if sounds are played within the visible screen area
4516 scroll_x = actual_scroll_x;
4517 scroll_y = actual_scroll_y;
4519 // needed to get player position for "follow finger" playing input method
4520 local_player->jx = actual_player_x;
4521 local_player->jy = actual_player_y;
4524 void InitMovDir(int x, int y)
4526 int i, element = Tile[x][y];
4527 static int xy[4][2] =
4534 static int direction[3][4] =
4536 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4537 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4538 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4547 Tile[x][y] = EL_BUG;
4548 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4551 case EL_SPACESHIP_RIGHT:
4552 case EL_SPACESHIP_UP:
4553 case EL_SPACESHIP_LEFT:
4554 case EL_SPACESHIP_DOWN:
4555 Tile[x][y] = EL_SPACESHIP;
4556 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4559 case EL_BD_BUTTERFLY_RIGHT:
4560 case EL_BD_BUTTERFLY_UP:
4561 case EL_BD_BUTTERFLY_LEFT:
4562 case EL_BD_BUTTERFLY_DOWN:
4563 Tile[x][y] = EL_BD_BUTTERFLY;
4564 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4567 case EL_BD_FIREFLY_RIGHT:
4568 case EL_BD_FIREFLY_UP:
4569 case EL_BD_FIREFLY_LEFT:
4570 case EL_BD_FIREFLY_DOWN:
4571 Tile[x][y] = EL_BD_FIREFLY;
4572 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4575 case EL_PACMAN_RIGHT:
4577 case EL_PACMAN_LEFT:
4578 case EL_PACMAN_DOWN:
4579 Tile[x][y] = EL_PACMAN;
4580 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4583 case EL_YAMYAM_LEFT:
4584 case EL_YAMYAM_RIGHT:
4586 case EL_YAMYAM_DOWN:
4587 Tile[x][y] = EL_YAMYAM;
4588 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4591 case EL_SP_SNIKSNAK:
4592 MovDir[x][y] = MV_UP;
4595 case EL_SP_ELECTRON:
4596 MovDir[x][y] = MV_LEFT;
4603 Tile[x][y] = EL_MOLE;
4604 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4607 case EL_SPRING_LEFT:
4608 case EL_SPRING_RIGHT:
4609 Tile[x][y] = EL_SPRING;
4610 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4614 if (IS_CUSTOM_ELEMENT(element))
4616 struct ElementInfo *ei = &element_info[element];
4617 int move_direction_initial = ei->move_direction_initial;
4618 int move_pattern = ei->move_pattern;
4620 if (move_direction_initial == MV_START_PREVIOUS)
4622 if (MovDir[x][y] != MV_NONE)
4625 move_direction_initial = MV_START_AUTOMATIC;
4628 if (move_direction_initial == MV_START_RANDOM)
4629 MovDir[x][y] = 1 << RND(4);
4630 else if (move_direction_initial & MV_ANY_DIRECTION)
4631 MovDir[x][y] = move_direction_initial;
4632 else if (move_pattern == MV_ALL_DIRECTIONS ||
4633 move_pattern == MV_TURNING_LEFT ||
4634 move_pattern == MV_TURNING_RIGHT ||
4635 move_pattern == MV_TURNING_LEFT_RIGHT ||
4636 move_pattern == MV_TURNING_RIGHT_LEFT ||
4637 move_pattern == MV_TURNING_RANDOM)
4638 MovDir[x][y] = 1 << RND(4);
4639 else if (move_pattern == MV_HORIZONTAL)
4640 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4641 else if (move_pattern == MV_VERTICAL)
4642 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4643 else if (move_pattern & MV_ANY_DIRECTION)
4644 MovDir[x][y] = element_info[element].move_pattern;
4645 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4646 move_pattern == MV_ALONG_RIGHT_SIDE)
4648 // use random direction as default start direction
4649 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4650 MovDir[x][y] = 1 << RND(4);
4652 for (i = 0; i < NUM_DIRECTIONS; i++)
4654 int x1 = x + xy[i][0];
4655 int y1 = y + xy[i][1];
4657 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4659 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4660 MovDir[x][y] = direction[0][i];
4662 MovDir[x][y] = direction[1][i];
4671 MovDir[x][y] = 1 << RND(4);
4673 if (element != EL_BUG &&
4674 element != EL_SPACESHIP &&
4675 element != EL_BD_BUTTERFLY &&
4676 element != EL_BD_FIREFLY)
4679 for (i = 0; i < NUM_DIRECTIONS; i++)
4681 int x1 = x + xy[i][0];
4682 int y1 = y + xy[i][1];
4684 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4686 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4688 MovDir[x][y] = direction[0][i];
4691 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4692 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4694 MovDir[x][y] = direction[1][i];
4703 GfxDir[x][y] = MovDir[x][y];
4706 void InitAmoebaNr(int x, int y)
4709 int group_nr = AmoebaNeighbourNr(x, y);
4713 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4715 if (AmoebaCnt[i] == 0)
4723 AmoebaNr[x][y] = group_nr;
4724 AmoebaCnt[group_nr]++;
4725 AmoebaCnt2[group_nr]++;
4728 static void LevelSolved_SetFinalGameValues(void)
4730 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4731 game.score_time_final = (level.use_step_counter ? TimePlayed :
4732 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4734 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4735 game_em.lev->score :
4736 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4740 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4741 MM_HEALTH(game_mm.laser_overload_value) :
4744 game.LevelSolved_CountingTime = game.time_final;
4745 game.LevelSolved_CountingScore = game.score_final;
4746 game.LevelSolved_CountingHealth = game.health_final;
4749 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4751 game.LevelSolved_CountingTime = time;
4752 game.LevelSolved_CountingScore = score;
4753 game.LevelSolved_CountingHealth = health;
4755 game_panel_controls[GAME_PANEL_TIME].value = time;
4756 game_panel_controls[GAME_PANEL_SCORE].value = score;
4757 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4759 DisplayGameControlValues();
4762 static void LevelSolved(void)
4764 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4765 game.players_still_needed > 0)
4768 game.LevelSolved = TRUE;
4769 game.GameOver = TRUE;
4771 // needed here to display correct panel values while player walks into exit
4772 LevelSolved_SetFinalGameValues();
4777 static int time_count_steps;
4778 static int time, time_final;
4779 static float score, score_final; // needed for time score < 10 for 10 seconds
4780 static int health, health_final;
4781 static int game_over_delay_1 = 0;
4782 static int game_over_delay_2 = 0;
4783 static int game_over_delay_3 = 0;
4784 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4785 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4787 if (!game.LevelSolved_GameWon)
4791 // do not start end game actions before the player stops moving (to exit)
4792 if (local_player->active && local_player->MovPos)
4795 // calculate final game values after player finished walking into exit
4796 LevelSolved_SetFinalGameValues();
4798 game.LevelSolved_GameWon = TRUE;
4799 game.LevelSolved_SaveTape = tape.recording;
4800 game.LevelSolved_SaveScore = !tape.playing;
4804 LevelStats_incSolved(level_nr);
4806 SaveLevelSetup_SeriesInfo();
4809 if (tape.auto_play) // tape might already be stopped here
4810 tape.auto_play_level_solved = TRUE;
4814 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4815 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4816 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4818 time = time_final = game.time_final;
4819 score = score_final = game.score_final;
4820 health = health_final = game.health_final;
4822 // update game panel values before (delayed) counting of score (if any)
4823 LevelSolved_DisplayFinalGameValues(time, score, health);
4825 // if level has time score defined, calculate new final game values
4828 int time_final_max = 999;
4829 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4830 int time_frames = 0;
4831 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4832 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4837 time_frames = time_frames_left;
4839 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4841 time_final = time_final_max;
4842 time_frames = time_frames_final_max - time_frames_played;
4845 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4847 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4849 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4852 score_final += health * time_score;
4855 game.score_final = score_final;
4856 game.health_final = health_final;
4859 // if not counting score after game, immediately update game panel values
4860 if (level_editor_test_game || !setup.count_score_after_game)
4863 score = score_final;
4865 LevelSolved_DisplayFinalGameValues(time, score, health);
4868 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4870 // check if last player has left the level
4871 if (game.exit_x >= 0 &&
4874 int x = game.exit_x;
4875 int y = game.exit_y;
4876 int element = Tile[x][y];
4878 // close exit door after last player
4879 if ((game.all_players_gone &&
4880 (element == EL_EXIT_OPEN ||
4881 element == EL_SP_EXIT_OPEN ||
4882 element == EL_STEEL_EXIT_OPEN)) ||
4883 element == EL_EM_EXIT_OPEN ||
4884 element == EL_EM_STEEL_EXIT_OPEN)
4888 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4889 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4890 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4891 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4892 EL_EM_STEEL_EXIT_CLOSING);
4894 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4897 // player disappears
4898 DrawLevelField(x, y);
4901 for (i = 0; i < MAX_PLAYERS; i++)
4903 struct PlayerInfo *player = &stored_player[i];
4905 if (player->present)
4907 RemovePlayer(player);
4909 // player disappears
4910 DrawLevelField(player->jx, player->jy);
4915 PlaySound(SND_GAME_WINNING);
4918 if (setup.count_score_after_game)
4920 if (time != time_final)
4922 if (game_over_delay_1 > 0)
4924 game_over_delay_1--;
4929 int time_to_go = ABS(time_final - time);
4930 int time_count_dir = (time < time_final ? +1 : -1);
4932 if (time_to_go < time_count_steps)
4933 time_count_steps = 1;
4935 time += time_count_steps * time_count_dir;
4936 score += time_count_steps * time_score;
4938 // set final score to correct rounding differences after counting score
4939 if (time == time_final)
4940 score = score_final;
4942 LevelSolved_DisplayFinalGameValues(time, score, health);
4944 if (time == time_final)
4945 StopSound(SND_GAME_LEVELTIME_BONUS);
4946 else if (setup.sound_loops)
4947 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4949 PlaySound(SND_GAME_LEVELTIME_BONUS);
4954 if (health != health_final)
4956 if (game_over_delay_2 > 0)
4958 game_over_delay_2--;
4963 int health_count_dir = (health < health_final ? +1 : -1);
4965 health += health_count_dir;
4966 score += time_score;
4968 LevelSolved_DisplayFinalGameValues(time, score, health);
4970 if (health == health_final)
4971 StopSound(SND_GAME_LEVELTIME_BONUS);
4972 else if (setup.sound_loops)
4973 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4975 PlaySound(SND_GAME_LEVELTIME_BONUS);
4981 game.panel.active = FALSE;
4983 if (game_over_delay_3 > 0)
4985 game_over_delay_3--;
4995 // used instead of "level_nr" (needed for network games)
4996 int last_level_nr = levelset.level_nr;
4997 boolean tape_saved = FALSE;
4999 game.LevelSolved_GameEnd = TRUE;
5001 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5003 // make sure that request dialog to save tape does not open door again
5004 if (!global.use_envelope_request)
5005 CloseDoor(DOOR_CLOSE_1);
5008 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5010 // set unique basename for score tape (also saved in high score table)
5011 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5014 // if no tape is to be saved, close both doors simultaneously
5015 CloseDoor(DOOR_CLOSE_ALL);
5017 if (level_editor_test_game || score_info_tape_play)
5019 SetGameStatus(GAME_MODE_MAIN);
5026 if (!game.LevelSolved_SaveScore)
5028 SetGameStatus(GAME_MODE_MAIN);
5035 if (level_nr == leveldir_current->handicap_level)
5037 leveldir_current->handicap_level++;
5039 SaveLevelSetup_SeriesInfo();
5042 // save score and score tape before potentially erasing tape below
5043 NewHighScore(last_level_nr, tape_saved);
5045 if (setup.increment_levels &&
5046 level_nr < leveldir_current->last_level &&
5049 level_nr++; // advance to next level
5050 TapeErase(); // start with empty tape
5052 if (setup.auto_play_next_level)
5054 scores.continue_playing = TRUE;
5055 scores.next_level_nr = level_nr;
5057 LoadLevel(level_nr);
5059 SaveLevelSetup_SeriesInfo();
5063 if (scores.last_added >= 0 && setup.show_scores_after_game)
5065 SetGameStatus(GAME_MODE_SCORES);
5067 DrawHallOfFame(last_level_nr);
5069 else if (scores.continue_playing)
5071 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5075 SetGameStatus(GAME_MODE_MAIN);
5081 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5082 boolean one_score_entry_per_name)
5086 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5089 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5091 struct ScoreEntry *entry = &list->entry[i];
5092 boolean score_is_better = (new_entry->score > entry->score);
5093 boolean score_is_equal = (new_entry->score == entry->score);
5094 boolean time_is_better = (new_entry->time < entry->time);
5095 boolean time_is_equal = (new_entry->time == entry->time);
5096 boolean better_by_score = (score_is_better ||
5097 (score_is_equal && time_is_better));
5098 boolean better_by_time = (time_is_better ||
5099 (time_is_equal && score_is_better));
5100 boolean is_better = (level.rate_time_over_score ? better_by_time :
5102 boolean entry_is_empty = (entry->score == 0 &&
5105 // prevent adding server score entries if also existing in local score file
5106 // (special case: historic score entries have an empty tape basename entry)
5107 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5108 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5110 // special case: use server score instead of local score value if higher
5111 // (historic scores might have been truncated to 16-bit values locally)
5112 if (score_is_better)
5113 entry->score = new_entry->score;
5118 if (is_better || entry_is_empty)
5120 // player has made it to the hall of fame
5122 if (i < MAX_SCORE_ENTRIES - 1)
5124 int m = MAX_SCORE_ENTRIES - 1;
5127 if (one_score_entry_per_name)
5129 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5130 if (strEqual(list->entry[l].name, new_entry->name))
5133 if (m == i) // player's new highscore overwrites his old one
5137 for (l = m; l > i; l--)
5138 list->entry[l] = list->entry[l - 1];
5143 *entry = *new_entry;
5147 else if (one_score_entry_per_name &&
5148 strEqual(entry->name, new_entry->name))
5150 // player already in high score list with better score or time
5156 // special case: new score is beyond the last high score list position
5157 return MAX_SCORE_ENTRIES;
5160 void NewHighScore(int level_nr, boolean tape_saved)
5162 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5163 boolean one_per_name = FALSE;
5165 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5166 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5168 new_entry.score = game.score_final;
5169 new_entry.time = game.score_time_final;
5171 LoadScore(level_nr);
5173 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5175 if (scores.last_added >= MAX_SCORE_ENTRIES)
5177 scores.last_added = MAX_SCORE_ENTRIES - 1;
5178 scores.force_last_added = TRUE;
5180 scores.entry[scores.last_added] = new_entry;
5182 // store last added local score entry (before merging server scores)
5183 scores.last_added_local = scores.last_added;
5188 if (scores.last_added < 0)
5191 SaveScore(level_nr);
5193 // store last added local score entry (before merging server scores)
5194 scores.last_added_local = scores.last_added;
5196 if (!game.LevelSolved_SaveTape)
5199 SaveScoreTape(level_nr);
5201 if (setup.ask_for_using_api_server)
5203 setup.use_api_server =
5204 Request("Upload your score and tape to the high score server?", REQ_ASK);
5206 if (!setup.use_api_server)
5207 Request("Not using high score server! Use setup menu to enable again!",
5210 runtime.use_api_server = setup.use_api_server;
5212 // after asking for using API server once, do not ask again
5213 setup.ask_for_using_api_server = FALSE;
5215 SaveSetup_ServerSetup();
5218 SaveServerScore(level_nr, tape_saved);
5221 void MergeServerScore(void)
5223 struct ScoreEntry last_added_entry;
5224 boolean one_per_name = FALSE;
5227 if (scores.last_added >= 0)
5228 last_added_entry = scores.entry[scores.last_added];
5230 for (i = 0; i < server_scores.num_entries; i++)
5232 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5234 if (pos >= 0 && pos <= scores.last_added)
5235 scores.last_added++;
5238 if (scores.last_added >= MAX_SCORE_ENTRIES)
5240 scores.last_added = MAX_SCORE_ENTRIES - 1;
5241 scores.force_last_added = TRUE;
5243 scores.entry[scores.last_added] = last_added_entry;
5247 static int getElementMoveStepsizeExt(int x, int y, int direction)
5249 int element = Tile[x][y];
5250 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5251 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5252 int horiz_move = (dx != 0);
5253 int sign = (horiz_move ? dx : dy);
5254 int step = sign * element_info[element].move_stepsize;
5256 // special values for move stepsize for spring and things on conveyor belt
5259 if (CAN_FALL(element) &&
5260 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5261 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5262 else if (element == EL_SPRING)
5263 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5269 static int getElementMoveStepsize(int x, int y)
5271 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5274 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5276 if (player->GfxAction != action || player->GfxDir != dir)
5278 player->GfxAction = action;
5279 player->GfxDir = dir;
5281 player->StepFrame = 0;
5285 static void ResetGfxFrame(int x, int y)
5287 // profiling showed that "autotest" spends 10~20% of its time in this function
5288 if (DrawingDeactivatedField())
5291 int element = Tile[x][y];
5292 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5294 if (graphic_info[graphic].anim_global_sync)
5295 GfxFrame[x][y] = FrameCounter;
5296 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5297 GfxFrame[x][y] = CustomValue[x][y];
5298 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5299 GfxFrame[x][y] = element_info[element].collect_score;
5300 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5301 GfxFrame[x][y] = ChangeDelay[x][y];
5304 static void ResetGfxAnimation(int x, int y)
5306 GfxAction[x][y] = ACTION_DEFAULT;
5307 GfxDir[x][y] = MovDir[x][y];
5310 ResetGfxFrame(x, y);
5313 static void ResetRandomAnimationValue(int x, int y)
5315 GfxRandom[x][y] = INIT_GFX_RANDOM();
5318 static void InitMovingField(int x, int y, int direction)
5320 int element = Tile[x][y];
5321 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5322 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5325 boolean is_moving_before, is_moving_after;
5327 // check if element was/is moving or being moved before/after mode change
5328 is_moving_before = (WasJustMoving[x][y] != 0);
5329 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5331 // reset animation only for moving elements which change direction of moving
5332 // or which just started or stopped moving
5333 // (else CEs with property "can move" / "not moving" are reset each frame)
5334 if (is_moving_before != is_moving_after ||
5335 direction != MovDir[x][y])
5336 ResetGfxAnimation(x, y);
5338 MovDir[x][y] = direction;
5339 GfxDir[x][y] = direction;
5341 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5342 direction == MV_DOWN && CAN_FALL(element) ?
5343 ACTION_FALLING : ACTION_MOVING);
5345 // this is needed for CEs with property "can move" / "not moving"
5347 if (is_moving_after)
5349 if (Tile[newx][newy] == EL_EMPTY)
5350 Tile[newx][newy] = EL_BLOCKED;
5352 MovDir[newx][newy] = MovDir[x][y];
5354 CustomValue[newx][newy] = CustomValue[x][y];
5356 GfxFrame[newx][newy] = GfxFrame[x][y];
5357 GfxRandom[newx][newy] = GfxRandom[x][y];
5358 GfxAction[newx][newy] = GfxAction[x][y];
5359 GfxDir[newx][newy] = GfxDir[x][y];
5363 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5365 int direction = MovDir[x][y];
5366 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5367 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5373 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5375 int oldx = x, oldy = y;
5376 int direction = MovDir[x][y];
5378 if (direction == MV_LEFT)
5380 else if (direction == MV_RIGHT)
5382 else if (direction == MV_UP)
5384 else if (direction == MV_DOWN)
5387 *comes_from_x = oldx;
5388 *comes_from_y = oldy;
5391 static int MovingOrBlocked2Element(int x, int y)
5393 int element = Tile[x][y];
5395 if (element == EL_BLOCKED)
5399 Blocked2Moving(x, y, &oldx, &oldy);
5400 return Tile[oldx][oldy];
5406 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5408 // like MovingOrBlocked2Element(), but if element is moving
5409 // and (x,y) is the field the moving element is just leaving,
5410 // return EL_BLOCKED instead of the element value
5411 int element = Tile[x][y];
5413 if (IS_MOVING(x, y))
5415 if (element == EL_BLOCKED)
5419 Blocked2Moving(x, y, &oldx, &oldy);
5420 return Tile[oldx][oldy];
5429 static void RemoveField(int x, int y)
5431 Tile[x][y] = EL_EMPTY;
5437 CustomValue[x][y] = 0;
5440 ChangeDelay[x][y] = 0;
5441 ChangePage[x][y] = -1;
5442 Pushed[x][y] = FALSE;
5444 GfxElement[x][y] = EL_UNDEFINED;
5445 GfxAction[x][y] = ACTION_DEFAULT;
5446 GfxDir[x][y] = MV_NONE;
5449 static void RemoveMovingField(int x, int y)
5451 int oldx = x, oldy = y, newx = x, newy = y;
5452 int element = Tile[x][y];
5453 int next_element = EL_UNDEFINED;
5455 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5458 if (IS_MOVING(x, y))
5460 Moving2Blocked(x, y, &newx, &newy);
5462 if (Tile[newx][newy] != EL_BLOCKED)
5464 // element is moving, but target field is not free (blocked), but
5465 // already occupied by something different (example: acid pool);
5466 // in this case, only remove the moving field, but not the target
5468 RemoveField(oldx, oldy);
5470 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5472 TEST_DrawLevelField(oldx, oldy);
5477 else if (element == EL_BLOCKED)
5479 Blocked2Moving(x, y, &oldx, &oldy);
5480 if (!IS_MOVING(oldx, oldy))
5484 if (element == EL_BLOCKED &&
5485 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5486 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5487 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5488 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5489 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5490 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5491 next_element = get_next_element(Tile[oldx][oldy]);
5493 RemoveField(oldx, oldy);
5494 RemoveField(newx, newy);
5496 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5498 if (next_element != EL_UNDEFINED)
5499 Tile[oldx][oldy] = next_element;
5501 TEST_DrawLevelField(oldx, oldy);
5502 TEST_DrawLevelField(newx, newy);
5505 void DrawDynamite(int x, int y)
5507 int sx = SCREENX(x), sy = SCREENY(y);
5508 int graphic = el2img(Tile[x][y]);
5511 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5514 if (IS_WALKABLE_INSIDE(Back[x][y]))
5518 DrawLevelElement(x, y, Back[x][y]);
5519 else if (Store[x][y])
5520 DrawLevelElement(x, y, Store[x][y]);
5521 else if (game.use_masked_elements)
5522 DrawLevelElement(x, y, EL_EMPTY);
5524 frame = getGraphicAnimationFrameXY(graphic, x, y);
5526 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5527 DrawGraphicThruMask(sx, sy, graphic, frame);
5529 DrawGraphic(sx, sy, graphic, frame);
5532 static void CheckDynamite(int x, int y)
5534 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5538 if (MovDelay[x][y] != 0)
5541 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5547 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5552 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5554 boolean num_checked_players = 0;
5557 for (i = 0; i < MAX_PLAYERS; i++)
5559 if (stored_player[i].active)
5561 int sx = stored_player[i].jx;
5562 int sy = stored_player[i].jy;
5564 if (num_checked_players == 0)
5571 *sx1 = MIN(*sx1, sx);
5572 *sy1 = MIN(*sy1, sy);
5573 *sx2 = MAX(*sx2, sx);
5574 *sy2 = MAX(*sy2, sy);
5577 num_checked_players++;
5582 static boolean checkIfAllPlayersFitToScreen_RND(void)
5584 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5586 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5588 return (sx2 - sx1 < SCR_FIELDX &&
5589 sy2 - sy1 < SCR_FIELDY);
5592 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5594 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5596 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5598 *sx = (sx1 + sx2) / 2;
5599 *sy = (sy1 + sy2) / 2;
5602 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5603 boolean center_screen, boolean quick_relocation)
5605 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5606 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5607 boolean no_delay = (tape.warp_forward);
5608 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5609 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5610 int new_scroll_x, new_scroll_y;
5612 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5614 // case 1: quick relocation inside visible screen (without scrolling)
5621 if (!level.shifted_relocation || center_screen)
5623 // relocation _with_ centering of screen
5625 new_scroll_x = SCROLL_POSITION_X(x);
5626 new_scroll_y = SCROLL_POSITION_Y(y);
5630 // relocation _without_ centering of screen
5632 int center_scroll_x = SCROLL_POSITION_X(old_x);
5633 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5634 int offset_x = x + (scroll_x - center_scroll_x);
5635 int offset_y = y + (scroll_y - center_scroll_y);
5637 // for new screen position, apply previous offset to center position
5638 new_scroll_x = SCROLL_POSITION_X(offset_x);
5639 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5642 if (quick_relocation)
5644 // case 2: quick relocation (redraw without visible scrolling)
5646 scroll_x = new_scroll_x;
5647 scroll_y = new_scroll_y;
5654 // case 3: visible relocation (with scrolling to new position)
5656 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5658 SetVideoFrameDelay(wait_delay_value);
5660 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5662 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5663 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5665 if (dx == 0 && dy == 0) // no scrolling needed at all
5671 // set values for horizontal/vertical screen scrolling (half tile size)
5672 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5673 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5674 int pos_x = dx * TILEX / 2;
5675 int pos_y = dy * TILEY / 2;
5676 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5677 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5679 ScrollLevel(dx, dy);
5682 // scroll in two steps of half tile size to make things smoother
5683 BlitScreenToBitmapExt_RND(window, fx, fy);
5685 // scroll second step to align at full tile size
5686 BlitScreenToBitmap(window);
5692 SetVideoFrameDelay(frame_delay_value_old);
5695 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5697 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5698 int player_nr = GET_PLAYER_NR(el_player);
5699 struct PlayerInfo *player = &stored_player[player_nr];
5700 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5701 boolean no_delay = (tape.warp_forward);
5702 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5703 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5704 int old_jx = player->jx;
5705 int old_jy = player->jy;
5706 int old_element = Tile[old_jx][old_jy];
5707 int element = Tile[jx][jy];
5708 boolean player_relocated = (old_jx != jx || old_jy != jy);
5710 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5711 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5712 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5713 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5714 int leave_side_horiz = move_dir_horiz;
5715 int leave_side_vert = move_dir_vert;
5716 int enter_side = enter_side_horiz | enter_side_vert;
5717 int leave_side = leave_side_horiz | leave_side_vert;
5719 if (player->buried) // do not reanimate dead player
5722 if (!player_relocated) // no need to relocate the player
5725 if (IS_PLAYER(jx, jy)) // player already placed at new position
5727 RemoveField(jx, jy); // temporarily remove newly placed player
5728 DrawLevelField(jx, jy);
5731 if (player->present)
5733 while (player->MovPos)
5735 ScrollPlayer(player, SCROLL_GO_ON);
5736 ScrollScreen(NULL, SCROLL_GO_ON);
5738 AdvanceFrameAndPlayerCounters(player->index_nr);
5742 BackToFront_WithFrameDelay(wait_delay_value);
5745 DrawPlayer(player); // needed here only to cleanup last field
5746 DrawLevelField(player->jx, player->jy); // remove player graphic
5748 player->is_moving = FALSE;
5751 if (IS_CUSTOM_ELEMENT(old_element))
5752 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5754 player->index_bit, leave_side);
5756 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5758 player->index_bit, leave_side);
5760 Tile[jx][jy] = el_player;
5761 InitPlayerField(jx, jy, el_player, TRUE);
5763 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5764 possible that the relocation target field did not contain a player element,
5765 but a walkable element, to which the new player was relocated -- in this
5766 case, restore that (already initialized!) element on the player field */
5767 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5769 Tile[jx][jy] = element; // restore previously existing element
5772 // only visually relocate centered player
5773 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5774 FALSE, level.instant_relocation);
5776 TestIfPlayerTouchesBadThing(jx, jy);
5777 TestIfPlayerTouchesCustomElement(jx, jy);
5779 if (IS_CUSTOM_ELEMENT(element))
5780 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5781 player->index_bit, enter_side);
5783 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5784 player->index_bit, enter_side);
5786 if (player->is_switching)
5788 /* ensure that relocation while still switching an element does not cause
5789 a new element to be treated as also switched directly after relocation
5790 (this is important for teleporter switches that teleport the player to
5791 a place where another teleporter switch is in the same direction, which
5792 would then incorrectly be treated as immediately switched before the
5793 direction key that caused the switch was released) */
5795 player->switch_x += jx - old_jx;
5796 player->switch_y += jy - old_jy;
5800 static void Explode(int ex, int ey, int phase, int mode)
5806 // !!! eliminate this variable !!!
5807 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5809 if (game.explosions_delayed)
5811 ExplodeField[ex][ey] = mode;
5815 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5817 int center_element = Tile[ex][ey];
5818 int artwork_element, explosion_element; // set these values later
5820 // remove things displayed in background while burning dynamite
5821 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5824 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5826 // put moving element to center field (and let it explode there)
5827 center_element = MovingOrBlocked2Element(ex, ey);
5828 RemoveMovingField(ex, ey);
5829 Tile[ex][ey] = center_element;
5832 // now "center_element" is finally determined -- set related values now
5833 artwork_element = center_element; // for custom player artwork
5834 explosion_element = center_element; // for custom player artwork
5836 if (IS_PLAYER(ex, ey))
5838 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5840 artwork_element = stored_player[player_nr].artwork_element;
5842 if (level.use_explosion_element[player_nr])
5844 explosion_element = level.explosion_element[player_nr];
5845 artwork_element = explosion_element;
5849 if (mode == EX_TYPE_NORMAL ||
5850 mode == EX_TYPE_CENTER ||
5851 mode == EX_TYPE_CROSS)
5852 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5854 last_phase = element_info[explosion_element].explosion_delay + 1;
5856 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5858 int xx = x - ex + 1;
5859 int yy = y - ey + 1;
5862 if (!IN_LEV_FIELD(x, y) ||
5863 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5864 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5867 element = Tile[x][y];
5869 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5871 element = MovingOrBlocked2Element(x, y);
5873 if (!IS_EXPLOSION_PROOF(element))
5874 RemoveMovingField(x, y);
5877 // indestructible elements can only explode in center (but not flames)
5878 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5879 mode == EX_TYPE_BORDER)) ||
5880 element == EL_FLAMES)
5883 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5884 behaviour, for example when touching a yamyam that explodes to rocks
5885 with active deadly shield, a rock is created under the player !!! */
5886 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5888 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5889 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5890 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5892 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5895 if (IS_ACTIVE_BOMB(element))
5897 // re-activate things under the bomb like gate or penguin
5898 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5905 // save walkable background elements while explosion on same tile
5906 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5907 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5908 Back[x][y] = element;
5910 // ignite explodable elements reached by other explosion
5911 if (element == EL_EXPLOSION)
5912 element = Store2[x][y];
5914 if (AmoebaNr[x][y] &&
5915 (element == EL_AMOEBA_FULL ||
5916 element == EL_BD_AMOEBA ||
5917 element == EL_AMOEBA_GROWING))
5919 AmoebaCnt[AmoebaNr[x][y]]--;
5920 AmoebaCnt2[AmoebaNr[x][y]]--;
5925 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5927 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5929 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5931 if (PLAYERINFO(ex, ey)->use_murphy)
5932 Store[x][y] = EL_EMPTY;
5935 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5936 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5937 else if (IS_PLAYER_ELEMENT(center_element))
5938 Store[x][y] = EL_EMPTY;
5939 else if (center_element == EL_YAMYAM)
5940 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5941 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5942 Store[x][y] = element_info[center_element].content.e[xx][yy];
5944 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5945 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5946 // otherwise) -- FIX THIS !!!
5947 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5948 Store[x][y] = element_info[element].content.e[1][1];
5950 else if (!CAN_EXPLODE(element))
5951 Store[x][y] = element_info[element].content.e[1][1];
5954 Store[x][y] = EL_EMPTY;
5956 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5957 center_element == EL_AMOEBA_TO_DIAMOND)
5958 Store2[x][y] = element;
5960 Tile[x][y] = EL_EXPLOSION;
5961 GfxElement[x][y] = artwork_element;
5963 ExplodePhase[x][y] = 1;
5964 ExplodeDelay[x][y] = last_phase;
5969 if (center_element == EL_YAMYAM)
5970 game.yamyam_content_nr =
5971 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5983 GfxFrame[x][y] = 0; // restart explosion animation
5985 last_phase = ExplodeDelay[x][y];
5987 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5989 // this can happen if the player leaves an explosion just in time
5990 if (GfxElement[x][y] == EL_UNDEFINED)
5991 GfxElement[x][y] = EL_EMPTY;
5993 border_element = Store2[x][y];
5994 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5995 border_element = StorePlayer[x][y];
5997 if (phase == element_info[border_element].ignition_delay ||
5998 phase == last_phase)
6000 boolean border_explosion = FALSE;
6002 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6003 !PLAYER_EXPLOSION_PROTECTED(x, y))
6005 KillPlayerUnlessExplosionProtected(x, y);
6006 border_explosion = TRUE;
6008 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6010 Tile[x][y] = Store2[x][y];
6013 border_explosion = TRUE;
6015 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6017 AmoebaToDiamond(x, y);
6019 border_explosion = TRUE;
6022 // if an element just explodes due to another explosion (chain-reaction),
6023 // do not immediately end the new explosion when it was the last frame of
6024 // the explosion (as it would be done in the following "if"-statement!)
6025 if (border_explosion && phase == last_phase)
6029 // this can happen if the player was just killed by an explosion
6030 if (GfxElement[x][y] == EL_UNDEFINED)
6031 GfxElement[x][y] = EL_EMPTY;
6033 if (phase == last_phase)
6037 element = Tile[x][y] = Store[x][y];
6038 Store[x][y] = Store2[x][y] = 0;
6039 GfxElement[x][y] = EL_UNDEFINED;
6041 // player can escape from explosions and might therefore be still alive
6042 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6043 element <= EL_PLAYER_IS_EXPLODING_4)
6045 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6046 int explosion_element = EL_PLAYER_1 + player_nr;
6047 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6048 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6050 if (level.use_explosion_element[player_nr])
6051 explosion_element = level.explosion_element[player_nr];
6053 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6054 element_info[explosion_element].content.e[xx][yy]);
6057 // restore probably existing indestructible background element
6058 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6059 element = Tile[x][y] = Back[x][y];
6062 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6063 GfxDir[x][y] = MV_NONE;
6064 ChangeDelay[x][y] = 0;
6065 ChangePage[x][y] = -1;
6067 CustomValue[x][y] = 0;
6069 InitField_WithBug2(x, y, FALSE);
6071 TEST_DrawLevelField(x, y);
6073 TestIfElementTouchesCustomElement(x, y);
6075 if (GFX_CRUMBLED(element))
6076 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6078 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6079 StorePlayer[x][y] = 0;
6081 if (IS_PLAYER_ELEMENT(element))
6082 RelocatePlayer(x, y, element);
6084 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6086 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6087 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6090 TEST_DrawLevelFieldCrumbled(x, y);
6092 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6094 DrawLevelElement(x, y, Back[x][y]);
6095 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6097 else if (IS_WALKABLE_UNDER(Back[x][y]))
6099 DrawLevelGraphic(x, y, graphic, frame);
6100 DrawLevelElementThruMask(x, y, Back[x][y]);
6102 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6103 DrawLevelGraphic(x, y, graphic, frame);
6107 static void DynaExplode(int ex, int ey)
6110 int dynabomb_element = Tile[ex][ey];
6111 int dynabomb_size = 1;
6112 boolean dynabomb_xl = FALSE;
6113 struct PlayerInfo *player;
6114 static int xy[4][2] =
6122 if (IS_ACTIVE_BOMB(dynabomb_element))
6124 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6125 dynabomb_size = player->dynabomb_size;
6126 dynabomb_xl = player->dynabomb_xl;
6127 player->dynabombs_left++;
6130 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6132 for (i = 0; i < NUM_DIRECTIONS; i++)
6134 for (j = 1; j <= dynabomb_size; j++)
6136 int x = ex + j * xy[i][0];
6137 int y = ey + j * xy[i][1];
6140 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6143 element = Tile[x][y];
6145 // do not restart explosions of fields with active bombs
6146 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6149 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6151 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6152 !IS_DIGGABLE(element) && !dynabomb_xl)
6158 void Bang(int x, int y)
6160 int element = MovingOrBlocked2Element(x, y);
6161 int explosion_type = EX_TYPE_NORMAL;
6163 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6165 struct PlayerInfo *player = PLAYERINFO(x, y);
6167 element = Tile[x][y] = player->initial_element;
6169 if (level.use_explosion_element[player->index_nr])
6171 int explosion_element = level.explosion_element[player->index_nr];
6173 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6174 explosion_type = EX_TYPE_CROSS;
6175 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6176 explosion_type = EX_TYPE_CENTER;
6184 case EL_BD_BUTTERFLY:
6187 case EL_DARK_YAMYAM:
6191 RaiseScoreElement(element);
6194 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6195 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6196 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6197 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6198 case EL_DYNABOMB_INCREASE_NUMBER:
6199 case EL_DYNABOMB_INCREASE_SIZE:
6200 case EL_DYNABOMB_INCREASE_POWER:
6201 explosion_type = EX_TYPE_DYNA;
6204 case EL_DC_LANDMINE:
6205 explosion_type = EX_TYPE_CENTER;
6210 case EL_LAMP_ACTIVE:
6211 case EL_AMOEBA_TO_DIAMOND:
6212 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6213 explosion_type = EX_TYPE_CENTER;
6217 if (element_info[element].explosion_type == EXPLODES_CROSS)
6218 explosion_type = EX_TYPE_CROSS;
6219 else if (element_info[element].explosion_type == EXPLODES_1X1)
6220 explosion_type = EX_TYPE_CENTER;
6224 if (explosion_type == EX_TYPE_DYNA)
6227 Explode(x, y, EX_PHASE_START, explosion_type);
6229 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6232 static void SplashAcid(int x, int y)
6234 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6235 (!IN_LEV_FIELD(x - 1, y - 2) ||
6236 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6237 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6239 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6240 (!IN_LEV_FIELD(x + 1, y - 2) ||
6241 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6242 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6244 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6247 static void InitBeltMovement(void)
6249 static int belt_base_element[4] =
6251 EL_CONVEYOR_BELT_1_LEFT,
6252 EL_CONVEYOR_BELT_2_LEFT,
6253 EL_CONVEYOR_BELT_3_LEFT,
6254 EL_CONVEYOR_BELT_4_LEFT
6256 static int belt_base_active_element[4] =
6258 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6259 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6260 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6261 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6266 // set frame order for belt animation graphic according to belt direction
6267 for (i = 0; i < NUM_BELTS; i++)
6271 for (j = 0; j < NUM_BELT_PARTS; j++)
6273 int element = belt_base_active_element[belt_nr] + j;
6274 int graphic_1 = el2img(element);
6275 int graphic_2 = el2panelimg(element);
6277 if (game.belt_dir[i] == MV_LEFT)
6279 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6280 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6284 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6285 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6290 SCAN_PLAYFIELD(x, y)
6292 int element = Tile[x][y];
6294 for (i = 0; i < NUM_BELTS; i++)
6296 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6298 int e_belt_nr = getBeltNrFromBeltElement(element);
6301 if (e_belt_nr == belt_nr)
6303 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6305 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6312 static void ToggleBeltSwitch(int x, int y)
6314 static int belt_base_element[4] =
6316 EL_CONVEYOR_BELT_1_LEFT,
6317 EL_CONVEYOR_BELT_2_LEFT,
6318 EL_CONVEYOR_BELT_3_LEFT,
6319 EL_CONVEYOR_BELT_4_LEFT
6321 static int belt_base_active_element[4] =
6323 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6324 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6325 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6326 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6328 static int belt_base_switch_element[4] =
6330 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6331 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6332 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6333 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6335 static int belt_move_dir[4] =
6343 int element = Tile[x][y];
6344 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6345 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6346 int belt_dir = belt_move_dir[belt_dir_nr];
6349 if (!IS_BELT_SWITCH(element))
6352 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6353 game.belt_dir[belt_nr] = belt_dir;
6355 if (belt_dir_nr == 3)
6358 // set frame order for belt animation graphic according to belt direction
6359 for (i = 0; i < NUM_BELT_PARTS; i++)
6361 int element = belt_base_active_element[belt_nr] + i;
6362 int graphic_1 = el2img(element);
6363 int graphic_2 = el2panelimg(element);
6365 if (belt_dir == MV_LEFT)
6367 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6368 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6372 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6373 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6377 SCAN_PLAYFIELD(xx, yy)
6379 int element = Tile[xx][yy];
6381 if (IS_BELT_SWITCH(element))
6383 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6385 if (e_belt_nr == belt_nr)
6387 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6388 TEST_DrawLevelField(xx, yy);
6391 else if (IS_BELT(element) && belt_dir != MV_NONE)
6393 int e_belt_nr = getBeltNrFromBeltElement(element);
6395 if (e_belt_nr == belt_nr)
6397 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6399 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6400 TEST_DrawLevelField(xx, yy);
6403 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6405 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6407 if (e_belt_nr == belt_nr)
6409 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6411 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6412 TEST_DrawLevelField(xx, yy);
6418 static void ToggleSwitchgateSwitch(int x, int y)
6422 game.switchgate_pos = !game.switchgate_pos;
6424 SCAN_PLAYFIELD(xx, yy)
6426 int element = Tile[xx][yy];
6428 if (element == EL_SWITCHGATE_SWITCH_UP)
6430 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6431 TEST_DrawLevelField(xx, yy);
6433 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6435 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6436 TEST_DrawLevelField(xx, yy);
6438 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6440 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6441 TEST_DrawLevelField(xx, yy);
6443 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6445 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6446 TEST_DrawLevelField(xx, yy);
6448 else if (element == EL_SWITCHGATE_OPEN ||
6449 element == EL_SWITCHGATE_OPENING)
6451 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6453 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6455 else if (element == EL_SWITCHGATE_CLOSED ||
6456 element == EL_SWITCHGATE_CLOSING)
6458 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6460 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6465 static int getInvisibleActiveFromInvisibleElement(int element)
6467 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6468 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6469 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6473 static int getInvisibleFromInvisibleActiveElement(int element)
6475 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6476 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6477 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6481 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6485 SCAN_PLAYFIELD(x, y)
6487 int element = Tile[x][y];
6489 if (element == EL_LIGHT_SWITCH &&
6490 game.light_time_left > 0)
6492 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6493 TEST_DrawLevelField(x, y);
6495 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6496 game.light_time_left == 0)
6498 Tile[x][y] = EL_LIGHT_SWITCH;
6499 TEST_DrawLevelField(x, y);
6501 else if (element == EL_EMC_DRIPPER &&
6502 game.light_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.light_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.light_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.light_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 RedrawAllInvisibleElementsForLenses(void)
6546 SCAN_PLAYFIELD(x, y)
6548 int element = Tile[x][y];
6550 if (element == EL_EMC_DRIPPER &&
6551 game.lenses_time_left > 0)
6553 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6554 TEST_DrawLevelField(x, y);
6556 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6557 game.lenses_time_left == 0)
6559 Tile[x][y] = EL_EMC_DRIPPER;
6560 TEST_DrawLevelField(x, y);
6562 else if (element == EL_INVISIBLE_STEELWALL ||
6563 element == EL_INVISIBLE_WALL ||
6564 element == EL_INVISIBLE_SAND)
6566 if (game.lenses_time_left > 0)
6567 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6569 TEST_DrawLevelField(x, y);
6571 // uncrumble neighbour fields, if needed
6572 if (element == EL_INVISIBLE_SAND)
6573 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6575 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6576 element == EL_INVISIBLE_WALL_ACTIVE ||
6577 element == EL_INVISIBLE_SAND_ACTIVE)
6579 if (game.lenses_time_left == 0)
6580 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6582 TEST_DrawLevelField(x, y);
6584 // re-crumble neighbour fields, if needed
6585 if (element == EL_INVISIBLE_SAND)
6586 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6591 static void RedrawAllInvisibleElementsForMagnifier(void)
6595 SCAN_PLAYFIELD(x, y)
6597 int element = Tile[x][y];
6599 if (element == EL_EMC_FAKE_GRASS &&
6600 game.magnify_time_left > 0)
6602 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6603 TEST_DrawLevelField(x, y);
6605 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6606 game.magnify_time_left == 0)
6608 Tile[x][y] = EL_EMC_FAKE_GRASS;
6609 TEST_DrawLevelField(x, y);
6611 else if (IS_GATE_GRAY(element) &&
6612 game.magnify_time_left > 0)
6614 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6615 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6616 IS_EM_GATE_GRAY(element) ?
6617 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6618 IS_EMC_GATE_GRAY(element) ?
6619 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6620 IS_DC_GATE_GRAY(element) ?
6621 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6623 TEST_DrawLevelField(x, y);
6625 else if (IS_GATE_GRAY_ACTIVE(element) &&
6626 game.magnify_time_left == 0)
6628 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6629 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6630 IS_EM_GATE_GRAY_ACTIVE(element) ?
6631 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6632 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6633 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6634 IS_DC_GATE_GRAY_ACTIVE(element) ?
6635 EL_DC_GATE_WHITE_GRAY :
6637 TEST_DrawLevelField(x, y);
6642 static void ToggleLightSwitch(int x, int y)
6644 int element = Tile[x][y];
6646 game.light_time_left =
6647 (element == EL_LIGHT_SWITCH ?
6648 level.time_light * FRAMES_PER_SECOND : 0);
6650 RedrawAllLightSwitchesAndInvisibleElements();
6653 static void ActivateTimegateSwitch(int x, int y)
6657 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6659 SCAN_PLAYFIELD(xx, yy)
6661 int element = Tile[xx][yy];
6663 if (element == EL_TIMEGATE_CLOSED ||
6664 element == EL_TIMEGATE_CLOSING)
6666 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6667 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6671 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6673 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6674 TEST_DrawLevelField(xx, yy);
6680 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6681 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6684 static void Impact(int x, int y)
6686 boolean last_line = (y == lev_fieldy - 1);
6687 boolean object_hit = FALSE;
6688 boolean impact = (last_line || object_hit);
6689 int element = Tile[x][y];
6690 int smashed = EL_STEELWALL;
6692 if (!last_line) // check if element below was hit
6694 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6697 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6698 MovDir[x][y + 1] != MV_DOWN ||
6699 MovPos[x][y + 1] <= TILEY / 2));
6701 // do not smash moving elements that left the smashed field in time
6702 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6703 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6706 #if USE_QUICKSAND_IMPACT_BUGFIX
6707 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6709 RemoveMovingField(x, y + 1);
6710 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6711 Tile[x][y + 2] = EL_ROCK;
6712 TEST_DrawLevelField(x, y + 2);
6717 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6719 RemoveMovingField(x, y + 1);
6720 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6721 Tile[x][y + 2] = EL_ROCK;
6722 TEST_DrawLevelField(x, y + 2);
6729 smashed = MovingOrBlocked2Element(x, y + 1);
6731 impact = (last_line || object_hit);
6734 if (!last_line && smashed == EL_ACID) // element falls into acid
6736 SplashAcid(x, y + 1);
6740 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6741 // only reset graphic animation if graphic really changes after impact
6743 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6745 ResetGfxAnimation(x, y);
6746 TEST_DrawLevelField(x, y);
6749 if (impact && CAN_EXPLODE_IMPACT(element))
6754 else if (impact && element == EL_PEARL &&
6755 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6757 ResetGfxAnimation(x, y);
6759 Tile[x][y] = EL_PEARL_BREAKING;
6760 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6763 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6765 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6770 if (impact && element == EL_AMOEBA_DROP)
6772 if (object_hit && IS_PLAYER(x, y + 1))
6773 KillPlayerUnlessEnemyProtected(x, y + 1);
6774 else if (object_hit && smashed == EL_PENGUIN)
6778 Tile[x][y] = EL_AMOEBA_GROWING;
6779 Store[x][y] = EL_AMOEBA_WET;
6781 ResetRandomAnimationValue(x, y);
6786 if (object_hit) // check which object was hit
6788 if ((CAN_PASS_MAGIC_WALL(element) &&
6789 (smashed == EL_MAGIC_WALL ||
6790 smashed == EL_BD_MAGIC_WALL)) ||
6791 (CAN_PASS_DC_MAGIC_WALL(element) &&
6792 smashed == EL_DC_MAGIC_WALL))
6795 int activated_magic_wall =
6796 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6797 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6798 EL_DC_MAGIC_WALL_ACTIVE);
6800 // activate magic wall / mill
6801 SCAN_PLAYFIELD(xx, yy)
6803 if (Tile[xx][yy] == smashed)
6804 Tile[xx][yy] = activated_magic_wall;
6807 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6808 game.magic_wall_active = TRUE;
6810 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6811 SND_MAGIC_WALL_ACTIVATING :
6812 smashed == EL_BD_MAGIC_WALL ?
6813 SND_BD_MAGIC_WALL_ACTIVATING :
6814 SND_DC_MAGIC_WALL_ACTIVATING));
6817 if (IS_PLAYER(x, y + 1))
6819 if (CAN_SMASH_PLAYER(element))
6821 KillPlayerUnlessEnemyProtected(x, y + 1);
6825 else if (smashed == EL_PENGUIN)
6827 if (CAN_SMASH_PLAYER(element))
6833 else if (element == EL_BD_DIAMOND)
6835 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6841 else if (((element == EL_SP_INFOTRON ||
6842 element == EL_SP_ZONK) &&
6843 (smashed == EL_SP_SNIKSNAK ||
6844 smashed == EL_SP_ELECTRON ||
6845 smashed == EL_SP_DISK_ORANGE)) ||
6846 (element == EL_SP_INFOTRON &&
6847 smashed == EL_SP_DISK_YELLOW))
6852 else if (CAN_SMASH_EVERYTHING(element))
6854 if (IS_CLASSIC_ENEMY(smashed) ||
6855 CAN_EXPLODE_SMASHED(smashed))
6860 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6862 if (smashed == EL_LAMP ||
6863 smashed == EL_LAMP_ACTIVE)
6868 else if (smashed == EL_NUT)
6870 Tile[x][y + 1] = EL_NUT_BREAKING;
6871 PlayLevelSound(x, y, SND_NUT_BREAKING);
6872 RaiseScoreElement(EL_NUT);
6875 else if (smashed == EL_PEARL)
6877 ResetGfxAnimation(x, y);
6879 Tile[x][y + 1] = EL_PEARL_BREAKING;
6880 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6883 else if (smashed == EL_DIAMOND)
6885 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6886 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6889 else if (IS_BELT_SWITCH(smashed))
6891 ToggleBeltSwitch(x, y + 1);
6893 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6894 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6895 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6896 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6898 ToggleSwitchgateSwitch(x, y + 1);
6900 else if (smashed == EL_LIGHT_SWITCH ||
6901 smashed == EL_LIGHT_SWITCH_ACTIVE)
6903 ToggleLightSwitch(x, y + 1);
6907 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6909 CheckElementChangeBySide(x, y + 1, smashed, element,
6910 CE_SWITCHED, CH_SIDE_TOP);
6911 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6917 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6922 // play sound of magic wall / mill
6924 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6925 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6926 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6928 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6929 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6930 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6931 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6932 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6933 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6938 // play sound of object that hits the ground
6939 if (last_line || object_hit)
6940 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6943 static void TurnRoundExt(int x, int y)
6955 { 0, 0 }, { 0, 0 }, { 0, 0 },
6960 int left, right, back;
6964 { MV_DOWN, MV_UP, MV_RIGHT },
6965 { MV_UP, MV_DOWN, MV_LEFT },
6967 { MV_LEFT, MV_RIGHT, MV_DOWN },
6971 { MV_RIGHT, MV_LEFT, MV_UP }
6974 int element = Tile[x][y];
6975 int move_pattern = element_info[element].move_pattern;
6977 int old_move_dir = MovDir[x][y];
6978 int left_dir = turn[old_move_dir].left;
6979 int right_dir = turn[old_move_dir].right;
6980 int back_dir = turn[old_move_dir].back;
6982 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6983 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6984 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6985 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6987 int left_x = x + left_dx, left_y = y + left_dy;
6988 int right_x = x + right_dx, right_y = y + right_dy;
6989 int move_x = x + move_dx, move_y = y + move_dy;
6993 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6995 TestIfBadThingTouchesOtherBadThing(x, y);
6997 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6998 MovDir[x][y] = right_dir;
6999 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7000 MovDir[x][y] = left_dir;
7002 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7004 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7007 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7009 TestIfBadThingTouchesOtherBadThing(x, y);
7011 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7012 MovDir[x][y] = left_dir;
7013 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7014 MovDir[x][y] = right_dir;
7016 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7018 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7021 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7023 TestIfBadThingTouchesOtherBadThing(x, y);
7025 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7026 MovDir[x][y] = left_dir;
7027 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7028 MovDir[x][y] = right_dir;
7030 if (MovDir[x][y] != old_move_dir)
7033 else if (element == EL_YAMYAM)
7035 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7036 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7038 if (can_turn_left && can_turn_right)
7039 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7040 else if (can_turn_left)
7041 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7042 else if (can_turn_right)
7043 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7045 MovDir[x][y] = back_dir;
7047 MovDelay[x][y] = 16 + 16 * RND(3);
7049 else if (element == EL_DARK_YAMYAM)
7051 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7053 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7056 if (can_turn_left && can_turn_right)
7057 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7058 else if (can_turn_left)
7059 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7060 else if (can_turn_right)
7061 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7063 MovDir[x][y] = back_dir;
7065 MovDelay[x][y] = 16 + 16 * RND(3);
7067 else if (element == EL_PACMAN)
7069 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7070 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7072 if (can_turn_left && can_turn_right)
7073 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7074 else if (can_turn_left)
7075 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7076 else if (can_turn_right)
7077 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7079 MovDir[x][y] = back_dir;
7081 MovDelay[x][y] = 6 + RND(40);
7083 else if (element == EL_PIG)
7085 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7086 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7087 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7088 boolean should_turn_left, should_turn_right, should_move_on;
7090 int rnd = RND(rnd_value);
7092 should_turn_left = (can_turn_left &&
7094 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7095 y + back_dy + left_dy)));
7096 should_turn_right = (can_turn_right &&
7098 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7099 y + back_dy + right_dy)));
7100 should_move_on = (can_move_on &&
7103 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7104 y + move_dy + left_dy) ||
7105 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7106 y + move_dy + right_dy)));
7108 if (should_turn_left || should_turn_right || should_move_on)
7110 if (should_turn_left && should_turn_right && should_move_on)
7111 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7112 rnd < 2 * rnd_value / 3 ? right_dir :
7114 else if (should_turn_left && should_turn_right)
7115 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7116 else if (should_turn_left && should_move_on)
7117 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7118 else if (should_turn_right && should_move_on)
7119 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7120 else if (should_turn_left)
7121 MovDir[x][y] = left_dir;
7122 else if (should_turn_right)
7123 MovDir[x][y] = right_dir;
7124 else if (should_move_on)
7125 MovDir[x][y] = old_move_dir;
7127 else if (can_move_on && rnd > rnd_value / 8)
7128 MovDir[x][y] = old_move_dir;
7129 else if (can_turn_left && can_turn_right)
7130 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7131 else if (can_turn_left && rnd > rnd_value / 8)
7132 MovDir[x][y] = left_dir;
7133 else if (can_turn_right && rnd > rnd_value/8)
7134 MovDir[x][y] = right_dir;
7136 MovDir[x][y] = back_dir;
7138 xx = x + move_xy[MovDir[x][y]].dx;
7139 yy = y + move_xy[MovDir[x][y]].dy;
7141 if (!IN_LEV_FIELD(xx, yy) ||
7142 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7143 MovDir[x][y] = old_move_dir;
7147 else if (element == EL_DRAGON)
7149 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7150 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7151 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7153 int rnd = RND(rnd_value);
7155 if (can_move_on && rnd > rnd_value / 8)
7156 MovDir[x][y] = old_move_dir;
7157 else if (can_turn_left && can_turn_right)
7158 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7159 else if (can_turn_left && rnd > rnd_value / 8)
7160 MovDir[x][y] = left_dir;
7161 else if (can_turn_right && rnd > rnd_value / 8)
7162 MovDir[x][y] = right_dir;
7164 MovDir[x][y] = back_dir;
7166 xx = x + move_xy[MovDir[x][y]].dx;
7167 yy = y + move_xy[MovDir[x][y]].dy;
7169 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7170 MovDir[x][y] = old_move_dir;
7174 else if (element == EL_MOLE)
7176 boolean can_move_on =
7177 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7178 IS_AMOEBOID(Tile[move_x][move_y]) ||
7179 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7182 boolean can_turn_left =
7183 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7184 IS_AMOEBOID(Tile[left_x][left_y])));
7186 boolean can_turn_right =
7187 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7188 IS_AMOEBOID(Tile[right_x][right_y])));
7190 if (can_turn_left && can_turn_right)
7191 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7192 else if (can_turn_left)
7193 MovDir[x][y] = left_dir;
7195 MovDir[x][y] = right_dir;
7198 if (MovDir[x][y] != old_move_dir)
7201 else if (element == EL_BALLOON)
7203 MovDir[x][y] = game.wind_direction;
7206 else if (element == EL_SPRING)
7208 if (MovDir[x][y] & MV_HORIZONTAL)
7210 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7211 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7213 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7214 ResetGfxAnimation(move_x, move_y);
7215 TEST_DrawLevelField(move_x, move_y);
7217 MovDir[x][y] = back_dir;
7219 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7220 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7221 MovDir[x][y] = MV_NONE;
7226 else if (element == EL_ROBOT ||
7227 element == EL_SATELLITE ||
7228 element == EL_PENGUIN ||
7229 element == EL_EMC_ANDROID)
7231 int attr_x = -1, attr_y = -1;
7233 if (game.all_players_gone)
7235 attr_x = game.exit_x;
7236 attr_y = game.exit_y;
7242 for (i = 0; i < MAX_PLAYERS; i++)
7244 struct PlayerInfo *player = &stored_player[i];
7245 int jx = player->jx, jy = player->jy;
7247 if (!player->active)
7251 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7259 if (element == EL_ROBOT &&
7260 game.robot_wheel_x >= 0 &&
7261 game.robot_wheel_y >= 0 &&
7262 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7263 game.engine_version < VERSION_IDENT(3,1,0,0)))
7265 attr_x = game.robot_wheel_x;
7266 attr_y = game.robot_wheel_y;
7269 if (element == EL_PENGUIN)
7272 static int xy[4][2] =
7280 for (i = 0; i < NUM_DIRECTIONS; i++)
7282 int ex = x + xy[i][0];
7283 int ey = y + xy[i][1];
7285 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7286 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7287 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7288 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7297 MovDir[x][y] = MV_NONE;
7299 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7300 else if (attr_x > x)
7301 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7303 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7304 else if (attr_y > y)
7305 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7307 if (element == EL_ROBOT)
7311 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7312 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7313 Moving2Blocked(x, y, &newx, &newy);
7315 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7316 MovDelay[x][y] = 8 + 8 * !RND(3);
7318 MovDelay[x][y] = 16;
7320 else if (element == EL_PENGUIN)
7326 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7328 boolean first_horiz = RND(2);
7329 int new_move_dir = MovDir[x][y];
7332 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7333 Moving2Blocked(x, y, &newx, &newy);
7335 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7339 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7340 Moving2Blocked(x, y, &newx, &newy);
7342 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7345 MovDir[x][y] = old_move_dir;
7349 else if (element == EL_SATELLITE)
7355 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7357 boolean first_horiz = RND(2);
7358 int new_move_dir = MovDir[x][y];
7361 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7362 Moving2Blocked(x, y, &newx, &newy);
7364 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7368 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7369 Moving2Blocked(x, y, &newx, &newy);
7371 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7374 MovDir[x][y] = old_move_dir;
7378 else if (element == EL_EMC_ANDROID)
7380 static int check_pos[16] =
7382 -1, // 0 => (invalid)
7385 -1, // 3 => (invalid)
7387 0, // 5 => MV_LEFT | MV_UP
7388 2, // 6 => MV_RIGHT | MV_UP
7389 -1, // 7 => (invalid)
7391 6, // 9 => MV_LEFT | MV_DOWN
7392 4, // 10 => MV_RIGHT | MV_DOWN
7393 -1, // 11 => (invalid)
7394 -1, // 12 => (invalid)
7395 -1, // 13 => (invalid)
7396 -1, // 14 => (invalid)
7397 -1, // 15 => (invalid)
7405 { -1, -1, MV_LEFT | MV_UP },
7407 { +1, -1, MV_RIGHT | MV_UP },
7408 { +1, 0, MV_RIGHT },
7409 { +1, +1, MV_RIGHT | MV_DOWN },
7411 { -1, +1, MV_LEFT | MV_DOWN },
7414 int start_pos, check_order;
7415 boolean can_clone = FALSE;
7418 // check if there is any free field around current position
7419 for (i = 0; i < 8; i++)
7421 int newx = x + check_xy[i].dx;
7422 int newy = y + check_xy[i].dy;
7424 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7432 if (can_clone) // randomly find an element to clone
7436 start_pos = check_pos[RND(8)];
7437 check_order = (RND(2) ? -1 : +1);
7439 for (i = 0; i < 8; i++)
7441 int pos_raw = start_pos + i * check_order;
7442 int pos = (pos_raw + 8) % 8;
7443 int newx = x + check_xy[pos].dx;
7444 int newy = y + check_xy[pos].dy;
7446 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7448 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7449 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7451 Store[x][y] = Tile[newx][newy];
7460 if (can_clone) // randomly find a direction to move
7464 start_pos = check_pos[RND(8)];
7465 check_order = (RND(2) ? -1 : +1);
7467 for (i = 0; i < 8; i++)
7469 int pos_raw = start_pos + i * check_order;
7470 int pos = (pos_raw + 8) % 8;
7471 int newx = x + check_xy[pos].dx;
7472 int newy = y + check_xy[pos].dy;
7473 int new_move_dir = check_xy[pos].dir;
7475 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7477 MovDir[x][y] = new_move_dir;
7478 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7487 if (can_clone) // cloning and moving successful
7490 // cannot clone -- try to move towards player
7492 start_pos = check_pos[MovDir[x][y] & 0x0f];
7493 check_order = (RND(2) ? -1 : +1);
7495 for (i = 0; i < 3; i++)
7497 // first check start_pos, then previous/next or (next/previous) pos
7498 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7499 int pos = (pos_raw + 8) % 8;
7500 int newx = x + check_xy[pos].dx;
7501 int newy = y + check_xy[pos].dy;
7502 int new_move_dir = check_xy[pos].dir;
7504 if (IS_PLAYER(newx, newy))
7507 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7509 MovDir[x][y] = new_move_dir;
7510 MovDelay[x][y] = level.android_move_time * 8 + 1;
7517 else if (move_pattern == MV_TURNING_LEFT ||
7518 move_pattern == MV_TURNING_RIGHT ||
7519 move_pattern == MV_TURNING_LEFT_RIGHT ||
7520 move_pattern == MV_TURNING_RIGHT_LEFT ||
7521 move_pattern == MV_TURNING_RANDOM ||
7522 move_pattern == MV_ALL_DIRECTIONS)
7524 boolean can_turn_left =
7525 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7526 boolean can_turn_right =
7527 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7529 if (element_info[element].move_stepsize == 0) // "not moving"
7532 if (move_pattern == MV_TURNING_LEFT)
7533 MovDir[x][y] = left_dir;
7534 else if (move_pattern == MV_TURNING_RIGHT)
7535 MovDir[x][y] = right_dir;
7536 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7537 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7538 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7539 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7540 else if (move_pattern == MV_TURNING_RANDOM)
7541 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7542 can_turn_right && !can_turn_left ? right_dir :
7543 RND(2) ? left_dir : right_dir);
7544 else if (can_turn_left && can_turn_right)
7545 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7546 else if (can_turn_left)
7547 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7548 else if (can_turn_right)
7549 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7551 MovDir[x][y] = back_dir;
7553 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7555 else if (move_pattern == MV_HORIZONTAL ||
7556 move_pattern == MV_VERTICAL)
7558 if (move_pattern & old_move_dir)
7559 MovDir[x][y] = back_dir;
7560 else if (move_pattern == MV_HORIZONTAL)
7561 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7562 else if (move_pattern == MV_VERTICAL)
7563 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7565 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7567 else if (move_pattern & MV_ANY_DIRECTION)
7569 MovDir[x][y] = move_pattern;
7570 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7572 else if (move_pattern & MV_WIND_DIRECTION)
7574 MovDir[x][y] = game.wind_direction;
7575 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7577 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7579 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7580 MovDir[x][y] = left_dir;
7581 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7582 MovDir[x][y] = right_dir;
7584 if (MovDir[x][y] != old_move_dir)
7585 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7587 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7589 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7590 MovDir[x][y] = right_dir;
7591 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7592 MovDir[x][y] = left_dir;
7594 if (MovDir[x][y] != old_move_dir)
7595 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7597 else if (move_pattern == MV_TOWARDS_PLAYER ||
7598 move_pattern == MV_AWAY_FROM_PLAYER)
7600 int attr_x = -1, attr_y = -1;
7602 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7604 if (game.all_players_gone)
7606 attr_x = game.exit_x;
7607 attr_y = game.exit_y;
7613 for (i = 0; i < MAX_PLAYERS; i++)
7615 struct PlayerInfo *player = &stored_player[i];
7616 int jx = player->jx, jy = player->jy;
7618 if (!player->active)
7622 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7630 MovDir[x][y] = MV_NONE;
7632 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7633 else if (attr_x > x)
7634 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7636 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7637 else if (attr_y > y)
7638 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7640 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7642 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7644 boolean first_horiz = RND(2);
7645 int new_move_dir = MovDir[x][y];
7647 if (element_info[element].move_stepsize == 0) // "not moving"
7649 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7650 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7656 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7657 Moving2Blocked(x, y, &newx, &newy);
7659 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7663 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7664 Moving2Blocked(x, y, &newx, &newy);
7666 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7669 MovDir[x][y] = old_move_dir;
7672 else if (move_pattern == MV_WHEN_PUSHED ||
7673 move_pattern == MV_WHEN_DROPPED)
7675 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7676 MovDir[x][y] = MV_NONE;
7680 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7682 static int test_xy[7][2] =
7692 static int test_dir[7] =
7702 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7703 int move_preference = -1000000; // start with very low preference
7704 int new_move_dir = MV_NONE;
7705 int start_test = RND(4);
7708 for (i = 0; i < NUM_DIRECTIONS; i++)
7710 int move_dir = test_dir[start_test + i];
7711 int move_dir_preference;
7713 xx = x + test_xy[start_test + i][0];
7714 yy = y + test_xy[start_test + i][1];
7716 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7717 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7719 new_move_dir = move_dir;
7724 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7727 move_dir_preference = -1 * RunnerVisit[xx][yy];
7728 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7729 move_dir_preference = PlayerVisit[xx][yy];
7731 if (move_dir_preference > move_preference)
7733 // prefer field that has not been visited for the longest time
7734 move_preference = move_dir_preference;
7735 new_move_dir = move_dir;
7737 else if (move_dir_preference == move_preference &&
7738 move_dir == old_move_dir)
7740 // prefer last direction when all directions are preferred equally
7741 move_preference = move_dir_preference;
7742 new_move_dir = move_dir;
7746 MovDir[x][y] = new_move_dir;
7747 if (old_move_dir != new_move_dir)
7748 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7752 static void TurnRound(int x, int y)
7754 int direction = MovDir[x][y];
7758 GfxDir[x][y] = MovDir[x][y];
7760 if (direction != MovDir[x][y])
7764 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7766 ResetGfxFrame(x, y);
7769 static boolean JustBeingPushed(int x, int y)
7773 for (i = 0; i < MAX_PLAYERS; i++)
7775 struct PlayerInfo *player = &stored_player[i];
7777 if (player->active && player->is_pushing && player->MovPos)
7779 int next_jx = player->jx + (player->jx - player->last_jx);
7780 int next_jy = player->jy + (player->jy - player->last_jy);
7782 if (x == next_jx && y == next_jy)
7790 static void StartMoving(int x, int y)
7792 boolean started_moving = FALSE; // some elements can fall _and_ move
7793 int element = Tile[x][y];
7798 if (MovDelay[x][y] == 0)
7799 GfxAction[x][y] = ACTION_DEFAULT;
7801 if (CAN_FALL(element) && y < lev_fieldy - 1)
7803 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7804 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7805 if (JustBeingPushed(x, y))
7808 if (element == EL_QUICKSAND_FULL)
7810 if (IS_FREE(x, y + 1))
7812 InitMovingField(x, y, MV_DOWN);
7813 started_moving = TRUE;
7815 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7816 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7817 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7818 Store[x][y] = EL_ROCK;
7820 Store[x][y] = EL_ROCK;
7823 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7825 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7827 if (!MovDelay[x][y])
7829 MovDelay[x][y] = TILEY + 1;
7831 ResetGfxAnimation(x, y);
7832 ResetGfxAnimation(x, y + 1);
7837 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7838 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7845 Tile[x][y] = EL_QUICKSAND_EMPTY;
7846 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7847 Store[x][y + 1] = Store[x][y];
7850 PlayLevelSoundAction(x, y, ACTION_FILLING);
7852 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7854 if (!MovDelay[x][y])
7856 MovDelay[x][y] = TILEY + 1;
7858 ResetGfxAnimation(x, y);
7859 ResetGfxAnimation(x, y + 1);
7864 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7865 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7872 Tile[x][y] = EL_QUICKSAND_EMPTY;
7873 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7874 Store[x][y + 1] = Store[x][y];
7877 PlayLevelSoundAction(x, y, ACTION_FILLING);
7880 else if (element == EL_QUICKSAND_FAST_FULL)
7882 if (IS_FREE(x, y + 1))
7884 InitMovingField(x, y, MV_DOWN);
7885 started_moving = TRUE;
7887 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7888 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7889 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7890 Store[x][y] = EL_ROCK;
7892 Store[x][y] = EL_ROCK;
7895 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7897 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7899 if (!MovDelay[x][y])
7901 MovDelay[x][y] = TILEY + 1;
7903 ResetGfxAnimation(x, y);
7904 ResetGfxAnimation(x, y + 1);
7909 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7910 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7917 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7918 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7919 Store[x][y + 1] = Store[x][y];
7922 PlayLevelSoundAction(x, y, ACTION_FILLING);
7924 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7926 if (!MovDelay[x][y])
7928 MovDelay[x][y] = TILEY + 1;
7930 ResetGfxAnimation(x, y);
7931 ResetGfxAnimation(x, y + 1);
7936 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7937 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7944 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7945 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7946 Store[x][y + 1] = Store[x][y];
7949 PlayLevelSoundAction(x, y, ACTION_FILLING);
7952 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7953 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7955 InitMovingField(x, y, MV_DOWN);
7956 started_moving = TRUE;
7958 Tile[x][y] = EL_QUICKSAND_FILLING;
7959 Store[x][y] = element;
7961 PlayLevelSoundAction(x, y, ACTION_FILLING);
7963 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7964 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7966 InitMovingField(x, y, MV_DOWN);
7967 started_moving = TRUE;
7969 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7970 Store[x][y] = element;
7972 PlayLevelSoundAction(x, y, ACTION_FILLING);
7974 else if (element == EL_MAGIC_WALL_FULL)
7976 if (IS_FREE(x, y + 1))
7978 InitMovingField(x, y, MV_DOWN);
7979 started_moving = TRUE;
7981 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7982 Store[x][y] = EL_CHANGED(Store[x][y]);
7984 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7986 if (!MovDelay[x][y])
7987 MovDelay[x][y] = TILEY / 4 + 1;
7996 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7997 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7998 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8002 else if (element == EL_BD_MAGIC_WALL_FULL)
8004 if (IS_FREE(x, y + 1))
8006 InitMovingField(x, y, MV_DOWN);
8007 started_moving = TRUE;
8009 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8010 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8012 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8014 if (!MovDelay[x][y])
8015 MovDelay[x][y] = TILEY / 4 + 1;
8024 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8025 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8026 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8030 else if (element == EL_DC_MAGIC_WALL_FULL)
8032 if (IS_FREE(x, y + 1))
8034 InitMovingField(x, y, MV_DOWN);
8035 started_moving = TRUE;
8037 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8038 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8040 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8042 if (!MovDelay[x][y])
8043 MovDelay[x][y] = TILEY / 4 + 1;
8052 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8053 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8054 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8058 else if ((CAN_PASS_MAGIC_WALL(element) &&
8059 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8060 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8061 (CAN_PASS_DC_MAGIC_WALL(element) &&
8062 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8065 InitMovingField(x, y, MV_DOWN);
8066 started_moving = TRUE;
8069 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8070 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8071 EL_DC_MAGIC_WALL_FILLING);
8072 Store[x][y] = element;
8074 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8076 SplashAcid(x, y + 1);
8078 InitMovingField(x, y, MV_DOWN);
8079 started_moving = TRUE;
8081 Store[x][y] = EL_ACID;
8084 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8085 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8086 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8087 CAN_FALL(element) && WasJustFalling[x][y] &&
8088 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8090 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8091 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8092 (Tile[x][y + 1] == EL_BLOCKED)))
8094 /* this is needed for a special case not covered by calling "Impact()"
8095 from "ContinueMoving()": if an element moves to a tile directly below
8096 another element which was just falling on that tile (which was empty
8097 in the previous frame), the falling element above would just stop
8098 instead of smashing the element below (in previous version, the above
8099 element was just checked for "moving" instead of "falling", resulting
8100 in incorrect smashes caused by horizontal movement of the above
8101 element; also, the case of the player being the element to smash was
8102 simply not covered here... :-/ ) */
8104 CheckCollision[x][y] = 0;
8105 CheckImpact[x][y] = 0;
8109 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8111 if (MovDir[x][y] == MV_NONE)
8113 InitMovingField(x, y, MV_DOWN);
8114 started_moving = TRUE;
8117 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8119 if (WasJustFalling[x][y]) // prevent animation from being restarted
8120 MovDir[x][y] = MV_DOWN;
8122 InitMovingField(x, y, MV_DOWN);
8123 started_moving = TRUE;
8125 else if (element == EL_AMOEBA_DROP)
8127 Tile[x][y] = EL_AMOEBA_GROWING;
8128 Store[x][y] = EL_AMOEBA_WET;
8130 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8131 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8132 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8133 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8135 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8136 (IS_FREE(x - 1, y + 1) ||
8137 Tile[x - 1][y + 1] == EL_ACID));
8138 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8139 (IS_FREE(x + 1, y + 1) ||
8140 Tile[x + 1][y + 1] == EL_ACID));
8141 boolean can_fall_any = (can_fall_left || can_fall_right);
8142 boolean can_fall_both = (can_fall_left && can_fall_right);
8143 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8145 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8147 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8148 can_fall_right = FALSE;
8149 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8150 can_fall_left = FALSE;
8151 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8152 can_fall_right = FALSE;
8153 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8154 can_fall_left = FALSE;
8156 can_fall_any = (can_fall_left || can_fall_right);
8157 can_fall_both = FALSE;
8162 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8163 can_fall_right = FALSE; // slip down on left side
8165 can_fall_left = !(can_fall_right = RND(2));
8167 can_fall_both = FALSE;
8172 // if not determined otherwise, prefer left side for slipping down
8173 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8174 started_moving = TRUE;
8177 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8179 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8180 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8181 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8182 int belt_dir = game.belt_dir[belt_nr];
8184 if ((belt_dir == MV_LEFT && left_is_free) ||
8185 (belt_dir == MV_RIGHT && right_is_free))
8187 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8189 InitMovingField(x, y, belt_dir);
8190 started_moving = TRUE;
8192 Pushed[x][y] = TRUE;
8193 Pushed[nextx][y] = TRUE;
8195 GfxAction[x][y] = ACTION_DEFAULT;
8199 MovDir[x][y] = 0; // if element was moving, stop it
8204 // not "else if" because of elements that can fall and move (EL_SPRING)
8205 if (CAN_MOVE(element) && !started_moving)
8207 int move_pattern = element_info[element].move_pattern;
8210 Moving2Blocked(x, y, &newx, &newy);
8212 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8215 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8216 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8218 WasJustMoving[x][y] = 0;
8219 CheckCollision[x][y] = 0;
8221 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8223 if (Tile[x][y] != element) // element has changed
8227 if (!MovDelay[x][y]) // start new movement phase
8229 // all objects that can change their move direction after each step
8230 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8232 if (element != EL_YAMYAM &&
8233 element != EL_DARK_YAMYAM &&
8234 element != EL_PACMAN &&
8235 !(move_pattern & MV_ANY_DIRECTION) &&
8236 move_pattern != MV_TURNING_LEFT &&
8237 move_pattern != MV_TURNING_RIGHT &&
8238 move_pattern != MV_TURNING_LEFT_RIGHT &&
8239 move_pattern != MV_TURNING_RIGHT_LEFT &&
8240 move_pattern != MV_TURNING_RANDOM)
8244 if (MovDelay[x][y] && (element == EL_BUG ||
8245 element == EL_SPACESHIP ||
8246 element == EL_SP_SNIKSNAK ||
8247 element == EL_SP_ELECTRON ||
8248 element == EL_MOLE))
8249 TEST_DrawLevelField(x, y);
8253 if (MovDelay[x][y]) // wait some time before next movement
8257 if (element == EL_ROBOT ||
8258 element == EL_YAMYAM ||
8259 element == EL_DARK_YAMYAM)
8261 DrawLevelElementAnimationIfNeeded(x, y, element);
8262 PlayLevelSoundAction(x, y, ACTION_WAITING);
8264 else if (element == EL_SP_ELECTRON)
8265 DrawLevelElementAnimationIfNeeded(x, y, element);
8266 else if (element == EL_DRAGON)
8269 int dir = MovDir[x][y];
8270 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8271 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8272 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8273 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8274 dir == MV_UP ? IMG_FLAMES_1_UP :
8275 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8276 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8278 GfxAction[x][y] = ACTION_ATTACKING;
8280 if (IS_PLAYER(x, y))
8281 DrawPlayerField(x, y);
8283 TEST_DrawLevelField(x, y);
8285 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8287 for (i = 1; i <= 3; i++)
8289 int xx = x + i * dx;
8290 int yy = y + i * dy;
8291 int sx = SCREENX(xx);
8292 int sy = SCREENY(yy);
8293 int flame_graphic = graphic + (i - 1);
8295 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8300 int flamed = MovingOrBlocked2Element(xx, yy);
8302 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8305 RemoveMovingField(xx, yy);
8307 ChangeDelay[xx][yy] = 0;
8309 Tile[xx][yy] = EL_FLAMES;
8311 if (IN_SCR_FIELD(sx, sy))
8313 TEST_DrawLevelFieldCrumbled(xx, yy);
8314 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8319 if (Tile[xx][yy] == EL_FLAMES)
8320 Tile[xx][yy] = EL_EMPTY;
8321 TEST_DrawLevelField(xx, yy);
8326 if (MovDelay[x][y]) // element still has to wait some time
8328 PlayLevelSoundAction(x, y, ACTION_WAITING);
8334 // now make next step
8336 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8338 if (DONT_COLLIDE_WITH(element) &&
8339 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8340 !PLAYER_ENEMY_PROTECTED(newx, newy))
8342 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8347 else if (CAN_MOVE_INTO_ACID(element) &&
8348 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8349 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8350 (MovDir[x][y] == MV_DOWN ||
8351 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8353 SplashAcid(newx, newy);
8354 Store[x][y] = EL_ACID;
8356 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8358 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8359 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8360 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8361 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8364 TEST_DrawLevelField(x, y);
8366 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8367 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8368 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8370 game.friends_still_needed--;
8371 if (!game.friends_still_needed &&
8373 game.all_players_gone)
8378 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8380 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8381 TEST_DrawLevelField(newx, newy);
8383 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8385 else if (!IS_FREE(newx, newy))
8387 GfxAction[x][y] = ACTION_WAITING;
8389 if (IS_PLAYER(x, y))
8390 DrawPlayerField(x, y);
8392 TEST_DrawLevelField(x, y);
8397 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8399 if (IS_FOOD_PIG(Tile[newx][newy]))
8401 if (IS_MOVING(newx, newy))
8402 RemoveMovingField(newx, newy);
8405 Tile[newx][newy] = EL_EMPTY;
8406 TEST_DrawLevelField(newx, newy);
8409 PlayLevelSound(x, y, SND_PIG_DIGGING);
8411 else if (!IS_FREE(newx, newy))
8413 if (IS_PLAYER(x, y))
8414 DrawPlayerField(x, y);
8416 TEST_DrawLevelField(x, y);
8421 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8423 if (Store[x][y] != EL_EMPTY)
8425 boolean can_clone = FALSE;
8428 // check if element to clone is still there
8429 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8431 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8439 // cannot clone or target field not free anymore -- do not clone
8440 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8441 Store[x][y] = EL_EMPTY;
8444 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8446 if (IS_MV_DIAGONAL(MovDir[x][y]))
8448 int diagonal_move_dir = MovDir[x][y];
8449 int stored = Store[x][y];
8450 int change_delay = 8;
8453 // android is moving diagonally
8455 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8457 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8458 GfxElement[x][y] = EL_EMC_ANDROID;
8459 GfxAction[x][y] = ACTION_SHRINKING;
8460 GfxDir[x][y] = diagonal_move_dir;
8461 ChangeDelay[x][y] = change_delay;
8463 if (Store[x][y] == EL_EMPTY)
8464 Store[x][y] = GfxElementEmpty[x][y];
8466 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8469 DrawLevelGraphicAnimation(x, y, graphic);
8470 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8472 if (Tile[newx][newy] == EL_ACID)
8474 SplashAcid(newx, newy);
8479 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8481 Store[newx][newy] = EL_EMC_ANDROID;
8482 GfxElement[newx][newy] = EL_EMC_ANDROID;
8483 GfxAction[newx][newy] = ACTION_GROWING;
8484 GfxDir[newx][newy] = diagonal_move_dir;
8485 ChangeDelay[newx][newy] = change_delay;
8487 graphic = el_act_dir2img(GfxElement[newx][newy],
8488 GfxAction[newx][newy], GfxDir[newx][newy]);
8490 DrawLevelGraphicAnimation(newx, newy, graphic);
8491 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8497 Tile[newx][newy] = EL_EMPTY;
8498 TEST_DrawLevelField(newx, newy);
8500 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8503 else if (!IS_FREE(newx, newy))
8508 else if (IS_CUSTOM_ELEMENT(element) &&
8509 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8511 if (!DigFieldByCE(newx, newy, element))
8514 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8516 RunnerVisit[x][y] = FrameCounter;
8517 PlayerVisit[x][y] /= 8; // expire player visit path
8520 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8522 if (!IS_FREE(newx, newy))
8524 if (IS_PLAYER(x, y))
8525 DrawPlayerField(x, y);
8527 TEST_DrawLevelField(x, y);
8533 boolean wanna_flame = !RND(10);
8534 int dx = newx - x, dy = newy - y;
8535 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8536 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8537 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8538 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8539 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8540 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8543 IS_CLASSIC_ENEMY(element1) ||
8544 IS_CLASSIC_ENEMY(element2)) &&
8545 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8546 element1 != EL_FLAMES && element2 != EL_FLAMES)
8548 ResetGfxAnimation(x, y);
8549 GfxAction[x][y] = ACTION_ATTACKING;
8551 if (IS_PLAYER(x, y))
8552 DrawPlayerField(x, y);
8554 TEST_DrawLevelField(x, y);
8556 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8558 MovDelay[x][y] = 50;
8560 Tile[newx][newy] = EL_FLAMES;
8561 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8562 Tile[newx1][newy1] = EL_FLAMES;
8563 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8564 Tile[newx2][newy2] = EL_FLAMES;
8570 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8571 Tile[newx][newy] == EL_DIAMOND)
8573 if (IS_MOVING(newx, newy))
8574 RemoveMovingField(newx, newy);
8577 Tile[newx][newy] = EL_EMPTY;
8578 TEST_DrawLevelField(newx, newy);
8581 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8583 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8584 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8586 if (AmoebaNr[newx][newy])
8588 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8589 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8590 Tile[newx][newy] == EL_BD_AMOEBA)
8591 AmoebaCnt[AmoebaNr[newx][newy]]--;
8594 if (IS_MOVING(newx, newy))
8596 RemoveMovingField(newx, newy);
8600 Tile[newx][newy] = EL_EMPTY;
8601 TEST_DrawLevelField(newx, newy);
8604 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8606 else if ((element == EL_PACMAN || element == EL_MOLE)
8607 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8609 if (AmoebaNr[newx][newy])
8611 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8612 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8613 Tile[newx][newy] == EL_BD_AMOEBA)
8614 AmoebaCnt[AmoebaNr[newx][newy]]--;
8617 if (element == EL_MOLE)
8619 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8620 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8622 ResetGfxAnimation(x, y);
8623 GfxAction[x][y] = ACTION_DIGGING;
8624 TEST_DrawLevelField(x, y);
8626 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8628 return; // wait for shrinking amoeba
8630 else // element == EL_PACMAN
8632 Tile[newx][newy] = EL_EMPTY;
8633 TEST_DrawLevelField(newx, newy);
8634 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8637 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8638 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8639 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8641 // wait for shrinking amoeba to completely disappear
8644 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8646 // object was running against a wall
8650 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8651 DrawLevelElementAnimation(x, y, element);
8653 if (DONT_TOUCH(element))
8654 TestIfBadThingTouchesPlayer(x, y);
8659 InitMovingField(x, y, MovDir[x][y]);
8661 PlayLevelSoundAction(x, y, ACTION_MOVING);
8665 ContinueMoving(x, y);
8668 void ContinueMoving(int x, int y)
8670 int element = Tile[x][y];
8671 struct ElementInfo *ei = &element_info[element];
8672 int direction = MovDir[x][y];
8673 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8674 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8675 int newx = x + dx, newy = y + dy;
8676 int stored = Store[x][y];
8677 int stored_new = Store[newx][newy];
8678 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8679 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8680 boolean last_line = (newy == lev_fieldy - 1);
8681 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8683 if (pushed_by_player) // special case: moving object pushed by player
8685 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8687 else if (use_step_delay) // special case: moving object has step delay
8689 if (!MovDelay[x][y])
8690 MovPos[x][y] += getElementMoveStepsize(x, y);
8695 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8699 TEST_DrawLevelField(x, y);
8701 return; // element is still waiting
8704 else // normal case: generically moving object
8706 MovPos[x][y] += getElementMoveStepsize(x, y);
8709 if (ABS(MovPos[x][y]) < TILEX)
8711 TEST_DrawLevelField(x, y);
8713 return; // element is still moving
8716 // element reached destination field
8718 Tile[x][y] = EL_EMPTY;
8719 Tile[newx][newy] = element;
8720 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8722 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8724 element = Tile[newx][newy] = EL_ACID;
8726 else if (element == EL_MOLE)
8728 Tile[x][y] = EL_SAND;
8730 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8732 else if (element == EL_QUICKSAND_FILLING)
8734 element = Tile[newx][newy] = get_next_element(element);
8735 Store[newx][newy] = Store[x][y];
8737 else if (element == EL_QUICKSAND_EMPTYING)
8739 Tile[x][y] = get_next_element(element);
8740 element = Tile[newx][newy] = Store[x][y];
8742 else if (element == EL_QUICKSAND_FAST_FILLING)
8744 element = Tile[newx][newy] = get_next_element(element);
8745 Store[newx][newy] = Store[x][y];
8747 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8749 Tile[x][y] = get_next_element(element);
8750 element = Tile[newx][newy] = Store[x][y];
8752 else if (element == EL_MAGIC_WALL_FILLING)
8754 element = Tile[newx][newy] = get_next_element(element);
8755 if (!game.magic_wall_active)
8756 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8757 Store[newx][newy] = Store[x][y];
8759 else if (element == EL_MAGIC_WALL_EMPTYING)
8761 Tile[x][y] = get_next_element(element);
8762 if (!game.magic_wall_active)
8763 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8764 element = Tile[newx][newy] = Store[x][y];
8766 InitField(newx, newy, FALSE);
8768 else if (element == EL_BD_MAGIC_WALL_FILLING)
8770 element = Tile[newx][newy] = get_next_element(element);
8771 if (!game.magic_wall_active)
8772 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8773 Store[newx][newy] = Store[x][y];
8775 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8777 Tile[x][y] = get_next_element(element);
8778 if (!game.magic_wall_active)
8779 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8780 element = Tile[newx][newy] = Store[x][y];
8782 InitField(newx, newy, FALSE);
8784 else if (element == EL_DC_MAGIC_WALL_FILLING)
8786 element = Tile[newx][newy] = get_next_element(element);
8787 if (!game.magic_wall_active)
8788 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8789 Store[newx][newy] = Store[x][y];
8791 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8793 Tile[x][y] = get_next_element(element);
8794 if (!game.magic_wall_active)
8795 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8796 element = Tile[newx][newy] = Store[x][y];
8798 InitField(newx, newy, FALSE);
8800 else if (element == EL_AMOEBA_DROPPING)
8802 Tile[x][y] = get_next_element(element);
8803 element = Tile[newx][newy] = Store[x][y];
8805 else if (element == EL_SOKOBAN_OBJECT)
8808 Tile[x][y] = Back[x][y];
8810 if (Back[newx][newy])
8811 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8813 Back[x][y] = Back[newx][newy] = 0;
8816 Store[x][y] = EL_EMPTY;
8821 MovDelay[newx][newy] = 0;
8823 if (CAN_CHANGE_OR_HAS_ACTION(element))
8825 // copy element change control values to new field
8826 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8827 ChangePage[newx][newy] = ChangePage[x][y];
8828 ChangeCount[newx][newy] = ChangeCount[x][y];
8829 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8832 CustomValue[newx][newy] = CustomValue[x][y];
8834 ChangeDelay[x][y] = 0;
8835 ChangePage[x][y] = -1;
8836 ChangeCount[x][y] = 0;
8837 ChangeEvent[x][y] = -1;
8839 CustomValue[x][y] = 0;
8841 // copy animation control values to new field
8842 GfxFrame[newx][newy] = GfxFrame[x][y];
8843 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8844 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8845 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8847 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8849 // some elements can leave other elements behind after moving
8850 if (ei->move_leave_element != EL_EMPTY &&
8851 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8852 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8854 int move_leave_element = ei->move_leave_element;
8856 // this makes it possible to leave the removed element again
8857 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8858 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8860 Tile[x][y] = move_leave_element;
8862 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8863 MovDir[x][y] = direction;
8865 InitField(x, y, FALSE);
8867 if (GFX_CRUMBLED(Tile[x][y]))
8868 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8870 if (IS_PLAYER_ELEMENT(move_leave_element))
8871 RelocatePlayer(x, y, move_leave_element);
8874 // do this after checking for left-behind element
8875 ResetGfxAnimation(x, y); // reset animation values for old field
8877 if (!CAN_MOVE(element) ||
8878 (CAN_FALL(element) && direction == MV_DOWN &&
8879 (element == EL_SPRING ||
8880 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8881 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8882 GfxDir[x][y] = MovDir[newx][newy] = 0;
8884 TEST_DrawLevelField(x, y);
8885 TEST_DrawLevelField(newx, newy);
8887 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8889 // prevent pushed element from moving on in pushed direction
8890 if (pushed_by_player && CAN_MOVE(element) &&
8891 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8892 !(element_info[element].move_pattern & direction))
8893 TurnRound(newx, newy);
8895 // prevent elements on conveyor belt from moving on in last direction
8896 if (pushed_by_conveyor && CAN_FALL(element) &&
8897 direction & MV_HORIZONTAL)
8898 MovDir[newx][newy] = 0;
8900 if (!pushed_by_player)
8902 int nextx = newx + dx, nexty = newy + dy;
8903 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8905 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8907 if (CAN_FALL(element) && direction == MV_DOWN)
8908 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8910 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8911 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8913 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8914 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8917 if (DONT_TOUCH(element)) // object may be nasty to player or others
8919 TestIfBadThingTouchesPlayer(newx, newy);
8920 TestIfBadThingTouchesFriend(newx, newy);
8922 if (!IS_CUSTOM_ELEMENT(element))
8923 TestIfBadThingTouchesOtherBadThing(newx, newy);
8925 else if (element == EL_PENGUIN)
8926 TestIfFriendTouchesBadThing(newx, newy);
8928 if (DONT_GET_HIT_BY(element))
8930 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8933 // give the player one last chance (one more frame) to move away
8934 if (CAN_FALL(element) && direction == MV_DOWN &&
8935 (last_line || (!IS_FREE(x, newy + 1) &&
8936 (!IS_PLAYER(x, newy + 1) ||
8937 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8940 if (pushed_by_player && !game.use_change_when_pushing_bug)
8942 int push_side = MV_DIR_OPPOSITE(direction);
8943 struct PlayerInfo *player = PLAYERINFO(x, y);
8945 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8946 player->index_bit, push_side);
8947 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8948 player->index_bit, push_side);
8951 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8952 MovDelay[newx][newy] = 1;
8954 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8956 TestIfElementTouchesCustomElement(x, y); // empty or new element
8957 TestIfElementHitsCustomElement(newx, newy, direction);
8958 TestIfPlayerTouchesCustomElement(newx, newy);
8959 TestIfElementTouchesCustomElement(newx, newy);
8961 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8962 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8963 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8964 MV_DIR_OPPOSITE(direction));
8967 int AmoebaNeighbourNr(int ax, int ay)
8970 int element = Tile[ax][ay];
8972 static int xy[4][2] =
8980 for (i = 0; i < NUM_DIRECTIONS; i++)
8982 int x = ax + xy[i][0];
8983 int y = ay + xy[i][1];
8985 if (!IN_LEV_FIELD(x, y))
8988 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8989 group_nr = AmoebaNr[x][y];
8995 static void AmoebaMerge(int ax, int ay)
8997 int i, x, y, xx, yy;
8998 int new_group_nr = AmoebaNr[ax][ay];
8999 static int xy[4][2] =
9007 if (new_group_nr == 0)
9010 for (i = 0; i < NUM_DIRECTIONS; i++)
9015 if (!IN_LEV_FIELD(x, y))
9018 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9019 Tile[x][y] == EL_BD_AMOEBA ||
9020 Tile[x][y] == EL_AMOEBA_DEAD) &&
9021 AmoebaNr[x][y] != new_group_nr)
9023 int old_group_nr = AmoebaNr[x][y];
9025 if (old_group_nr == 0)
9028 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9029 AmoebaCnt[old_group_nr] = 0;
9030 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9031 AmoebaCnt2[old_group_nr] = 0;
9033 SCAN_PLAYFIELD(xx, yy)
9035 if (AmoebaNr[xx][yy] == old_group_nr)
9036 AmoebaNr[xx][yy] = new_group_nr;
9042 void AmoebaToDiamond(int ax, int ay)
9046 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9048 int group_nr = AmoebaNr[ax][ay];
9053 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9054 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9060 SCAN_PLAYFIELD(x, y)
9062 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9065 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9069 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9070 SND_AMOEBA_TURNING_TO_GEM :
9071 SND_AMOEBA_TURNING_TO_ROCK));
9076 static int xy[4][2] =
9084 for (i = 0; i < NUM_DIRECTIONS; i++)
9089 if (!IN_LEV_FIELD(x, y))
9092 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9094 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9095 SND_AMOEBA_TURNING_TO_GEM :
9096 SND_AMOEBA_TURNING_TO_ROCK));
9103 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9106 int group_nr = AmoebaNr[ax][ay];
9107 boolean done = FALSE;
9112 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9113 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9119 SCAN_PLAYFIELD(x, y)
9121 if (AmoebaNr[x][y] == group_nr &&
9122 (Tile[x][y] == EL_AMOEBA_DEAD ||
9123 Tile[x][y] == EL_BD_AMOEBA ||
9124 Tile[x][y] == EL_AMOEBA_GROWING))
9127 Tile[x][y] = new_element;
9128 InitField(x, y, FALSE);
9129 TEST_DrawLevelField(x, y);
9135 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9136 SND_BD_AMOEBA_TURNING_TO_ROCK :
9137 SND_BD_AMOEBA_TURNING_TO_GEM));
9140 static void AmoebaGrowing(int x, int y)
9142 static unsigned int sound_delay = 0;
9143 static unsigned int sound_delay_value = 0;
9145 if (!MovDelay[x][y]) // start new growing cycle
9149 if (DelayReached(&sound_delay, sound_delay_value))
9151 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9152 sound_delay_value = 30;
9156 if (MovDelay[x][y]) // wait some time before growing bigger
9159 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9161 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9162 6 - MovDelay[x][y]);
9164 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9167 if (!MovDelay[x][y])
9169 Tile[x][y] = Store[x][y];
9171 TEST_DrawLevelField(x, y);
9176 static void AmoebaShrinking(int x, int y)
9178 static unsigned int sound_delay = 0;
9179 static unsigned int sound_delay_value = 0;
9181 if (!MovDelay[x][y]) // start new shrinking cycle
9185 if (DelayReached(&sound_delay, sound_delay_value))
9186 sound_delay_value = 30;
9189 if (MovDelay[x][y]) // wait some time before shrinking
9192 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9194 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9195 6 - MovDelay[x][y]);
9197 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9200 if (!MovDelay[x][y])
9202 Tile[x][y] = EL_EMPTY;
9203 TEST_DrawLevelField(x, y);
9205 // don't let mole enter this field in this cycle;
9206 // (give priority to objects falling to this field from above)
9212 static void AmoebaReproduce(int ax, int ay)
9215 int element = Tile[ax][ay];
9216 int graphic = el2img(element);
9217 int newax = ax, neway = ay;
9218 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9219 static int xy[4][2] =
9227 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9229 Tile[ax][ay] = EL_AMOEBA_DEAD;
9230 TEST_DrawLevelField(ax, ay);
9234 if (IS_ANIMATED(graphic))
9235 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9237 if (!MovDelay[ax][ay]) // start making new amoeba field
9238 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9240 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9243 if (MovDelay[ax][ay])
9247 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9250 int x = ax + xy[start][0];
9251 int y = ay + xy[start][1];
9253 if (!IN_LEV_FIELD(x, y))
9256 if (IS_FREE(x, y) ||
9257 CAN_GROW_INTO(Tile[x][y]) ||
9258 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9259 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9265 if (newax == ax && neway == ay)
9268 else // normal or "filled" (BD style) amoeba
9271 boolean waiting_for_player = FALSE;
9273 for (i = 0; i < NUM_DIRECTIONS; i++)
9275 int j = (start + i) % 4;
9276 int x = ax + xy[j][0];
9277 int y = ay + xy[j][1];
9279 if (!IN_LEV_FIELD(x, y))
9282 if (IS_FREE(x, y) ||
9283 CAN_GROW_INTO(Tile[x][y]) ||
9284 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9285 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9291 else if (IS_PLAYER(x, y))
9292 waiting_for_player = TRUE;
9295 if (newax == ax && neway == ay) // amoeba cannot grow
9297 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9299 Tile[ax][ay] = EL_AMOEBA_DEAD;
9300 TEST_DrawLevelField(ax, ay);
9301 AmoebaCnt[AmoebaNr[ax][ay]]--;
9303 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9305 if (element == EL_AMOEBA_FULL)
9306 AmoebaToDiamond(ax, ay);
9307 else if (element == EL_BD_AMOEBA)
9308 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9313 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9315 // amoeba gets larger by growing in some direction
9317 int new_group_nr = AmoebaNr[ax][ay];
9320 if (new_group_nr == 0)
9322 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9324 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9330 AmoebaNr[newax][neway] = new_group_nr;
9331 AmoebaCnt[new_group_nr]++;
9332 AmoebaCnt2[new_group_nr]++;
9334 // if amoeba touches other amoeba(s) after growing, unify them
9335 AmoebaMerge(newax, neway);
9337 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9339 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9345 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9346 (neway == lev_fieldy - 1 && newax != ax))
9348 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9349 Store[newax][neway] = element;
9351 else if (neway == ay || element == EL_EMC_DRIPPER)
9353 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9355 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9359 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9360 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9361 Store[ax][ay] = EL_AMOEBA_DROP;
9362 ContinueMoving(ax, ay);
9366 TEST_DrawLevelField(newax, neway);
9369 static void Life(int ax, int ay)
9373 int element = Tile[ax][ay];
9374 int graphic = el2img(element);
9375 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9377 boolean changed = FALSE;
9379 if (IS_ANIMATED(graphic))
9380 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9385 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9386 MovDelay[ax][ay] = life_time;
9388 if (MovDelay[ax][ay]) // wait some time before next cycle
9391 if (MovDelay[ax][ay])
9395 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9397 int xx = ax+x1, yy = ay+y1;
9398 int old_element = Tile[xx][yy];
9399 int num_neighbours = 0;
9401 if (!IN_LEV_FIELD(xx, yy))
9404 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9406 int x = xx+x2, y = yy+y2;
9408 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9411 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9412 boolean is_neighbour = FALSE;
9414 if (level.use_life_bugs)
9416 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9417 (IS_FREE(x, y) && Stop[x][y]));
9420 (Last[x][y] == element || is_player_cell);
9426 boolean is_free = FALSE;
9428 if (level.use_life_bugs)
9429 is_free = (IS_FREE(xx, yy));
9431 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9433 if (xx == ax && yy == ay) // field in the middle
9435 if (num_neighbours < life_parameter[0] ||
9436 num_neighbours > life_parameter[1])
9438 Tile[xx][yy] = EL_EMPTY;
9439 if (Tile[xx][yy] != old_element)
9440 TEST_DrawLevelField(xx, yy);
9441 Stop[xx][yy] = TRUE;
9445 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9446 { // free border field
9447 if (num_neighbours >= life_parameter[2] &&
9448 num_neighbours <= life_parameter[3])
9450 Tile[xx][yy] = element;
9451 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9452 if (Tile[xx][yy] != old_element)
9453 TEST_DrawLevelField(xx, yy);
9454 Stop[xx][yy] = TRUE;
9461 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9462 SND_GAME_OF_LIFE_GROWING);
9465 static void InitRobotWheel(int x, int y)
9467 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9470 static void RunRobotWheel(int x, int y)
9472 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9475 static void StopRobotWheel(int x, int y)
9477 if (game.robot_wheel_x == x &&
9478 game.robot_wheel_y == y)
9480 game.robot_wheel_x = -1;
9481 game.robot_wheel_y = -1;
9482 game.robot_wheel_active = FALSE;
9486 static void InitTimegateWheel(int x, int y)
9488 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9491 static void RunTimegateWheel(int x, int y)
9493 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9496 static void InitMagicBallDelay(int x, int y)
9498 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9501 static void ActivateMagicBall(int bx, int by)
9505 if (level.ball_random)
9507 int pos_border = RND(8); // select one of the eight border elements
9508 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9509 int xx = pos_content % 3;
9510 int yy = pos_content / 3;
9515 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9516 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9520 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9522 int xx = x - bx + 1;
9523 int yy = y - by + 1;
9525 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9526 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9530 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9533 static void CheckExit(int x, int y)
9535 if (game.gems_still_needed > 0 ||
9536 game.sokoban_fields_still_needed > 0 ||
9537 game.sokoban_objects_still_needed > 0 ||
9538 game.lights_still_needed > 0)
9540 int element = Tile[x][y];
9541 int graphic = el2img(element);
9543 if (IS_ANIMATED(graphic))
9544 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9549 // do not re-open exit door closed after last player
9550 if (game.all_players_gone)
9553 Tile[x][y] = EL_EXIT_OPENING;
9555 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9558 static void CheckExitEM(int x, int y)
9560 if (game.gems_still_needed > 0 ||
9561 game.sokoban_fields_still_needed > 0 ||
9562 game.sokoban_objects_still_needed > 0 ||
9563 game.lights_still_needed > 0)
9565 int element = Tile[x][y];
9566 int graphic = el2img(element);
9568 if (IS_ANIMATED(graphic))
9569 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9574 // do not re-open exit door closed after last player
9575 if (game.all_players_gone)
9578 Tile[x][y] = EL_EM_EXIT_OPENING;
9580 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9583 static void CheckExitSteel(int x, int y)
9585 if (game.gems_still_needed > 0 ||
9586 game.sokoban_fields_still_needed > 0 ||
9587 game.sokoban_objects_still_needed > 0 ||
9588 game.lights_still_needed > 0)
9590 int element = Tile[x][y];
9591 int graphic = el2img(element);
9593 if (IS_ANIMATED(graphic))
9594 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9599 // do not re-open exit door closed after last player
9600 if (game.all_players_gone)
9603 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9605 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9608 static void CheckExitSteelEM(int x, int y)
9610 if (game.gems_still_needed > 0 ||
9611 game.sokoban_fields_still_needed > 0 ||
9612 game.sokoban_objects_still_needed > 0 ||
9613 game.lights_still_needed > 0)
9615 int element = Tile[x][y];
9616 int graphic = el2img(element);
9618 if (IS_ANIMATED(graphic))
9619 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9624 // do not re-open exit door closed after last player
9625 if (game.all_players_gone)
9628 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9630 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9633 static void CheckExitSP(int x, int y)
9635 if (game.gems_still_needed > 0)
9637 int element = Tile[x][y];
9638 int graphic = el2img(element);
9640 if (IS_ANIMATED(graphic))
9641 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9646 // do not re-open exit door closed after last player
9647 if (game.all_players_gone)
9650 Tile[x][y] = EL_SP_EXIT_OPENING;
9652 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9655 static void CloseAllOpenTimegates(void)
9659 SCAN_PLAYFIELD(x, y)
9661 int element = Tile[x][y];
9663 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9665 Tile[x][y] = EL_TIMEGATE_CLOSING;
9667 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9672 static void DrawTwinkleOnField(int x, int y)
9674 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9677 if (Tile[x][y] == EL_BD_DIAMOND)
9680 if (MovDelay[x][y] == 0) // next animation frame
9681 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9683 if (MovDelay[x][y] != 0) // wait some time before next frame
9687 DrawLevelElementAnimation(x, y, Tile[x][y]);
9689 if (MovDelay[x][y] != 0)
9691 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9692 10 - MovDelay[x][y]);
9694 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9699 static void MauerWaechst(int x, int y)
9703 if (!MovDelay[x][y]) // next animation frame
9704 MovDelay[x][y] = 3 * delay;
9706 if (MovDelay[x][y]) // wait some time before next frame
9710 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9712 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9713 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9715 DrawLevelGraphic(x, y, graphic, frame);
9718 if (!MovDelay[x][y])
9720 if (MovDir[x][y] == MV_LEFT)
9722 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9723 TEST_DrawLevelField(x - 1, y);
9725 else if (MovDir[x][y] == MV_RIGHT)
9727 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9728 TEST_DrawLevelField(x + 1, y);
9730 else if (MovDir[x][y] == MV_UP)
9732 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9733 TEST_DrawLevelField(x, y - 1);
9737 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9738 TEST_DrawLevelField(x, y + 1);
9741 Tile[x][y] = Store[x][y];
9743 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9744 TEST_DrawLevelField(x, y);
9749 static void MauerAbleger(int ax, int ay)
9751 int element = Tile[ax][ay];
9752 int graphic = el2img(element);
9753 boolean oben_frei = FALSE, unten_frei = FALSE;
9754 boolean links_frei = FALSE, rechts_frei = FALSE;
9755 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9756 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9757 boolean new_wall = FALSE;
9759 if (IS_ANIMATED(graphic))
9760 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9762 if (!MovDelay[ax][ay]) // start building new wall
9763 MovDelay[ax][ay] = 6;
9765 if (MovDelay[ax][ay]) // wait some time before building new wall
9768 if (MovDelay[ax][ay])
9772 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9774 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9776 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9778 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9781 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9782 element == EL_EXPANDABLE_WALL_ANY)
9786 Tile[ax][ay - 1] = EL_EXPANDABLE_WALL_GROWING;
9787 Store[ax][ay - 1] = element;
9788 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9789 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9790 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9795 Tile[ax][ay + 1] = EL_EXPANDABLE_WALL_GROWING;
9796 Store[ax][ay + 1] = element;
9797 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9798 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9799 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9804 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9805 element == EL_EXPANDABLE_WALL_ANY ||
9806 element == EL_EXPANDABLE_WALL ||
9807 element == EL_BD_EXPANDABLE_WALL)
9811 Tile[ax - 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9812 Store[ax - 1][ay] = element;
9813 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9814 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9815 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9821 Tile[ax + 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9822 Store[ax + 1][ay] = element;
9823 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9824 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9825 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9830 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9831 TEST_DrawLevelField(ax, ay);
9833 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9835 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9836 unten_massiv = TRUE;
9837 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9838 links_massiv = TRUE;
9839 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9840 rechts_massiv = TRUE;
9842 if (((oben_massiv && unten_massiv) ||
9843 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9844 element == EL_EXPANDABLE_WALL) &&
9845 ((links_massiv && rechts_massiv) ||
9846 element == EL_EXPANDABLE_WALL_VERTICAL))
9847 Tile[ax][ay] = EL_WALL;
9850 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9853 static void MauerAblegerStahl(int ax, int ay)
9855 int element = Tile[ax][ay];
9856 int graphic = el2img(element);
9857 boolean oben_frei = FALSE, unten_frei = FALSE;
9858 boolean links_frei = FALSE, rechts_frei = FALSE;
9859 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9860 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9861 boolean new_wall = FALSE;
9863 if (IS_ANIMATED(graphic))
9864 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9866 if (!MovDelay[ax][ay]) // start building new wall
9867 MovDelay[ax][ay] = 6;
9869 if (MovDelay[ax][ay]) // wait some time before building new wall
9872 if (MovDelay[ax][ay])
9876 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9878 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9880 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9882 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9885 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9886 element == EL_EXPANDABLE_STEELWALL_ANY)
9890 Tile[ax][ay - 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9891 Store[ax][ay - 1] = element;
9892 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9893 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9894 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9899 Tile[ax][ay + 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9900 Store[ax][ay + 1] = element;
9901 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9902 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9903 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9908 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9909 element == EL_EXPANDABLE_STEELWALL_ANY)
9913 Tile[ax - 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9914 Store[ax - 1][ay] = element;
9915 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9916 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9917 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9923 Tile[ax + 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9924 Store[ax + 1][ay] = element;
9925 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9926 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9927 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9932 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9934 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9935 unten_massiv = TRUE;
9936 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9937 links_massiv = TRUE;
9938 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9939 rechts_massiv = TRUE;
9941 if (((oben_massiv && unten_massiv) ||
9942 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9943 ((links_massiv && rechts_massiv) ||
9944 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9945 Tile[ax][ay] = EL_STEELWALL;
9948 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9951 static void CheckForDragon(int x, int y)
9954 boolean dragon_found = FALSE;
9955 static int xy[4][2] =
9963 for (i = 0; i < NUM_DIRECTIONS; i++)
9965 for (j = 0; j < 4; j++)
9967 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9969 if (IN_LEV_FIELD(xx, yy) &&
9970 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9972 if (Tile[xx][yy] == EL_DRAGON)
9973 dragon_found = TRUE;
9982 for (i = 0; i < NUM_DIRECTIONS; i++)
9984 for (j = 0; j < 3; j++)
9986 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9988 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9990 Tile[xx][yy] = EL_EMPTY;
9991 TEST_DrawLevelField(xx, yy);
10000 static void InitBuggyBase(int x, int y)
10002 int element = Tile[x][y];
10003 int activating_delay = FRAMES_PER_SECOND / 4;
10005 ChangeDelay[x][y] =
10006 (element == EL_SP_BUGGY_BASE ?
10007 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10008 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10010 element == EL_SP_BUGGY_BASE_ACTIVE ?
10011 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10014 static void WarnBuggyBase(int x, int y)
10017 static int xy[4][2] =
10025 for (i = 0; i < NUM_DIRECTIONS; i++)
10027 int xx = x + xy[i][0];
10028 int yy = y + xy[i][1];
10030 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10032 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10039 static void InitTrap(int x, int y)
10041 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10044 static void ActivateTrap(int x, int y)
10046 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10049 static void ChangeActiveTrap(int x, int y)
10051 int graphic = IMG_TRAP_ACTIVE;
10053 // if new animation frame was drawn, correct crumbled sand border
10054 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10055 TEST_DrawLevelFieldCrumbled(x, y);
10058 static int getSpecialActionElement(int element, int number, int base_element)
10060 return (element != EL_EMPTY ? element :
10061 number != -1 ? base_element + number - 1 :
10065 static int getModifiedActionNumber(int value_old, int operator, int operand,
10066 int value_min, int value_max)
10068 int value_new = (operator == CA_MODE_SET ? operand :
10069 operator == CA_MODE_ADD ? value_old + operand :
10070 operator == CA_MODE_SUBTRACT ? value_old - operand :
10071 operator == CA_MODE_MULTIPLY ? value_old * operand :
10072 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10073 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10076 return (value_new < value_min ? value_min :
10077 value_new > value_max ? value_max :
10081 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10083 struct ElementInfo *ei = &element_info[element];
10084 struct ElementChangeInfo *change = &ei->change_page[page];
10085 int target_element = change->target_element;
10086 int action_type = change->action_type;
10087 int action_mode = change->action_mode;
10088 int action_arg = change->action_arg;
10089 int action_element = change->action_element;
10092 if (!change->has_action)
10095 // ---------- determine action paramater values -----------------------------
10097 int level_time_value =
10098 (level.time > 0 ? TimeLeft :
10101 int action_arg_element_raw =
10102 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10103 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10104 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10105 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10106 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10107 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10108 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10110 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10112 int action_arg_direction =
10113 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10114 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10115 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10116 change->actual_trigger_side :
10117 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10118 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10121 int action_arg_number_min =
10122 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10125 int action_arg_number_max =
10126 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10127 action_type == CA_SET_LEVEL_GEMS ? 999 :
10128 action_type == CA_SET_LEVEL_TIME ? 9999 :
10129 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10130 action_type == CA_SET_CE_VALUE ? 9999 :
10131 action_type == CA_SET_CE_SCORE ? 9999 :
10134 int action_arg_number_reset =
10135 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10136 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10137 action_type == CA_SET_LEVEL_TIME ? level.time :
10138 action_type == CA_SET_LEVEL_SCORE ? 0 :
10139 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10140 action_type == CA_SET_CE_SCORE ? 0 :
10143 int action_arg_number =
10144 (action_arg <= CA_ARG_MAX ? action_arg :
10145 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10146 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10147 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10148 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10149 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10150 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10151 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10152 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10153 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10154 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10155 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10156 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10157 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10158 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10159 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10160 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10161 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10162 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10163 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10164 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10165 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10168 int action_arg_number_old =
10169 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10170 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10171 action_type == CA_SET_LEVEL_SCORE ? game.score :
10172 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10173 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10176 int action_arg_number_new =
10177 getModifiedActionNumber(action_arg_number_old,
10178 action_mode, action_arg_number,
10179 action_arg_number_min, action_arg_number_max);
10181 int trigger_player_bits =
10182 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10183 change->actual_trigger_player_bits : change->trigger_player);
10185 int action_arg_player_bits =
10186 (action_arg >= CA_ARG_PLAYER_1 &&
10187 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10188 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10189 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10192 // ---------- execute action -----------------------------------------------
10194 switch (action_type)
10201 // ---------- level actions ----------------------------------------------
10203 case CA_RESTART_LEVEL:
10205 game.restart_level = TRUE;
10210 case CA_SHOW_ENVELOPE:
10212 int element = getSpecialActionElement(action_arg_element,
10213 action_arg_number, EL_ENVELOPE_1);
10215 if (IS_ENVELOPE(element))
10216 local_player->show_envelope = element;
10221 case CA_SET_LEVEL_TIME:
10223 if (level.time > 0) // only modify limited time value
10225 TimeLeft = action_arg_number_new;
10227 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10229 DisplayGameControlValues();
10231 if (!TimeLeft && game.time_limit)
10232 for (i = 0; i < MAX_PLAYERS; i++)
10233 KillPlayer(&stored_player[i]);
10239 case CA_SET_LEVEL_SCORE:
10241 game.score = action_arg_number_new;
10243 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10245 DisplayGameControlValues();
10250 case CA_SET_LEVEL_GEMS:
10252 game.gems_still_needed = action_arg_number_new;
10254 game.snapshot.collected_item = TRUE;
10256 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10258 DisplayGameControlValues();
10263 case CA_SET_LEVEL_WIND:
10265 game.wind_direction = action_arg_direction;
10270 case CA_SET_LEVEL_RANDOM_SEED:
10272 // ensure that setting a new random seed while playing is predictable
10273 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10278 // ---------- player actions ---------------------------------------------
10280 case CA_MOVE_PLAYER:
10281 case CA_MOVE_PLAYER_NEW:
10283 // automatically move to the next field in specified direction
10284 for (i = 0; i < MAX_PLAYERS; i++)
10285 if (trigger_player_bits & (1 << i))
10286 if (action_type == CA_MOVE_PLAYER ||
10287 stored_player[i].MovPos == 0)
10288 stored_player[i].programmed_action = action_arg_direction;
10293 case CA_EXIT_PLAYER:
10295 for (i = 0; i < MAX_PLAYERS; i++)
10296 if (action_arg_player_bits & (1 << i))
10297 ExitPlayer(&stored_player[i]);
10299 if (game.players_still_needed == 0)
10305 case CA_KILL_PLAYER:
10307 for (i = 0; i < MAX_PLAYERS; i++)
10308 if (action_arg_player_bits & (1 << i))
10309 KillPlayer(&stored_player[i]);
10314 case CA_SET_PLAYER_KEYS:
10316 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10317 int element = getSpecialActionElement(action_arg_element,
10318 action_arg_number, EL_KEY_1);
10320 if (IS_KEY(element))
10322 for (i = 0; i < MAX_PLAYERS; i++)
10324 if (trigger_player_bits & (1 << i))
10326 stored_player[i].key[KEY_NR(element)] = key_state;
10328 DrawGameDoorValues();
10336 case CA_SET_PLAYER_SPEED:
10338 for (i = 0; i < MAX_PLAYERS; i++)
10340 if (trigger_player_bits & (1 << i))
10342 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10344 if (action_arg == CA_ARG_SPEED_FASTER &&
10345 stored_player[i].cannot_move)
10347 action_arg_number = STEPSIZE_VERY_SLOW;
10349 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10350 action_arg == CA_ARG_SPEED_FASTER)
10352 action_arg_number = 2;
10353 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10356 else if (action_arg == CA_ARG_NUMBER_RESET)
10358 action_arg_number = level.initial_player_stepsize[i];
10362 getModifiedActionNumber(move_stepsize,
10365 action_arg_number_min,
10366 action_arg_number_max);
10368 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10375 case CA_SET_PLAYER_SHIELD:
10377 for (i = 0; i < MAX_PLAYERS; i++)
10379 if (trigger_player_bits & (1 << i))
10381 if (action_arg == CA_ARG_SHIELD_OFF)
10383 stored_player[i].shield_normal_time_left = 0;
10384 stored_player[i].shield_deadly_time_left = 0;
10386 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10388 stored_player[i].shield_normal_time_left = 999999;
10390 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10392 stored_player[i].shield_normal_time_left = 999999;
10393 stored_player[i].shield_deadly_time_left = 999999;
10401 case CA_SET_PLAYER_GRAVITY:
10403 for (i = 0; i < MAX_PLAYERS; i++)
10405 if (trigger_player_bits & (1 << i))
10407 stored_player[i].gravity =
10408 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10409 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10410 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10411 stored_player[i].gravity);
10418 case CA_SET_PLAYER_ARTWORK:
10420 for (i = 0; i < MAX_PLAYERS; i++)
10422 if (trigger_player_bits & (1 << i))
10424 int artwork_element = action_arg_element;
10426 if (action_arg == CA_ARG_ELEMENT_RESET)
10428 (level.use_artwork_element[i] ? level.artwork_element[i] :
10429 stored_player[i].element_nr);
10431 if (stored_player[i].artwork_element != artwork_element)
10432 stored_player[i].Frame = 0;
10434 stored_player[i].artwork_element = artwork_element;
10436 SetPlayerWaiting(&stored_player[i], FALSE);
10438 // set number of special actions for bored and sleeping animation
10439 stored_player[i].num_special_action_bored =
10440 get_num_special_action(artwork_element,
10441 ACTION_BORING_1, ACTION_BORING_LAST);
10442 stored_player[i].num_special_action_sleeping =
10443 get_num_special_action(artwork_element,
10444 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10451 case CA_SET_PLAYER_INVENTORY:
10453 for (i = 0; i < MAX_PLAYERS; i++)
10455 struct PlayerInfo *player = &stored_player[i];
10458 if (trigger_player_bits & (1 << i))
10460 int inventory_element = action_arg_element;
10462 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10463 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10464 action_arg == CA_ARG_ELEMENT_ACTION)
10466 int element = inventory_element;
10467 int collect_count = element_info[element].collect_count_initial;
10469 if (!IS_CUSTOM_ELEMENT(element))
10472 if (collect_count == 0)
10473 player->inventory_infinite_element = element;
10475 for (k = 0; k < collect_count; k++)
10476 if (player->inventory_size < MAX_INVENTORY_SIZE)
10477 player->inventory_element[player->inventory_size++] =
10480 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10481 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10482 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10484 if (player->inventory_infinite_element != EL_UNDEFINED &&
10485 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10486 action_arg_element_raw))
10487 player->inventory_infinite_element = EL_UNDEFINED;
10489 for (k = 0, j = 0; j < player->inventory_size; j++)
10491 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10492 action_arg_element_raw))
10493 player->inventory_element[k++] = player->inventory_element[j];
10496 player->inventory_size = k;
10498 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10500 if (player->inventory_size > 0)
10502 for (j = 0; j < player->inventory_size - 1; j++)
10503 player->inventory_element[j] = player->inventory_element[j + 1];
10505 player->inventory_size--;
10508 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10510 if (player->inventory_size > 0)
10511 player->inventory_size--;
10513 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10515 player->inventory_infinite_element = EL_UNDEFINED;
10516 player->inventory_size = 0;
10518 else if (action_arg == CA_ARG_INVENTORY_RESET)
10520 player->inventory_infinite_element = EL_UNDEFINED;
10521 player->inventory_size = 0;
10523 if (level.use_initial_inventory[i])
10525 for (j = 0; j < level.initial_inventory_size[i]; j++)
10527 int element = level.initial_inventory_content[i][j];
10528 int collect_count = element_info[element].collect_count_initial;
10530 if (!IS_CUSTOM_ELEMENT(element))
10533 if (collect_count == 0)
10534 player->inventory_infinite_element = element;
10536 for (k = 0; k < collect_count; k++)
10537 if (player->inventory_size < MAX_INVENTORY_SIZE)
10538 player->inventory_element[player->inventory_size++] =
10549 // ---------- CE actions -------------------------------------------------
10551 case CA_SET_CE_VALUE:
10553 int last_ce_value = CustomValue[x][y];
10555 CustomValue[x][y] = action_arg_number_new;
10557 if (CustomValue[x][y] != last_ce_value)
10559 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10560 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10562 if (CustomValue[x][y] == 0)
10564 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10565 ChangeCount[x][y] = 0; // allow at least one more change
10567 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10568 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10575 case CA_SET_CE_SCORE:
10577 int last_ce_score = ei->collect_score;
10579 ei->collect_score = action_arg_number_new;
10581 if (ei->collect_score != last_ce_score)
10583 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10584 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10586 if (ei->collect_score == 0)
10590 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10591 ChangeCount[x][y] = 0; // allow at least one more change
10593 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10594 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10597 This is a very special case that seems to be a mixture between
10598 CheckElementChange() and CheckTriggeredElementChange(): while
10599 the first one only affects single elements that are triggered
10600 directly, the second one affects multiple elements in the playfield
10601 that are triggered indirectly by another element. This is a third
10602 case: Changing the CE score always affects multiple identical CEs,
10603 so every affected CE must be checked, not only the single CE for
10604 which the CE score was changed in the first place (as every instance
10605 of that CE shares the same CE score, and therefore also can change)!
10607 SCAN_PLAYFIELD(xx, yy)
10609 if (Tile[xx][yy] == element)
10610 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10611 CE_SCORE_GETS_ZERO);
10619 case CA_SET_CE_ARTWORK:
10621 int artwork_element = action_arg_element;
10622 boolean reset_frame = FALSE;
10625 if (action_arg == CA_ARG_ELEMENT_RESET)
10626 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10629 if (ei->gfx_element != artwork_element)
10630 reset_frame = TRUE;
10632 ei->gfx_element = artwork_element;
10634 SCAN_PLAYFIELD(xx, yy)
10636 if (Tile[xx][yy] == element)
10640 ResetGfxAnimation(xx, yy);
10641 ResetRandomAnimationValue(xx, yy);
10644 TEST_DrawLevelField(xx, yy);
10651 // ---------- engine actions ---------------------------------------------
10653 case CA_SET_ENGINE_SCAN_MODE:
10655 InitPlayfieldScanMode(action_arg);
10665 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10667 int old_element = Tile[x][y];
10668 int new_element = GetElementFromGroupElement(element);
10669 int previous_move_direction = MovDir[x][y];
10670 int last_ce_value = CustomValue[x][y];
10671 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10672 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10673 boolean add_player_onto_element = (new_element_is_player &&
10674 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10675 IS_WALKABLE(old_element));
10677 if (!add_player_onto_element)
10679 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10680 RemoveMovingField(x, y);
10684 Tile[x][y] = new_element;
10686 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10687 MovDir[x][y] = previous_move_direction;
10689 if (element_info[new_element].use_last_ce_value)
10690 CustomValue[x][y] = last_ce_value;
10692 InitField_WithBug1(x, y, FALSE);
10694 new_element = Tile[x][y]; // element may have changed
10696 ResetGfxAnimation(x, y);
10697 ResetRandomAnimationValue(x, y);
10699 TEST_DrawLevelField(x, y);
10701 if (GFX_CRUMBLED(new_element))
10702 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10705 // check if element under the player changes from accessible to unaccessible
10706 // (needed for special case of dropping element which then changes)
10707 // (must be checked after creating new element for walkable group elements)
10708 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10709 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10716 // "ChangeCount" not set yet to allow "entered by player" change one time
10717 if (new_element_is_player)
10718 RelocatePlayer(x, y, new_element);
10721 ChangeCount[x][y]++; // count number of changes in the same frame
10723 TestIfBadThingTouchesPlayer(x, y);
10724 TestIfPlayerTouchesCustomElement(x, y);
10725 TestIfElementTouchesCustomElement(x, y);
10728 static void CreateField(int x, int y, int element)
10730 CreateFieldExt(x, y, element, FALSE);
10733 static void CreateElementFromChange(int x, int y, int element)
10735 element = GET_VALID_RUNTIME_ELEMENT(element);
10737 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10739 int old_element = Tile[x][y];
10741 // prevent changed element from moving in same engine frame
10742 // unless both old and new element can either fall or move
10743 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10744 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10748 CreateFieldExt(x, y, element, TRUE);
10751 static boolean ChangeElement(int x, int y, int element, int page)
10753 struct ElementInfo *ei = &element_info[element];
10754 struct ElementChangeInfo *change = &ei->change_page[page];
10755 int ce_value = CustomValue[x][y];
10756 int ce_score = ei->collect_score;
10757 int target_element;
10758 int old_element = Tile[x][y];
10760 // always use default change event to prevent running into a loop
10761 if (ChangeEvent[x][y] == -1)
10762 ChangeEvent[x][y] = CE_DELAY;
10764 if (ChangeEvent[x][y] == CE_DELAY)
10766 // reset actual trigger element, trigger player and action element
10767 change->actual_trigger_element = EL_EMPTY;
10768 change->actual_trigger_player = EL_EMPTY;
10769 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10770 change->actual_trigger_side = CH_SIDE_NONE;
10771 change->actual_trigger_ce_value = 0;
10772 change->actual_trigger_ce_score = 0;
10775 // do not change elements more than a specified maximum number of changes
10776 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10779 ChangeCount[x][y]++; // count number of changes in the same frame
10781 if (change->explode)
10788 if (change->use_target_content)
10790 boolean complete_replace = TRUE;
10791 boolean can_replace[3][3];
10794 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10797 boolean is_walkable;
10798 boolean is_diggable;
10799 boolean is_collectible;
10800 boolean is_removable;
10801 boolean is_destructible;
10802 int ex = x + xx - 1;
10803 int ey = y + yy - 1;
10804 int content_element = change->target_content.e[xx][yy];
10807 can_replace[xx][yy] = TRUE;
10809 if (ex == x && ey == y) // do not check changing element itself
10812 if (content_element == EL_EMPTY_SPACE)
10814 can_replace[xx][yy] = FALSE; // do not replace border with space
10819 if (!IN_LEV_FIELD(ex, ey))
10821 can_replace[xx][yy] = FALSE;
10822 complete_replace = FALSE;
10829 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10830 e = MovingOrBlocked2Element(ex, ey);
10832 is_empty = (IS_FREE(ex, ey) ||
10833 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10835 is_walkable = (is_empty || IS_WALKABLE(e));
10836 is_diggable = (is_empty || IS_DIGGABLE(e));
10837 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10838 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10839 is_removable = (is_diggable || is_collectible);
10841 can_replace[xx][yy] =
10842 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10843 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10844 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10845 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10846 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10847 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10848 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10850 if (!can_replace[xx][yy])
10851 complete_replace = FALSE;
10854 if (!change->only_if_complete || complete_replace)
10856 boolean something_has_changed = FALSE;
10858 if (change->only_if_complete && change->use_random_replace &&
10859 RND(100) < change->random_percentage)
10862 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10864 int ex = x + xx - 1;
10865 int ey = y + yy - 1;
10866 int content_element;
10868 if (can_replace[xx][yy] && (!change->use_random_replace ||
10869 RND(100) < change->random_percentage))
10871 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10872 RemoveMovingField(ex, ey);
10874 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10876 content_element = change->target_content.e[xx][yy];
10877 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10878 ce_value, ce_score);
10880 CreateElementFromChange(ex, ey, target_element);
10882 something_has_changed = TRUE;
10884 // for symmetry reasons, freeze newly created border elements
10885 if (ex != x || ey != y)
10886 Stop[ex][ey] = TRUE; // no more moving in this frame
10890 if (something_has_changed)
10892 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10893 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10899 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10900 ce_value, ce_score);
10902 if (element == EL_DIAGONAL_GROWING ||
10903 element == EL_DIAGONAL_SHRINKING)
10905 target_element = Store[x][y];
10907 Store[x][y] = EL_EMPTY;
10910 // special case: element changes to player (and may be kept if walkable)
10911 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10912 CreateElementFromChange(x, y, EL_EMPTY);
10914 CreateElementFromChange(x, y, target_element);
10916 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10917 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10920 // this uses direct change before indirect change
10921 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10926 static void HandleElementChange(int x, int y, int page)
10928 int element = MovingOrBlocked2Element(x, y);
10929 struct ElementInfo *ei = &element_info[element];
10930 struct ElementChangeInfo *change = &ei->change_page[page];
10931 boolean handle_action_before_change = FALSE;
10934 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10935 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10937 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10938 x, y, element, element_info[element].token_name);
10939 Debug("game:playing:HandleElementChange", "This should never happen!");
10943 // this can happen with classic bombs on walkable, changing elements
10944 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10949 if (ChangeDelay[x][y] == 0) // initialize element change
10951 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10953 if (change->can_change)
10955 // !!! not clear why graphic animation should be reset at all here !!!
10956 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10957 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10960 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10962 When using an animation frame delay of 1 (this only happens with
10963 "sp_zonk.moving.left/right" in the classic graphics), the default
10964 (non-moving) animation shows wrong animation frames (while the
10965 moving animation, like "sp_zonk.moving.left/right", is correct,
10966 so this graphical bug never shows up with the classic graphics).
10967 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10968 be drawn instead of the correct frames 0,1,2,3. This is caused by
10969 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10970 an element change: First when the change delay ("ChangeDelay[][]")
10971 counter has reached zero after decrementing, then a second time in
10972 the next frame (after "GfxFrame[][]" was already incremented) when
10973 "ChangeDelay[][]" is reset to the initial delay value again.
10975 This causes frame 0 to be drawn twice, while the last frame won't
10976 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10978 As some animations may already be cleverly designed around this bug
10979 (at least the "Snake Bite" snake tail animation does this), it cannot
10980 simply be fixed here without breaking such existing animations.
10981 Unfortunately, it cannot easily be detected if a graphics set was
10982 designed "before" or "after" the bug was fixed. As a workaround,
10983 a new graphics set option "game.graphics_engine_version" was added
10984 to be able to specify the game's major release version for which the
10985 graphics set was designed, which can then be used to decide if the
10986 bugfix should be used (version 4 and above) or not (version 3 or
10987 below, or if no version was specified at all, as with old sets).
10989 (The wrong/fixed animation frames can be tested with the test level set
10990 "test_gfxframe" and level "000", which contains a specially prepared
10991 custom element at level position (x/y) == (11/9) which uses the zonk
10992 animation mentioned above. Using "game.graphics_engine_version: 4"
10993 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10994 This can also be seen from the debug output for this test element.)
10997 // when a custom element is about to change (for example by change delay),
10998 // do not reset graphic animation when the custom element is moving
10999 if (game.graphics_engine_version < 4 &&
11002 ResetGfxAnimation(x, y);
11003 ResetRandomAnimationValue(x, y);
11006 if (change->pre_change_function)
11007 change->pre_change_function(x, y);
11011 ChangeDelay[x][y]--;
11013 if (ChangeDelay[x][y] != 0) // continue element change
11015 if (change->can_change)
11017 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11019 if (IS_ANIMATED(graphic))
11020 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11022 if (change->change_function)
11023 change->change_function(x, y);
11026 else // finish element change
11028 if (ChangePage[x][y] != -1) // remember page from delayed change
11030 page = ChangePage[x][y];
11031 ChangePage[x][y] = -1;
11033 change = &ei->change_page[page];
11036 if (IS_MOVING(x, y)) // never change a running system ;-)
11038 ChangeDelay[x][y] = 1; // try change after next move step
11039 ChangePage[x][y] = page; // remember page to use for change
11044 // special case: set new level random seed before changing element
11045 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11046 handle_action_before_change = TRUE;
11048 if (change->has_action && handle_action_before_change)
11049 ExecuteCustomElementAction(x, y, element, page);
11051 if (change->can_change)
11053 if (ChangeElement(x, y, element, page))
11055 if (change->post_change_function)
11056 change->post_change_function(x, y);
11060 if (change->has_action && !handle_action_before_change)
11061 ExecuteCustomElementAction(x, y, element, page);
11065 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11066 int trigger_element,
11068 int trigger_player,
11072 boolean change_done_any = FALSE;
11073 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11076 if (!(trigger_events[trigger_element][trigger_event]))
11079 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11081 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11083 int element = EL_CUSTOM_START + i;
11084 boolean change_done = FALSE;
11087 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11088 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11091 for (p = 0; p < element_info[element].num_change_pages; p++)
11093 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11095 if (change->can_change_or_has_action &&
11096 change->has_event[trigger_event] &&
11097 change->trigger_side & trigger_side &&
11098 change->trigger_player & trigger_player &&
11099 change->trigger_page & trigger_page_bits &&
11100 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11102 change->actual_trigger_element = trigger_element;
11103 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11104 change->actual_trigger_player_bits = trigger_player;
11105 change->actual_trigger_side = trigger_side;
11106 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11107 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11109 if ((change->can_change && !change_done) || change->has_action)
11113 SCAN_PLAYFIELD(x, y)
11115 if (Tile[x][y] == element)
11117 if (change->can_change && !change_done)
11119 // if element already changed in this frame, not only prevent
11120 // another element change (checked in ChangeElement()), but
11121 // also prevent additional element actions for this element
11123 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11124 !level.use_action_after_change_bug)
11127 ChangeDelay[x][y] = 1;
11128 ChangeEvent[x][y] = trigger_event;
11130 HandleElementChange(x, y, p);
11132 else if (change->has_action)
11134 // if element already changed in this frame, not only prevent
11135 // another element change (checked in ChangeElement()), but
11136 // also prevent additional element actions for this element
11138 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11139 !level.use_action_after_change_bug)
11142 ExecuteCustomElementAction(x, y, element, p);
11143 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11148 if (change->can_change)
11150 change_done = TRUE;
11151 change_done_any = TRUE;
11158 RECURSION_LOOP_DETECTION_END();
11160 return change_done_any;
11163 static boolean CheckElementChangeExt(int x, int y,
11165 int trigger_element,
11167 int trigger_player,
11170 boolean change_done = FALSE;
11173 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11174 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11177 if (Tile[x][y] == EL_BLOCKED)
11179 Blocked2Moving(x, y, &x, &y);
11180 element = Tile[x][y];
11183 // check if element has already changed or is about to change after moving
11184 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11185 Tile[x][y] != element) ||
11187 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11188 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11189 ChangePage[x][y] != -1)))
11192 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11194 for (p = 0; p < element_info[element].num_change_pages; p++)
11196 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11198 /* check trigger element for all events where the element that is checked
11199 for changing interacts with a directly adjacent element -- this is
11200 different to element changes that affect other elements to change on the
11201 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11202 boolean check_trigger_element =
11203 (trigger_event == CE_NEXT_TO_X ||
11204 trigger_event == CE_TOUCHING_X ||
11205 trigger_event == CE_HITTING_X ||
11206 trigger_event == CE_HIT_BY_X ||
11207 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11209 if (change->can_change_or_has_action &&
11210 change->has_event[trigger_event] &&
11211 change->trigger_side & trigger_side &&
11212 change->trigger_player & trigger_player &&
11213 (!check_trigger_element ||
11214 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11216 change->actual_trigger_element = trigger_element;
11217 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11218 change->actual_trigger_player_bits = trigger_player;
11219 change->actual_trigger_side = trigger_side;
11220 change->actual_trigger_ce_value = CustomValue[x][y];
11221 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11223 // special case: trigger element not at (x,y) position for some events
11224 if (check_trigger_element)
11236 { 0, 0 }, { 0, 0 }, { 0, 0 },
11240 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11241 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11243 change->actual_trigger_ce_value = CustomValue[xx][yy];
11244 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11247 if (change->can_change && !change_done)
11249 ChangeDelay[x][y] = 1;
11250 ChangeEvent[x][y] = trigger_event;
11252 HandleElementChange(x, y, p);
11254 change_done = TRUE;
11256 else if (change->has_action)
11258 ExecuteCustomElementAction(x, y, element, p);
11259 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11264 RECURSION_LOOP_DETECTION_END();
11266 return change_done;
11269 static void PlayPlayerSound(struct PlayerInfo *player)
11271 int jx = player->jx, jy = player->jy;
11272 int sound_element = player->artwork_element;
11273 int last_action = player->last_action_waiting;
11274 int action = player->action_waiting;
11276 if (player->is_waiting)
11278 if (action != last_action)
11279 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11281 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11285 if (action != last_action)
11286 StopSound(element_info[sound_element].sound[last_action]);
11288 if (last_action == ACTION_SLEEPING)
11289 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11293 static void PlayAllPlayersSound(void)
11297 for (i = 0; i < MAX_PLAYERS; i++)
11298 if (stored_player[i].active)
11299 PlayPlayerSound(&stored_player[i]);
11302 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11304 boolean last_waiting = player->is_waiting;
11305 int move_dir = player->MovDir;
11307 player->dir_waiting = move_dir;
11308 player->last_action_waiting = player->action_waiting;
11312 if (!last_waiting) // not waiting -> waiting
11314 player->is_waiting = TRUE;
11316 player->frame_counter_bored =
11318 game.player_boring_delay_fixed +
11319 GetSimpleRandom(game.player_boring_delay_random);
11320 player->frame_counter_sleeping =
11322 game.player_sleeping_delay_fixed +
11323 GetSimpleRandom(game.player_sleeping_delay_random);
11325 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11328 if (game.player_sleeping_delay_fixed +
11329 game.player_sleeping_delay_random > 0 &&
11330 player->anim_delay_counter == 0 &&
11331 player->post_delay_counter == 0 &&
11332 FrameCounter >= player->frame_counter_sleeping)
11333 player->is_sleeping = TRUE;
11334 else if (game.player_boring_delay_fixed +
11335 game.player_boring_delay_random > 0 &&
11336 FrameCounter >= player->frame_counter_bored)
11337 player->is_bored = TRUE;
11339 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11340 player->is_bored ? ACTION_BORING :
11343 if (player->is_sleeping && player->use_murphy)
11345 // special case for sleeping Murphy when leaning against non-free tile
11347 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11348 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11349 !IS_MOVING(player->jx - 1, player->jy)))
11350 move_dir = MV_LEFT;
11351 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11352 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11353 !IS_MOVING(player->jx + 1, player->jy)))
11354 move_dir = MV_RIGHT;
11356 player->is_sleeping = FALSE;
11358 player->dir_waiting = move_dir;
11361 if (player->is_sleeping)
11363 if (player->num_special_action_sleeping > 0)
11365 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11367 int last_special_action = player->special_action_sleeping;
11368 int num_special_action = player->num_special_action_sleeping;
11369 int special_action =
11370 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11371 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11372 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11373 last_special_action + 1 : ACTION_SLEEPING);
11374 int special_graphic =
11375 el_act_dir2img(player->artwork_element, special_action, move_dir);
11377 player->anim_delay_counter =
11378 graphic_info[special_graphic].anim_delay_fixed +
11379 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11380 player->post_delay_counter =
11381 graphic_info[special_graphic].post_delay_fixed +
11382 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11384 player->special_action_sleeping = special_action;
11387 if (player->anim_delay_counter > 0)
11389 player->action_waiting = player->special_action_sleeping;
11390 player->anim_delay_counter--;
11392 else if (player->post_delay_counter > 0)
11394 player->post_delay_counter--;
11398 else if (player->is_bored)
11400 if (player->num_special_action_bored > 0)
11402 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11404 int special_action =
11405 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11406 int special_graphic =
11407 el_act_dir2img(player->artwork_element, special_action, move_dir);
11409 player->anim_delay_counter =
11410 graphic_info[special_graphic].anim_delay_fixed +
11411 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11412 player->post_delay_counter =
11413 graphic_info[special_graphic].post_delay_fixed +
11414 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11416 player->special_action_bored = special_action;
11419 if (player->anim_delay_counter > 0)
11421 player->action_waiting = player->special_action_bored;
11422 player->anim_delay_counter--;
11424 else if (player->post_delay_counter > 0)
11426 player->post_delay_counter--;
11431 else if (last_waiting) // waiting -> not waiting
11433 player->is_waiting = FALSE;
11434 player->is_bored = FALSE;
11435 player->is_sleeping = FALSE;
11437 player->frame_counter_bored = -1;
11438 player->frame_counter_sleeping = -1;
11440 player->anim_delay_counter = 0;
11441 player->post_delay_counter = 0;
11443 player->dir_waiting = player->MovDir;
11444 player->action_waiting = ACTION_DEFAULT;
11446 player->special_action_bored = ACTION_DEFAULT;
11447 player->special_action_sleeping = ACTION_DEFAULT;
11451 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11453 if ((!player->is_moving && player->was_moving) ||
11454 (player->MovPos == 0 && player->was_moving) ||
11455 (player->is_snapping && !player->was_snapping) ||
11456 (player->is_dropping && !player->was_dropping))
11458 if (!CheckSaveEngineSnapshotToList())
11461 player->was_moving = FALSE;
11462 player->was_snapping = TRUE;
11463 player->was_dropping = TRUE;
11467 if (player->is_moving)
11468 player->was_moving = TRUE;
11470 if (!player->is_snapping)
11471 player->was_snapping = FALSE;
11473 if (!player->is_dropping)
11474 player->was_dropping = FALSE;
11477 static struct MouseActionInfo mouse_action_last = { 0 };
11478 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11479 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11482 CheckSaveEngineSnapshotToList();
11484 mouse_action_last = mouse_action;
11487 static void CheckSingleStepMode(struct PlayerInfo *player)
11489 if (tape.single_step && tape.recording && !tape.pausing)
11491 // as it is called "single step mode", just return to pause mode when the
11492 // player stopped moving after one tile (or never starts moving at all)
11493 // (reverse logic needed here in case single step mode used in team mode)
11494 if (player->is_moving ||
11495 player->is_pushing ||
11496 player->is_dropping_pressed ||
11497 player->effective_mouse_action.button)
11498 game.enter_single_step_mode = FALSE;
11501 CheckSaveEngineSnapshot(player);
11504 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11506 int left = player_action & JOY_LEFT;
11507 int right = player_action & JOY_RIGHT;
11508 int up = player_action & JOY_UP;
11509 int down = player_action & JOY_DOWN;
11510 int button1 = player_action & JOY_BUTTON_1;
11511 int button2 = player_action & JOY_BUTTON_2;
11512 int dx = (left ? -1 : right ? 1 : 0);
11513 int dy = (up ? -1 : down ? 1 : 0);
11515 if (!player->active || tape.pausing)
11521 SnapField(player, dx, dy);
11525 DropElement(player);
11527 MovePlayer(player, dx, dy);
11530 CheckSingleStepMode(player);
11532 SetPlayerWaiting(player, FALSE);
11534 return player_action;
11538 // no actions for this player (no input at player's configured device)
11540 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11541 SnapField(player, 0, 0);
11542 CheckGravityMovementWhenNotMoving(player);
11544 if (player->MovPos == 0)
11545 SetPlayerWaiting(player, TRUE);
11547 if (player->MovPos == 0) // needed for tape.playing
11548 player->is_moving = FALSE;
11550 player->is_dropping = FALSE;
11551 player->is_dropping_pressed = FALSE;
11552 player->drop_pressed_delay = 0;
11554 CheckSingleStepMode(player);
11560 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11563 if (!tape.use_mouse_actions)
11566 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11567 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11568 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11571 static void SetTapeActionFromMouseAction(byte *tape_action,
11572 struct MouseActionInfo *mouse_action)
11574 if (!tape.use_mouse_actions)
11577 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11578 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11579 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11582 static void CheckLevelSolved(void)
11584 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11586 if (game_em.level_solved &&
11587 !game_em.game_over) // game won
11591 game_em.game_over = TRUE;
11593 game.all_players_gone = TRUE;
11596 if (game_em.game_over) // game lost
11597 game.all_players_gone = TRUE;
11599 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11601 if (game_sp.level_solved &&
11602 !game_sp.game_over) // game won
11606 game_sp.game_over = TRUE;
11608 game.all_players_gone = TRUE;
11611 if (game_sp.game_over) // game lost
11612 game.all_players_gone = TRUE;
11614 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11616 if (game_mm.level_solved &&
11617 !game_mm.game_over) // game won
11621 game_mm.game_over = TRUE;
11623 game.all_players_gone = TRUE;
11626 if (game_mm.game_over) // game lost
11627 game.all_players_gone = TRUE;
11631 static void CheckLevelTime_StepCounter(void)
11641 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11642 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11644 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11646 DisplayGameControlValues();
11648 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11649 for (i = 0; i < MAX_PLAYERS; i++)
11650 KillPlayer(&stored_player[i]);
11652 else if (game.no_level_time_limit && !game.all_players_gone)
11654 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11656 DisplayGameControlValues();
11660 static void CheckLevelTime(void)
11664 if (TimeFrames >= FRAMES_PER_SECOND)
11669 for (i = 0; i < MAX_PLAYERS; i++)
11671 struct PlayerInfo *player = &stored_player[i];
11673 if (SHIELD_ON(player))
11675 player->shield_normal_time_left--;
11677 if (player->shield_deadly_time_left > 0)
11678 player->shield_deadly_time_left--;
11682 if (!game.LevelSolved && !level.use_step_counter)
11690 if (TimeLeft <= 10 && game.time_limit)
11691 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11693 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11694 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11696 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11698 if (!TimeLeft && game.time_limit)
11700 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11701 game_em.lev->killed_out_of_time = TRUE;
11703 for (i = 0; i < MAX_PLAYERS; i++)
11704 KillPlayer(&stored_player[i]);
11707 else if (game.no_level_time_limit && !game.all_players_gone)
11709 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11712 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11715 if (tape.recording || tape.playing)
11716 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11719 if (tape.recording || tape.playing)
11720 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11722 UpdateAndDisplayGameControlValues();
11725 void AdvanceFrameAndPlayerCounters(int player_nr)
11729 // advance frame counters (global frame counter and time frame counter)
11733 // advance player counters (counters for move delay, move animation etc.)
11734 for (i = 0; i < MAX_PLAYERS; i++)
11736 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11737 int move_delay_value = stored_player[i].move_delay_value;
11738 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11740 if (!advance_player_counters) // not all players may be affected
11743 if (move_frames == 0) // less than one move per game frame
11745 int stepsize = TILEX / move_delay_value;
11746 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11747 int count = (stored_player[i].is_moving ?
11748 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11750 if (count % delay == 0)
11754 stored_player[i].Frame += move_frames;
11756 if (stored_player[i].MovPos != 0)
11757 stored_player[i].StepFrame += move_frames;
11759 if (stored_player[i].move_delay > 0)
11760 stored_player[i].move_delay--;
11762 // due to bugs in previous versions, counter must count up, not down
11763 if (stored_player[i].push_delay != -1)
11764 stored_player[i].push_delay++;
11766 if (stored_player[i].drop_delay > 0)
11767 stored_player[i].drop_delay--;
11769 if (stored_player[i].is_dropping_pressed)
11770 stored_player[i].drop_pressed_delay++;
11774 void StartGameActions(boolean init_network_game, boolean record_tape,
11777 unsigned int new_random_seed = InitRND(random_seed);
11780 TapeStartRecording(new_random_seed);
11782 if (setup.auto_pause_on_start && !tape.pausing)
11783 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11785 if (init_network_game)
11787 SendToServer_LevelFile();
11788 SendToServer_StartPlaying();
11796 static void GameActionsExt(void)
11799 static unsigned int game_frame_delay = 0;
11801 unsigned int game_frame_delay_value;
11802 byte *recorded_player_action;
11803 byte summarized_player_action = 0;
11804 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11807 // detect endless loops, caused by custom element programming
11808 if (recursion_loop_detected && recursion_loop_depth == 0)
11810 char *message = getStringCat3("Internal Error! Element ",
11811 EL_NAME(recursion_loop_element),
11812 " caused endless loop! Quit the game?");
11814 Warn("element '%s' caused endless loop in game engine",
11815 EL_NAME(recursion_loop_element));
11817 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11819 recursion_loop_detected = FALSE; // if game should be continued
11826 if (game.restart_level)
11827 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11829 CheckLevelSolved();
11831 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11834 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11837 if (game_status != GAME_MODE_PLAYING) // status might have changed
11840 game_frame_delay_value =
11841 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11843 if (tape.playing && tape.warp_forward && !tape.pausing)
11844 game_frame_delay_value = 0;
11846 SetVideoFrameDelay(game_frame_delay_value);
11848 // (de)activate virtual buttons depending on current game status
11849 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11851 if (game.all_players_gone) // if no players there to be controlled anymore
11852 SetOverlayActive(FALSE);
11853 else if (!tape.playing) // if game continues after tape stopped playing
11854 SetOverlayActive(TRUE);
11859 // ---------- main game synchronization point ----------
11861 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11863 Debug("game:playing:skip", "skip == %d", skip);
11866 // ---------- main game synchronization point ----------
11868 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11872 if (network_playing && !network_player_action_received)
11874 // try to get network player actions in time
11876 // last chance to get network player actions without main loop delay
11877 HandleNetworking();
11879 // game was quit by network peer
11880 if (game_status != GAME_MODE_PLAYING)
11883 // check if network player actions still missing and game still running
11884 if (!network_player_action_received && !checkGameEnded())
11885 return; // failed to get network player actions in time
11887 // do not yet reset "network_player_action_received" (for tape.pausing)
11893 // at this point we know that we really continue executing the game
11895 network_player_action_received = FALSE;
11897 // when playing tape, read previously recorded player input from tape data
11898 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11900 local_player->effective_mouse_action = local_player->mouse_action;
11902 if (recorded_player_action != NULL)
11903 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11904 recorded_player_action);
11906 // TapePlayAction() may return NULL when toggling to "pause before death"
11910 if (tape.set_centered_player)
11912 game.centered_player_nr_next = tape.centered_player_nr_next;
11913 game.set_centered_player = TRUE;
11916 for (i = 0; i < MAX_PLAYERS; i++)
11918 summarized_player_action |= stored_player[i].action;
11920 if (!network_playing && (game.team_mode || tape.playing))
11921 stored_player[i].effective_action = stored_player[i].action;
11924 if (network_playing && !checkGameEnded())
11925 SendToServer_MovePlayer(summarized_player_action);
11927 // summarize all actions at local players mapped input device position
11928 // (this allows using different input devices in single player mode)
11929 if (!network.enabled && !game.team_mode)
11930 stored_player[map_player_action[local_player->index_nr]].effective_action =
11931 summarized_player_action;
11933 // summarize all actions at centered player in local team mode
11934 if (tape.recording &&
11935 setup.team_mode && !network.enabled &&
11936 setup.input_on_focus &&
11937 game.centered_player_nr != -1)
11939 for (i = 0; i < MAX_PLAYERS; i++)
11940 stored_player[map_player_action[i]].effective_action =
11941 (i == game.centered_player_nr ? summarized_player_action : 0);
11944 if (recorded_player_action != NULL)
11945 for (i = 0; i < MAX_PLAYERS; i++)
11946 stored_player[i].effective_action = recorded_player_action[i];
11948 for (i = 0; i < MAX_PLAYERS; i++)
11950 tape_action[i] = stored_player[i].effective_action;
11952 /* (this may happen in the RND game engine if a player was not present on
11953 the playfield on level start, but appeared later from a custom element */
11954 if (setup.team_mode &&
11957 !tape.player_participates[i])
11958 tape.player_participates[i] = TRUE;
11961 SetTapeActionFromMouseAction(tape_action,
11962 &local_player->effective_mouse_action);
11964 // only record actions from input devices, but not programmed actions
11965 if (tape.recording)
11966 TapeRecordAction(tape_action);
11968 // remember if game was played (especially after tape stopped playing)
11969 if (!tape.playing && summarized_player_action)
11970 game.GamePlayed = TRUE;
11972 #if USE_NEW_PLAYER_ASSIGNMENTS
11973 // !!! also map player actions in single player mode !!!
11974 // if (game.team_mode)
11977 byte mapped_action[MAX_PLAYERS];
11979 #if DEBUG_PLAYER_ACTIONS
11980 for (i = 0; i < MAX_PLAYERS; i++)
11981 DebugContinued("", "%d, ", stored_player[i].effective_action);
11984 for (i = 0; i < MAX_PLAYERS; i++)
11985 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11987 for (i = 0; i < MAX_PLAYERS; i++)
11988 stored_player[i].effective_action = mapped_action[i];
11990 #if DEBUG_PLAYER_ACTIONS
11991 DebugContinued("", "=> ");
11992 for (i = 0; i < MAX_PLAYERS; i++)
11993 DebugContinued("", "%d, ", stored_player[i].effective_action);
11994 DebugContinued("game:playing:player", "\n");
11997 #if DEBUG_PLAYER_ACTIONS
12000 for (i = 0; i < MAX_PLAYERS; i++)
12001 DebugContinued("", "%d, ", stored_player[i].effective_action);
12002 DebugContinued("game:playing:player", "\n");
12007 for (i = 0; i < MAX_PLAYERS; i++)
12009 // allow engine snapshot in case of changed movement attempt
12010 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12011 (stored_player[i].effective_action & KEY_MOTION))
12012 game.snapshot.changed_action = TRUE;
12014 // allow engine snapshot in case of snapping/dropping attempt
12015 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12016 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12017 game.snapshot.changed_action = TRUE;
12019 game.snapshot.last_action[i] = stored_player[i].effective_action;
12022 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12024 GameActions_EM_Main();
12026 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12028 GameActions_SP_Main();
12030 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12032 GameActions_MM_Main();
12036 GameActions_RND_Main();
12039 BlitScreenToBitmap(backbuffer);
12041 CheckLevelSolved();
12044 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12046 if (global.show_frames_per_second)
12048 static unsigned int fps_counter = 0;
12049 static int fps_frames = 0;
12050 unsigned int fps_delay_ms = Counter() - fps_counter;
12054 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12056 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12059 fps_counter = Counter();
12061 // always draw FPS to screen after FPS value was updated
12062 redraw_mask |= REDRAW_FPS;
12065 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12066 if (GetDrawDeactivationMask() == REDRAW_NONE)
12067 redraw_mask |= REDRAW_FPS;
12071 static void GameActions_CheckSaveEngineSnapshot(void)
12073 if (!game.snapshot.save_snapshot)
12076 // clear flag for saving snapshot _before_ saving snapshot
12077 game.snapshot.save_snapshot = FALSE;
12079 SaveEngineSnapshotToList();
12082 void GameActions(void)
12086 GameActions_CheckSaveEngineSnapshot();
12089 void GameActions_EM_Main(void)
12091 byte effective_action[MAX_PLAYERS];
12092 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12095 for (i = 0; i < MAX_PLAYERS; i++)
12096 effective_action[i] = stored_player[i].effective_action;
12098 GameActions_EM(effective_action, warp_mode);
12101 void GameActions_SP_Main(void)
12103 byte effective_action[MAX_PLAYERS];
12104 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12107 for (i = 0; i < MAX_PLAYERS; i++)
12108 effective_action[i] = stored_player[i].effective_action;
12110 GameActions_SP(effective_action, warp_mode);
12112 for (i = 0; i < MAX_PLAYERS; i++)
12114 if (stored_player[i].force_dropping)
12115 stored_player[i].action |= KEY_BUTTON_DROP;
12117 stored_player[i].force_dropping = FALSE;
12121 void GameActions_MM_Main(void)
12123 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12125 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12128 void GameActions_RND_Main(void)
12133 void GameActions_RND(void)
12135 static struct MouseActionInfo mouse_action_last = { 0 };
12136 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12137 int magic_wall_x = 0, magic_wall_y = 0;
12138 int i, x, y, element, graphic, last_gfx_frame;
12140 InitPlayfieldScanModeVars();
12142 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12144 SCAN_PLAYFIELD(x, y)
12146 ChangeCount[x][y] = 0;
12147 ChangeEvent[x][y] = -1;
12151 if (game.set_centered_player)
12153 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12155 // switching to "all players" only possible if all players fit to screen
12156 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12158 game.centered_player_nr_next = game.centered_player_nr;
12159 game.set_centered_player = FALSE;
12162 // do not switch focus to non-existing (or non-active) player
12163 if (game.centered_player_nr_next >= 0 &&
12164 !stored_player[game.centered_player_nr_next].active)
12166 game.centered_player_nr_next = game.centered_player_nr;
12167 game.set_centered_player = FALSE;
12171 if (game.set_centered_player &&
12172 ScreenMovPos == 0) // screen currently aligned at tile position
12176 if (game.centered_player_nr_next == -1)
12178 setScreenCenteredToAllPlayers(&sx, &sy);
12182 sx = stored_player[game.centered_player_nr_next].jx;
12183 sy = stored_player[game.centered_player_nr_next].jy;
12186 game.centered_player_nr = game.centered_player_nr_next;
12187 game.set_centered_player = FALSE;
12189 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12190 DrawGameDoorValues();
12193 // check single step mode (set flag and clear again if any player is active)
12194 game.enter_single_step_mode =
12195 (tape.single_step && tape.recording && !tape.pausing);
12197 for (i = 0; i < MAX_PLAYERS; i++)
12199 int actual_player_action = stored_player[i].effective_action;
12202 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12203 - rnd_equinox_tetrachloride 048
12204 - rnd_equinox_tetrachloride_ii 096
12205 - rnd_emanuel_schmieg 002
12206 - doctor_sloan_ww 001, 020
12208 if (stored_player[i].MovPos == 0)
12209 CheckGravityMovement(&stored_player[i]);
12212 // overwrite programmed action with tape action
12213 if (stored_player[i].programmed_action)
12214 actual_player_action = stored_player[i].programmed_action;
12216 PlayerActions(&stored_player[i], actual_player_action);
12218 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12221 // single step pause mode may already have been toggled by "ScrollPlayer()"
12222 if (game.enter_single_step_mode && !tape.pausing)
12223 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12225 ScrollScreen(NULL, SCROLL_GO_ON);
12227 /* for backwards compatibility, the following code emulates a fixed bug that
12228 occured when pushing elements (causing elements that just made their last
12229 pushing step to already (if possible) make their first falling step in the
12230 same game frame, which is bad); this code is also needed to use the famous
12231 "spring push bug" which is used in older levels and might be wanted to be
12232 used also in newer levels, but in this case the buggy pushing code is only
12233 affecting the "spring" element and no other elements */
12235 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12237 for (i = 0; i < MAX_PLAYERS; i++)
12239 struct PlayerInfo *player = &stored_player[i];
12240 int x = player->jx;
12241 int y = player->jy;
12243 if (player->active && player->is_pushing && player->is_moving &&
12245 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12246 Tile[x][y] == EL_SPRING))
12248 ContinueMoving(x, y);
12250 // continue moving after pushing (this is actually a bug)
12251 if (!IS_MOVING(x, y))
12252 Stop[x][y] = FALSE;
12257 SCAN_PLAYFIELD(x, y)
12259 Last[x][y] = Tile[x][y];
12261 ChangeCount[x][y] = 0;
12262 ChangeEvent[x][y] = -1;
12264 // this must be handled before main playfield loop
12265 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12268 if (MovDelay[x][y] <= 0)
12272 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12275 if (MovDelay[x][y] <= 0)
12277 int element = Store[x][y];
12278 int move_direction = MovDir[x][y];
12279 int player_index_bit = Store2[x][y];
12285 TEST_DrawLevelField(x, y);
12287 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12289 if (IS_ENVELOPE(element))
12290 local_player->show_envelope = element;
12295 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12297 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12299 Debug("game:playing:GameActions_RND", "This should never happen!");
12301 ChangePage[x][y] = -1;
12305 Stop[x][y] = FALSE;
12306 if (WasJustMoving[x][y] > 0)
12307 WasJustMoving[x][y]--;
12308 if (WasJustFalling[x][y] > 0)
12309 WasJustFalling[x][y]--;
12310 if (CheckCollision[x][y] > 0)
12311 CheckCollision[x][y]--;
12312 if (CheckImpact[x][y] > 0)
12313 CheckImpact[x][y]--;
12317 /* reset finished pushing action (not done in ContinueMoving() to allow
12318 continuous pushing animation for elements with zero push delay) */
12319 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12321 ResetGfxAnimation(x, y);
12322 TEST_DrawLevelField(x, y);
12326 if (IS_BLOCKED(x, y))
12330 Blocked2Moving(x, y, &oldx, &oldy);
12331 if (!IS_MOVING(oldx, oldy))
12333 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12334 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12335 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12336 Debug("game:playing:GameActions_RND", "This should never happen!");
12342 if (mouse_action.button)
12344 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12345 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12347 x = mouse_action.lx;
12348 y = mouse_action.ly;
12349 element = Tile[x][y];
12353 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12354 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12358 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12359 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12362 if (level.use_step_counter)
12364 boolean counted_click = FALSE;
12366 // element clicked that can change when clicked/pressed
12367 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12368 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12369 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12370 counted_click = TRUE;
12372 // element clicked that can trigger change when clicked/pressed
12373 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12374 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12375 counted_click = TRUE;
12377 if (new_button && counted_click)
12378 CheckLevelTime_StepCounter();
12382 SCAN_PLAYFIELD(x, y)
12384 element = Tile[x][y];
12385 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12386 last_gfx_frame = GfxFrame[x][y];
12388 if (element == EL_EMPTY)
12389 graphic = el2img(GfxElementEmpty[x][y]);
12391 ResetGfxFrame(x, y);
12393 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12394 DrawLevelGraphicAnimation(x, y, graphic);
12396 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12397 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12398 ResetRandomAnimationValue(x, y);
12400 SetRandomAnimationValue(x, y);
12402 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12404 if (IS_INACTIVE(element))
12406 if (IS_ANIMATED(graphic))
12407 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12412 // this may take place after moving, so 'element' may have changed
12413 if (IS_CHANGING(x, y) &&
12414 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12416 int page = element_info[element].event_page_nr[CE_DELAY];
12418 HandleElementChange(x, y, page);
12420 element = Tile[x][y];
12421 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12424 CheckNextToConditions(x, y);
12426 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12430 element = Tile[x][y];
12431 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12433 if (IS_ANIMATED(graphic) &&
12434 !IS_MOVING(x, y) &&
12436 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12438 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12439 TEST_DrawTwinkleOnField(x, y);
12441 else if (element == EL_ACID)
12444 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12446 else if ((element == EL_EXIT_OPEN ||
12447 element == EL_EM_EXIT_OPEN ||
12448 element == EL_SP_EXIT_OPEN ||
12449 element == EL_STEEL_EXIT_OPEN ||
12450 element == EL_EM_STEEL_EXIT_OPEN ||
12451 element == EL_SP_TERMINAL ||
12452 element == EL_SP_TERMINAL_ACTIVE ||
12453 element == EL_EXTRA_TIME ||
12454 element == EL_SHIELD_NORMAL ||
12455 element == EL_SHIELD_DEADLY) &&
12456 IS_ANIMATED(graphic))
12457 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12458 else if (IS_MOVING(x, y))
12459 ContinueMoving(x, y);
12460 else if (IS_ACTIVE_BOMB(element))
12461 CheckDynamite(x, y);
12462 else if (element == EL_AMOEBA_GROWING)
12463 AmoebaGrowing(x, y);
12464 else if (element == EL_AMOEBA_SHRINKING)
12465 AmoebaShrinking(x, y);
12467 #if !USE_NEW_AMOEBA_CODE
12468 else if (IS_AMOEBALIVE(element))
12469 AmoebaReproduce(x, y);
12472 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12474 else if (element == EL_EXIT_CLOSED)
12476 else if (element == EL_EM_EXIT_CLOSED)
12478 else if (element == EL_STEEL_EXIT_CLOSED)
12479 CheckExitSteel(x, y);
12480 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12481 CheckExitSteelEM(x, y);
12482 else if (element == EL_SP_EXIT_CLOSED)
12484 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12485 element == EL_EXPANDABLE_STEELWALL_GROWING)
12486 MauerWaechst(x, y);
12487 else if (element == EL_EXPANDABLE_WALL ||
12488 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12489 element == EL_EXPANDABLE_WALL_VERTICAL ||
12490 element == EL_EXPANDABLE_WALL_ANY ||
12491 element == EL_BD_EXPANDABLE_WALL)
12492 MauerAbleger(x, y);
12493 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12494 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12495 element == EL_EXPANDABLE_STEELWALL_ANY)
12496 MauerAblegerStahl(x, y);
12497 else if (element == EL_FLAMES)
12498 CheckForDragon(x, y);
12499 else if (element == EL_EXPLOSION)
12500 ; // drawing of correct explosion animation is handled separately
12501 else if (element == EL_ELEMENT_SNAPPING ||
12502 element == EL_DIAGONAL_SHRINKING ||
12503 element == EL_DIAGONAL_GROWING)
12505 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12507 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12509 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12510 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12512 if (IS_BELT_ACTIVE(element))
12513 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12515 if (game.magic_wall_active)
12517 int jx = local_player->jx, jy = local_player->jy;
12519 // play the element sound at the position nearest to the player
12520 if ((element == EL_MAGIC_WALL_FULL ||
12521 element == EL_MAGIC_WALL_ACTIVE ||
12522 element == EL_MAGIC_WALL_EMPTYING ||
12523 element == EL_BD_MAGIC_WALL_FULL ||
12524 element == EL_BD_MAGIC_WALL_ACTIVE ||
12525 element == EL_BD_MAGIC_WALL_EMPTYING ||
12526 element == EL_DC_MAGIC_WALL_FULL ||
12527 element == EL_DC_MAGIC_WALL_ACTIVE ||
12528 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12529 ABS(x - jx) + ABS(y - jy) <
12530 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12538 #if USE_NEW_AMOEBA_CODE
12539 // new experimental amoeba growth stuff
12540 if (!(FrameCounter % 8))
12542 static unsigned int random = 1684108901;
12544 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12546 x = RND(lev_fieldx);
12547 y = RND(lev_fieldy);
12548 element = Tile[x][y];
12550 if (!IS_PLAYER(x,y) &&
12551 (element == EL_EMPTY ||
12552 CAN_GROW_INTO(element) ||
12553 element == EL_QUICKSAND_EMPTY ||
12554 element == EL_QUICKSAND_FAST_EMPTY ||
12555 element == EL_ACID_SPLASH_LEFT ||
12556 element == EL_ACID_SPLASH_RIGHT))
12558 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12559 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12560 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12561 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12562 Tile[x][y] = EL_AMOEBA_DROP;
12565 random = random * 129 + 1;
12570 game.explosions_delayed = FALSE;
12572 SCAN_PLAYFIELD(x, y)
12574 element = Tile[x][y];
12576 if (ExplodeField[x][y])
12577 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12578 else if (element == EL_EXPLOSION)
12579 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12581 ExplodeField[x][y] = EX_TYPE_NONE;
12584 game.explosions_delayed = TRUE;
12586 if (game.magic_wall_active)
12588 if (!(game.magic_wall_time_left % 4))
12590 int element = Tile[magic_wall_x][magic_wall_y];
12592 if (element == EL_BD_MAGIC_WALL_FULL ||
12593 element == EL_BD_MAGIC_WALL_ACTIVE ||
12594 element == EL_BD_MAGIC_WALL_EMPTYING)
12595 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12596 else if (element == EL_DC_MAGIC_WALL_FULL ||
12597 element == EL_DC_MAGIC_WALL_ACTIVE ||
12598 element == EL_DC_MAGIC_WALL_EMPTYING)
12599 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12601 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12604 if (game.magic_wall_time_left > 0)
12606 game.magic_wall_time_left--;
12608 if (!game.magic_wall_time_left)
12610 SCAN_PLAYFIELD(x, y)
12612 element = Tile[x][y];
12614 if (element == EL_MAGIC_WALL_ACTIVE ||
12615 element == EL_MAGIC_WALL_FULL)
12617 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12618 TEST_DrawLevelField(x, y);
12620 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12621 element == EL_BD_MAGIC_WALL_FULL)
12623 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12624 TEST_DrawLevelField(x, y);
12626 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12627 element == EL_DC_MAGIC_WALL_FULL)
12629 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12630 TEST_DrawLevelField(x, y);
12634 game.magic_wall_active = FALSE;
12639 if (game.light_time_left > 0)
12641 game.light_time_left--;
12643 if (game.light_time_left == 0)
12644 RedrawAllLightSwitchesAndInvisibleElements();
12647 if (game.timegate_time_left > 0)
12649 game.timegate_time_left--;
12651 if (game.timegate_time_left == 0)
12652 CloseAllOpenTimegates();
12655 if (game.lenses_time_left > 0)
12657 game.lenses_time_left--;
12659 if (game.lenses_time_left == 0)
12660 RedrawAllInvisibleElementsForLenses();
12663 if (game.magnify_time_left > 0)
12665 game.magnify_time_left--;
12667 if (game.magnify_time_left == 0)
12668 RedrawAllInvisibleElementsForMagnifier();
12671 for (i = 0; i < MAX_PLAYERS; i++)
12673 struct PlayerInfo *player = &stored_player[i];
12675 if (SHIELD_ON(player))
12677 if (player->shield_deadly_time_left)
12678 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12679 else if (player->shield_normal_time_left)
12680 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12684 #if USE_DELAYED_GFX_REDRAW
12685 SCAN_PLAYFIELD(x, y)
12687 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12689 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12690 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12692 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12693 DrawLevelField(x, y);
12695 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12696 DrawLevelFieldCrumbled(x, y);
12698 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12699 DrawLevelFieldCrumbledNeighbours(x, y);
12701 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12702 DrawTwinkleOnField(x, y);
12705 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12710 PlayAllPlayersSound();
12712 for (i = 0; i < MAX_PLAYERS; i++)
12714 struct PlayerInfo *player = &stored_player[i];
12716 if (player->show_envelope != 0 && (!player->active ||
12717 player->MovPos == 0))
12719 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12721 player->show_envelope = 0;
12725 // use random number generator in every frame to make it less predictable
12726 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12729 mouse_action_last = mouse_action;
12732 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12734 int min_x = x, min_y = y, max_x = x, max_y = y;
12735 int scr_fieldx = getScreenFieldSizeX();
12736 int scr_fieldy = getScreenFieldSizeY();
12739 for (i = 0; i < MAX_PLAYERS; i++)
12741 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12743 if (!stored_player[i].active || &stored_player[i] == player)
12746 min_x = MIN(min_x, jx);
12747 min_y = MIN(min_y, jy);
12748 max_x = MAX(max_x, jx);
12749 max_y = MAX(max_y, jy);
12752 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12755 static boolean AllPlayersInVisibleScreen(void)
12759 for (i = 0; i < MAX_PLAYERS; i++)
12761 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12763 if (!stored_player[i].active)
12766 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12773 void ScrollLevel(int dx, int dy)
12775 int scroll_offset = 2 * TILEX_VAR;
12778 BlitBitmap(drawto_field, drawto_field,
12779 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12780 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12781 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12782 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12783 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12784 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12788 x = (dx == 1 ? BX1 : BX2);
12789 for (y = BY1; y <= BY2; y++)
12790 DrawScreenField(x, y);
12795 y = (dy == 1 ? BY1 : BY2);
12796 for (x = BX1; x <= BX2; x++)
12797 DrawScreenField(x, y);
12800 redraw_mask |= REDRAW_FIELD;
12803 static boolean canFallDown(struct PlayerInfo *player)
12805 int jx = player->jx, jy = player->jy;
12807 return (IN_LEV_FIELD(jx, jy + 1) &&
12808 (IS_FREE(jx, jy + 1) ||
12809 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12810 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12811 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12814 static boolean canPassField(int x, int y, int move_dir)
12816 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12817 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12818 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12819 int nextx = x + dx;
12820 int nexty = y + dy;
12821 int element = Tile[x][y];
12823 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12824 !CAN_MOVE(element) &&
12825 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12826 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12827 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12830 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12832 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12833 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12834 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12838 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12839 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12840 (IS_DIGGABLE(Tile[newx][newy]) ||
12841 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12842 canPassField(newx, newy, move_dir)));
12845 static void CheckGravityMovement(struct PlayerInfo *player)
12847 if (player->gravity && !player->programmed_action)
12849 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12850 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12851 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12852 int jx = player->jx, jy = player->jy;
12853 boolean player_is_moving_to_valid_field =
12854 (!player_is_snapping &&
12855 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12856 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12857 boolean player_can_fall_down = canFallDown(player);
12859 if (player_can_fall_down &&
12860 !player_is_moving_to_valid_field)
12861 player->programmed_action = MV_DOWN;
12865 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12867 return CheckGravityMovement(player);
12869 if (player->gravity && !player->programmed_action)
12871 int jx = player->jx, jy = player->jy;
12872 boolean field_under_player_is_free =
12873 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12874 boolean player_is_standing_on_valid_field =
12875 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12876 (IS_WALKABLE(Tile[jx][jy]) &&
12877 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12879 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12880 player->programmed_action = MV_DOWN;
12885 MovePlayerOneStep()
12886 -----------------------------------------------------------------------------
12887 dx, dy: direction (non-diagonal) to try to move the player to
12888 real_dx, real_dy: direction as read from input device (can be diagonal)
12891 boolean MovePlayerOneStep(struct PlayerInfo *player,
12892 int dx, int dy, int real_dx, int real_dy)
12894 int jx = player->jx, jy = player->jy;
12895 int new_jx = jx + dx, new_jy = jy + dy;
12897 boolean player_can_move = !player->cannot_move;
12899 if (!player->active || (!dx && !dy))
12900 return MP_NO_ACTION;
12902 player->MovDir = (dx < 0 ? MV_LEFT :
12903 dx > 0 ? MV_RIGHT :
12905 dy > 0 ? MV_DOWN : MV_NONE);
12907 if (!IN_LEV_FIELD(new_jx, new_jy))
12908 return MP_NO_ACTION;
12910 if (!player_can_move)
12912 if (player->MovPos == 0)
12914 player->is_moving = FALSE;
12915 player->is_digging = FALSE;
12916 player->is_collecting = FALSE;
12917 player->is_snapping = FALSE;
12918 player->is_pushing = FALSE;
12922 if (!network.enabled && game.centered_player_nr == -1 &&
12923 !AllPlayersInSight(player, new_jx, new_jy))
12924 return MP_NO_ACTION;
12926 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12927 if (can_move != MP_MOVING)
12930 // check if DigField() has caused relocation of the player
12931 if (player->jx != jx || player->jy != jy)
12932 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12934 StorePlayer[jx][jy] = 0;
12935 player->last_jx = jx;
12936 player->last_jy = jy;
12937 player->jx = new_jx;
12938 player->jy = new_jy;
12939 StorePlayer[new_jx][new_jy] = player->element_nr;
12941 if (player->move_delay_value_next != -1)
12943 player->move_delay_value = player->move_delay_value_next;
12944 player->move_delay_value_next = -1;
12948 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12950 player->step_counter++;
12952 PlayerVisit[jx][jy] = FrameCounter;
12954 player->is_moving = TRUE;
12957 // should better be called in MovePlayer(), but this breaks some tapes
12958 ScrollPlayer(player, SCROLL_INIT);
12964 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12966 int jx = player->jx, jy = player->jy;
12967 int old_jx = jx, old_jy = jy;
12968 int moved = MP_NO_ACTION;
12970 if (!player->active)
12975 if (player->MovPos == 0)
12977 player->is_moving = FALSE;
12978 player->is_digging = FALSE;
12979 player->is_collecting = FALSE;
12980 player->is_snapping = FALSE;
12981 player->is_pushing = FALSE;
12987 if (player->move_delay > 0)
12990 player->move_delay = -1; // set to "uninitialized" value
12992 // store if player is automatically moved to next field
12993 player->is_auto_moving = (player->programmed_action != MV_NONE);
12995 // remove the last programmed player action
12996 player->programmed_action = 0;
12998 if (player->MovPos)
13000 // should only happen if pre-1.2 tape recordings are played
13001 // this is only for backward compatibility
13003 int original_move_delay_value = player->move_delay_value;
13006 Debug("game:playing:MovePlayer",
13007 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13011 // scroll remaining steps with finest movement resolution
13012 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13014 while (player->MovPos)
13016 ScrollPlayer(player, SCROLL_GO_ON);
13017 ScrollScreen(NULL, SCROLL_GO_ON);
13019 AdvanceFrameAndPlayerCounters(player->index_nr);
13022 BackToFront_WithFrameDelay(0);
13025 player->move_delay_value = original_move_delay_value;
13028 player->is_active = FALSE;
13030 if (player->last_move_dir & MV_HORIZONTAL)
13032 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13033 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13037 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13038 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13041 if (!moved && !player->is_active)
13043 player->is_moving = FALSE;
13044 player->is_digging = FALSE;
13045 player->is_collecting = FALSE;
13046 player->is_snapping = FALSE;
13047 player->is_pushing = FALSE;
13053 if (moved & MP_MOVING && !ScreenMovPos &&
13054 (player->index_nr == game.centered_player_nr ||
13055 game.centered_player_nr == -1))
13057 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13059 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13061 // actual player has left the screen -- scroll in that direction
13062 if (jx != old_jx) // player has moved horizontally
13063 scroll_x += (jx - old_jx);
13064 else // player has moved vertically
13065 scroll_y += (jy - old_jy);
13069 int offset_raw = game.scroll_delay_value;
13071 if (jx != old_jx) // player has moved horizontally
13073 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13074 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13075 int new_scroll_x = jx - MIDPOSX + offset_x;
13077 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13078 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13079 scroll_x = new_scroll_x;
13081 // don't scroll over playfield boundaries
13082 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13084 // don't scroll more than one field at a time
13085 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13087 // don't scroll against the player's moving direction
13088 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13089 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13090 scroll_x = old_scroll_x;
13092 else // player has moved vertically
13094 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13095 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13096 int new_scroll_y = jy - MIDPOSY + offset_y;
13098 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13099 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13100 scroll_y = new_scroll_y;
13102 // don't scroll over playfield boundaries
13103 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13105 // don't scroll more than one field at a time
13106 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13108 // don't scroll against the player's moving direction
13109 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13110 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13111 scroll_y = old_scroll_y;
13115 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13117 if (!network.enabled && game.centered_player_nr == -1 &&
13118 !AllPlayersInVisibleScreen())
13120 scroll_x = old_scroll_x;
13121 scroll_y = old_scroll_y;
13125 ScrollScreen(player, SCROLL_INIT);
13126 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13131 player->StepFrame = 0;
13133 if (moved & MP_MOVING)
13135 if (old_jx != jx && old_jy == jy)
13136 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13137 else if (old_jx == jx && old_jy != jy)
13138 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13140 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13142 player->last_move_dir = player->MovDir;
13143 player->is_moving = TRUE;
13144 player->is_snapping = FALSE;
13145 player->is_switching = FALSE;
13146 player->is_dropping = FALSE;
13147 player->is_dropping_pressed = FALSE;
13148 player->drop_pressed_delay = 0;
13151 // should better be called here than above, but this breaks some tapes
13152 ScrollPlayer(player, SCROLL_INIT);
13157 CheckGravityMovementWhenNotMoving(player);
13159 player->is_moving = FALSE;
13161 /* at this point, the player is allowed to move, but cannot move right now
13162 (e.g. because of something blocking the way) -- ensure that the player
13163 is also allowed to move in the next frame (in old versions before 3.1.1,
13164 the player was forced to wait again for eight frames before next try) */
13166 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13167 player->move_delay = 0; // allow direct movement in the next frame
13170 if (player->move_delay == -1) // not yet initialized by DigField()
13171 player->move_delay = player->move_delay_value;
13173 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13175 TestIfPlayerTouchesBadThing(jx, jy);
13176 TestIfPlayerTouchesCustomElement(jx, jy);
13179 if (!player->active)
13180 RemovePlayer(player);
13185 void ScrollPlayer(struct PlayerInfo *player, int mode)
13187 int jx = player->jx, jy = player->jy;
13188 int last_jx = player->last_jx, last_jy = player->last_jy;
13189 int move_stepsize = TILEX / player->move_delay_value;
13191 if (!player->active)
13194 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13197 if (mode == SCROLL_INIT)
13199 player->actual_frame_counter = FrameCounter;
13200 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13202 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13203 Tile[last_jx][last_jy] == EL_EMPTY)
13205 int last_field_block_delay = 0; // start with no blocking at all
13206 int block_delay_adjustment = player->block_delay_adjustment;
13208 // if player blocks last field, add delay for exactly one move
13209 if (player->block_last_field)
13211 last_field_block_delay += player->move_delay_value;
13213 // when blocking enabled, prevent moving up despite gravity
13214 if (player->gravity && player->MovDir == MV_UP)
13215 block_delay_adjustment = -1;
13218 // add block delay adjustment (also possible when not blocking)
13219 last_field_block_delay += block_delay_adjustment;
13221 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13222 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13225 if (player->MovPos != 0) // player has not yet reached destination
13228 else if (!FrameReached(&player->actual_frame_counter, 1))
13231 if (player->MovPos != 0)
13233 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13234 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13236 // before DrawPlayer() to draw correct player graphic for this case
13237 if (player->MovPos == 0)
13238 CheckGravityMovement(player);
13241 if (player->MovPos == 0) // player reached destination field
13243 if (player->move_delay_reset_counter > 0)
13245 player->move_delay_reset_counter--;
13247 if (player->move_delay_reset_counter == 0)
13249 // continue with normal speed after quickly moving through gate
13250 HALVE_PLAYER_SPEED(player);
13252 // be able to make the next move without delay
13253 player->move_delay = 0;
13257 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13258 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13259 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13260 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13261 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13262 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13263 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13264 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13266 ExitPlayer(player);
13268 if (game.players_still_needed == 0 &&
13269 (game.friends_still_needed == 0 ||
13270 IS_SP_ELEMENT(Tile[jx][jy])))
13274 player->last_jx = jx;
13275 player->last_jy = jy;
13277 // this breaks one level: "machine", level 000
13279 int move_direction = player->MovDir;
13280 int enter_side = MV_DIR_OPPOSITE(move_direction);
13281 int leave_side = move_direction;
13282 int old_jx = last_jx;
13283 int old_jy = last_jy;
13284 int old_element = Tile[old_jx][old_jy];
13285 int new_element = Tile[jx][jy];
13287 if (IS_CUSTOM_ELEMENT(old_element))
13288 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13290 player->index_bit, leave_side);
13292 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13293 CE_PLAYER_LEAVES_X,
13294 player->index_bit, leave_side);
13296 if (IS_CUSTOM_ELEMENT(new_element))
13297 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13298 player->index_bit, enter_side);
13300 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13301 CE_PLAYER_ENTERS_X,
13302 player->index_bit, enter_side);
13304 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13305 CE_MOVE_OF_X, move_direction);
13308 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13310 TestIfPlayerTouchesBadThing(jx, jy);
13311 TestIfPlayerTouchesCustomElement(jx, jy);
13313 /* needed because pushed element has not yet reached its destination,
13314 so it would trigger a change event at its previous field location */
13315 if (!player->is_pushing)
13316 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13318 if (level.finish_dig_collect &&
13319 (player->is_digging || player->is_collecting))
13321 int last_element = player->last_removed_element;
13322 int move_direction = player->MovDir;
13323 int enter_side = MV_DIR_OPPOSITE(move_direction);
13324 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13325 CE_PLAYER_COLLECTS_X);
13327 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13328 player->index_bit, enter_side);
13330 player->last_removed_element = EL_UNDEFINED;
13333 if (!player->active)
13334 RemovePlayer(player);
13337 if (level.use_step_counter)
13338 CheckLevelTime_StepCounter();
13340 if (tape.single_step && tape.recording && !tape.pausing &&
13341 !player->programmed_action)
13342 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13344 if (!player->programmed_action)
13345 CheckSaveEngineSnapshot(player);
13349 void ScrollScreen(struct PlayerInfo *player, int mode)
13351 static unsigned int screen_frame_counter = 0;
13353 if (mode == SCROLL_INIT)
13355 // set scrolling step size according to actual player's moving speed
13356 ScrollStepSize = TILEX / player->move_delay_value;
13358 screen_frame_counter = FrameCounter;
13359 ScreenMovDir = player->MovDir;
13360 ScreenMovPos = player->MovPos;
13361 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13364 else if (!FrameReached(&screen_frame_counter, 1))
13369 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13370 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13371 redraw_mask |= REDRAW_FIELD;
13374 ScreenMovDir = MV_NONE;
13377 void CheckNextToConditions(int x, int y)
13379 int element = Tile[x][y];
13381 if (IS_PLAYER(x, y))
13382 TestIfPlayerNextToCustomElement(x, y);
13384 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13385 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13386 TestIfElementNextToCustomElement(x, y);
13389 void TestIfPlayerNextToCustomElement(int x, int y)
13391 static int xy[4][2] =
13398 static int trigger_sides[4][2] =
13400 // center side border side
13401 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13402 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13403 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13404 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13408 if (!IS_PLAYER(x, y))
13411 struct PlayerInfo *player = PLAYERINFO(x, y);
13413 if (player->is_moving)
13416 for (i = 0; i < NUM_DIRECTIONS; i++)
13418 int xx = x + xy[i][0];
13419 int yy = y + xy[i][1];
13420 int border_side = trigger_sides[i][1];
13421 int border_element;
13423 if (!IN_LEV_FIELD(xx, yy))
13426 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13427 continue; // center and border element not connected
13429 border_element = Tile[xx][yy];
13431 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13432 player->index_bit, border_side);
13433 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13434 CE_PLAYER_NEXT_TO_X,
13435 player->index_bit, border_side);
13437 /* use player element that is initially defined in the level playfield,
13438 not the player element that corresponds to the runtime player number
13439 (example: a level that contains EL_PLAYER_3 as the only player would
13440 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13442 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13443 CE_NEXT_TO_X, border_side);
13447 void TestIfPlayerTouchesCustomElement(int x, int y)
13449 static int xy[4][2] =
13456 static int trigger_sides[4][2] =
13458 // center side border side
13459 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13460 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13461 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13462 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13464 static int touch_dir[4] =
13466 MV_LEFT | MV_RIGHT,
13471 int center_element = Tile[x][y]; // should always be non-moving!
13474 for (i = 0; i < NUM_DIRECTIONS; i++)
13476 int xx = x + xy[i][0];
13477 int yy = y + xy[i][1];
13478 int center_side = trigger_sides[i][0];
13479 int border_side = trigger_sides[i][1];
13480 int border_element;
13482 if (!IN_LEV_FIELD(xx, yy))
13485 if (IS_PLAYER(x, y)) // player found at center element
13487 struct PlayerInfo *player = PLAYERINFO(x, y);
13489 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13490 border_element = Tile[xx][yy]; // may be moving!
13491 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13492 border_element = Tile[xx][yy];
13493 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13494 border_element = MovingOrBlocked2Element(xx, yy);
13496 continue; // center and border element do not touch
13498 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13499 player->index_bit, border_side);
13500 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13501 CE_PLAYER_TOUCHES_X,
13502 player->index_bit, border_side);
13505 /* use player element that is initially defined in the level playfield,
13506 not the player element that corresponds to the runtime player number
13507 (example: a level that contains EL_PLAYER_3 as the only player would
13508 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13509 int player_element = PLAYERINFO(x, y)->initial_element;
13511 CheckElementChangeBySide(xx, yy, border_element, player_element,
13512 CE_TOUCHING_X, border_side);
13515 else if (IS_PLAYER(xx, yy)) // player found at border element
13517 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13519 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13521 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13522 continue; // center and border element do not touch
13525 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13526 player->index_bit, center_side);
13527 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13528 CE_PLAYER_TOUCHES_X,
13529 player->index_bit, center_side);
13532 /* use player element that is initially defined in the level playfield,
13533 not the player element that corresponds to the runtime player number
13534 (example: a level that contains EL_PLAYER_3 as the only player would
13535 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13536 int player_element = PLAYERINFO(xx, yy)->initial_element;
13538 CheckElementChangeBySide(x, y, center_element, player_element,
13539 CE_TOUCHING_X, center_side);
13547 void TestIfElementNextToCustomElement(int x, int y)
13549 static int xy[4][2] =
13556 static int trigger_sides[4][2] =
13558 // center side border side
13559 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13560 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13561 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13562 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13564 int center_element = Tile[x][y]; // should always be non-moving!
13567 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13570 for (i = 0; i < NUM_DIRECTIONS; i++)
13572 int xx = x + xy[i][0];
13573 int yy = y + xy[i][1];
13574 int border_side = trigger_sides[i][1];
13575 int border_element;
13577 if (!IN_LEV_FIELD(xx, yy))
13580 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13581 continue; // center and border element not connected
13583 border_element = Tile[xx][yy];
13585 // check for change of center element (but change it only once)
13586 if (CheckElementChangeBySide(x, y, center_element, border_element,
13587 CE_NEXT_TO_X, border_side))
13592 void TestIfElementTouchesCustomElement(int x, int y)
13594 static int xy[4][2] =
13601 static int trigger_sides[4][2] =
13603 // center side border side
13604 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13605 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13606 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13607 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13609 static int touch_dir[4] =
13611 MV_LEFT | MV_RIGHT,
13616 boolean change_center_element = FALSE;
13617 int center_element = Tile[x][y]; // should always be non-moving!
13618 int border_element_old[NUM_DIRECTIONS];
13621 for (i = 0; i < NUM_DIRECTIONS; i++)
13623 int xx = x + xy[i][0];
13624 int yy = y + xy[i][1];
13625 int border_element;
13627 border_element_old[i] = -1;
13629 if (!IN_LEV_FIELD(xx, yy))
13632 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13633 border_element = Tile[xx][yy]; // may be moving!
13634 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13635 border_element = Tile[xx][yy];
13636 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13637 border_element = MovingOrBlocked2Element(xx, yy);
13639 continue; // center and border element do not touch
13641 border_element_old[i] = border_element;
13644 for (i = 0; i < NUM_DIRECTIONS; i++)
13646 int xx = x + xy[i][0];
13647 int yy = y + xy[i][1];
13648 int center_side = trigger_sides[i][0];
13649 int border_element = border_element_old[i];
13651 if (border_element == -1)
13654 // check for change of border element
13655 CheckElementChangeBySide(xx, yy, border_element, center_element,
13656 CE_TOUCHING_X, center_side);
13658 // (center element cannot be player, so we dont have to check this here)
13661 for (i = 0; i < NUM_DIRECTIONS; i++)
13663 int xx = x + xy[i][0];
13664 int yy = y + xy[i][1];
13665 int border_side = trigger_sides[i][1];
13666 int border_element = border_element_old[i];
13668 if (border_element == -1)
13671 // check for change of center element (but change it only once)
13672 if (!change_center_element)
13673 change_center_element =
13674 CheckElementChangeBySide(x, y, center_element, border_element,
13675 CE_TOUCHING_X, border_side);
13677 if (IS_PLAYER(xx, yy))
13679 /* use player element that is initially defined in the level playfield,
13680 not the player element that corresponds to the runtime player number
13681 (example: a level that contains EL_PLAYER_3 as the only player would
13682 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13683 int player_element = PLAYERINFO(xx, yy)->initial_element;
13685 CheckElementChangeBySide(x, y, center_element, player_element,
13686 CE_TOUCHING_X, border_side);
13691 void TestIfElementHitsCustomElement(int x, int y, int direction)
13693 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13694 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13695 int hitx = x + dx, hity = y + dy;
13696 int hitting_element = Tile[x][y];
13697 int touched_element;
13699 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13702 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13703 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13705 if (IN_LEV_FIELD(hitx, hity))
13707 int opposite_direction = MV_DIR_OPPOSITE(direction);
13708 int hitting_side = direction;
13709 int touched_side = opposite_direction;
13710 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13711 MovDir[hitx][hity] != direction ||
13712 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13718 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13719 CE_HITTING_X, touched_side);
13721 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13722 CE_HIT_BY_X, hitting_side);
13724 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13725 CE_HIT_BY_SOMETHING, opposite_direction);
13727 if (IS_PLAYER(hitx, hity))
13729 /* use player element that is initially defined in the level playfield,
13730 not the player element that corresponds to the runtime player number
13731 (example: a level that contains EL_PLAYER_3 as the only player would
13732 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13733 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13735 CheckElementChangeBySide(x, y, hitting_element, player_element,
13736 CE_HITTING_X, touched_side);
13741 // "hitting something" is also true when hitting the playfield border
13742 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13743 CE_HITTING_SOMETHING, direction);
13746 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13748 int i, kill_x = -1, kill_y = -1;
13750 int bad_element = -1;
13751 static int test_xy[4][2] =
13758 static int test_dir[4] =
13766 for (i = 0; i < NUM_DIRECTIONS; i++)
13768 int test_x, test_y, test_move_dir, test_element;
13770 test_x = good_x + test_xy[i][0];
13771 test_y = good_y + test_xy[i][1];
13773 if (!IN_LEV_FIELD(test_x, test_y))
13777 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13779 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13781 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13782 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13784 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13785 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13789 bad_element = test_element;
13795 if (kill_x != -1 || kill_y != -1)
13797 if (IS_PLAYER(good_x, good_y))
13799 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13801 if (player->shield_deadly_time_left > 0 &&
13802 !IS_INDESTRUCTIBLE(bad_element))
13803 Bang(kill_x, kill_y);
13804 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13805 KillPlayer(player);
13808 Bang(good_x, good_y);
13812 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13814 int i, kill_x = -1, kill_y = -1;
13815 int bad_element = Tile[bad_x][bad_y];
13816 static int test_xy[4][2] =
13823 static int touch_dir[4] =
13825 MV_LEFT | MV_RIGHT,
13830 static int test_dir[4] =
13838 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13841 for (i = 0; i < NUM_DIRECTIONS; i++)
13843 int test_x, test_y, test_move_dir, test_element;
13845 test_x = bad_x + test_xy[i][0];
13846 test_y = bad_y + test_xy[i][1];
13848 if (!IN_LEV_FIELD(test_x, test_y))
13852 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13854 test_element = Tile[test_x][test_y];
13856 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13857 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13859 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13860 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13862 // good thing is player or penguin that does not move away
13863 if (IS_PLAYER(test_x, test_y))
13865 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13867 if (bad_element == EL_ROBOT && player->is_moving)
13868 continue; // robot does not kill player if he is moving
13870 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13872 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13873 continue; // center and border element do not touch
13881 else if (test_element == EL_PENGUIN)
13891 if (kill_x != -1 || kill_y != -1)
13893 if (IS_PLAYER(kill_x, kill_y))
13895 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13897 if (player->shield_deadly_time_left > 0 &&
13898 !IS_INDESTRUCTIBLE(bad_element))
13899 Bang(bad_x, bad_y);
13900 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13901 KillPlayer(player);
13904 Bang(kill_x, kill_y);
13908 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13910 int bad_element = Tile[bad_x][bad_y];
13911 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13912 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13913 int test_x = bad_x + dx, test_y = bad_y + dy;
13914 int test_move_dir, test_element;
13915 int kill_x = -1, kill_y = -1;
13917 if (!IN_LEV_FIELD(test_x, test_y))
13921 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13923 test_element = Tile[test_x][test_y];
13925 if (test_move_dir != bad_move_dir)
13927 // good thing can be player or penguin that does not move away
13928 if (IS_PLAYER(test_x, test_y))
13930 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13932 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13933 player as being hit when he is moving towards the bad thing, because
13934 the "get hit by" condition would be lost after the player stops) */
13935 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13936 return; // player moves away from bad thing
13941 else if (test_element == EL_PENGUIN)
13948 if (kill_x != -1 || kill_y != -1)
13950 if (IS_PLAYER(kill_x, kill_y))
13952 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13954 if (player->shield_deadly_time_left > 0 &&
13955 !IS_INDESTRUCTIBLE(bad_element))
13956 Bang(bad_x, bad_y);
13957 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13958 KillPlayer(player);
13961 Bang(kill_x, kill_y);
13965 void TestIfPlayerTouchesBadThing(int x, int y)
13967 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13970 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13972 TestIfGoodThingHitsBadThing(x, y, move_dir);
13975 void TestIfBadThingTouchesPlayer(int x, int y)
13977 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13980 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13982 TestIfBadThingHitsGoodThing(x, y, move_dir);
13985 void TestIfFriendTouchesBadThing(int x, int y)
13987 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13990 void TestIfBadThingTouchesFriend(int x, int y)
13992 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13995 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13997 int i, kill_x = bad_x, kill_y = bad_y;
13998 static int xy[4][2] =
14006 for (i = 0; i < NUM_DIRECTIONS; i++)
14010 x = bad_x + xy[i][0];
14011 y = bad_y + xy[i][1];
14012 if (!IN_LEV_FIELD(x, y))
14015 element = Tile[x][y];
14016 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14017 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14025 if (kill_x != bad_x || kill_y != bad_y)
14026 Bang(bad_x, bad_y);
14029 void KillPlayer(struct PlayerInfo *player)
14031 int jx = player->jx, jy = player->jy;
14033 if (!player->active)
14037 Debug("game:playing:KillPlayer",
14038 "0: killed == %d, active == %d, reanimated == %d",
14039 player->killed, player->active, player->reanimated);
14042 /* the following code was introduced to prevent an infinite loop when calling
14044 -> CheckTriggeredElementChangeExt()
14045 -> ExecuteCustomElementAction()
14047 -> (infinitely repeating the above sequence of function calls)
14048 which occurs when killing the player while having a CE with the setting
14049 "kill player X when explosion of <player X>"; the solution using a new
14050 field "player->killed" was chosen for backwards compatibility, although
14051 clever use of the fields "player->active" etc. would probably also work */
14053 if (player->killed)
14057 player->killed = TRUE;
14059 // remove accessible field at the player's position
14060 Tile[jx][jy] = EL_EMPTY;
14062 // deactivate shield (else Bang()/Explode() would not work right)
14063 player->shield_normal_time_left = 0;
14064 player->shield_deadly_time_left = 0;
14067 Debug("game:playing:KillPlayer",
14068 "1: killed == %d, active == %d, reanimated == %d",
14069 player->killed, player->active, player->reanimated);
14075 Debug("game:playing:KillPlayer",
14076 "2: killed == %d, active == %d, reanimated == %d",
14077 player->killed, player->active, player->reanimated);
14080 if (player->reanimated) // killed player may have been reanimated
14081 player->killed = player->reanimated = FALSE;
14083 BuryPlayer(player);
14086 static void KillPlayerUnlessEnemyProtected(int x, int y)
14088 if (!PLAYER_ENEMY_PROTECTED(x, y))
14089 KillPlayer(PLAYERINFO(x, y));
14092 static void KillPlayerUnlessExplosionProtected(int x, int y)
14094 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14095 KillPlayer(PLAYERINFO(x, y));
14098 void BuryPlayer(struct PlayerInfo *player)
14100 int jx = player->jx, jy = player->jy;
14102 if (!player->active)
14105 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14106 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14108 RemovePlayer(player);
14110 player->buried = TRUE;
14112 if (game.all_players_gone)
14113 game.GameOver = TRUE;
14116 void RemovePlayer(struct PlayerInfo *player)
14118 int jx = player->jx, jy = player->jy;
14119 int i, found = FALSE;
14121 player->present = FALSE;
14122 player->active = FALSE;
14124 // required for some CE actions (even if the player is not active anymore)
14125 player->MovPos = 0;
14127 if (!ExplodeField[jx][jy])
14128 StorePlayer[jx][jy] = 0;
14130 if (player->is_moving)
14131 TEST_DrawLevelField(player->last_jx, player->last_jy);
14133 for (i = 0; i < MAX_PLAYERS; i++)
14134 if (stored_player[i].active)
14139 game.all_players_gone = TRUE;
14140 game.GameOver = TRUE;
14143 game.exit_x = game.robot_wheel_x = jx;
14144 game.exit_y = game.robot_wheel_y = jy;
14147 void ExitPlayer(struct PlayerInfo *player)
14149 DrawPlayer(player); // needed here only to cleanup last field
14150 RemovePlayer(player);
14152 if (game.players_still_needed > 0)
14153 game.players_still_needed--;
14156 static void SetFieldForSnapping(int x, int y, int element, int direction,
14157 int player_index_bit)
14159 struct ElementInfo *ei = &element_info[element];
14160 int direction_bit = MV_DIR_TO_BIT(direction);
14161 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14162 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14163 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14165 Tile[x][y] = EL_ELEMENT_SNAPPING;
14166 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14167 MovDir[x][y] = direction;
14168 Store[x][y] = element;
14169 Store2[x][y] = player_index_bit;
14171 ResetGfxAnimation(x, y);
14173 GfxElement[x][y] = element;
14174 GfxAction[x][y] = action;
14175 GfxDir[x][y] = direction;
14176 GfxFrame[x][y] = -1;
14179 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14180 int player_index_bit)
14182 TestIfElementTouchesCustomElement(x, y); // for empty space
14184 if (level.finish_dig_collect)
14186 int dig_side = MV_DIR_OPPOSITE(direction);
14187 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14188 CE_PLAYER_COLLECTS_X);
14190 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14191 player_index_bit, dig_side);
14192 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14193 player_index_bit, dig_side);
14198 =============================================================================
14199 checkDiagonalPushing()
14200 -----------------------------------------------------------------------------
14201 check if diagonal input device direction results in pushing of object
14202 (by checking if the alternative direction is walkable, diggable, ...)
14203 =============================================================================
14206 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14207 int x, int y, int real_dx, int real_dy)
14209 int jx, jy, dx, dy, xx, yy;
14211 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14214 // diagonal direction: check alternative direction
14219 xx = jx + (dx == 0 ? real_dx : 0);
14220 yy = jy + (dy == 0 ? real_dy : 0);
14222 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14226 =============================================================================
14228 -----------------------------------------------------------------------------
14229 x, y: field next to player (non-diagonal) to try to dig to
14230 real_dx, real_dy: direction as read from input device (can be diagonal)
14231 =============================================================================
14234 static int DigField(struct PlayerInfo *player,
14235 int oldx, int oldy, int x, int y,
14236 int real_dx, int real_dy, int mode)
14238 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14239 boolean player_was_pushing = player->is_pushing;
14240 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14241 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14242 int jx = oldx, jy = oldy;
14243 int dx = x - jx, dy = y - jy;
14244 int nextx = x + dx, nexty = y + dy;
14245 int move_direction = (dx == -1 ? MV_LEFT :
14246 dx == +1 ? MV_RIGHT :
14248 dy == +1 ? MV_DOWN : MV_NONE);
14249 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14250 int dig_side = MV_DIR_OPPOSITE(move_direction);
14251 int old_element = Tile[jx][jy];
14252 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14255 if (is_player) // function can also be called by EL_PENGUIN
14257 if (player->MovPos == 0)
14259 player->is_digging = FALSE;
14260 player->is_collecting = FALSE;
14263 if (player->MovPos == 0) // last pushing move finished
14264 player->is_pushing = FALSE;
14266 if (mode == DF_NO_PUSH) // player just stopped pushing
14268 player->is_switching = FALSE;
14269 player->push_delay = -1;
14271 return MP_NO_ACTION;
14274 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14275 old_element = Back[jx][jy];
14277 // in case of element dropped at player position, check background
14278 else if (Back[jx][jy] != EL_EMPTY &&
14279 game.engine_version >= VERSION_IDENT(2,2,0,0))
14280 old_element = Back[jx][jy];
14282 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14283 return MP_NO_ACTION; // field has no opening in this direction
14285 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14286 return MP_NO_ACTION; // field has no opening in this direction
14288 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14292 Tile[jx][jy] = player->artwork_element;
14293 InitMovingField(jx, jy, MV_DOWN);
14294 Store[jx][jy] = EL_ACID;
14295 ContinueMoving(jx, jy);
14296 BuryPlayer(player);
14298 return MP_DONT_RUN_INTO;
14301 if (player_can_move && DONT_RUN_INTO(element))
14303 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14305 return MP_DONT_RUN_INTO;
14308 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14309 return MP_NO_ACTION;
14311 collect_count = element_info[element].collect_count_initial;
14313 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14314 return MP_NO_ACTION;
14316 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14317 player_can_move = player_can_move_or_snap;
14319 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14320 game.engine_version >= VERSION_IDENT(2,2,0,0))
14322 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14323 player->index_bit, dig_side);
14324 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14325 player->index_bit, dig_side);
14327 if (element == EL_DC_LANDMINE)
14330 if (Tile[x][y] != element) // field changed by snapping
14333 return MP_NO_ACTION;
14336 if (player->gravity && is_player && !player->is_auto_moving &&
14337 canFallDown(player) && move_direction != MV_DOWN &&
14338 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14339 return MP_NO_ACTION; // player cannot walk here due to gravity
14341 if (player_can_move &&
14342 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14344 int sound_element = SND_ELEMENT(element);
14345 int sound_action = ACTION_WALKING;
14347 if (IS_RND_GATE(element))
14349 if (!player->key[RND_GATE_NR(element)])
14350 return MP_NO_ACTION;
14352 else if (IS_RND_GATE_GRAY(element))
14354 if (!player->key[RND_GATE_GRAY_NR(element)])
14355 return MP_NO_ACTION;
14357 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14359 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14360 return MP_NO_ACTION;
14362 else if (element == EL_EXIT_OPEN ||
14363 element == EL_EM_EXIT_OPEN ||
14364 element == EL_EM_EXIT_OPENING ||
14365 element == EL_STEEL_EXIT_OPEN ||
14366 element == EL_EM_STEEL_EXIT_OPEN ||
14367 element == EL_EM_STEEL_EXIT_OPENING ||
14368 element == EL_SP_EXIT_OPEN ||
14369 element == EL_SP_EXIT_OPENING)
14371 sound_action = ACTION_PASSING; // player is passing exit
14373 else if (element == EL_EMPTY)
14375 sound_action = ACTION_MOVING; // nothing to walk on
14378 // play sound from background or player, whatever is available
14379 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14380 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14382 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14384 else if (player_can_move &&
14385 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14387 if (!ACCESS_FROM(element, opposite_direction))
14388 return MP_NO_ACTION; // field not accessible from this direction
14390 if (CAN_MOVE(element)) // only fixed elements can be passed!
14391 return MP_NO_ACTION;
14393 if (IS_EM_GATE(element))
14395 if (!player->key[EM_GATE_NR(element)])
14396 return MP_NO_ACTION;
14398 else if (IS_EM_GATE_GRAY(element))
14400 if (!player->key[EM_GATE_GRAY_NR(element)])
14401 return MP_NO_ACTION;
14403 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14405 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14406 return MP_NO_ACTION;
14408 else if (IS_EMC_GATE(element))
14410 if (!player->key[EMC_GATE_NR(element)])
14411 return MP_NO_ACTION;
14413 else if (IS_EMC_GATE_GRAY(element))
14415 if (!player->key[EMC_GATE_GRAY_NR(element)])
14416 return MP_NO_ACTION;
14418 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14420 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14421 return MP_NO_ACTION;
14423 else if (element == EL_DC_GATE_WHITE ||
14424 element == EL_DC_GATE_WHITE_GRAY ||
14425 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14427 if (player->num_white_keys == 0)
14428 return MP_NO_ACTION;
14430 player->num_white_keys--;
14432 else if (IS_SP_PORT(element))
14434 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14435 element == EL_SP_GRAVITY_PORT_RIGHT ||
14436 element == EL_SP_GRAVITY_PORT_UP ||
14437 element == EL_SP_GRAVITY_PORT_DOWN)
14438 player->gravity = !player->gravity;
14439 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14440 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14441 element == EL_SP_GRAVITY_ON_PORT_UP ||
14442 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14443 player->gravity = TRUE;
14444 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14445 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14446 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14447 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14448 player->gravity = FALSE;
14451 // automatically move to the next field with double speed
14452 player->programmed_action = move_direction;
14454 if (player->move_delay_reset_counter == 0)
14456 player->move_delay_reset_counter = 2; // two double speed steps
14458 DOUBLE_PLAYER_SPEED(player);
14461 PlayLevelSoundAction(x, y, ACTION_PASSING);
14463 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14467 if (mode != DF_SNAP)
14469 GfxElement[x][y] = GFX_ELEMENT(element);
14470 player->is_digging = TRUE;
14473 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14475 // use old behaviour for old levels (digging)
14476 if (!level.finish_dig_collect)
14478 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14479 player->index_bit, dig_side);
14481 // if digging triggered player relocation, finish digging tile
14482 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14483 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14486 if (mode == DF_SNAP)
14488 if (level.block_snap_field)
14489 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14491 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14493 // use old behaviour for old levels (snapping)
14494 if (!level.finish_dig_collect)
14495 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14496 player->index_bit, dig_side);
14499 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14503 if (is_player && mode != DF_SNAP)
14505 GfxElement[x][y] = element;
14506 player->is_collecting = TRUE;
14509 if (element == EL_SPEED_PILL)
14511 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14513 else if (element == EL_EXTRA_TIME && level.time > 0)
14515 TimeLeft += level.extra_time;
14517 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14519 DisplayGameControlValues();
14521 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14523 player->shield_normal_time_left += level.shield_normal_time;
14524 if (element == EL_SHIELD_DEADLY)
14525 player->shield_deadly_time_left += level.shield_deadly_time;
14527 else if (element == EL_DYNAMITE ||
14528 element == EL_EM_DYNAMITE ||
14529 element == EL_SP_DISK_RED)
14531 if (player->inventory_size < MAX_INVENTORY_SIZE)
14532 player->inventory_element[player->inventory_size++] = element;
14534 DrawGameDoorValues();
14536 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14538 player->dynabomb_count++;
14539 player->dynabombs_left++;
14541 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14543 player->dynabomb_size++;
14545 else if (element == EL_DYNABOMB_INCREASE_POWER)
14547 player->dynabomb_xl = TRUE;
14549 else if (IS_KEY(element))
14551 player->key[KEY_NR(element)] = TRUE;
14553 DrawGameDoorValues();
14555 else if (element == EL_DC_KEY_WHITE)
14557 player->num_white_keys++;
14559 // display white keys?
14560 // DrawGameDoorValues();
14562 else if (IS_ENVELOPE(element))
14564 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14566 if (!wait_for_snapping)
14567 player->show_envelope = element;
14569 else if (element == EL_EMC_LENSES)
14571 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14573 RedrawAllInvisibleElementsForLenses();
14575 else if (element == EL_EMC_MAGNIFIER)
14577 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14579 RedrawAllInvisibleElementsForMagnifier();
14581 else if (IS_DROPPABLE(element) ||
14582 IS_THROWABLE(element)) // can be collected and dropped
14586 if (collect_count == 0)
14587 player->inventory_infinite_element = element;
14589 for (i = 0; i < collect_count; i++)
14590 if (player->inventory_size < MAX_INVENTORY_SIZE)
14591 player->inventory_element[player->inventory_size++] = element;
14593 DrawGameDoorValues();
14595 else if (collect_count > 0)
14597 game.gems_still_needed -= collect_count;
14598 if (game.gems_still_needed < 0)
14599 game.gems_still_needed = 0;
14601 game.snapshot.collected_item = TRUE;
14603 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14605 DisplayGameControlValues();
14608 RaiseScoreElement(element);
14609 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14611 // use old behaviour for old levels (collecting)
14612 if (!level.finish_dig_collect && is_player)
14614 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14615 player->index_bit, dig_side);
14617 // if collecting triggered player relocation, finish collecting tile
14618 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14619 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14622 if (mode == DF_SNAP)
14624 if (level.block_snap_field)
14625 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14627 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14629 // use old behaviour for old levels (snapping)
14630 if (!level.finish_dig_collect)
14631 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14632 player->index_bit, dig_side);
14635 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14637 if (mode == DF_SNAP && element != EL_BD_ROCK)
14638 return MP_NO_ACTION;
14640 if (CAN_FALL(element) && dy)
14641 return MP_NO_ACTION;
14643 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14644 !(element == EL_SPRING && level.use_spring_bug))
14645 return MP_NO_ACTION;
14647 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14648 ((move_direction & MV_VERTICAL &&
14649 ((element_info[element].move_pattern & MV_LEFT &&
14650 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14651 (element_info[element].move_pattern & MV_RIGHT &&
14652 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14653 (move_direction & MV_HORIZONTAL &&
14654 ((element_info[element].move_pattern & MV_UP &&
14655 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14656 (element_info[element].move_pattern & MV_DOWN &&
14657 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14658 return MP_NO_ACTION;
14660 // do not push elements already moving away faster than player
14661 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14662 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14663 return MP_NO_ACTION;
14665 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14667 if (player->push_delay_value == -1 || !player_was_pushing)
14668 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14670 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14672 if (player->push_delay_value == -1)
14673 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14675 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14677 if (!player->is_pushing)
14678 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14681 player->is_pushing = TRUE;
14682 player->is_active = TRUE;
14684 if (!(IN_LEV_FIELD(nextx, nexty) &&
14685 (IS_FREE(nextx, nexty) ||
14686 (IS_SB_ELEMENT(element) &&
14687 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14688 (IS_CUSTOM_ELEMENT(element) &&
14689 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14690 return MP_NO_ACTION;
14692 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14693 return MP_NO_ACTION;
14695 if (player->push_delay == -1) // new pushing; restart delay
14696 player->push_delay = 0;
14698 if (player->push_delay < player->push_delay_value &&
14699 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14700 element != EL_SPRING && element != EL_BALLOON)
14702 // make sure that there is no move delay before next try to push
14703 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14704 player->move_delay = 0;
14706 return MP_NO_ACTION;
14709 if (IS_CUSTOM_ELEMENT(element) &&
14710 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14712 if (!DigFieldByCE(nextx, nexty, element))
14713 return MP_NO_ACTION;
14716 if (IS_SB_ELEMENT(element))
14718 boolean sokoban_task_solved = FALSE;
14720 if (element == EL_SOKOBAN_FIELD_FULL)
14722 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14724 IncrementSokobanFieldsNeeded();
14725 IncrementSokobanObjectsNeeded();
14728 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14730 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14732 DecrementSokobanFieldsNeeded();
14733 DecrementSokobanObjectsNeeded();
14735 // sokoban object was pushed from empty field to sokoban field
14736 if (Back[x][y] == EL_EMPTY)
14737 sokoban_task_solved = TRUE;
14740 Tile[x][y] = EL_SOKOBAN_OBJECT;
14742 if (Back[x][y] == Back[nextx][nexty])
14743 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14744 else if (Back[x][y] != 0)
14745 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14748 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14751 if (sokoban_task_solved &&
14752 game.sokoban_fields_still_needed == 0 &&
14753 game.sokoban_objects_still_needed == 0 &&
14754 level.auto_exit_sokoban)
14756 game.players_still_needed = 0;
14760 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14764 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14766 InitMovingField(x, y, move_direction);
14767 GfxAction[x][y] = ACTION_PUSHING;
14769 if (mode == DF_SNAP)
14770 ContinueMoving(x, y);
14772 MovPos[x][y] = (dx != 0 ? dx : dy);
14774 Pushed[x][y] = TRUE;
14775 Pushed[nextx][nexty] = TRUE;
14777 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14778 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14780 player->push_delay_value = -1; // get new value later
14782 // check for element change _after_ element has been pushed
14783 if (game.use_change_when_pushing_bug)
14785 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14786 player->index_bit, dig_side);
14787 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14788 player->index_bit, dig_side);
14791 else if (IS_SWITCHABLE(element))
14793 if (PLAYER_SWITCHING(player, x, y))
14795 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14796 player->index_bit, dig_side);
14801 player->is_switching = TRUE;
14802 player->switch_x = x;
14803 player->switch_y = y;
14805 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14807 if (element == EL_ROBOT_WHEEL)
14809 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14811 game.robot_wheel_x = x;
14812 game.robot_wheel_y = y;
14813 game.robot_wheel_active = TRUE;
14815 TEST_DrawLevelField(x, y);
14817 else if (element == EL_SP_TERMINAL)
14821 SCAN_PLAYFIELD(xx, yy)
14823 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14827 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14829 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14831 ResetGfxAnimation(xx, yy);
14832 TEST_DrawLevelField(xx, yy);
14836 else if (IS_BELT_SWITCH(element))
14838 ToggleBeltSwitch(x, y);
14840 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14841 element == EL_SWITCHGATE_SWITCH_DOWN ||
14842 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14843 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14845 ToggleSwitchgateSwitch(x, y);
14847 else if (element == EL_LIGHT_SWITCH ||
14848 element == EL_LIGHT_SWITCH_ACTIVE)
14850 ToggleLightSwitch(x, y);
14852 else if (element == EL_TIMEGATE_SWITCH ||
14853 element == EL_DC_TIMEGATE_SWITCH)
14855 ActivateTimegateSwitch(x, y);
14857 else if (element == EL_BALLOON_SWITCH_LEFT ||
14858 element == EL_BALLOON_SWITCH_RIGHT ||
14859 element == EL_BALLOON_SWITCH_UP ||
14860 element == EL_BALLOON_SWITCH_DOWN ||
14861 element == EL_BALLOON_SWITCH_NONE ||
14862 element == EL_BALLOON_SWITCH_ANY)
14864 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14865 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14866 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14867 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14868 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14871 else if (element == EL_LAMP)
14873 Tile[x][y] = EL_LAMP_ACTIVE;
14874 game.lights_still_needed--;
14876 ResetGfxAnimation(x, y);
14877 TEST_DrawLevelField(x, y);
14879 else if (element == EL_TIME_ORB_FULL)
14881 Tile[x][y] = EL_TIME_ORB_EMPTY;
14883 if (level.time > 0 || level.use_time_orb_bug)
14885 TimeLeft += level.time_orb_time;
14886 game.no_level_time_limit = FALSE;
14888 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14890 DisplayGameControlValues();
14893 ResetGfxAnimation(x, y);
14894 TEST_DrawLevelField(x, y);
14896 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14897 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14901 game.ball_active = !game.ball_active;
14903 SCAN_PLAYFIELD(xx, yy)
14905 int e = Tile[xx][yy];
14907 if (game.ball_active)
14909 if (e == EL_EMC_MAGIC_BALL)
14910 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14911 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14912 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14916 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14917 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14918 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14919 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14924 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14925 player->index_bit, dig_side);
14927 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14928 player->index_bit, dig_side);
14930 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14931 player->index_bit, dig_side);
14937 if (!PLAYER_SWITCHING(player, x, y))
14939 player->is_switching = TRUE;
14940 player->switch_x = x;
14941 player->switch_y = y;
14943 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14944 player->index_bit, dig_side);
14945 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14946 player->index_bit, dig_side);
14948 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14949 player->index_bit, dig_side);
14950 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14951 player->index_bit, dig_side);
14954 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14955 player->index_bit, dig_side);
14956 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14957 player->index_bit, dig_side);
14959 return MP_NO_ACTION;
14962 player->push_delay = -1;
14964 if (is_player) // function can also be called by EL_PENGUIN
14966 if (Tile[x][y] != element) // really digged/collected something
14968 player->is_collecting = !player->is_digging;
14969 player->is_active = TRUE;
14971 player->last_removed_element = element;
14978 static boolean DigFieldByCE(int x, int y, int digging_element)
14980 int element = Tile[x][y];
14982 if (!IS_FREE(x, y))
14984 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14985 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14988 // no element can dig solid indestructible elements
14989 if (IS_INDESTRUCTIBLE(element) &&
14990 !IS_DIGGABLE(element) &&
14991 !IS_COLLECTIBLE(element))
14994 if (AmoebaNr[x][y] &&
14995 (element == EL_AMOEBA_FULL ||
14996 element == EL_BD_AMOEBA ||
14997 element == EL_AMOEBA_GROWING))
14999 AmoebaCnt[AmoebaNr[x][y]]--;
15000 AmoebaCnt2[AmoebaNr[x][y]]--;
15003 if (IS_MOVING(x, y))
15004 RemoveMovingField(x, y);
15008 TEST_DrawLevelField(x, y);
15011 // if digged element was about to explode, prevent the explosion
15012 ExplodeField[x][y] = EX_TYPE_NONE;
15014 PlayLevelSoundAction(x, y, action);
15017 Store[x][y] = EL_EMPTY;
15019 // this makes it possible to leave the removed element again
15020 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15021 Store[x][y] = element;
15026 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15028 int jx = player->jx, jy = player->jy;
15029 int x = jx + dx, y = jy + dy;
15030 int snap_direction = (dx == -1 ? MV_LEFT :
15031 dx == +1 ? MV_RIGHT :
15033 dy == +1 ? MV_DOWN : MV_NONE);
15034 boolean can_continue_snapping = (level.continuous_snapping &&
15035 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15037 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15040 if (!player->active || !IN_LEV_FIELD(x, y))
15048 if (player->MovPos == 0)
15049 player->is_pushing = FALSE;
15051 player->is_snapping = FALSE;
15053 if (player->MovPos == 0)
15055 player->is_moving = FALSE;
15056 player->is_digging = FALSE;
15057 player->is_collecting = FALSE;
15063 // prevent snapping with already pressed snap key when not allowed
15064 if (player->is_snapping && !can_continue_snapping)
15067 player->MovDir = snap_direction;
15069 if (player->MovPos == 0)
15071 player->is_moving = FALSE;
15072 player->is_digging = FALSE;
15073 player->is_collecting = FALSE;
15076 player->is_dropping = FALSE;
15077 player->is_dropping_pressed = FALSE;
15078 player->drop_pressed_delay = 0;
15080 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15083 player->is_snapping = TRUE;
15084 player->is_active = TRUE;
15086 if (player->MovPos == 0)
15088 player->is_moving = FALSE;
15089 player->is_digging = FALSE;
15090 player->is_collecting = FALSE;
15093 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15094 TEST_DrawLevelField(player->last_jx, player->last_jy);
15096 TEST_DrawLevelField(x, y);
15101 static boolean DropElement(struct PlayerInfo *player)
15103 int old_element, new_element;
15104 int dropx = player->jx, dropy = player->jy;
15105 int drop_direction = player->MovDir;
15106 int drop_side = drop_direction;
15107 int drop_element = get_next_dropped_element(player);
15109 /* do not drop an element on top of another element; when holding drop key
15110 pressed without moving, dropped element must move away before the next
15111 element can be dropped (this is especially important if the next element
15112 is dynamite, which can be placed on background for historical reasons) */
15113 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15116 if (IS_THROWABLE(drop_element))
15118 dropx += GET_DX_FROM_DIR(drop_direction);
15119 dropy += GET_DY_FROM_DIR(drop_direction);
15121 if (!IN_LEV_FIELD(dropx, dropy))
15125 old_element = Tile[dropx][dropy]; // old element at dropping position
15126 new_element = drop_element; // default: no change when dropping
15128 // check if player is active, not moving and ready to drop
15129 if (!player->active || player->MovPos || player->drop_delay > 0)
15132 // check if player has anything that can be dropped
15133 if (new_element == EL_UNDEFINED)
15136 // only set if player has anything that can be dropped
15137 player->is_dropping_pressed = TRUE;
15139 // check if drop key was pressed long enough for EM style dynamite
15140 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15143 // check if anything can be dropped at the current position
15144 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15147 // collected custom elements can only be dropped on empty fields
15148 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15151 if (old_element != EL_EMPTY)
15152 Back[dropx][dropy] = old_element; // store old element on this field
15154 ResetGfxAnimation(dropx, dropy);
15155 ResetRandomAnimationValue(dropx, dropy);
15157 if (player->inventory_size > 0 ||
15158 player->inventory_infinite_element != EL_UNDEFINED)
15160 if (player->inventory_size > 0)
15162 player->inventory_size--;
15164 DrawGameDoorValues();
15166 if (new_element == EL_DYNAMITE)
15167 new_element = EL_DYNAMITE_ACTIVE;
15168 else if (new_element == EL_EM_DYNAMITE)
15169 new_element = EL_EM_DYNAMITE_ACTIVE;
15170 else if (new_element == EL_SP_DISK_RED)
15171 new_element = EL_SP_DISK_RED_ACTIVE;
15174 Tile[dropx][dropy] = new_element;
15176 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15177 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15178 el2img(Tile[dropx][dropy]), 0);
15180 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15182 // needed if previous element just changed to "empty" in the last frame
15183 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15185 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15186 player->index_bit, drop_side);
15187 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15189 player->index_bit, drop_side);
15191 TestIfElementTouchesCustomElement(dropx, dropy);
15193 else // player is dropping a dyna bomb
15195 player->dynabombs_left--;
15197 Tile[dropx][dropy] = new_element;
15199 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15200 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15201 el2img(Tile[dropx][dropy]), 0);
15203 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15206 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15207 InitField_WithBug1(dropx, dropy, FALSE);
15209 new_element = Tile[dropx][dropy]; // element might have changed
15211 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15212 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15214 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15215 MovDir[dropx][dropy] = drop_direction;
15217 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15219 // do not cause impact style collision by dropping elements that can fall
15220 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15223 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15224 player->is_dropping = TRUE;
15226 player->drop_pressed_delay = 0;
15227 player->is_dropping_pressed = FALSE;
15229 player->drop_x = dropx;
15230 player->drop_y = dropy;
15235 // ----------------------------------------------------------------------------
15236 // game sound playing functions
15237 // ----------------------------------------------------------------------------
15239 static int *loop_sound_frame = NULL;
15240 static int *loop_sound_volume = NULL;
15242 void InitPlayLevelSound(void)
15244 int num_sounds = getSoundListSize();
15246 checked_free(loop_sound_frame);
15247 checked_free(loop_sound_volume);
15249 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15250 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15253 static void PlayLevelSound(int x, int y, int nr)
15255 int sx = SCREENX(x), sy = SCREENY(y);
15256 int volume, stereo_position;
15257 int max_distance = 8;
15258 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15260 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15261 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15264 if (!IN_LEV_FIELD(x, y) ||
15265 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15266 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15269 volume = SOUND_MAX_VOLUME;
15271 if (!IN_SCR_FIELD(sx, sy))
15273 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15274 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15276 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15279 stereo_position = (SOUND_MAX_LEFT +
15280 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15281 (SCR_FIELDX + 2 * max_distance));
15283 if (IS_LOOP_SOUND(nr))
15285 /* This assures that quieter loop sounds do not overwrite louder ones,
15286 while restarting sound volume comparison with each new game frame. */
15288 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15291 loop_sound_volume[nr] = volume;
15292 loop_sound_frame[nr] = FrameCounter;
15295 PlaySoundExt(nr, volume, stereo_position, type);
15298 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15300 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15301 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15302 y < LEVELY(BY1) ? LEVELY(BY1) :
15303 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15307 static void PlayLevelSoundAction(int x, int y, int action)
15309 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15312 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15314 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15316 if (sound_effect != SND_UNDEFINED)
15317 PlayLevelSound(x, y, sound_effect);
15320 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15323 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15325 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15326 PlayLevelSound(x, y, sound_effect);
15329 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15331 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15333 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15334 PlayLevelSound(x, y, sound_effect);
15337 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15339 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15341 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15342 StopSound(sound_effect);
15345 static int getLevelMusicNr(void)
15347 if (levelset.music[level_nr] != MUS_UNDEFINED)
15348 return levelset.music[level_nr]; // from config file
15350 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15353 static void FadeLevelSounds(void)
15358 static void FadeLevelMusic(void)
15360 int music_nr = getLevelMusicNr();
15361 char *curr_music = getCurrentlyPlayingMusicFilename();
15362 char *next_music = getMusicInfoEntryFilename(music_nr);
15364 if (!strEqual(curr_music, next_music))
15368 void FadeLevelSoundsAndMusic(void)
15374 static void PlayLevelMusic(void)
15376 int music_nr = getLevelMusicNr();
15377 char *curr_music = getCurrentlyPlayingMusicFilename();
15378 char *next_music = getMusicInfoEntryFilename(music_nr);
15380 if (!strEqual(curr_music, next_music))
15381 PlayMusicLoop(music_nr);
15384 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15386 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15388 int x = xx - offset;
15389 int y = yy - offset;
15394 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15398 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15402 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15406 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15410 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15414 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15418 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15421 case SOUND_android_clone:
15422 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15425 case SOUND_android_move:
15426 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15430 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15434 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15438 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15441 case SOUND_eater_eat:
15442 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15446 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15449 case SOUND_collect:
15450 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15453 case SOUND_diamond:
15454 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15458 // !!! CHECK THIS !!!
15460 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15462 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15466 case SOUND_wonderfall:
15467 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15471 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15475 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15479 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15483 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15487 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15491 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15495 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15499 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15502 case SOUND_exit_open:
15503 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15506 case SOUND_exit_leave:
15507 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15510 case SOUND_dynamite:
15511 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15515 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15519 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15523 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15527 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15531 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15535 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15539 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15544 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15546 int element = map_element_SP_to_RND(element_sp);
15547 int action = map_action_SP_to_RND(action_sp);
15548 int offset = (setup.sp_show_border_elements ? 0 : 1);
15549 int x = xx - offset;
15550 int y = yy - offset;
15552 PlayLevelSoundElementAction(x, y, element, action);
15555 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15557 int element = map_element_MM_to_RND(element_mm);
15558 int action = map_action_MM_to_RND(action_mm);
15560 int x = xx - offset;
15561 int y = yy - offset;
15563 if (!IS_MM_ELEMENT(element))
15564 element = EL_MM_DEFAULT;
15566 PlayLevelSoundElementAction(x, y, element, action);
15569 void PlaySound_MM(int sound_mm)
15571 int sound = map_sound_MM_to_RND(sound_mm);
15573 if (sound == SND_UNDEFINED)
15579 void PlaySoundLoop_MM(int sound_mm)
15581 int sound = map_sound_MM_to_RND(sound_mm);
15583 if (sound == SND_UNDEFINED)
15586 PlaySoundLoop(sound);
15589 void StopSound_MM(int sound_mm)
15591 int sound = map_sound_MM_to_RND(sound_mm);
15593 if (sound == SND_UNDEFINED)
15599 void RaiseScore(int value)
15601 game.score += value;
15603 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15605 DisplayGameControlValues();
15608 void RaiseScoreElement(int element)
15613 case EL_BD_DIAMOND:
15614 case EL_EMERALD_YELLOW:
15615 case EL_EMERALD_RED:
15616 case EL_EMERALD_PURPLE:
15617 case EL_SP_INFOTRON:
15618 RaiseScore(level.score[SC_EMERALD]);
15621 RaiseScore(level.score[SC_DIAMOND]);
15624 RaiseScore(level.score[SC_CRYSTAL]);
15627 RaiseScore(level.score[SC_PEARL]);
15630 case EL_BD_BUTTERFLY:
15631 case EL_SP_ELECTRON:
15632 RaiseScore(level.score[SC_BUG]);
15635 case EL_BD_FIREFLY:
15636 case EL_SP_SNIKSNAK:
15637 RaiseScore(level.score[SC_SPACESHIP]);
15640 case EL_DARK_YAMYAM:
15641 RaiseScore(level.score[SC_YAMYAM]);
15644 RaiseScore(level.score[SC_ROBOT]);
15647 RaiseScore(level.score[SC_PACMAN]);
15650 RaiseScore(level.score[SC_NUT]);
15653 case EL_EM_DYNAMITE:
15654 case EL_SP_DISK_RED:
15655 case EL_DYNABOMB_INCREASE_NUMBER:
15656 case EL_DYNABOMB_INCREASE_SIZE:
15657 case EL_DYNABOMB_INCREASE_POWER:
15658 RaiseScore(level.score[SC_DYNAMITE]);
15660 case EL_SHIELD_NORMAL:
15661 case EL_SHIELD_DEADLY:
15662 RaiseScore(level.score[SC_SHIELD]);
15664 case EL_EXTRA_TIME:
15665 RaiseScore(level.extra_time_score);
15679 case EL_DC_KEY_WHITE:
15680 RaiseScore(level.score[SC_KEY]);
15683 RaiseScore(element_info[element].collect_score);
15688 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15690 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15694 // prevent short reactivation of overlay buttons while closing door
15695 SetOverlayActive(FALSE);
15696 UnmapGameButtons();
15698 // door may still be open due to skipped or envelope style request
15699 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15702 if (network.enabled)
15703 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15707 FadeSkipNextFadeIn();
15709 SetGameStatus(GAME_MODE_MAIN);
15714 else // continue playing the game
15716 if (tape.playing && tape.deactivate_display)
15717 TapeDeactivateDisplayOff(TRUE);
15719 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15721 if (tape.playing && tape.deactivate_display)
15722 TapeDeactivateDisplayOn();
15726 void RequestQuitGame(boolean escape_key_pressed)
15728 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15729 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15730 level_editor_test_game);
15731 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15732 quick_quit || score_info_tape_play);
15734 RequestQuitGameExt(skip_request, quick_quit,
15735 "Do you really want to quit the game?");
15738 void RequestRestartGame(char *message)
15740 game.restart_game_message = NULL;
15742 boolean has_started_game = hasStartedNetworkGame();
15743 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15745 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15747 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15751 // needed in case of envelope request to close game panel
15752 CloseDoor(DOOR_CLOSE_1);
15754 SetGameStatus(GAME_MODE_MAIN);
15760 void CheckGameOver(void)
15762 static boolean last_game_over = FALSE;
15763 static int game_over_delay = 0;
15764 int game_over_delay_value = 50;
15765 boolean game_over = checkGameFailed();
15767 // do not handle game over if request dialog is already active
15768 if (game.request_active)
15771 // do not ask to play again if game was never actually played
15772 if (!game.GamePlayed)
15777 last_game_over = FALSE;
15778 game_over_delay = game_over_delay_value;
15783 if (game_over_delay > 0)
15790 if (last_game_over != game_over)
15791 game.restart_game_message = (hasStartedNetworkGame() ?
15792 "Game over! Play it again?" :
15795 last_game_over = game_over;
15798 boolean checkGameSolved(void)
15800 // set for all game engines if level was solved
15801 return game.LevelSolved_GameEnd;
15804 boolean checkGameFailed(void)
15806 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15807 return (game_em.game_over && !game_em.level_solved);
15808 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15809 return (game_sp.game_over && !game_sp.level_solved);
15810 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15811 return (game_mm.game_over && !game_mm.level_solved);
15812 else // GAME_ENGINE_TYPE_RND
15813 return (game.GameOver && !game.LevelSolved);
15816 boolean checkGameEnded(void)
15818 return (checkGameSolved() || checkGameFailed());
15822 // ----------------------------------------------------------------------------
15823 // random generator functions
15824 // ----------------------------------------------------------------------------
15826 unsigned int InitEngineRandom_RND(int seed)
15828 game.num_random_calls = 0;
15830 return InitEngineRandom(seed);
15833 unsigned int RND(int max)
15837 game.num_random_calls++;
15839 return GetEngineRandom(max);
15846 // ----------------------------------------------------------------------------
15847 // game engine snapshot handling functions
15848 // ----------------------------------------------------------------------------
15850 struct EngineSnapshotInfo
15852 // runtime values for custom element collect score
15853 int collect_score[NUM_CUSTOM_ELEMENTS];
15855 // runtime values for group element choice position
15856 int choice_pos[NUM_GROUP_ELEMENTS];
15858 // runtime values for belt position animations
15859 int belt_graphic[4][NUM_BELT_PARTS];
15860 int belt_anim_mode[4][NUM_BELT_PARTS];
15863 static struct EngineSnapshotInfo engine_snapshot_rnd;
15864 static char *snapshot_level_identifier = NULL;
15865 static int snapshot_level_nr = -1;
15867 static void SaveEngineSnapshotValues_RND(void)
15869 static int belt_base_active_element[4] =
15871 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15872 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15873 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15874 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15878 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15880 int element = EL_CUSTOM_START + i;
15882 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15885 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15887 int element = EL_GROUP_START + i;
15889 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15892 for (i = 0; i < 4; i++)
15894 for (j = 0; j < NUM_BELT_PARTS; j++)
15896 int element = belt_base_active_element[i] + j;
15897 int graphic = el2img(element);
15898 int anim_mode = graphic_info[graphic].anim_mode;
15900 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15901 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15906 static void LoadEngineSnapshotValues_RND(void)
15908 unsigned int num_random_calls = game.num_random_calls;
15911 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15913 int element = EL_CUSTOM_START + i;
15915 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15918 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15920 int element = EL_GROUP_START + i;
15922 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15925 for (i = 0; i < 4; i++)
15927 for (j = 0; j < NUM_BELT_PARTS; j++)
15929 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15930 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15932 graphic_info[graphic].anim_mode = anim_mode;
15936 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15938 InitRND(tape.random_seed);
15939 for (i = 0; i < num_random_calls; i++)
15943 if (game.num_random_calls != num_random_calls)
15945 Error("number of random calls out of sync");
15946 Error("number of random calls should be %d", num_random_calls);
15947 Error("number of random calls is %d", game.num_random_calls);
15949 Fail("this should not happen -- please debug");
15953 void FreeEngineSnapshotSingle(void)
15955 FreeSnapshotSingle();
15957 setString(&snapshot_level_identifier, NULL);
15958 snapshot_level_nr = -1;
15961 void FreeEngineSnapshotList(void)
15963 FreeSnapshotList();
15966 static ListNode *SaveEngineSnapshotBuffers(void)
15968 ListNode *buffers = NULL;
15970 // copy some special values to a structure better suited for the snapshot
15972 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15973 SaveEngineSnapshotValues_RND();
15974 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15975 SaveEngineSnapshotValues_EM();
15976 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15977 SaveEngineSnapshotValues_SP(&buffers);
15978 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15979 SaveEngineSnapshotValues_MM(&buffers);
15981 // save values stored in special snapshot structure
15983 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15985 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15987 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15989 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15990 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15992 // save further RND engine values
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15995 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15999 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
16000 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
16001 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16004 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16005 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16006 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16008 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16010 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16011 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16014 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16024 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16026 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16028 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16029 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16030 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16036 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16039 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16040 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16044 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16046 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16049 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16050 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16053 ListNode *node = engine_snapshot_list_rnd;
16056 while (node != NULL)
16058 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16063 Debug("game:playing:SaveEngineSnapshotBuffers",
16064 "size of engine snapshot: %d bytes", num_bytes);
16070 void SaveEngineSnapshotSingle(void)
16072 ListNode *buffers = SaveEngineSnapshotBuffers();
16074 // finally save all snapshot buffers to single snapshot
16075 SaveSnapshotSingle(buffers);
16077 // save level identification information
16078 setString(&snapshot_level_identifier, leveldir_current->identifier);
16079 snapshot_level_nr = level_nr;
16082 boolean CheckSaveEngineSnapshotToList(void)
16084 boolean save_snapshot =
16085 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16086 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16087 game.snapshot.changed_action) ||
16088 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16089 game.snapshot.collected_item));
16091 game.snapshot.changed_action = FALSE;
16092 game.snapshot.collected_item = FALSE;
16093 game.snapshot.save_snapshot = save_snapshot;
16095 return save_snapshot;
16098 void SaveEngineSnapshotToList(void)
16100 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16104 ListNode *buffers = SaveEngineSnapshotBuffers();
16106 // finally save all snapshot buffers to snapshot list
16107 SaveSnapshotToList(buffers);
16110 void SaveEngineSnapshotToListInitial(void)
16112 FreeEngineSnapshotList();
16114 SaveEngineSnapshotToList();
16117 static void LoadEngineSnapshotValues(void)
16119 // restore special values from snapshot structure
16121 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16122 LoadEngineSnapshotValues_RND();
16123 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16124 LoadEngineSnapshotValues_EM();
16125 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16126 LoadEngineSnapshotValues_SP();
16127 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16128 LoadEngineSnapshotValues_MM();
16131 void LoadEngineSnapshotSingle(void)
16133 LoadSnapshotSingle();
16135 LoadEngineSnapshotValues();
16138 static void LoadEngineSnapshot_Undo(int steps)
16140 LoadSnapshotFromList_Older(steps);
16142 LoadEngineSnapshotValues();
16145 static void LoadEngineSnapshot_Redo(int steps)
16147 LoadSnapshotFromList_Newer(steps);
16149 LoadEngineSnapshotValues();
16152 boolean CheckEngineSnapshotSingle(void)
16154 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16155 snapshot_level_nr == level_nr);
16158 boolean CheckEngineSnapshotList(void)
16160 return CheckSnapshotList();
16164 // ---------- new game button stuff -------------------------------------------
16171 boolean *setup_value;
16172 boolean allowed_on_tape;
16173 boolean is_touch_button;
16175 } gamebutton_info[NUM_GAME_BUTTONS] =
16178 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16179 GAME_CTRL_ID_STOP, NULL,
16180 TRUE, FALSE, "stop game"
16183 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16184 GAME_CTRL_ID_PAUSE, NULL,
16185 TRUE, FALSE, "pause game"
16188 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16189 GAME_CTRL_ID_PLAY, NULL,
16190 TRUE, FALSE, "play game"
16193 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16194 GAME_CTRL_ID_UNDO, NULL,
16195 TRUE, FALSE, "undo step"
16198 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16199 GAME_CTRL_ID_REDO, NULL,
16200 TRUE, FALSE, "redo step"
16203 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16204 GAME_CTRL_ID_SAVE, NULL,
16205 TRUE, FALSE, "save game"
16208 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16209 GAME_CTRL_ID_PAUSE2, NULL,
16210 TRUE, FALSE, "pause game"
16213 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16214 GAME_CTRL_ID_LOAD, NULL,
16215 TRUE, FALSE, "load game"
16218 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16219 GAME_CTRL_ID_PANEL_STOP, NULL,
16220 FALSE, FALSE, "stop game"
16223 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16224 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16225 FALSE, FALSE, "pause game"
16228 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16229 GAME_CTRL_ID_PANEL_PLAY, NULL,
16230 FALSE, FALSE, "play game"
16233 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16234 GAME_CTRL_ID_TOUCH_STOP, NULL,
16235 FALSE, TRUE, "stop game"
16238 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16239 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16240 FALSE, TRUE, "pause game"
16243 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16244 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16245 TRUE, FALSE, "background music on/off"
16248 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16249 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16250 TRUE, FALSE, "sound loops on/off"
16253 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16254 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16255 TRUE, FALSE, "normal sounds on/off"
16258 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16259 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16260 FALSE, FALSE, "background music on/off"
16263 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16264 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16265 FALSE, FALSE, "sound loops on/off"
16268 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16269 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16270 FALSE, FALSE, "normal sounds on/off"
16274 void CreateGameButtons(void)
16278 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16280 int graphic = gamebutton_info[i].graphic;
16281 struct GraphicInfo *gfx = &graphic_info[graphic];
16282 struct XY *pos = gamebutton_info[i].pos;
16283 struct GadgetInfo *gi;
16286 unsigned int event_mask;
16287 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16288 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16289 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16290 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16291 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16292 int gd_x = gfx->src_x;
16293 int gd_y = gfx->src_y;
16294 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16295 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16296 int gd_xa = gfx->src_x + gfx->active_xoffset;
16297 int gd_ya = gfx->src_y + gfx->active_yoffset;
16298 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16299 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16300 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16301 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16304 // do not use touch buttons if overlay touch buttons are disabled
16305 if (is_touch_button && !setup.touch.overlay_buttons)
16308 if (gfx->bitmap == NULL)
16310 game_gadget[id] = NULL;
16315 if (id == GAME_CTRL_ID_STOP ||
16316 id == GAME_CTRL_ID_PANEL_STOP ||
16317 id == GAME_CTRL_ID_TOUCH_STOP ||
16318 id == GAME_CTRL_ID_PLAY ||
16319 id == GAME_CTRL_ID_PANEL_PLAY ||
16320 id == GAME_CTRL_ID_SAVE ||
16321 id == GAME_CTRL_ID_LOAD)
16323 button_type = GD_TYPE_NORMAL_BUTTON;
16325 event_mask = GD_EVENT_RELEASED;
16327 else if (id == GAME_CTRL_ID_UNDO ||
16328 id == GAME_CTRL_ID_REDO)
16330 button_type = GD_TYPE_NORMAL_BUTTON;
16332 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16336 button_type = GD_TYPE_CHECK_BUTTON;
16337 checked = (gamebutton_info[i].setup_value != NULL ?
16338 *gamebutton_info[i].setup_value : FALSE);
16339 event_mask = GD_EVENT_PRESSED;
16342 gi = CreateGadget(GDI_CUSTOM_ID, id,
16343 GDI_IMAGE_ID, graphic,
16344 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16347 GDI_WIDTH, gfx->width,
16348 GDI_HEIGHT, gfx->height,
16349 GDI_TYPE, button_type,
16350 GDI_STATE, GD_BUTTON_UNPRESSED,
16351 GDI_CHECKED, checked,
16352 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16353 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16354 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16355 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16356 GDI_DIRECT_DRAW, FALSE,
16357 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16358 GDI_EVENT_MASK, event_mask,
16359 GDI_CALLBACK_ACTION, HandleGameButtons,
16363 Fail("cannot create gadget");
16365 game_gadget[id] = gi;
16369 void FreeGameButtons(void)
16373 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16374 FreeGadget(game_gadget[i]);
16377 static void UnmapGameButtonsAtSamePosition(int id)
16381 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16383 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16384 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16385 UnmapGadget(game_gadget[i]);
16388 static void UnmapGameButtonsAtSamePosition_All(void)
16390 if (setup.show_load_save_buttons)
16392 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16393 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16394 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16396 else if (setup.show_undo_redo_buttons)
16398 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16399 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16400 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16404 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16405 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16406 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16408 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16409 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16410 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16414 void MapLoadSaveButtons(void)
16416 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16417 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16419 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16420 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16423 void MapUndoRedoButtons(void)
16425 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16426 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16428 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16429 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16432 void ModifyPauseButtons(void)
16436 GAME_CTRL_ID_PAUSE,
16437 GAME_CTRL_ID_PAUSE2,
16438 GAME_CTRL_ID_PANEL_PAUSE,
16439 GAME_CTRL_ID_TOUCH_PAUSE,
16444 for (i = 0; ids[i] > -1; i++)
16445 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16448 static void MapGameButtonsExt(boolean on_tape)
16452 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16454 if ((i == GAME_CTRL_ID_UNDO ||
16455 i == GAME_CTRL_ID_REDO) &&
16456 game_status != GAME_MODE_PLAYING)
16459 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16460 MapGadget(game_gadget[i]);
16463 UnmapGameButtonsAtSamePosition_All();
16465 RedrawGameButtons();
16468 static void UnmapGameButtonsExt(boolean on_tape)
16472 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16473 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16474 UnmapGadget(game_gadget[i]);
16477 static void RedrawGameButtonsExt(boolean on_tape)
16481 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16482 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16483 RedrawGadget(game_gadget[i]);
16486 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16491 gi->checked = state;
16494 static void RedrawSoundButtonGadget(int id)
16496 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16497 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16498 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16499 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16500 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16501 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16504 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16505 RedrawGadget(game_gadget[id2]);
16508 void MapGameButtons(void)
16510 MapGameButtonsExt(FALSE);
16513 void UnmapGameButtons(void)
16515 UnmapGameButtonsExt(FALSE);
16518 void RedrawGameButtons(void)
16520 RedrawGameButtonsExt(FALSE);
16523 void MapGameButtonsOnTape(void)
16525 MapGameButtonsExt(TRUE);
16528 void UnmapGameButtonsOnTape(void)
16530 UnmapGameButtonsExt(TRUE);
16533 void RedrawGameButtonsOnTape(void)
16535 RedrawGameButtonsExt(TRUE);
16538 static void GameUndoRedoExt(void)
16540 ClearPlayerAction();
16542 tape.pausing = TRUE;
16545 UpdateAndDisplayGameControlValues();
16547 DrawCompleteVideoDisplay();
16548 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16549 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16550 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16552 ModifyPauseButtons();
16557 static void GameUndo(int steps)
16559 if (!CheckEngineSnapshotList())
16562 int tape_property_bits = tape.property_bits;
16564 LoadEngineSnapshot_Undo(steps);
16566 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16571 static void GameRedo(int steps)
16573 if (!CheckEngineSnapshotList())
16576 int tape_property_bits = tape.property_bits;
16578 LoadEngineSnapshot_Redo(steps);
16580 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16585 static void HandleGameButtonsExt(int id, int button)
16587 static boolean game_undo_executed = FALSE;
16588 int steps = BUTTON_STEPSIZE(button);
16589 boolean handle_game_buttons =
16590 (game_status == GAME_MODE_PLAYING ||
16591 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16593 if (!handle_game_buttons)
16598 case GAME_CTRL_ID_STOP:
16599 case GAME_CTRL_ID_PANEL_STOP:
16600 case GAME_CTRL_ID_TOUCH_STOP:
16605 case GAME_CTRL_ID_PAUSE:
16606 case GAME_CTRL_ID_PAUSE2:
16607 case GAME_CTRL_ID_PANEL_PAUSE:
16608 case GAME_CTRL_ID_TOUCH_PAUSE:
16609 if (network.enabled && game_status == GAME_MODE_PLAYING)
16612 SendToServer_ContinuePlaying();
16614 SendToServer_PausePlaying();
16617 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16619 game_undo_executed = FALSE;
16623 case GAME_CTRL_ID_PLAY:
16624 case GAME_CTRL_ID_PANEL_PLAY:
16625 if (game_status == GAME_MODE_MAIN)
16627 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16629 else if (tape.pausing)
16631 if (network.enabled)
16632 SendToServer_ContinuePlaying();
16634 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16638 case GAME_CTRL_ID_UNDO:
16639 // Important: When using "save snapshot when collecting an item" mode,
16640 // load last (current) snapshot for first "undo" after pressing "pause"
16641 // (else the last-but-one snapshot would be loaded, because the snapshot
16642 // pointer already points to the last snapshot when pressing "pause",
16643 // which is fine for "every step/move" mode, but not for "every collect")
16644 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16645 !game_undo_executed)
16648 game_undo_executed = TRUE;
16653 case GAME_CTRL_ID_REDO:
16657 case GAME_CTRL_ID_SAVE:
16661 case GAME_CTRL_ID_LOAD:
16665 case SOUND_CTRL_ID_MUSIC:
16666 case SOUND_CTRL_ID_PANEL_MUSIC:
16667 if (setup.sound_music)
16669 setup.sound_music = FALSE;
16673 else if (audio.music_available)
16675 setup.sound = setup.sound_music = TRUE;
16677 SetAudioMode(setup.sound);
16679 if (game_status == GAME_MODE_PLAYING)
16683 RedrawSoundButtonGadget(id);
16687 case SOUND_CTRL_ID_LOOPS:
16688 case SOUND_CTRL_ID_PANEL_LOOPS:
16689 if (setup.sound_loops)
16690 setup.sound_loops = FALSE;
16691 else if (audio.loops_available)
16693 setup.sound = setup.sound_loops = TRUE;
16695 SetAudioMode(setup.sound);
16698 RedrawSoundButtonGadget(id);
16702 case SOUND_CTRL_ID_SIMPLE:
16703 case SOUND_CTRL_ID_PANEL_SIMPLE:
16704 if (setup.sound_simple)
16705 setup.sound_simple = FALSE;
16706 else if (audio.sound_available)
16708 setup.sound = setup.sound_simple = TRUE;
16710 SetAudioMode(setup.sound);
16713 RedrawSoundButtonGadget(id);
16722 static void HandleGameButtons(struct GadgetInfo *gi)
16724 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16727 void HandleSoundButtonKeys(Key key)
16729 if (key == setup.shortcut.sound_simple)
16730 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16731 else if (key == setup.shortcut.sound_loops)
16732 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16733 else if (key == setup.shortcut.sound_music)
16734 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);