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.count = 0;
3684 player->actual_frame_counter.value = 1;
3686 player->step_counter = 0;
3688 player->last_move_dir = initial_move_dir;
3690 player->is_active = FALSE;
3692 player->is_waiting = FALSE;
3693 player->is_moving = FALSE;
3694 player->is_auto_moving = FALSE;
3695 player->is_digging = FALSE;
3696 player->is_snapping = FALSE;
3697 player->is_collecting = FALSE;
3698 player->is_pushing = FALSE;
3699 player->is_switching = FALSE;
3700 player->is_dropping = FALSE;
3701 player->is_dropping_pressed = FALSE;
3703 player->is_bored = FALSE;
3704 player->is_sleeping = FALSE;
3706 player->was_waiting = TRUE;
3707 player->was_moving = FALSE;
3708 player->was_snapping = FALSE;
3709 player->was_dropping = FALSE;
3711 player->force_dropping = FALSE;
3713 player->frame_counter_bored = -1;
3714 player->frame_counter_sleeping = -1;
3716 player->anim_delay_counter = 0;
3717 player->post_delay_counter = 0;
3719 player->dir_waiting = initial_move_dir;
3720 player->action_waiting = ACTION_DEFAULT;
3721 player->last_action_waiting = ACTION_DEFAULT;
3722 player->special_action_bored = ACTION_DEFAULT;
3723 player->special_action_sleeping = ACTION_DEFAULT;
3725 player->switch_x = -1;
3726 player->switch_y = -1;
3728 player->drop_x = -1;
3729 player->drop_y = -1;
3731 player->show_envelope = 0;
3733 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3735 player->push_delay = -1; // initialized when pushing starts
3736 player->push_delay_value = game.initial_push_delay_value;
3738 player->drop_delay = 0;
3739 player->drop_pressed_delay = 0;
3741 player->last_jx = -1;
3742 player->last_jy = -1;
3746 player->shield_normal_time_left = 0;
3747 player->shield_deadly_time_left = 0;
3749 player->last_removed_element = EL_UNDEFINED;
3751 player->inventory_infinite_element = EL_UNDEFINED;
3752 player->inventory_size = 0;
3754 if (level.use_initial_inventory[i])
3756 for (j = 0; j < level.initial_inventory_size[i]; j++)
3758 int element = level.initial_inventory_content[i][j];
3759 int collect_count = element_info[element].collect_count_initial;
3762 if (!IS_CUSTOM_ELEMENT(element))
3765 if (collect_count == 0)
3766 player->inventory_infinite_element = element;
3768 for (k = 0; k < collect_count; k++)
3769 if (player->inventory_size < MAX_INVENTORY_SIZE)
3770 player->inventory_element[player->inventory_size++] = element;
3774 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3775 SnapField(player, 0, 0);
3777 map_player_action[i] = i;
3780 network_player_action_received = FALSE;
3782 // initial null action
3783 if (network_playing)
3784 SendToServer_MovePlayer(MV_NONE);
3789 TimeLeft = level.time;
3792 ScreenMovDir = MV_NONE;
3796 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3798 game.robot_wheel_x = -1;
3799 game.robot_wheel_y = -1;
3804 game.all_players_gone = FALSE;
3806 game.LevelSolved = FALSE;
3807 game.GameOver = FALSE;
3809 game.GamePlayed = !tape.playing;
3811 game.LevelSolved_GameWon = FALSE;
3812 game.LevelSolved_GameEnd = FALSE;
3813 game.LevelSolved_SaveTape = FALSE;
3814 game.LevelSolved_SaveScore = FALSE;
3816 game.LevelSolved_CountingTime = 0;
3817 game.LevelSolved_CountingScore = 0;
3818 game.LevelSolved_CountingHealth = 0;
3820 game.panel.active = TRUE;
3822 game.no_level_time_limit = (level.time == 0);
3823 game.time_limit = (leveldir_current->time_limit && setup.time_limit);
3825 game.yamyam_content_nr = 0;
3826 game.robot_wheel_active = FALSE;
3827 game.magic_wall_active = FALSE;
3828 game.magic_wall_time_left = 0;
3829 game.light_time_left = 0;
3830 game.timegate_time_left = 0;
3831 game.switchgate_pos = 0;
3832 game.wind_direction = level.wind_direction_initial;
3834 game.time_final = 0;
3835 game.score_time_final = 0;
3838 game.score_final = 0;
3840 game.health = MAX_HEALTH;
3841 game.health_final = MAX_HEALTH;
3843 game.gems_still_needed = level.gems_needed;
3844 game.sokoban_fields_still_needed = 0;
3845 game.sokoban_objects_still_needed = 0;
3846 game.lights_still_needed = 0;
3847 game.players_still_needed = 0;
3848 game.friends_still_needed = 0;
3850 game.lenses_time_left = 0;
3851 game.magnify_time_left = 0;
3853 game.ball_active = level.ball_active_initial;
3854 game.ball_content_nr = 0;
3856 game.explosions_delayed = TRUE;
3858 game.envelope_active = FALSE;
3860 // special case: set custom artwork setting to initial value
3861 game.use_masked_elements = game.use_masked_elements_initial;
3863 for (i = 0; i < NUM_BELTS; i++)
3865 game.belt_dir[i] = MV_NONE;
3866 game.belt_dir_nr[i] = 3; // not moving, next moving left
3869 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3870 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3872 #if DEBUG_INIT_PLAYER
3873 DebugPrintPlayerStatus("Player status at level initialization");
3876 SCAN_PLAYFIELD(x, y)
3878 Tile[x][y] = Last[x][y] = level.field[x][y];
3879 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3880 ChangeDelay[x][y] = 0;
3881 ChangePage[x][y] = -1;
3882 CustomValue[x][y] = 0; // initialized in InitField()
3883 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3885 WasJustMoving[x][y] = 0;
3886 WasJustFalling[x][y] = 0;
3887 CheckCollision[x][y] = 0;
3888 CheckImpact[x][y] = 0;
3890 Pushed[x][y] = FALSE;
3892 ChangeCount[x][y] = 0;
3893 ChangeEvent[x][y] = -1;
3895 ExplodePhase[x][y] = 0;
3896 ExplodeDelay[x][y] = 0;
3897 ExplodeField[x][y] = EX_TYPE_NONE;
3899 RunnerVisit[x][y] = 0;
3900 PlayerVisit[x][y] = 0;
3903 GfxRandom[x][y] = INIT_GFX_RANDOM();
3904 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3905 GfxElement[x][y] = EL_UNDEFINED;
3906 GfxElementEmpty[x][y] = EL_EMPTY;
3907 GfxAction[x][y] = ACTION_DEFAULT;
3908 GfxDir[x][y] = MV_NONE;
3909 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3912 SCAN_PLAYFIELD(x, y)
3914 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3916 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3919 InitField(x, y, TRUE);
3921 ResetGfxAnimation(x, y);
3926 for (i = 0; i < MAX_PLAYERS; i++)
3928 struct PlayerInfo *player = &stored_player[i];
3930 // set number of special actions for bored and sleeping animation
3931 player->num_special_action_bored =
3932 get_num_special_action(player->artwork_element,
3933 ACTION_BORING_1, ACTION_BORING_LAST);
3934 player->num_special_action_sleeping =
3935 get_num_special_action(player->artwork_element,
3936 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3939 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3940 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3942 // initialize type of slippery elements
3943 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3945 if (!IS_CUSTOM_ELEMENT(i))
3947 // default: elements slip down either to the left or right randomly
3948 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3950 // SP style elements prefer to slip down on the left side
3951 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3952 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3954 // BD style elements prefer to slip down on the left side
3955 if (game.emulation == EMU_BOULDERDASH)
3956 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3960 // initialize explosion and ignition delay
3961 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3963 if (!IS_CUSTOM_ELEMENT(i))
3966 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3967 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3968 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3969 int last_phase = (num_phase + 1) * delay;
3970 int half_phase = (num_phase / 2) * delay;
3972 element_info[i].explosion_delay = last_phase - 1;
3973 element_info[i].ignition_delay = half_phase;
3975 if (i == EL_BLACK_ORB)
3976 element_info[i].ignition_delay = 1;
3980 // correct non-moving belts to start moving left
3981 for (i = 0; i < NUM_BELTS; i++)
3982 if (game.belt_dir[i] == MV_NONE)
3983 game.belt_dir_nr[i] = 3; // not moving, next moving left
3985 #if USE_NEW_PLAYER_ASSIGNMENTS
3986 // use preferred player also in local single-player mode
3987 if (!network.enabled && !game.team_mode)
3989 int new_index_nr = setup.network_player_nr;
3991 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3993 for (i = 0; i < MAX_PLAYERS; i++)
3994 stored_player[i].connected_locally = FALSE;
3996 stored_player[new_index_nr].connected_locally = TRUE;
4000 for (i = 0; i < MAX_PLAYERS; i++)
4002 stored_player[i].connected = FALSE;
4004 // in network game mode, the local player might not be the first player
4005 if (stored_player[i].connected_locally)
4006 local_player = &stored_player[i];
4009 if (!network.enabled)
4010 local_player->connected = TRUE;
4014 for (i = 0; i < MAX_PLAYERS; i++)
4015 stored_player[i].connected = tape.player_participates[i];
4017 else if (network.enabled)
4019 // add team mode players connected over the network (needed for correct
4020 // assignment of player figures from level to locally playing players)
4022 for (i = 0; i < MAX_PLAYERS; i++)
4023 if (stored_player[i].connected_network)
4024 stored_player[i].connected = TRUE;
4026 else if (game.team_mode)
4028 // try to guess locally connected team mode players (needed for correct
4029 // assignment of player figures from level to locally playing players)
4031 for (i = 0; i < MAX_PLAYERS; i++)
4032 if (setup.input[i].use_joystick ||
4033 setup.input[i].key.left != KSYM_UNDEFINED)
4034 stored_player[i].connected = TRUE;
4037 #if DEBUG_INIT_PLAYER
4038 DebugPrintPlayerStatus("Player status after level initialization");
4041 #if DEBUG_INIT_PLAYER
4042 Debug("game:init:player", "Reassigning players ...");
4045 // check if any connected player was not found in playfield
4046 for (i = 0; i < MAX_PLAYERS; i++)
4048 struct PlayerInfo *player = &stored_player[i];
4050 if (player->connected && !player->present)
4052 struct PlayerInfo *field_player = NULL;
4054 #if DEBUG_INIT_PLAYER
4055 Debug("game:init:player",
4056 "- looking for field player for player %d ...", i + 1);
4059 // assign first free player found that is present in the playfield
4061 // first try: look for unmapped playfield player that is not connected
4062 for (j = 0; j < MAX_PLAYERS; j++)
4063 if (field_player == NULL &&
4064 stored_player[j].present &&
4065 !stored_player[j].mapped &&
4066 !stored_player[j].connected)
4067 field_player = &stored_player[j];
4069 // second try: look for *any* unmapped playfield player
4070 for (j = 0; j < MAX_PLAYERS; j++)
4071 if (field_player == NULL &&
4072 stored_player[j].present &&
4073 !stored_player[j].mapped)
4074 field_player = &stored_player[j];
4076 if (field_player != NULL)
4078 int jx = field_player->jx, jy = field_player->jy;
4080 #if DEBUG_INIT_PLAYER
4081 Debug("game:init:player", "- found player %d",
4082 field_player->index_nr + 1);
4085 player->present = FALSE;
4086 player->active = FALSE;
4088 field_player->present = TRUE;
4089 field_player->active = TRUE;
4092 player->initial_element = field_player->initial_element;
4093 player->artwork_element = field_player->artwork_element;
4095 player->block_last_field = field_player->block_last_field;
4096 player->block_delay_adjustment = field_player->block_delay_adjustment;
4099 StorePlayer[jx][jy] = field_player->element_nr;
4101 field_player->jx = field_player->last_jx = jx;
4102 field_player->jy = field_player->last_jy = jy;
4104 if (local_player == player)
4105 local_player = field_player;
4107 map_player_action[field_player->index_nr] = i;
4109 field_player->mapped = TRUE;
4111 #if DEBUG_INIT_PLAYER
4112 Debug("game:init:player", "- map_player_action[%d] == %d",
4113 field_player->index_nr + 1, i + 1);
4118 if (player->connected && player->present)
4119 player->mapped = TRUE;
4122 #if DEBUG_INIT_PLAYER
4123 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4128 // check if any connected player was not found in playfield
4129 for (i = 0; i < MAX_PLAYERS; i++)
4131 struct PlayerInfo *player = &stored_player[i];
4133 if (player->connected && !player->present)
4135 for (j = 0; j < MAX_PLAYERS; j++)
4137 struct PlayerInfo *field_player = &stored_player[j];
4138 int jx = field_player->jx, jy = field_player->jy;
4140 // assign first free player found that is present in the playfield
4141 if (field_player->present && !field_player->connected)
4143 player->present = TRUE;
4144 player->active = TRUE;
4146 field_player->present = FALSE;
4147 field_player->active = FALSE;
4149 player->initial_element = field_player->initial_element;
4150 player->artwork_element = field_player->artwork_element;
4152 player->block_last_field = field_player->block_last_field;
4153 player->block_delay_adjustment = field_player->block_delay_adjustment;
4155 StorePlayer[jx][jy] = player->element_nr;
4157 player->jx = player->last_jx = jx;
4158 player->jy = player->last_jy = jy;
4168 Debug("game:init:player", "local_player->present == %d",
4169 local_player->present);
4172 // set focus to local player for network games, else to all players
4173 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4174 game.centered_player_nr_next = game.centered_player_nr;
4175 game.set_centered_player = FALSE;
4176 game.set_centered_player_wrap = FALSE;
4178 if (network_playing && tape.recording)
4180 // store client dependent player focus when recording network games
4181 tape.centered_player_nr_next = game.centered_player_nr_next;
4182 tape.set_centered_player = TRUE;
4187 // when playing a tape, eliminate all players who do not participate
4189 #if USE_NEW_PLAYER_ASSIGNMENTS
4191 if (!game.team_mode)
4193 for (i = 0; i < MAX_PLAYERS; i++)
4195 if (stored_player[i].active &&
4196 !tape.player_participates[map_player_action[i]])
4198 struct PlayerInfo *player = &stored_player[i];
4199 int jx = player->jx, jy = player->jy;
4201 #if DEBUG_INIT_PLAYER
4202 Debug("game:init:player", "Removing player %d at (%d, %d)",
4206 player->active = FALSE;
4207 StorePlayer[jx][jy] = 0;
4208 Tile[jx][jy] = EL_EMPTY;
4215 for (i = 0; i < MAX_PLAYERS; i++)
4217 if (stored_player[i].active &&
4218 !tape.player_participates[i])
4220 struct PlayerInfo *player = &stored_player[i];
4221 int jx = player->jx, jy = player->jy;
4223 player->active = FALSE;
4224 StorePlayer[jx][jy] = 0;
4225 Tile[jx][jy] = EL_EMPTY;
4230 else if (!network.enabled && !game.team_mode) // && !tape.playing
4232 // when in single player mode, eliminate all but the local player
4234 for (i = 0; i < MAX_PLAYERS; i++)
4236 struct PlayerInfo *player = &stored_player[i];
4238 if (player->active && player != local_player)
4240 int jx = player->jx, jy = player->jy;
4242 player->active = FALSE;
4243 player->present = FALSE;
4245 StorePlayer[jx][jy] = 0;
4246 Tile[jx][jy] = EL_EMPTY;
4251 for (i = 0; i < MAX_PLAYERS; i++)
4252 if (stored_player[i].active)
4253 game.players_still_needed++;
4255 if (level.solved_by_one_player)
4256 game.players_still_needed = 1;
4258 // when recording the game, store which players take part in the game
4261 #if USE_NEW_PLAYER_ASSIGNMENTS
4262 for (i = 0; i < MAX_PLAYERS; i++)
4263 if (stored_player[i].connected)
4264 tape.player_participates[i] = TRUE;
4266 for (i = 0; i < MAX_PLAYERS; i++)
4267 if (stored_player[i].active)
4268 tape.player_participates[i] = TRUE;
4272 #if DEBUG_INIT_PLAYER
4273 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4276 if (BorderElement == EL_EMPTY)
4279 SBX_Right = lev_fieldx - SCR_FIELDX;
4281 SBY_Lower = lev_fieldy - SCR_FIELDY;
4286 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4288 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4291 if (full_lev_fieldx <= SCR_FIELDX)
4292 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4293 if (full_lev_fieldy <= SCR_FIELDY)
4294 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4296 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4298 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4301 // if local player not found, look for custom element that might create
4302 // the player (make some assumptions about the right custom element)
4303 if (!local_player->present)
4305 int start_x = 0, start_y = 0;
4306 int found_rating = 0;
4307 int found_element = EL_UNDEFINED;
4308 int player_nr = local_player->index_nr;
4310 SCAN_PLAYFIELD(x, y)
4312 int element = Tile[x][y];
4317 if (level.use_start_element[player_nr] &&
4318 level.start_element[player_nr] == element &&
4325 found_element = element;
4328 if (!IS_CUSTOM_ELEMENT(element))
4331 if (CAN_CHANGE(element))
4333 for (i = 0; i < element_info[element].num_change_pages; i++)
4335 // check for player created from custom element as single target
4336 content = element_info[element].change_page[i].target_element;
4337 is_player = IS_PLAYER_ELEMENT(content);
4339 if (is_player && (found_rating < 3 ||
4340 (found_rating == 3 && element < found_element)))
4346 found_element = element;
4351 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4353 // check for player created from custom element as explosion content
4354 content = element_info[element].content.e[xx][yy];
4355 is_player = IS_PLAYER_ELEMENT(content);
4357 if (is_player && (found_rating < 2 ||
4358 (found_rating == 2 && element < found_element)))
4360 start_x = x + xx - 1;
4361 start_y = y + yy - 1;
4364 found_element = element;
4367 if (!CAN_CHANGE(element))
4370 for (i = 0; i < element_info[element].num_change_pages; i++)
4372 // check for player created from custom element as extended target
4374 element_info[element].change_page[i].target_content.e[xx][yy];
4376 is_player = IS_PLAYER_ELEMENT(content);
4378 if (is_player && (found_rating < 1 ||
4379 (found_rating == 1 && element < found_element)))
4381 start_x = x + xx - 1;
4382 start_y = y + yy - 1;
4385 found_element = element;
4391 scroll_x = SCROLL_POSITION_X(start_x);
4392 scroll_y = SCROLL_POSITION_Y(start_y);
4396 scroll_x = SCROLL_POSITION_X(local_player->jx);
4397 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4400 // !!! FIX THIS (START) !!!
4401 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4403 InitGameEngine_EM();
4405 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4407 InitGameEngine_SP();
4409 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4411 InitGameEngine_MM();
4415 DrawLevel(REDRAW_FIELD);
4418 // after drawing the level, correct some elements
4419 if (game.timegate_time_left == 0)
4420 CloseAllOpenTimegates();
4423 // blit playfield from scroll buffer to normal back buffer for fading in
4424 BlitScreenToBitmap(backbuffer);
4425 // !!! FIX THIS (END) !!!
4427 DrawMaskedBorder(fade_mask);
4432 // full screen redraw is required at this point in the following cases:
4433 // - special editor door undrawn when game was started from level editor
4434 // - drawing area (playfield) was changed and has to be removed completely
4435 redraw_mask = REDRAW_ALL;
4439 if (!game.restart_level)
4441 // copy default game door content to main double buffer
4443 // !!! CHECK AGAIN !!!
4444 SetPanelBackground();
4445 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4446 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4449 SetPanelBackground();
4450 SetDrawBackgroundMask(REDRAW_DOOR_1);
4452 UpdateAndDisplayGameControlValues();
4454 if (!game.restart_level)
4460 CreateGameButtons();
4465 // copy actual game door content to door double buffer for OpenDoor()
4466 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4468 OpenDoor(DOOR_OPEN_ALL);
4470 KeyboardAutoRepeatOffUnlessAutoplay();
4472 #if DEBUG_INIT_PLAYER
4473 DebugPrintPlayerStatus("Player status (final)");
4482 if (!game.restart_level && !tape.playing)
4484 LevelStats_incPlayed(level_nr);
4486 SaveLevelSetup_SeriesInfo();
4489 game.restart_level = FALSE;
4490 game.restart_game_message = NULL;
4492 game.request_active = FALSE;
4493 game.request_active_or_moving = FALSE;
4495 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4496 InitGameActions_MM();
4498 SaveEngineSnapshotToListInitial();
4500 if (!game.restart_level)
4502 PlaySound(SND_GAME_STARTING);
4504 if (setup.sound_music)
4508 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4511 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4512 int actual_player_x, int actual_player_y)
4514 // this is used for non-R'n'D game engines to update certain engine values
4516 // needed to determine if sounds are played within the visible screen area
4517 scroll_x = actual_scroll_x;
4518 scroll_y = actual_scroll_y;
4520 // needed to get player position for "follow finger" playing input method
4521 local_player->jx = actual_player_x;
4522 local_player->jy = actual_player_y;
4525 void InitMovDir(int x, int y)
4527 int i, element = Tile[x][y];
4528 static int xy[4][2] =
4535 static int direction[3][4] =
4537 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4538 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4539 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4548 Tile[x][y] = EL_BUG;
4549 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4552 case EL_SPACESHIP_RIGHT:
4553 case EL_SPACESHIP_UP:
4554 case EL_SPACESHIP_LEFT:
4555 case EL_SPACESHIP_DOWN:
4556 Tile[x][y] = EL_SPACESHIP;
4557 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4560 case EL_BD_BUTTERFLY_RIGHT:
4561 case EL_BD_BUTTERFLY_UP:
4562 case EL_BD_BUTTERFLY_LEFT:
4563 case EL_BD_BUTTERFLY_DOWN:
4564 Tile[x][y] = EL_BD_BUTTERFLY;
4565 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4568 case EL_BD_FIREFLY_RIGHT:
4569 case EL_BD_FIREFLY_UP:
4570 case EL_BD_FIREFLY_LEFT:
4571 case EL_BD_FIREFLY_DOWN:
4572 Tile[x][y] = EL_BD_FIREFLY;
4573 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4576 case EL_PACMAN_RIGHT:
4578 case EL_PACMAN_LEFT:
4579 case EL_PACMAN_DOWN:
4580 Tile[x][y] = EL_PACMAN;
4581 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4584 case EL_YAMYAM_LEFT:
4585 case EL_YAMYAM_RIGHT:
4587 case EL_YAMYAM_DOWN:
4588 Tile[x][y] = EL_YAMYAM;
4589 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4592 case EL_SP_SNIKSNAK:
4593 MovDir[x][y] = MV_UP;
4596 case EL_SP_ELECTRON:
4597 MovDir[x][y] = MV_LEFT;
4604 Tile[x][y] = EL_MOLE;
4605 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4608 case EL_SPRING_LEFT:
4609 case EL_SPRING_RIGHT:
4610 Tile[x][y] = EL_SPRING;
4611 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4615 if (IS_CUSTOM_ELEMENT(element))
4617 struct ElementInfo *ei = &element_info[element];
4618 int move_direction_initial = ei->move_direction_initial;
4619 int move_pattern = ei->move_pattern;
4621 if (move_direction_initial == MV_START_PREVIOUS)
4623 if (MovDir[x][y] != MV_NONE)
4626 move_direction_initial = MV_START_AUTOMATIC;
4629 if (move_direction_initial == MV_START_RANDOM)
4630 MovDir[x][y] = 1 << RND(4);
4631 else if (move_direction_initial & MV_ANY_DIRECTION)
4632 MovDir[x][y] = move_direction_initial;
4633 else if (move_pattern == MV_ALL_DIRECTIONS ||
4634 move_pattern == MV_TURNING_LEFT ||
4635 move_pattern == MV_TURNING_RIGHT ||
4636 move_pattern == MV_TURNING_LEFT_RIGHT ||
4637 move_pattern == MV_TURNING_RIGHT_LEFT ||
4638 move_pattern == MV_TURNING_RANDOM)
4639 MovDir[x][y] = 1 << RND(4);
4640 else if (move_pattern == MV_HORIZONTAL)
4641 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4642 else if (move_pattern == MV_VERTICAL)
4643 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4644 else if (move_pattern & MV_ANY_DIRECTION)
4645 MovDir[x][y] = element_info[element].move_pattern;
4646 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4647 move_pattern == MV_ALONG_RIGHT_SIDE)
4649 // use random direction as default start direction
4650 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4651 MovDir[x][y] = 1 << RND(4);
4653 for (i = 0; i < NUM_DIRECTIONS; i++)
4655 int x1 = x + xy[i][0];
4656 int y1 = y + xy[i][1];
4658 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4660 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4661 MovDir[x][y] = direction[0][i];
4663 MovDir[x][y] = direction[1][i];
4672 MovDir[x][y] = 1 << RND(4);
4674 if (element != EL_BUG &&
4675 element != EL_SPACESHIP &&
4676 element != EL_BD_BUTTERFLY &&
4677 element != EL_BD_FIREFLY)
4680 for (i = 0; i < NUM_DIRECTIONS; i++)
4682 int x1 = x + xy[i][0];
4683 int y1 = y + xy[i][1];
4685 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4687 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4689 MovDir[x][y] = direction[0][i];
4692 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4693 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4695 MovDir[x][y] = direction[1][i];
4704 GfxDir[x][y] = MovDir[x][y];
4707 void InitAmoebaNr(int x, int y)
4710 int group_nr = AmoebaNeighbourNr(x, y);
4714 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4716 if (AmoebaCnt[i] == 0)
4724 AmoebaNr[x][y] = group_nr;
4725 AmoebaCnt[group_nr]++;
4726 AmoebaCnt2[group_nr]++;
4729 static void LevelSolved_SetFinalGameValues(void)
4731 game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft);
4732 game.score_time_final = (level.use_step_counter ? TimePlayed :
4733 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4735 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4736 game_em.lev->score :
4737 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4741 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4742 MM_HEALTH(game_mm.laser_overload_value) :
4745 game.LevelSolved_CountingTime = game.time_final;
4746 game.LevelSolved_CountingScore = game.score_final;
4747 game.LevelSolved_CountingHealth = game.health_final;
4750 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4752 game.LevelSolved_CountingTime = time;
4753 game.LevelSolved_CountingScore = score;
4754 game.LevelSolved_CountingHealth = health;
4756 game_panel_controls[GAME_PANEL_TIME].value = time;
4757 game_panel_controls[GAME_PANEL_SCORE].value = score;
4758 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4760 DisplayGameControlValues();
4763 static void LevelSolved(void)
4765 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4766 game.players_still_needed > 0)
4769 game.LevelSolved = TRUE;
4770 game.GameOver = TRUE;
4772 // needed here to display correct panel values while player walks into exit
4773 LevelSolved_SetFinalGameValues();
4778 static int time_count_steps;
4779 static int time, time_final;
4780 static float score, score_final; // needed for time score < 10 for 10 seconds
4781 static int health, health_final;
4782 static int game_over_delay_1 = 0;
4783 static int game_over_delay_2 = 0;
4784 static int game_over_delay_3 = 0;
4785 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4786 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4788 if (!game.LevelSolved_GameWon)
4792 // do not start end game actions before the player stops moving (to exit)
4793 if (local_player->active && local_player->MovPos)
4796 // calculate final game values after player finished walking into exit
4797 LevelSolved_SetFinalGameValues();
4799 game.LevelSolved_GameWon = TRUE;
4800 game.LevelSolved_SaveTape = tape.recording;
4801 game.LevelSolved_SaveScore = !tape.playing;
4805 LevelStats_incSolved(level_nr);
4807 SaveLevelSetup_SeriesInfo();
4810 if (tape.auto_play) // tape might already be stopped here
4811 tape.auto_play_level_solved = TRUE;
4815 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4816 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4817 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4819 time = time_final = game.time_final;
4820 score = score_final = game.score_final;
4821 health = health_final = game.health_final;
4823 // update game panel values before (delayed) counting of score (if any)
4824 LevelSolved_DisplayFinalGameValues(time, score, health);
4826 // if level has time score defined, calculate new final game values
4829 int time_final_max = 999;
4830 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4831 int time_frames = 0;
4832 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4833 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4838 time_frames = time_frames_left;
4840 else if (game.no_level_time_limit && TimePlayed < time_final_max)
4842 time_final = time_final_max;
4843 time_frames = time_frames_final_max - time_frames_played;
4846 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4848 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4850 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4853 score_final += health * time_score;
4856 game.score_final = score_final;
4857 game.health_final = health_final;
4860 // if not counting score after game, immediately update game panel values
4861 if (level_editor_test_game || !setup.count_score_after_game)
4864 score = score_final;
4866 LevelSolved_DisplayFinalGameValues(time, score, health);
4869 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4871 // check if last player has left the level
4872 if (game.exit_x >= 0 &&
4875 int x = game.exit_x;
4876 int y = game.exit_y;
4877 int element = Tile[x][y];
4879 // close exit door after last player
4880 if ((game.all_players_gone &&
4881 (element == EL_EXIT_OPEN ||
4882 element == EL_SP_EXIT_OPEN ||
4883 element == EL_STEEL_EXIT_OPEN)) ||
4884 element == EL_EM_EXIT_OPEN ||
4885 element == EL_EM_STEEL_EXIT_OPEN)
4889 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4890 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4891 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4892 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4893 EL_EM_STEEL_EXIT_CLOSING);
4895 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4898 // player disappears
4899 DrawLevelField(x, y);
4902 for (i = 0; i < MAX_PLAYERS; i++)
4904 struct PlayerInfo *player = &stored_player[i];
4906 if (player->present)
4908 RemovePlayer(player);
4910 // player disappears
4911 DrawLevelField(player->jx, player->jy);
4916 PlaySound(SND_GAME_WINNING);
4919 if (setup.count_score_after_game)
4921 if (time != time_final)
4923 if (game_over_delay_1 > 0)
4925 game_over_delay_1--;
4930 int time_to_go = ABS(time_final - time);
4931 int time_count_dir = (time < time_final ? +1 : -1);
4933 if (time_to_go < time_count_steps)
4934 time_count_steps = 1;
4936 time += time_count_steps * time_count_dir;
4937 score += time_count_steps * time_score;
4939 // set final score to correct rounding differences after counting score
4940 if (time == time_final)
4941 score = score_final;
4943 LevelSolved_DisplayFinalGameValues(time, score, health);
4945 if (time == time_final)
4946 StopSound(SND_GAME_LEVELTIME_BONUS);
4947 else if (setup.sound_loops)
4948 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4950 PlaySound(SND_GAME_LEVELTIME_BONUS);
4955 if (health != health_final)
4957 if (game_over_delay_2 > 0)
4959 game_over_delay_2--;
4964 int health_count_dir = (health < health_final ? +1 : -1);
4966 health += health_count_dir;
4967 score += time_score;
4969 LevelSolved_DisplayFinalGameValues(time, score, health);
4971 if (health == health_final)
4972 StopSound(SND_GAME_LEVELTIME_BONUS);
4973 else if (setup.sound_loops)
4974 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4976 PlaySound(SND_GAME_LEVELTIME_BONUS);
4982 game.panel.active = FALSE;
4984 if (game_over_delay_3 > 0)
4986 game_over_delay_3--;
4996 // used instead of "level_nr" (needed for network games)
4997 int last_level_nr = levelset.level_nr;
4998 boolean tape_saved = FALSE;
5000 game.LevelSolved_GameEnd = TRUE;
5002 if (game.LevelSolved_SaveTape && !score_info_tape_play)
5004 // make sure that request dialog to save tape does not open door again
5005 if (!global.use_envelope_request)
5006 CloseDoor(DOOR_CLOSE_1);
5009 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5011 // set unique basename for score tape (also saved in high score table)
5012 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5015 // if no tape is to be saved, close both doors simultaneously
5016 CloseDoor(DOOR_CLOSE_ALL);
5018 if (level_editor_test_game || score_info_tape_play)
5020 SetGameStatus(GAME_MODE_MAIN);
5027 if (!game.LevelSolved_SaveScore)
5029 SetGameStatus(GAME_MODE_MAIN);
5036 if (level_nr == leveldir_current->handicap_level)
5038 leveldir_current->handicap_level++;
5040 SaveLevelSetup_SeriesInfo();
5043 // save score and score tape before potentially erasing tape below
5044 NewHighScore(last_level_nr, tape_saved);
5046 if (setup.increment_levels &&
5047 level_nr < leveldir_current->last_level &&
5050 level_nr++; // advance to next level
5051 TapeErase(); // start with empty tape
5053 if (setup.auto_play_next_level)
5055 scores.continue_playing = TRUE;
5056 scores.next_level_nr = level_nr;
5058 LoadLevel(level_nr);
5060 SaveLevelSetup_SeriesInfo();
5064 if (scores.last_added >= 0 && setup.show_scores_after_game)
5066 SetGameStatus(GAME_MODE_SCORES);
5068 DrawHallOfFame(last_level_nr);
5070 else if (scores.continue_playing)
5072 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5076 SetGameStatus(GAME_MODE_MAIN);
5082 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5083 boolean one_score_entry_per_name)
5087 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5090 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5092 struct ScoreEntry *entry = &list->entry[i];
5093 boolean score_is_better = (new_entry->score > entry->score);
5094 boolean score_is_equal = (new_entry->score == entry->score);
5095 boolean time_is_better = (new_entry->time < entry->time);
5096 boolean time_is_equal = (new_entry->time == entry->time);
5097 boolean better_by_score = (score_is_better ||
5098 (score_is_equal && time_is_better));
5099 boolean better_by_time = (time_is_better ||
5100 (time_is_equal && score_is_better));
5101 boolean is_better = (level.rate_time_over_score ? better_by_time :
5103 boolean entry_is_empty = (entry->score == 0 &&
5106 // prevent adding server score entries if also existing in local score file
5107 // (special case: historic score entries have an empty tape basename entry)
5108 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5109 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5111 // special case: use server score instead of local score value if higher
5112 // (historic scores might have been truncated to 16-bit values locally)
5113 if (score_is_better)
5114 entry->score = new_entry->score;
5119 if (is_better || entry_is_empty)
5121 // player has made it to the hall of fame
5123 if (i < MAX_SCORE_ENTRIES - 1)
5125 int m = MAX_SCORE_ENTRIES - 1;
5128 if (one_score_entry_per_name)
5130 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5131 if (strEqual(list->entry[l].name, new_entry->name))
5134 if (m == i) // player's new highscore overwrites his old one
5138 for (l = m; l > i; l--)
5139 list->entry[l] = list->entry[l - 1];
5144 *entry = *new_entry;
5148 else if (one_score_entry_per_name &&
5149 strEqual(entry->name, new_entry->name))
5151 // player already in high score list with better score or time
5157 // special case: new score is beyond the last high score list position
5158 return MAX_SCORE_ENTRIES;
5161 void NewHighScore(int level_nr, boolean tape_saved)
5163 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5164 boolean one_per_name = FALSE;
5166 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5167 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5169 new_entry.score = game.score_final;
5170 new_entry.time = game.score_time_final;
5172 LoadScore(level_nr);
5174 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5176 if (scores.last_added >= MAX_SCORE_ENTRIES)
5178 scores.last_added = MAX_SCORE_ENTRIES - 1;
5179 scores.force_last_added = TRUE;
5181 scores.entry[scores.last_added] = new_entry;
5183 // store last added local score entry (before merging server scores)
5184 scores.last_added_local = scores.last_added;
5189 if (scores.last_added < 0)
5192 SaveScore(level_nr);
5194 // store last added local score entry (before merging server scores)
5195 scores.last_added_local = scores.last_added;
5197 if (!game.LevelSolved_SaveTape)
5200 SaveScoreTape(level_nr);
5202 if (setup.ask_for_using_api_server)
5204 setup.use_api_server =
5205 Request("Upload your score and tape to the high score server?", REQ_ASK);
5207 if (!setup.use_api_server)
5208 Request("Not using high score server! Use setup menu to enable again!",
5211 runtime.use_api_server = setup.use_api_server;
5213 // after asking for using API server once, do not ask again
5214 setup.ask_for_using_api_server = FALSE;
5216 SaveSetup_ServerSetup();
5219 SaveServerScore(level_nr, tape_saved);
5222 void MergeServerScore(void)
5224 struct ScoreEntry last_added_entry;
5225 boolean one_per_name = FALSE;
5228 if (scores.last_added >= 0)
5229 last_added_entry = scores.entry[scores.last_added];
5231 for (i = 0; i < server_scores.num_entries; i++)
5233 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5235 if (pos >= 0 && pos <= scores.last_added)
5236 scores.last_added++;
5239 if (scores.last_added >= MAX_SCORE_ENTRIES)
5241 scores.last_added = MAX_SCORE_ENTRIES - 1;
5242 scores.force_last_added = TRUE;
5244 scores.entry[scores.last_added] = last_added_entry;
5248 static int getElementMoveStepsizeExt(int x, int y, int direction)
5250 int element = Tile[x][y];
5251 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5252 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5253 int horiz_move = (dx != 0);
5254 int sign = (horiz_move ? dx : dy);
5255 int step = sign * element_info[element].move_stepsize;
5257 // special values for move stepsize for spring and things on conveyor belt
5260 if (CAN_FALL(element) &&
5261 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5262 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5263 else if (element == EL_SPRING)
5264 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5270 static int getElementMoveStepsize(int x, int y)
5272 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5275 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5277 if (player->GfxAction != action || player->GfxDir != dir)
5279 player->GfxAction = action;
5280 player->GfxDir = dir;
5282 player->StepFrame = 0;
5286 static void ResetGfxFrame(int x, int y)
5288 // profiling showed that "autotest" spends 10~20% of its time in this function
5289 if (DrawingDeactivatedField())
5292 int element = Tile[x][y];
5293 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5295 if (graphic_info[graphic].anim_global_sync)
5296 GfxFrame[x][y] = FrameCounter;
5297 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5298 GfxFrame[x][y] = CustomValue[x][y];
5299 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5300 GfxFrame[x][y] = element_info[element].collect_score;
5301 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5302 GfxFrame[x][y] = ChangeDelay[x][y];
5305 static void ResetGfxAnimation(int x, int y)
5307 GfxAction[x][y] = ACTION_DEFAULT;
5308 GfxDir[x][y] = MovDir[x][y];
5311 ResetGfxFrame(x, y);
5314 static void ResetRandomAnimationValue(int x, int y)
5316 GfxRandom[x][y] = INIT_GFX_RANDOM();
5319 static void InitMovingField(int x, int y, int direction)
5321 int element = Tile[x][y];
5322 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5323 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5326 boolean is_moving_before, is_moving_after;
5328 // check if element was/is moving or being moved before/after mode change
5329 is_moving_before = (WasJustMoving[x][y] != 0);
5330 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5332 // reset animation only for moving elements which change direction of moving
5333 // or which just started or stopped moving
5334 // (else CEs with property "can move" / "not moving" are reset each frame)
5335 if (is_moving_before != is_moving_after ||
5336 direction != MovDir[x][y])
5337 ResetGfxAnimation(x, y);
5339 MovDir[x][y] = direction;
5340 GfxDir[x][y] = direction;
5342 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5343 direction == MV_DOWN && CAN_FALL(element) ?
5344 ACTION_FALLING : ACTION_MOVING);
5346 // this is needed for CEs with property "can move" / "not moving"
5348 if (is_moving_after)
5350 if (Tile[newx][newy] == EL_EMPTY)
5351 Tile[newx][newy] = EL_BLOCKED;
5353 MovDir[newx][newy] = MovDir[x][y];
5355 CustomValue[newx][newy] = CustomValue[x][y];
5357 GfxFrame[newx][newy] = GfxFrame[x][y];
5358 GfxRandom[newx][newy] = GfxRandom[x][y];
5359 GfxAction[newx][newy] = GfxAction[x][y];
5360 GfxDir[newx][newy] = GfxDir[x][y];
5364 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5366 int direction = MovDir[x][y];
5367 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5368 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5374 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5376 int oldx = x, oldy = y;
5377 int direction = MovDir[x][y];
5379 if (direction == MV_LEFT)
5381 else if (direction == MV_RIGHT)
5383 else if (direction == MV_UP)
5385 else if (direction == MV_DOWN)
5388 *comes_from_x = oldx;
5389 *comes_from_y = oldy;
5392 static int MovingOrBlocked2Element(int x, int y)
5394 int element = Tile[x][y];
5396 if (element == EL_BLOCKED)
5400 Blocked2Moving(x, y, &oldx, &oldy);
5401 return Tile[oldx][oldy];
5407 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5409 // like MovingOrBlocked2Element(), but if element is moving
5410 // and (x,y) is the field the moving element is just leaving,
5411 // return EL_BLOCKED instead of the element value
5412 int element = Tile[x][y];
5414 if (IS_MOVING(x, y))
5416 if (element == EL_BLOCKED)
5420 Blocked2Moving(x, y, &oldx, &oldy);
5421 return Tile[oldx][oldy];
5430 static void RemoveField(int x, int y)
5432 Tile[x][y] = EL_EMPTY;
5438 CustomValue[x][y] = 0;
5441 ChangeDelay[x][y] = 0;
5442 ChangePage[x][y] = -1;
5443 Pushed[x][y] = FALSE;
5445 GfxElement[x][y] = EL_UNDEFINED;
5446 GfxAction[x][y] = ACTION_DEFAULT;
5447 GfxDir[x][y] = MV_NONE;
5450 static void RemoveMovingField(int x, int y)
5452 int oldx = x, oldy = y, newx = x, newy = y;
5453 int element = Tile[x][y];
5454 int next_element = EL_UNDEFINED;
5456 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5459 if (IS_MOVING(x, y))
5461 Moving2Blocked(x, y, &newx, &newy);
5463 if (Tile[newx][newy] != EL_BLOCKED)
5465 // element is moving, but target field is not free (blocked), but
5466 // already occupied by something different (example: acid pool);
5467 // in this case, only remove the moving field, but not the target
5469 RemoveField(oldx, oldy);
5471 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5473 TEST_DrawLevelField(oldx, oldy);
5478 else if (element == EL_BLOCKED)
5480 Blocked2Moving(x, y, &oldx, &oldy);
5481 if (!IS_MOVING(oldx, oldy))
5485 if (element == EL_BLOCKED &&
5486 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5487 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5488 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5489 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5490 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5491 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5492 next_element = get_next_element(Tile[oldx][oldy]);
5494 RemoveField(oldx, oldy);
5495 RemoveField(newx, newy);
5497 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5499 if (next_element != EL_UNDEFINED)
5500 Tile[oldx][oldy] = next_element;
5502 TEST_DrawLevelField(oldx, oldy);
5503 TEST_DrawLevelField(newx, newy);
5506 void DrawDynamite(int x, int y)
5508 int sx = SCREENX(x), sy = SCREENY(y);
5509 int graphic = el2img(Tile[x][y]);
5512 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5515 if (IS_WALKABLE_INSIDE(Back[x][y]))
5519 DrawLevelElement(x, y, Back[x][y]);
5520 else if (Store[x][y])
5521 DrawLevelElement(x, y, Store[x][y]);
5522 else if (game.use_masked_elements)
5523 DrawLevelElement(x, y, EL_EMPTY);
5525 frame = getGraphicAnimationFrameXY(graphic, x, y);
5527 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5528 DrawGraphicThruMask(sx, sy, graphic, frame);
5530 DrawGraphic(sx, sy, graphic, frame);
5533 static void CheckDynamite(int x, int y)
5535 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5539 if (MovDelay[x][y] != 0)
5542 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5548 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5553 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5555 boolean num_checked_players = 0;
5558 for (i = 0; i < MAX_PLAYERS; i++)
5560 if (stored_player[i].active)
5562 int sx = stored_player[i].jx;
5563 int sy = stored_player[i].jy;
5565 if (num_checked_players == 0)
5572 *sx1 = MIN(*sx1, sx);
5573 *sy1 = MIN(*sy1, sy);
5574 *sx2 = MAX(*sx2, sx);
5575 *sy2 = MAX(*sy2, sy);
5578 num_checked_players++;
5583 static boolean checkIfAllPlayersFitToScreen_RND(void)
5585 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5587 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5589 return (sx2 - sx1 < SCR_FIELDX &&
5590 sy2 - sy1 < SCR_FIELDY);
5593 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5595 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5597 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5599 *sx = (sx1 + sx2) / 2;
5600 *sy = (sy1 + sy2) / 2;
5603 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5604 boolean center_screen, boolean quick_relocation)
5606 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5607 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5608 boolean no_delay = (tape.warp_forward);
5609 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5610 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5611 int new_scroll_x, new_scroll_y;
5613 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5615 // case 1: quick relocation inside visible screen (without scrolling)
5622 if (!level.shifted_relocation || center_screen)
5624 // relocation _with_ centering of screen
5626 new_scroll_x = SCROLL_POSITION_X(x);
5627 new_scroll_y = SCROLL_POSITION_Y(y);
5631 // relocation _without_ centering of screen
5633 int center_scroll_x = SCROLL_POSITION_X(old_x);
5634 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5635 int offset_x = x + (scroll_x - center_scroll_x);
5636 int offset_y = y + (scroll_y - center_scroll_y);
5638 // for new screen position, apply previous offset to center position
5639 new_scroll_x = SCROLL_POSITION_X(offset_x);
5640 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5643 if (quick_relocation)
5645 // case 2: quick relocation (redraw without visible scrolling)
5647 scroll_x = new_scroll_x;
5648 scroll_y = new_scroll_y;
5655 // case 3: visible relocation (with scrolling to new position)
5657 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5659 SetVideoFrameDelay(wait_delay_value);
5661 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5663 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5664 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5666 if (dx == 0 && dy == 0) // no scrolling needed at all
5672 // set values for horizontal/vertical screen scrolling (half tile size)
5673 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5674 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5675 int pos_x = dx * TILEX / 2;
5676 int pos_y = dy * TILEY / 2;
5677 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5678 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5680 ScrollLevel(dx, dy);
5683 // scroll in two steps of half tile size to make things smoother
5684 BlitScreenToBitmapExt_RND(window, fx, fy);
5686 // scroll second step to align at full tile size
5687 BlitScreenToBitmap(window);
5693 SetVideoFrameDelay(frame_delay_value_old);
5696 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5698 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5699 int player_nr = GET_PLAYER_NR(el_player);
5700 struct PlayerInfo *player = &stored_player[player_nr];
5701 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5702 boolean no_delay = (tape.warp_forward);
5703 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5704 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5705 int old_jx = player->jx;
5706 int old_jy = player->jy;
5707 int old_element = Tile[old_jx][old_jy];
5708 int element = Tile[jx][jy];
5709 boolean player_relocated = (old_jx != jx || old_jy != jy);
5711 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5712 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5713 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5714 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5715 int leave_side_horiz = move_dir_horiz;
5716 int leave_side_vert = move_dir_vert;
5717 int enter_side = enter_side_horiz | enter_side_vert;
5718 int leave_side = leave_side_horiz | leave_side_vert;
5720 if (player->buried) // do not reanimate dead player
5723 if (!player_relocated) // no need to relocate the player
5726 if (IS_PLAYER(jx, jy)) // player already placed at new position
5728 RemoveField(jx, jy); // temporarily remove newly placed player
5729 DrawLevelField(jx, jy);
5732 if (player->present)
5734 while (player->MovPos)
5736 ScrollPlayer(player, SCROLL_GO_ON);
5737 ScrollScreen(NULL, SCROLL_GO_ON);
5739 AdvanceFrameAndPlayerCounters(player->index_nr);
5743 BackToFront_WithFrameDelay(wait_delay_value);
5746 DrawPlayer(player); // needed here only to cleanup last field
5747 DrawLevelField(player->jx, player->jy); // remove player graphic
5749 player->is_moving = FALSE;
5752 if (IS_CUSTOM_ELEMENT(old_element))
5753 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5755 player->index_bit, leave_side);
5757 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5759 player->index_bit, leave_side);
5761 Tile[jx][jy] = el_player;
5762 InitPlayerField(jx, jy, el_player, TRUE);
5764 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5765 possible that the relocation target field did not contain a player element,
5766 but a walkable element, to which the new player was relocated -- in this
5767 case, restore that (already initialized!) element on the player field */
5768 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5770 Tile[jx][jy] = element; // restore previously existing element
5773 // only visually relocate centered player
5774 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5775 FALSE, level.instant_relocation);
5777 TestIfPlayerTouchesBadThing(jx, jy);
5778 TestIfPlayerTouchesCustomElement(jx, jy);
5780 if (IS_CUSTOM_ELEMENT(element))
5781 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5782 player->index_bit, enter_side);
5784 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5785 player->index_bit, enter_side);
5787 if (player->is_switching)
5789 /* ensure that relocation while still switching an element does not cause
5790 a new element to be treated as also switched directly after relocation
5791 (this is important for teleporter switches that teleport the player to
5792 a place where another teleporter switch is in the same direction, which
5793 would then incorrectly be treated as immediately switched before the
5794 direction key that caused the switch was released) */
5796 player->switch_x += jx - old_jx;
5797 player->switch_y += jy - old_jy;
5801 static void Explode(int ex, int ey, int phase, int mode)
5807 // !!! eliminate this variable !!!
5808 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5810 if (game.explosions_delayed)
5812 ExplodeField[ex][ey] = mode;
5816 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5818 int center_element = Tile[ex][ey];
5819 int artwork_element, explosion_element; // set these values later
5821 // remove things displayed in background while burning dynamite
5822 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5825 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5827 // put moving element to center field (and let it explode there)
5828 center_element = MovingOrBlocked2Element(ex, ey);
5829 RemoveMovingField(ex, ey);
5830 Tile[ex][ey] = center_element;
5833 // now "center_element" is finally determined -- set related values now
5834 artwork_element = center_element; // for custom player artwork
5835 explosion_element = center_element; // for custom player artwork
5837 if (IS_PLAYER(ex, ey))
5839 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5841 artwork_element = stored_player[player_nr].artwork_element;
5843 if (level.use_explosion_element[player_nr])
5845 explosion_element = level.explosion_element[player_nr];
5846 artwork_element = explosion_element;
5850 if (mode == EX_TYPE_NORMAL ||
5851 mode == EX_TYPE_CENTER ||
5852 mode == EX_TYPE_CROSS)
5853 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5855 last_phase = element_info[explosion_element].explosion_delay + 1;
5857 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5859 int xx = x - ex + 1;
5860 int yy = y - ey + 1;
5863 if (!IN_LEV_FIELD(x, y) ||
5864 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5865 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5868 element = Tile[x][y];
5870 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5872 element = MovingOrBlocked2Element(x, y);
5874 if (!IS_EXPLOSION_PROOF(element))
5875 RemoveMovingField(x, y);
5878 // indestructible elements can only explode in center (but not flames)
5879 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5880 mode == EX_TYPE_BORDER)) ||
5881 element == EL_FLAMES)
5884 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5885 behaviour, for example when touching a yamyam that explodes to rocks
5886 with active deadly shield, a rock is created under the player !!! */
5887 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5889 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5890 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5891 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5893 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5896 if (IS_ACTIVE_BOMB(element))
5898 // re-activate things under the bomb like gate or penguin
5899 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5906 // save walkable background elements while explosion on same tile
5907 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5908 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5909 Back[x][y] = element;
5911 // ignite explodable elements reached by other explosion
5912 if (element == EL_EXPLOSION)
5913 element = Store2[x][y];
5915 if (AmoebaNr[x][y] &&
5916 (element == EL_AMOEBA_FULL ||
5917 element == EL_BD_AMOEBA ||
5918 element == EL_AMOEBA_GROWING))
5920 AmoebaCnt[AmoebaNr[x][y]]--;
5921 AmoebaCnt2[AmoebaNr[x][y]]--;
5926 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5928 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5930 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5932 if (PLAYERINFO(ex, ey)->use_murphy)
5933 Store[x][y] = EL_EMPTY;
5936 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5937 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5938 else if (IS_PLAYER_ELEMENT(center_element))
5939 Store[x][y] = EL_EMPTY;
5940 else if (center_element == EL_YAMYAM)
5941 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5942 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5943 Store[x][y] = element_info[center_element].content.e[xx][yy];
5945 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5946 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5947 // otherwise) -- FIX THIS !!!
5948 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5949 Store[x][y] = element_info[element].content.e[1][1];
5951 else if (!CAN_EXPLODE(element))
5952 Store[x][y] = element_info[element].content.e[1][1];
5955 Store[x][y] = EL_EMPTY;
5957 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5958 center_element == EL_AMOEBA_TO_DIAMOND)
5959 Store2[x][y] = element;
5961 Tile[x][y] = EL_EXPLOSION;
5962 GfxElement[x][y] = artwork_element;
5964 ExplodePhase[x][y] = 1;
5965 ExplodeDelay[x][y] = last_phase;
5970 if (center_element == EL_YAMYAM)
5971 game.yamyam_content_nr =
5972 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5984 GfxFrame[x][y] = 0; // restart explosion animation
5986 last_phase = ExplodeDelay[x][y];
5988 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5990 // this can happen if the player leaves an explosion just in time
5991 if (GfxElement[x][y] == EL_UNDEFINED)
5992 GfxElement[x][y] = EL_EMPTY;
5994 border_element = Store2[x][y];
5995 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5996 border_element = StorePlayer[x][y];
5998 if (phase == element_info[border_element].ignition_delay ||
5999 phase == last_phase)
6001 boolean border_explosion = FALSE;
6003 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6004 !PLAYER_EXPLOSION_PROTECTED(x, y))
6006 KillPlayerUnlessExplosionProtected(x, y);
6007 border_explosion = TRUE;
6009 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6011 Tile[x][y] = Store2[x][y];
6014 border_explosion = TRUE;
6016 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6018 AmoebaToDiamond(x, y);
6020 border_explosion = TRUE;
6023 // if an element just explodes due to another explosion (chain-reaction),
6024 // do not immediately end the new explosion when it was the last frame of
6025 // the explosion (as it would be done in the following "if"-statement!)
6026 if (border_explosion && phase == last_phase)
6030 // this can happen if the player was just killed by an explosion
6031 if (GfxElement[x][y] == EL_UNDEFINED)
6032 GfxElement[x][y] = EL_EMPTY;
6034 if (phase == last_phase)
6038 element = Tile[x][y] = Store[x][y];
6039 Store[x][y] = Store2[x][y] = 0;
6040 GfxElement[x][y] = EL_UNDEFINED;
6042 // player can escape from explosions and might therefore be still alive
6043 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6044 element <= EL_PLAYER_IS_EXPLODING_4)
6046 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6047 int explosion_element = EL_PLAYER_1 + player_nr;
6048 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6049 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6051 if (level.use_explosion_element[player_nr])
6052 explosion_element = level.explosion_element[player_nr];
6054 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6055 element_info[explosion_element].content.e[xx][yy]);
6058 // restore probably existing indestructible background element
6059 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6060 element = Tile[x][y] = Back[x][y];
6063 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6064 GfxDir[x][y] = MV_NONE;
6065 ChangeDelay[x][y] = 0;
6066 ChangePage[x][y] = -1;
6068 CustomValue[x][y] = 0;
6070 InitField_WithBug2(x, y, FALSE);
6072 TEST_DrawLevelField(x, y);
6074 TestIfElementTouchesCustomElement(x, y);
6076 if (GFX_CRUMBLED(element))
6077 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6079 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6080 StorePlayer[x][y] = 0;
6082 if (IS_PLAYER_ELEMENT(element))
6083 RelocatePlayer(x, y, element);
6085 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6087 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6088 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6091 TEST_DrawLevelFieldCrumbled(x, y);
6093 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6095 DrawLevelElement(x, y, Back[x][y]);
6096 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6098 else if (IS_WALKABLE_UNDER(Back[x][y]))
6100 DrawLevelGraphic(x, y, graphic, frame);
6101 DrawLevelElementThruMask(x, y, Back[x][y]);
6103 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6104 DrawLevelGraphic(x, y, graphic, frame);
6108 static void DynaExplode(int ex, int ey)
6111 int dynabomb_element = Tile[ex][ey];
6112 int dynabomb_size = 1;
6113 boolean dynabomb_xl = FALSE;
6114 struct PlayerInfo *player;
6115 static int xy[4][2] =
6123 if (IS_ACTIVE_BOMB(dynabomb_element))
6125 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6126 dynabomb_size = player->dynabomb_size;
6127 dynabomb_xl = player->dynabomb_xl;
6128 player->dynabombs_left++;
6131 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6133 for (i = 0; i < NUM_DIRECTIONS; i++)
6135 for (j = 1; j <= dynabomb_size; j++)
6137 int x = ex + j * xy[i][0];
6138 int y = ey + j * xy[i][1];
6141 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6144 element = Tile[x][y];
6146 // do not restart explosions of fields with active bombs
6147 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6150 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6152 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6153 !IS_DIGGABLE(element) && !dynabomb_xl)
6159 void Bang(int x, int y)
6161 int element = MovingOrBlocked2Element(x, y);
6162 int explosion_type = EX_TYPE_NORMAL;
6164 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6166 struct PlayerInfo *player = PLAYERINFO(x, y);
6168 element = Tile[x][y] = player->initial_element;
6170 if (level.use_explosion_element[player->index_nr])
6172 int explosion_element = level.explosion_element[player->index_nr];
6174 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6175 explosion_type = EX_TYPE_CROSS;
6176 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6177 explosion_type = EX_TYPE_CENTER;
6185 case EL_BD_BUTTERFLY:
6188 case EL_DARK_YAMYAM:
6192 RaiseScoreElement(element);
6195 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6196 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6197 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6198 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6199 case EL_DYNABOMB_INCREASE_NUMBER:
6200 case EL_DYNABOMB_INCREASE_SIZE:
6201 case EL_DYNABOMB_INCREASE_POWER:
6202 explosion_type = EX_TYPE_DYNA;
6205 case EL_DC_LANDMINE:
6206 explosion_type = EX_TYPE_CENTER;
6211 case EL_LAMP_ACTIVE:
6212 case EL_AMOEBA_TO_DIAMOND:
6213 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6214 explosion_type = EX_TYPE_CENTER;
6218 if (element_info[element].explosion_type == EXPLODES_CROSS)
6219 explosion_type = EX_TYPE_CROSS;
6220 else if (element_info[element].explosion_type == EXPLODES_1X1)
6221 explosion_type = EX_TYPE_CENTER;
6225 if (explosion_type == EX_TYPE_DYNA)
6228 Explode(x, y, EX_PHASE_START, explosion_type);
6230 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6233 static void SplashAcid(int x, int y)
6235 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6236 (!IN_LEV_FIELD(x - 1, y - 2) ||
6237 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6238 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6240 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6241 (!IN_LEV_FIELD(x + 1, y - 2) ||
6242 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6243 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6245 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6248 static void InitBeltMovement(void)
6250 static int belt_base_element[4] =
6252 EL_CONVEYOR_BELT_1_LEFT,
6253 EL_CONVEYOR_BELT_2_LEFT,
6254 EL_CONVEYOR_BELT_3_LEFT,
6255 EL_CONVEYOR_BELT_4_LEFT
6257 static int belt_base_active_element[4] =
6259 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6260 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6261 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6262 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6267 // set frame order for belt animation graphic according to belt direction
6268 for (i = 0; i < NUM_BELTS; i++)
6272 for (j = 0; j < NUM_BELT_PARTS; j++)
6274 int element = belt_base_active_element[belt_nr] + j;
6275 int graphic_1 = el2img(element);
6276 int graphic_2 = el2panelimg(element);
6278 if (game.belt_dir[i] == MV_LEFT)
6280 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6281 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6285 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6286 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6291 SCAN_PLAYFIELD(x, y)
6293 int element = Tile[x][y];
6295 for (i = 0; i < NUM_BELTS; i++)
6297 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6299 int e_belt_nr = getBeltNrFromBeltElement(element);
6302 if (e_belt_nr == belt_nr)
6304 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6306 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6313 static void ToggleBeltSwitch(int x, int y)
6315 static int belt_base_element[4] =
6317 EL_CONVEYOR_BELT_1_LEFT,
6318 EL_CONVEYOR_BELT_2_LEFT,
6319 EL_CONVEYOR_BELT_3_LEFT,
6320 EL_CONVEYOR_BELT_4_LEFT
6322 static int belt_base_active_element[4] =
6324 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6325 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6326 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6327 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6329 static int belt_base_switch_element[4] =
6331 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6332 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6333 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6334 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6336 static int belt_move_dir[4] =
6344 int element = Tile[x][y];
6345 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6346 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6347 int belt_dir = belt_move_dir[belt_dir_nr];
6350 if (!IS_BELT_SWITCH(element))
6353 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6354 game.belt_dir[belt_nr] = belt_dir;
6356 if (belt_dir_nr == 3)
6359 // set frame order for belt animation graphic according to belt direction
6360 for (i = 0; i < NUM_BELT_PARTS; i++)
6362 int element = belt_base_active_element[belt_nr] + i;
6363 int graphic_1 = el2img(element);
6364 int graphic_2 = el2panelimg(element);
6366 if (belt_dir == MV_LEFT)
6368 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6369 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6373 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6374 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6378 SCAN_PLAYFIELD(xx, yy)
6380 int element = Tile[xx][yy];
6382 if (IS_BELT_SWITCH(element))
6384 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6386 if (e_belt_nr == belt_nr)
6388 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6389 TEST_DrawLevelField(xx, yy);
6392 else if (IS_BELT(element) && belt_dir != MV_NONE)
6394 int e_belt_nr = getBeltNrFromBeltElement(element);
6396 if (e_belt_nr == belt_nr)
6398 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6400 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6401 TEST_DrawLevelField(xx, yy);
6404 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6406 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6408 if (e_belt_nr == belt_nr)
6410 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6412 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6413 TEST_DrawLevelField(xx, yy);
6419 static void ToggleSwitchgateSwitch(int x, int y)
6423 game.switchgate_pos = !game.switchgate_pos;
6425 SCAN_PLAYFIELD(xx, yy)
6427 int element = Tile[xx][yy];
6429 if (element == EL_SWITCHGATE_SWITCH_UP)
6431 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6432 TEST_DrawLevelField(xx, yy);
6434 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6436 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6437 TEST_DrawLevelField(xx, yy);
6439 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6441 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6442 TEST_DrawLevelField(xx, yy);
6444 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6446 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6447 TEST_DrawLevelField(xx, yy);
6449 else if (element == EL_SWITCHGATE_OPEN ||
6450 element == EL_SWITCHGATE_OPENING)
6452 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6454 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6456 else if (element == EL_SWITCHGATE_CLOSED ||
6457 element == EL_SWITCHGATE_CLOSING)
6459 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6461 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6466 static int getInvisibleActiveFromInvisibleElement(int element)
6468 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6469 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6470 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6474 static int getInvisibleFromInvisibleActiveElement(int element)
6476 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6477 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6478 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6482 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6486 SCAN_PLAYFIELD(x, y)
6488 int element = Tile[x][y];
6490 if (element == EL_LIGHT_SWITCH &&
6491 game.light_time_left > 0)
6493 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6494 TEST_DrawLevelField(x, y);
6496 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6497 game.light_time_left == 0)
6499 Tile[x][y] = EL_LIGHT_SWITCH;
6500 TEST_DrawLevelField(x, y);
6502 else if (element == EL_EMC_DRIPPER &&
6503 game.light_time_left > 0)
6505 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6506 TEST_DrawLevelField(x, y);
6508 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6509 game.light_time_left == 0)
6511 Tile[x][y] = EL_EMC_DRIPPER;
6512 TEST_DrawLevelField(x, y);
6514 else if (element == EL_INVISIBLE_STEELWALL ||
6515 element == EL_INVISIBLE_WALL ||
6516 element == EL_INVISIBLE_SAND)
6518 if (game.light_time_left > 0)
6519 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6521 TEST_DrawLevelField(x, y);
6523 // uncrumble neighbour fields, if needed
6524 if (element == EL_INVISIBLE_SAND)
6525 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6527 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6528 element == EL_INVISIBLE_WALL_ACTIVE ||
6529 element == EL_INVISIBLE_SAND_ACTIVE)
6531 if (game.light_time_left == 0)
6532 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6534 TEST_DrawLevelField(x, y);
6536 // re-crumble neighbour fields, if needed
6537 if (element == EL_INVISIBLE_SAND)
6538 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6543 static void RedrawAllInvisibleElementsForLenses(void)
6547 SCAN_PLAYFIELD(x, y)
6549 int element = Tile[x][y];
6551 if (element == EL_EMC_DRIPPER &&
6552 game.lenses_time_left > 0)
6554 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6555 TEST_DrawLevelField(x, y);
6557 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6558 game.lenses_time_left == 0)
6560 Tile[x][y] = EL_EMC_DRIPPER;
6561 TEST_DrawLevelField(x, y);
6563 else if (element == EL_INVISIBLE_STEELWALL ||
6564 element == EL_INVISIBLE_WALL ||
6565 element == EL_INVISIBLE_SAND)
6567 if (game.lenses_time_left > 0)
6568 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6570 TEST_DrawLevelField(x, y);
6572 // uncrumble neighbour fields, if needed
6573 if (element == EL_INVISIBLE_SAND)
6574 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6576 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6577 element == EL_INVISIBLE_WALL_ACTIVE ||
6578 element == EL_INVISIBLE_SAND_ACTIVE)
6580 if (game.lenses_time_left == 0)
6581 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6583 TEST_DrawLevelField(x, y);
6585 // re-crumble neighbour fields, if needed
6586 if (element == EL_INVISIBLE_SAND)
6587 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6592 static void RedrawAllInvisibleElementsForMagnifier(void)
6596 SCAN_PLAYFIELD(x, y)
6598 int element = Tile[x][y];
6600 if (element == EL_EMC_FAKE_GRASS &&
6601 game.magnify_time_left > 0)
6603 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6604 TEST_DrawLevelField(x, y);
6606 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6607 game.magnify_time_left == 0)
6609 Tile[x][y] = EL_EMC_FAKE_GRASS;
6610 TEST_DrawLevelField(x, y);
6612 else if (IS_GATE_GRAY(element) &&
6613 game.magnify_time_left > 0)
6615 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6616 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6617 IS_EM_GATE_GRAY(element) ?
6618 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6619 IS_EMC_GATE_GRAY(element) ?
6620 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6621 IS_DC_GATE_GRAY(element) ?
6622 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6624 TEST_DrawLevelField(x, y);
6626 else if (IS_GATE_GRAY_ACTIVE(element) &&
6627 game.magnify_time_left == 0)
6629 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6630 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6631 IS_EM_GATE_GRAY_ACTIVE(element) ?
6632 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6633 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6634 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6635 IS_DC_GATE_GRAY_ACTIVE(element) ?
6636 EL_DC_GATE_WHITE_GRAY :
6638 TEST_DrawLevelField(x, y);
6643 static void ToggleLightSwitch(int x, int y)
6645 int element = Tile[x][y];
6647 game.light_time_left =
6648 (element == EL_LIGHT_SWITCH ?
6649 level.time_light * FRAMES_PER_SECOND : 0);
6651 RedrawAllLightSwitchesAndInvisibleElements();
6654 static void ActivateTimegateSwitch(int x, int y)
6658 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6660 SCAN_PLAYFIELD(xx, yy)
6662 int element = Tile[xx][yy];
6664 if (element == EL_TIMEGATE_CLOSED ||
6665 element == EL_TIMEGATE_CLOSING)
6667 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6668 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6672 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6674 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6675 TEST_DrawLevelField(xx, yy);
6681 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6682 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6685 static void Impact(int x, int y)
6687 boolean last_line = (y == lev_fieldy - 1);
6688 boolean object_hit = FALSE;
6689 boolean impact = (last_line || object_hit);
6690 int element = Tile[x][y];
6691 int smashed = EL_STEELWALL;
6693 if (!last_line) // check if element below was hit
6695 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6698 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6699 MovDir[x][y + 1] != MV_DOWN ||
6700 MovPos[x][y + 1] <= TILEY / 2));
6702 // do not smash moving elements that left the smashed field in time
6703 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6704 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6707 #if USE_QUICKSAND_IMPACT_BUGFIX
6708 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6710 RemoveMovingField(x, y + 1);
6711 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6712 Tile[x][y + 2] = EL_ROCK;
6713 TEST_DrawLevelField(x, y + 2);
6718 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6720 RemoveMovingField(x, y + 1);
6721 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6722 Tile[x][y + 2] = EL_ROCK;
6723 TEST_DrawLevelField(x, y + 2);
6730 smashed = MovingOrBlocked2Element(x, y + 1);
6732 impact = (last_line || object_hit);
6735 if (!last_line && smashed == EL_ACID) // element falls into acid
6737 SplashAcid(x, y + 1);
6741 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6742 // only reset graphic animation if graphic really changes after impact
6744 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6746 ResetGfxAnimation(x, y);
6747 TEST_DrawLevelField(x, y);
6750 if (impact && CAN_EXPLODE_IMPACT(element))
6755 else if (impact && element == EL_PEARL &&
6756 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6758 ResetGfxAnimation(x, y);
6760 Tile[x][y] = EL_PEARL_BREAKING;
6761 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6764 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6766 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6771 if (impact && element == EL_AMOEBA_DROP)
6773 if (object_hit && IS_PLAYER(x, y + 1))
6774 KillPlayerUnlessEnemyProtected(x, y + 1);
6775 else if (object_hit && smashed == EL_PENGUIN)
6779 Tile[x][y] = EL_AMOEBA_GROWING;
6780 Store[x][y] = EL_AMOEBA_WET;
6782 ResetRandomAnimationValue(x, y);
6787 if (object_hit) // check which object was hit
6789 if ((CAN_PASS_MAGIC_WALL(element) &&
6790 (smashed == EL_MAGIC_WALL ||
6791 smashed == EL_BD_MAGIC_WALL)) ||
6792 (CAN_PASS_DC_MAGIC_WALL(element) &&
6793 smashed == EL_DC_MAGIC_WALL))
6796 int activated_magic_wall =
6797 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6798 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6799 EL_DC_MAGIC_WALL_ACTIVE);
6801 // activate magic wall / mill
6802 SCAN_PLAYFIELD(xx, yy)
6804 if (Tile[xx][yy] == smashed)
6805 Tile[xx][yy] = activated_magic_wall;
6808 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6809 game.magic_wall_active = TRUE;
6811 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6812 SND_MAGIC_WALL_ACTIVATING :
6813 smashed == EL_BD_MAGIC_WALL ?
6814 SND_BD_MAGIC_WALL_ACTIVATING :
6815 SND_DC_MAGIC_WALL_ACTIVATING));
6818 if (IS_PLAYER(x, y + 1))
6820 if (CAN_SMASH_PLAYER(element))
6822 KillPlayerUnlessEnemyProtected(x, y + 1);
6826 else if (smashed == EL_PENGUIN)
6828 if (CAN_SMASH_PLAYER(element))
6834 else if (element == EL_BD_DIAMOND)
6836 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6842 else if (((element == EL_SP_INFOTRON ||
6843 element == EL_SP_ZONK) &&
6844 (smashed == EL_SP_SNIKSNAK ||
6845 smashed == EL_SP_ELECTRON ||
6846 smashed == EL_SP_DISK_ORANGE)) ||
6847 (element == EL_SP_INFOTRON &&
6848 smashed == EL_SP_DISK_YELLOW))
6853 else if (CAN_SMASH_EVERYTHING(element))
6855 if (IS_CLASSIC_ENEMY(smashed) ||
6856 CAN_EXPLODE_SMASHED(smashed))
6861 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6863 if (smashed == EL_LAMP ||
6864 smashed == EL_LAMP_ACTIVE)
6869 else if (smashed == EL_NUT)
6871 Tile[x][y + 1] = EL_NUT_BREAKING;
6872 PlayLevelSound(x, y, SND_NUT_BREAKING);
6873 RaiseScoreElement(EL_NUT);
6876 else if (smashed == EL_PEARL)
6878 ResetGfxAnimation(x, y);
6880 Tile[x][y + 1] = EL_PEARL_BREAKING;
6881 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6884 else if (smashed == EL_DIAMOND)
6886 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6887 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6890 else if (IS_BELT_SWITCH(smashed))
6892 ToggleBeltSwitch(x, y + 1);
6894 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6895 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6896 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6897 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6899 ToggleSwitchgateSwitch(x, y + 1);
6901 else if (smashed == EL_LIGHT_SWITCH ||
6902 smashed == EL_LIGHT_SWITCH_ACTIVE)
6904 ToggleLightSwitch(x, y + 1);
6908 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6910 CheckElementChangeBySide(x, y + 1, smashed, element,
6911 CE_SWITCHED, CH_SIDE_TOP);
6912 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6918 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6923 // play sound of magic wall / mill
6925 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6926 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6927 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6929 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6930 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6931 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6932 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6933 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6934 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6939 // play sound of object that hits the ground
6940 if (last_line || object_hit)
6941 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6944 static void TurnRoundExt(int x, int y)
6956 { 0, 0 }, { 0, 0 }, { 0, 0 },
6961 int left, right, back;
6965 { MV_DOWN, MV_UP, MV_RIGHT },
6966 { MV_UP, MV_DOWN, MV_LEFT },
6968 { MV_LEFT, MV_RIGHT, MV_DOWN },
6972 { MV_RIGHT, MV_LEFT, MV_UP }
6975 int element = Tile[x][y];
6976 int move_pattern = element_info[element].move_pattern;
6978 int old_move_dir = MovDir[x][y];
6979 int left_dir = turn[old_move_dir].left;
6980 int right_dir = turn[old_move_dir].right;
6981 int back_dir = turn[old_move_dir].back;
6983 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6984 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6985 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6986 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6988 int left_x = x + left_dx, left_y = y + left_dy;
6989 int right_x = x + right_dx, right_y = y + right_dy;
6990 int move_x = x + move_dx, move_y = y + move_dy;
6994 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6996 TestIfBadThingTouchesOtherBadThing(x, y);
6998 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6999 MovDir[x][y] = right_dir;
7000 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7001 MovDir[x][y] = left_dir;
7003 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7005 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7008 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7010 TestIfBadThingTouchesOtherBadThing(x, y);
7012 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7013 MovDir[x][y] = left_dir;
7014 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7015 MovDir[x][y] = right_dir;
7017 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7019 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7022 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7024 TestIfBadThingTouchesOtherBadThing(x, y);
7026 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7027 MovDir[x][y] = left_dir;
7028 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7029 MovDir[x][y] = right_dir;
7031 if (MovDir[x][y] != old_move_dir)
7034 else if (element == EL_YAMYAM)
7036 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7037 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7039 if (can_turn_left && can_turn_right)
7040 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7041 else if (can_turn_left)
7042 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7043 else if (can_turn_right)
7044 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7046 MovDir[x][y] = back_dir;
7048 MovDelay[x][y] = 16 + 16 * RND(3);
7050 else if (element == EL_DARK_YAMYAM)
7052 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7054 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7057 if (can_turn_left && can_turn_right)
7058 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7059 else if (can_turn_left)
7060 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7061 else if (can_turn_right)
7062 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7064 MovDir[x][y] = back_dir;
7066 MovDelay[x][y] = 16 + 16 * RND(3);
7068 else if (element == EL_PACMAN)
7070 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7071 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7073 if (can_turn_left && can_turn_right)
7074 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7075 else if (can_turn_left)
7076 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7077 else if (can_turn_right)
7078 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7080 MovDir[x][y] = back_dir;
7082 MovDelay[x][y] = 6 + RND(40);
7084 else if (element == EL_PIG)
7086 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7087 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7088 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7089 boolean should_turn_left, should_turn_right, should_move_on;
7091 int rnd = RND(rnd_value);
7093 should_turn_left = (can_turn_left &&
7095 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7096 y + back_dy + left_dy)));
7097 should_turn_right = (can_turn_right &&
7099 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7100 y + back_dy + right_dy)));
7101 should_move_on = (can_move_on &&
7104 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7105 y + move_dy + left_dy) ||
7106 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7107 y + move_dy + right_dy)));
7109 if (should_turn_left || should_turn_right || should_move_on)
7111 if (should_turn_left && should_turn_right && should_move_on)
7112 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7113 rnd < 2 * rnd_value / 3 ? right_dir :
7115 else if (should_turn_left && should_turn_right)
7116 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7117 else if (should_turn_left && should_move_on)
7118 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7119 else if (should_turn_right && should_move_on)
7120 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7121 else if (should_turn_left)
7122 MovDir[x][y] = left_dir;
7123 else if (should_turn_right)
7124 MovDir[x][y] = right_dir;
7125 else if (should_move_on)
7126 MovDir[x][y] = old_move_dir;
7128 else if (can_move_on && rnd > rnd_value / 8)
7129 MovDir[x][y] = old_move_dir;
7130 else if (can_turn_left && can_turn_right)
7131 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7132 else if (can_turn_left && rnd > rnd_value / 8)
7133 MovDir[x][y] = left_dir;
7134 else if (can_turn_right && rnd > rnd_value/8)
7135 MovDir[x][y] = right_dir;
7137 MovDir[x][y] = back_dir;
7139 xx = x + move_xy[MovDir[x][y]].dx;
7140 yy = y + move_xy[MovDir[x][y]].dy;
7142 if (!IN_LEV_FIELD(xx, yy) ||
7143 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7144 MovDir[x][y] = old_move_dir;
7148 else if (element == EL_DRAGON)
7150 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7151 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7152 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7154 int rnd = RND(rnd_value);
7156 if (can_move_on && rnd > rnd_value / 8)
7157 MovDir[x][y] = old_move_dir;
7158 else if (can_turn_left && can_turn_right)
7159 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7160 else if (can_turn_left && rnd > rnd_value / 8)
7161 MovDir[x][y] = left_dir;
7162 else if (can_turn_right && rnd > rnd_value / 8)
7163 MovDir[x][y] = right_dir;
7165 MovDir[x][y] = back_dir;
7167 xx = x + move_xy[MovDir[x][y]].dx;
7168 yy = y + move_xy[MovDir[x][y]].dy;
7170 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7171 MovDir[x][y] = old_move_dir;
7175 else if (element == EL_MOLE)
7177 boolean can_move_on =
7178 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7179 IS_AMOEBOID(Tile[move_x][move_y]) ||
7180 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7183 boolean can_turn_left =
7184 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7185 IS_AMOEBOID(Tile[left_x][left_y])));
7187 boolean can_turn_right =
7188 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7189 IS_AMOEBOID(Tile[right_x][right_y])));
7191 if (can_turn_left && can_turn_right)
7192 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7193 else if (can_turn_left)
7194 MovDir[x][y] = left_dir;
7196 MovDir[x][y] = right_dir;
7199 if (MovDir[x][y] != old_move_dir)
7202 else if (element == EL_BALLOON)
7204 MovDir[x][y] = game.wind_direction;
7207 else if (element == EL_SPRING)
7209 if (MovDir[x][y] & MV_HORIZONTAL)
7211 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7212 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7214 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7215 ResetGfxAnimation(move_x, move_y);
7216 TEST_DrawLevelField(move_x, move_y);
7218 MovDir[x][y] = back_dir;
7220 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7221 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7222 MovDir[x][y] = MV_NONE;
7227 else if (element == EL_ROBOT ||
7228 element == EL_SATELLITE ||
7229 element == EL_PENGUIN ||
7230 element == EL_EMC_ANDROID)
7232 int attr_x = -1, attr_y = -1;
7234 if (game.all_players_gone)
7236 attr_x = game.exit_x;
7237 attr_y = game.exit_y;
7243 for (i = 0; i < MAX_PLAYERS; i++)
7245 struct PlayerInfo *player = &stored_player[i];
7246 int jx = player->jx, jy = player->jy;
7248 if (!player->active)
7252 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7260 if (element == EL_ROBOT &&
7261 game.robot_wheel_x >= 0 &&
7262 game.robot_wheel_y >= 0 &&
7263 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7264 game.engine_version < VERSION_IDENT(3,1,0,0)))
7266 attr_x = game.robot_wheel_x;
7267 attr_y = game.robot_wheel_y;
7270 if (element == EL_PENGUIN)
7273 static int xy[4][2] =
7281 for (i = 0; i < NUM_DIRECTIONS; i++)
7283 int ex = x + xy[i][0];
7284 int ey = y + xy[i][1];
7286 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7287 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7288 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7289 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7298 MovDir[x][y] = MV_NONE;
7300 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7301 else if (attr_x > x)
7302 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7304 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7305 else if (attr_y > y)
7306 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7308 if (element == EL_ROBOT)
7312 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7313 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7314 Moving2Blocked(x, y, &newx, &newy);
7316 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7317 MovDelay[x][y] = 8 + 8 * !RND(3);
7319 MovDelay[x][y] = 16;
7321 else if (element == EL_PENGUIN)
7327 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7329 boolean first_horiz = RND(2);
7330 int new_move_dir = MovDir[x][y];
7333 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7334 Moving2Blocked(x, y, &newx, &newy);
7336 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7340 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7341 Moving2Blocked(x, y, &newx, &newy);
7343 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7346 MovDir[x][y] = old_move_dir;
7350 else if (element == EL_SATELLITE)
7356 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7358 boolean first_horiz = RND(2);
7359 int new_move_dir = MovDir[x][y];
7362 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7363 Moving2Blocked(x, y, &newx, &newy);
7365 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7369 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7370 Moving2Blocked(x, y, &newx, &newy);
7372 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7375 MovDir[x][y] = old_move_dir;
7379 else if (element == EL_EMC_ANDROID)
7381 static int check_pos[16] =
7383 -1, // 0 => (invalid)
7386 -1, // 3 => (invalid)
7388 0, // 5 => MV_LEFT | MV_UP
7389 2, // 6 => MV_RIGHT | MV_UP
7390 -1, // 7 => (invalid)
7392 6, // 9 => MV_LEFT | MV_DOWN
7393 4, // 10 => MV_RIGHT | MV_DOWN
7394 -1, // 11 => (invalid)
7395 -1, // 12 => (invalid)
7396 -1, // 13 => (invalid)
7397 -1, // 14 => (invalid)
7398 -1, // 15 => (invalid)
7406 { -1, -1, MV_LEFT | MV_UP },
7408 { +1, -1, MV_RIGHT | MV_UP },
7409 { +1, 0, MV_RIGHT },
7410 { +1, +1, MV_RIGHT | MV_DOWN },
7412 { -1, +1, MV_LEFT | MV_DOWN },
7415 int start_pos, check_order;
7416 boolean can_clone = FALSE;
7419 // check if there is any free field around current position
7420 for (i = 0; i < 8; i++)
7422 int newx = x + check_xy[i].dx;
7423 int newy = y + check_xy[i].dy;
7425 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7433 if (can_clone) // randomly find an element to clone
7437 start_pos = check_pos[RND(8)];
7438 check_order = (RND(2) ? -1 : +1);
7440 for (i = 0; i < 8; i++)
7442 int pos_raw = start_pos + i * check_order;
7443 int pos = (pos_raw + 8) % 8;
7444 int newx = x + check_xy[pos].dx;
7445 int newy = y + check_xy[pos].dy;
7447 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7449 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7450 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7452 Store[x][y] = Tile[newx][newy];
7461 if (can_clone) // randomly find a direction to move
7465 start_pos = check_pos[RND(8)];
7466 check_order = (RND(2) ? -1 : +1);
7468 for (i = 0; i < 8; i++)
7470 int pos_raw = start_pos + i * check_order;
7471 int pos = (pos_raw + 8) % 8;
7472 int newx = x + check_xy[pos].dx;
7473 int newy = y + check_xy[pos].dy;
7474 int new_move_dir = check_xy[pos].dir;
7476 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7478 MovDir[x][y] = new_move_dir;
7479 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7488 if (can_clone) // cloning and moving successful
7491 // cannot clone -- try to move towards player
7493 start_pos = check_pos[MovDir[x][y] & 0x0f];
7494 check_order = (RND(2) ? -1 : +1);
7496 for (i = 0; i < 3; i++)
7498 // first check start_pos, then previous/next or (next/previous) pos
7499 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7500 int pos = (pos_raw + 8) % 8;
7501 int newx = x + check_xy[pos].dx;
7502 int newy = y + check_xy[pos].dy;
7503 int new_move_dir = check_xy[pos].dir;
7505 if (IS_PLAYER(newx, newy))
7508 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7510 MovDir[x][y] = new_move_dir;
7511 MovDelay[x][y] = level.android_move_time * 8 + 1;
7518 else if (move_pattern == MV_TURNING_LEFT ||
7519 move_pattern == MV_TURNING_RIGHT ||
7520 move_pattern == MV_TURNING_LEFT_RIGHT ||
7521 move_pattern == MV_TURNING_RIGHT_LEFT ||
7522 move_pattern == MV_TURNING_RANDOM ||
7523 move_pattern == MV_ALL_DIRECTIONS)
7525 boolean can_turn_left =
7526 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7527 boolean can_turn_right =
7528 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7530 if (element_info[element].move_stepsize == 0) // "not moving"
7533 if (move_pattern == MV_TURNING_LEFT)
7534 MovDir[x][y] = left_dir;
7535 else if (move_pattern == MV_TURNING_RIGHT)
7536 MovDir[x][y] = right_dir;
7537 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7538 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7539 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7540 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7541 else if (move_pattern == MV_TURNING_RANDOM)
7542 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7543 can_turn_right && !can_turn_left ? right_dir :
7544 RND(2) ? left_dir : right_dir);
7545 else if (can_turn_left && can_turn_right)
7546 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7547 else if (can_turn_left)
7548 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7549 else if (can_turn_right)
7550 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7552 MovDir[x][y] = back_dir;
7554 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7556 else if (move_pattern == MV_HORIZONTAL ||
7557 move_pattern == MV_VERTICAL)
7559 if (move_pattern & old_move_dir)
7560 MovDir[x][y] = back_dir;
7561 else if (move_pattern == MV_HORIZONTAL)
7562 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7563 else if (move_pattern == MV_VERTICAL)
7564 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7566 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7568 else if (move_pattern & MV_ANY_DIRECTION)
7570 MovDir[x][y] = move_pattern;
7571 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7573 else if (move_pattern & MV_WIND_DIRECTION)
7575 MovDir[x][y] = game.wind_direction;
7576 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7578 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7580 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7581 MovDir[x][y] = left_dir;
7582 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7583 MovDir[x][y] = right_dir;
7585 if (MovDir[x][y] != old_move_dir)
7586 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7588 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7590 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7591 MovDir[x][y] = right_dir;
7592 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7593 MovDir[x][y] = left_dir;
7595 if (MovDir[x][y] != old_move_dir)
7596 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7598 else if (move_pattern == MV_TOWARDS_PLAYER ||
7599 move_pattern == MV_AWAY_FROM_PLAYER)
7601 int attr_x = -1, attr_y = -1;
7603 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7605 if (game.all_players_gone)
7607 attr_x = game.exit_x;
7608 attr_y = game.exit_y;
7614 for (i = 0; i < MAX_PLAYERS; i++)
7616 struct PlayerInfo *player = &stored_player[i];
7617 int jx = player->jx, jy = player->jy;
7619 if (!player->active)
7623 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7631 MovDir[x][y] = MV_NONE;
7633 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7634 else if (attr_x > x)
7635 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7637 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7638 else if (attr_y > y)
7639 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7641 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7643 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7645 boolean first_horiz = RND(2);
7646 int new_move_dir = MovDir[x][y];
7648 if (element_info[element].move_stepsize == 0) // "not moving"
7650 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7651 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7657 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7658 Moving2Blocked(x, y, &newx, &newy);
7660 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7664 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7665 Moving2Blocked(x, y, &newx, &newy);
7667 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7670 MovDir[x][y] = old_move_dir;
7673 else if (move_pattern == MV_WHEN_PUSHED ||
7674 move_pattern == MV_WHEN_DROPPED)
7676 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7677 MovDir[x][y] = MV_NONE;
7681 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7683 static int test_xy[4][2] =
7690 static int test_dir[4] =
7697 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7698 int move_preference = -1000000; // start with very low preference
7699 int new_move_dir = MV_NONE;
7700 int start_test = RND(4);
7703 for (i = 0; i < NUM_DIRECTIONS; i++)
7705 int j = (start_test + i) % 4;
7706 int move_dir = test_dir[j];
7707 int move_dir_preference;
7709 xx = x + test_xy[j][0];
7710 yy = y + test_xy[j][1];
7712 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7713 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7715 new_move_dir = move_dir;
7720 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7723 move_dir_preference = -1 * RunnerVisit[xx][yy];
7724 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7725 move_dir_preference = PlayerVisit[xx][yy];
7727 if (move_dir_preference > move_preference)
7729 // prefer field that has not been visited for the longest time
7730 move_preference = move_dir_preference;
7731 new_move_dir = move_dir;
7733 else if (move_dir_preference == move_preference &&
7734 move_dir == old_move_dir)
7736 // prefer last direction when all directions are preferred equally
7737 move_preference = move_dir_preference;
7738 new_move_dir = move_dir;
7742 MovDir[x][y] = new_move_dir;
7743 if (old_move_dir != new_move_dir)
7744 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7748 static void TurnRound(int x, int y)
7750 int direction = MovDir[x][y];
7754 GfxDir[x][y] = MovDir[x][y];
7756 if (direction != MovDir[x][y])
7760 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7762 ResetGfxFrame(x, y);
7765 static boolean JustBeingPushed(int x, int y)
7769 for (i = 0; i < MAX_PLAYERS; i++)
7771 struct PlayerInfo *player = &stored_player[i];
7773 if (player->active && player->is_pushing && player->MovPos)
7775 int next_jx = player->jx + (player->jx - player->last_jx);
7776 int next_jy = player->jy + (player->jy - player->last_jy);
7778 if (x == next_jx && y == next_jy)
7786 static void StartMoving(int x, int y)
7788 boolean started_moving = FALSE; // some elements can fall _and_ move
7789 int element = Tile[x][y];
7794 if (MovDelay[x][y] == 0)
7795 GfxAction[x][y] = ACTION_DEFAULT;
7797 if (CAN_FALL(element) && y < lev_fieldy - 1)
7799 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7800 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7801 if (JustBeingPushed(x, y))
7804 if (element == EL_QUICKSAND_FULL)
7806 if (IS_FREE(x, y + 1))
7808 InitMovingField(x, y, MV_DOWN);
7809 started_moving = TRUE;
7811 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7812 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7813 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7814 Store[x][y] = EL_ROCK;
7816 Store[x][y] = EL_ROCK;
7819 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7821 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7823 if (!MovDelay[x][y])
7825 MovDelay[x][y] = TILEY + 1;
7827 ResetGfxAnimation(x, y);
7828 ResetGfxAnimation(x, y + 1);
7833 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7834 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7841 Tile[x][y] = EL_QUICKSAND_EMPTY;
7842 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7843 Store[x][y + 1] = Store[x][y];
7846 PlayLevelSoundAction(x, y, ACTION_FILLING);
7848 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7850 if (!MovDelay[x][y])
7852 MovDelay[x][y] = TILEY + 1;
7854 ResetGfxAnimation(x, y);
7855 ResetGfxAnimation(x, y + 1);
7860 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7861 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7868 Tile[x][y] = EL_QUICKSAND_EMPTY;
7869 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7870 Store[x][y + 1] = Store[x][y];
7873 PlayLevelSoundAction(x, y, ACTION_FILLING);
7876 else if (element == EL_QUICKSAND_FAST_FULL)
7878 if (IS_FREE(x, y + 1))
7880 InitMovingField(x, y, MV_DOWN);
7881 started_moving = TRUE;
7883 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7884 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7885 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7886 Store[x][y] = EL_ROCK;
7888 Store[x][y] = EL_ROCK;
7891 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7893 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7895 if (!MovDelay[x][y])
7897 MovDelay[x][y] = TILEY + 1;
7899 ResetGfxAnimation(x, y);
7900 ResetGfxAnimation(x, y + 1);
7905 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7906 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7913 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7914 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7915 Store[x][y + 1] = Store[x][y];
7918 PlayLevelSoundAction(x, y, ACTION_FILLING);
7920 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7922 if (!MovDelay[x][y])
7924 MovDelay[x][y] = TILEY + 1;
7926 ResetGfxAnimation(x, y);
7927 ResetGfxAnimation(x, y + 1);
7932 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7933 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7940 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7941 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7942 Store[x][y + 1] = Store[x][y];
7945 PlayLevelSoundAction(x, y, ACTION_FILLING);
7948 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7949 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7951 InitMovingField(x, y, MV_DOWN);
7952 started_moving = TRUE;
7954 Tile[x][y] = EL_QUICKSAND_FILLING;
7955 Store[x][y] = element;
7957 PlayLevelSoundAction(x, y, ACTION_FILLING);
7959 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7960 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7962 InitMovingField(x, y, MV_DOWN);
7963 started_moving = TRUE;
7965 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7966 Store[x][y] = element;
7968 PlayLevelSoundAction(x, y, ACTION_FILLING);
7970 else if (element == EL_MAGIC_WALL_FULL)
7972 if (IS_FREE(x, y + 1))
7974 InitMovingField(x, y, MV_DOWN);
7975 started_moving = TRUE;
7977 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7978 Store[x][y] = EL_CHANGED(Store[x][y]);
7980 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7982 if (!MovDelay[x][y])
7983 MovDelay[x][y] = TILEY / 4 + 1;
7992 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7993 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7994 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7998 else if (element == EL_BD_MAGIC_WALL_FULL)
8000 if (IS_FREE(x, y + 1))
8002 InitMovingField(x, y, MV_DOWN);
8003 started_moving = TRUE;
8005 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8006 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8008 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8010 if (!MovDelay[x][y])
8011 MovDelay[x][y] = TILEY / 4 + 1;
8020 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8021 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8022 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8026 else if (element == EL_DC_MAGIC_WALL_FULL)
8028 if (IS_FREE(x, y + 1))
8030 InitMovingField(x, y, MV_DOWN);
8031 started_moving = TRUE;
8033 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8034 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8036 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8038 if (!MovDelay[x][y])
8039 MovDelay[x][y] = TILEY / 4 + 1;
8048 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8049 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8050 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8054 else if ((CAN_PASS_MAGIC_WALL(element) &&
8055 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8056 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8057 (CAN_PASS_DC_MAGIC_WALL(element) &&
8058 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8061 InitMovingField(x, y, MV_DOWN);
8062 started_moving = TRUE;
8065 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8066 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8067 EL_DC_MAGIC_WALL_FILLING);
8068 Store[x][y] = element;
8070 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8072 SplashAcid(x, y + 1);
8074 InitMovingField(x, y, MV_DOWN);
8075 started_moving = TRUE;
8077 Store[x][y] = EL_ACID;
8080 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8081 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8082 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8083 CAN_FALL(element) && WasJustFalling[x][y] &&
8084 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8086 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8087 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8088 (Tile[x][y + 1] == EL_BLOCKED)))
8090 /* this is needed for a special case not covered by calling "Impact()"
8091 from "ContinueMoving()": if an element moves to a tile directly below
8092 another element which was just falling on that tile (which was empty
8093 in the previous frame), the falling element above would just stop
8094 instead of smashing the element below (in previous version, the above
8095 element was just checked for "moving" instead of "falling", resulting
8096 in incorrect smashes caused by horizontal movement of the above
8097 element; also, the case of the player being the element to smash was
8098 simply not covered here... :-/ ) */
8100 CheckCollision[x][y] = 0;
8101 CheckImpact[x][y] = 0;
8105 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8107 if (MovDir[x][y] == MV_NONE)
8109 InitMovingField(x, y, MV_DOWN);
8110 started_moving = TRUE;
8113 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8115 if (WasJustFalling[x][y]) // prevent animation from being restarted
8116 MovDir[x][y] = MV_DOWN;
8118 InitMovingField(x, y, MV_DOWN);
8119 started_moving = TRUE;
8121 else if (element == EL_AMOEBA_DROP)
8123 Tile[x][y] = EL_AMOEBA_GROWING;
8124 Store[x][y] = EL_AMOEBA_WET;
8126 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8127 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8128 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8129 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8131 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8132 (IS_FREE(x - 1, y + 1) ||
8133 Tile[x - 1][y + 1] == EL_ACID));
8134 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8135 (IS_FREE(x + 1, y + 1) ||
8136 Tile[x + 1][y + 1] == EL_ACID));
8137 boolean can_fall_any = (can_fall_left || can_fall_right);
8138 boolean can_fall_both = (can_fall_left && can_fall_right);
8139 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8141 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8143 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8144 can_fall_right = FALSE;
8145 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8146 can_fall_left = FALSE;
8147 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8148 can_fall_right = FALSE;
8149 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8150 can_fall_left = FALSE;
8152 can_fall_any = (can_fall_left || can_fall_right);
8153 can_fall_both = FALSE;
8158 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8159 can_fall_right = FALSE; // slip down on left side
8161 can_fall_left = !(can_fall_right = RND(2));
8163 can_fall_both = FALSE;
8168 // if not determined otherwise, prefer left side for slipping down
8169 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8170 started_moving = TRUE;
8173 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8175 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8176 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8177 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8178 int belt_dir = game.belt_dir[belt_nr];
8180 if ((belt_dir == MV_LEFT && left_is_free) ||
8181 (belt_dir == MV_RIGHT && right_is_free))
8183 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8185 InitMovingField(x, y, belt_dir);
8186 started_moving = TRUE;
8188 Pushed[x][y] = TRUE;
8189 Pushed[nextx][y] = TRUE;
8191 GfxAction[x][y] = ACTION_DEFAULT;
8195 MovDir[x][y] = 0; // if element was moving, stop it
8200 // not "else if" because of elements that can fall and move (EL_SPRING)
8201 if (CAN_MOVE(element) && !started_moving)
8203 int move_pattern = element_info[element].move_pattern;
8206 Moving2Blocked(x, y, &newx, &newy);
8208 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8211 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8212 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8214 WasJustMoving[x][y] = 0;
8215 CheckCollision[x][y] = 0;
8217 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8219 if (Tile[x][y] != element) // element has changed
8223 if (!MovDelay[x][y]) // start new movement phase
8225 // all objects that can change their move direction after each step
8226 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8228 if (element != EL_YAMYAM &&
8229 element != EL_DARK_YAMYAM &&
8230 element != EL_PACMAN &&
8231 !(move_pattern & MV_ANY_DIRECTION) &&
8232 move_pattern != MV_TURNING_LEFT &&
8233 move_pattern != MV_TURNING_RIGHT &&
8234 move_pattern != MV_TURNING_LEFT_RIGHT &&
8235 move_pattern != MV_TURNING_RIGHT_LEFT &&
8236 move_pattern != MV_TURNING_RANDOM)
8240 if (MovDelay[x][y] && (element == EL_BUG ||
8241 element == EL_SPACESHIP ||
8242 element == EL_SP_SNIKSNAK ||
8243 element == EL_SP_ELECTRON ||
8244 element == EL_MOLE))
8245 TEST_DrawLevelField(x, y);
8249 if (MovDelay[x][y]) // wait some time before next movement
8253 if (element == EL_ROBOT ||
8254 element == EL_YAMYAM ||
8255 element == EL_DARK_YAMYAM)
8257 DrawLevelElementAnimationIfNeeded(x, y, element);
8258 PlayLevelSoundAction(x, y, ACTION_WAITING);
8260 else if (element == EL_SP_ELECTRON)
8261 DrawLevelElementAnimationIfNeeded(x, y, element);
8262 else if (element == EL_DRAGON)
8265 int dir = MovDir[x][y];
8266 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8267 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8268 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8269 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8270 dir == MV_UP ? IMG_FLAMES_1_UP :
8271 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8272 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8274 GfxAction[x][y] = ACTION_ATTACKING;
8276 if (IS_PLAYER(x, y))
8277 DrawPlayerField(x, y);
8279 TEST_DrawLevelField(x, y);
8281 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8283 for (i = 1; i <= 3; i++)
8285 int xx = x + i * dx;
8286 int yy = y + i * dy;
8287 int sx = SCREENX(xx);
8288 int sy = SCREENY(yy);
8289 int flame_graphic = graphic + (i - 1);
8291 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8296 int flamed = MovingOrBlocked2Element(xx, yy);
8298 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8301 RemoveMovingField(xx, yy);
8303 ChangeDelay[xx][yy] = 0;
8305 Tile[xx][yy] = EL_FLAMES;
8307 if (IN_SCR_FIELD(sx, sy))
8309 TEST_DrawLevelFieldCrumbled(xx, yy);
8310 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8315 if (Tile[xx][yy] == EL_FLAMES)
8316 Tile[xx][yy] = EL_EMPTY;
8317 TEST_DrawLevelField(xx, yy);
8322 if (MovDelay[x][y]) // element still has to wait some time
8324 PlayLevelSoundAction(x, y, ACTION_WAITING);
8330 // now make next step
8332 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8334 if (DONT_COLLIDE_WITH(element) &&
8335 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8336 !PLAYER_ENEMY_PROTECTED(newx, newy))
8338 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8343 else if (CAN_MOVE_INTO_ACID(element) &&
8344 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8345 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8346 (MovDir[x][y] == MV_DOWN ||
8347 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8349 SplashAcid(newx, newy);
8350 Store[x][y] = EL_ACID;
8352 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8354 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8355 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8356 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8357 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8360 TEST_DrawLevelField(x, y);
8362 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8363 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8364 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8366 game.friends_still_needed--;
8367 if (!game.friends_still_needed &&
8369 game.all_players_gone)
8374 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8376 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8377 TEST_DrawLevelField(newx, newy);
8379 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8381 else if (!IS_FREE(newx, newy))
8383 GfxAction[x][y] = ACTION_WAITING;
8385 if (IS_PLAYER(x, y))
8386 DrawPlayerField(x, y);
8388 TEST_DrawLevelField(x, y);
8393 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8395 if (IS_FOOD_PIG(Tile[newx][newy]))
8397 if (IS_MOVING(newx, newy))
8398 RemoveMovingField(newx, newy);
8401 Tile[newx][newy] = EL_EMPTY;
8402 TEST_DrawLevelField(newx, newy);
8405 PlayLevelSound(x, y, SND_PIG_DIGGING);
8407 else if (!IS_FREE(newx, newy))
8409 if (IS_PLAYER(x, y))
8410 DrawPlayerField(x, y);
8412 TEST_DrawLevelField(x, y);
8417 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8419 if (Store[x][y] != EL_EMPTY)
8421 boolean can_clone = FALSE;
8424 // check if element to clone is still there
8425 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8427 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8435 // cannot clone or target field not free anymore -- do not clone
8436 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8437 Store[x][y] = EL_EMPTY;
8440 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8442 if (IS_MV_DIAGONAL(MovDir[x][y]))
8444 int diagonal_move_dir = MovDir[x][y];
8445 int stored = Store[x][y];
8446 int change_delay = 8;
8449 // android is moving diagonally
8451 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8453 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8454 GfxElement[x][y] = EL_EMC_ANDROID;
8455 GfxAction[x][y] = ACTION_SHRINKING;
8456 GfxDir[x][y] = diagonal_move_dir;
8457 ChangeDelay[x][y] = change_delay;
8459 if (Store[x][y] == EL_EMPTY)
8460 Store[x][y] = GfxElementEmpty[x][y];
8462 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8465 DrawLevelGraphicAnimation(x, y, graphic);
8466 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8468 if (Tile[newx][newy] == EL_ACID)
8470 SplashAcid(newx, newy);
8475 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8477 Store[newx][newy] = EL_EMC_ANDROID;
8478 GfxElement[newx][newy] = EL_EMC_ANDROID;
8479 GfxAction[newx][newy] = ACTION_GROWING;
8480 GfxDir[newx][newy] = diagonal_move_dir;
8481 ChangeDelay[newx][newy] = change_delay;
8483 graphic = el_act_dir2img(GfxElement[newx][newy],
8484 GfxAction[newx][newy], GfxDir[newx][newy]);
8486 DrawLevelGraphicAnimation(newx, newy, graphic);
8487 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8493 Tile[newx][newy] = EL_EMPTY;
8494 TEST_DrawLevelField(newx, newy);
8496 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8499 else if (!IS_FREE(newx, newy))
8504 else if (IS_CUSTOM_ELEMENT(element) &&
8505 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8507 if (!DigFieldByCE(newx, newy, element))
8510 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8512 RunnerVisit[x][y] = FrameCounter;
8513 PlayerVisit[x][y] /= 8; // expire player visit path
8516 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8518 if (!IS_FREE(newx, newy))
8520 if (IS_PLAYER(x, y))
8521 DrawPlayerField(x, y);
8523 TEST_DrawLevelField(x, y);
8529 boolean wanna_flame = !RND(10);
8530 int dx = newx - x, dy = newy - y;
8531 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8532 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8533 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8534 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8535 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8536 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8539 IS_CLASSIC_ENEMY(element1) ||
8540 IS_CLASSIC_ENEMY(element2)) &&
8541 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8542 element1 != EL_FLAMES && element2 != EL_FLAMES)
8544 ResetGfxAnimation(x, y);
8545 GfxAction[x][y] = ACTION_ATTACKING;
8547 if (IS_PLAYER(x, y))
8548 DrawPlayerField(x, y);
8550 TEST_DrawLevelField(x, y);
8552 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8554 MovDelay[x][y] = 50;
8556 Tile[newx][newy] = EL_FLAMES;
8557 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8558 Tile[newx1][newy1] = EL_FLAMES;
8559 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8560 Tile[newx2][newy2] = EL_FLAMES;
8566 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8567 Tile[newx][newy] == EL_DIAMOND)
8569 if (IS_MOVING(newx, newy))
8570 RemoveMovingField(newx, newy);
8573 Tile[newx][newy] = EL_EMPTY;
8574 TEST_DrawLevelField(newx, newy);
8577 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8579 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8580 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8582 if (AmoebaNr[newx][newy])
8584 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8585 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8586 Tile[newx][newy] == EL_BD_AMOEBA)
8587 AmoebaCnt[AmoebaNr[newx][newy]]--;
8590 if (IS_MOVING(newx, newy))
8592 RemoveMovingField(newx, newy);
8596 Tile[newx][newy] = EL_EMPTY;
8597 TEST_DrawLevelField(newx, newy);
8600 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8602 else if ((element == EL_PACMAN || element == EL_MOLE)
8603 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8605 if (AmoebaNr[newx][newy])
8607 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8608 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8609 Tile[newx][newy] == EL_BD_AMOEBA)
8610 AmoebaCnt[AmoebaNr[newx][newy]]--;
8613 if (element == EL_MOLE)
8615 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8616 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8618 ResetGfxAnimation(x, y);
8619 GfxAction[x][y] = ACTION_DIGGING;
8620 TEST_DrawLevelField(x, y);
8622 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8624 return; // wait for shrinking amoeba
8626 else // element == EL_PACMAN
8628 Tile[newx][newy] = EL_EMPTY;
8629 TEST_DrawLevelField(newx, newy);
8630 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8633 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8634 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8635 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8637 // wait for shrinking amoeba to completely disappear
8640 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8642 // object was running against a wall
8646 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8647 DrawLevelElementAnimation(x, y, element);
8649 if (DONT_TOUCH(element))
8650 TestIfBadThingTouchesPlayer(x, y);
8655 InitMovingField(x, y, MovDir[x][y]);
8657 PlayLevelSoundAction(x, y, ACTION_MOVING);
8661 ContinueMoving(x, y);
8664 void ContinueMoving(int x, int y)
8666 int element = Tile[x][y];
8667 struct ElementInfo *ei = &element_info[element];
8668 int direction = MovDir[x][y];
8669 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8670 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8671 int newx = x + dx, newy = y + dy;
8672 int stored = Store[x][y];
8673 int stored_new = Store[newx][newy];
8674 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8675 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8676 boolean last_line = (newy == lev_fieldy - 1);
8677 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8679 if (pushed_by_player) // special case: moving object pushed by player
8681 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8683 else if (use_step_delay) // special case: moving object has step delay
8685 if (!MovDelay[x][y])
8686 MovPos[x][y] += getElementMoveStepsize(x, y);
8691 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8695 TEST_DrawLevelField(x, y);
8697 return; // element is still waiting
8700 else // normal case: generically moving object
8702 MovPos[x][y] += getElementMoveStepsize(x, y);
8705 if (ABS(MovPos[x][y]) < TILEX)
8707 TEST_DrawLevelField(x, y);
8709 return; // element is still moving
8712 // element reached destination field
8714 Tile[x][y] = EL_EMPTY;
8715 Tile[newx][newy] = element;
8716 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8718 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8720 element = Tile[newx][newy] = EL_ACID;
8722 else if (element == EL_MOLE)
8724 Tile[x][y] = EL_SAND;
8726 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8728 else if (element == EL_QUICKSAND_FILLING)
8730 element = Tile[newx][newy] = get_next_element(element);
8731 Store[newx][newy] = Store[x][y];
8733 else if (element == EL_QUICKSAND_EMPTYING)
8735 Tile[x][y] = get_next_element(element);
8736 element = Tile[newx][newy] = Store[x][y];
8738 else if (element == EL_QUICKSAND_FAST_FILLING)
8740 element = Tile[newx][newy] = get_next_element(element);
8741 Store[newx][newy] = Store[x][y];
8743 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8745 Tile[x][y] = get_next_element(element);
8746 element = Tile[newx][newy] = Store[x][y];
8748 else if (element == EL_MAGIC_WALL_FILLING)
8750 element = Tile[newx][newy] = get_next_element(element);
8751 if (!game.magic_wall_active)
8752 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8753 Store[newx][newy] = Store[x][y];
8755 else if (element == EL_MAGIC_WALL_EMPTYING)
8757 Tile[x][y] = get_next_element(element);
8758 if (!game.magic_wall_active)
8759 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8760 element = Tile[newx][newy] = Store[x][y];
8762 InitField(newx, newy, FALSE);
8764 else if (element == EL_BD_MAGIC_WALL_FILLING)
8766 element = Tile[newx][newy] = get_next_element(element);
8767 if (!game.magic_wall_active)
8768 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8769 Store[newx][newy] = Store[x][y];
8771 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8773 Tile[x][y] = get_next_element(element);
8774 if (!game.magic_wall_active)
8775 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8776 element = Tile[newx][newy] = Store[x][y];
8778 InitField(newx, newy, FALSE);
8780 else if (element == EL_DC_MAGIC_WALL_FILLING)
8782 element = Tile[newx][newy] = get_next_element(element);
8783 if (!game.magic_wall_active)
8784 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8785 Store[newx][newy] = Store[x][y];
8787 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8789 Tile[x][y] = get_next_element(element);
8790 if (!game.magic_wall_active)
8791 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8792 element = Tile[newx][newy] = Store[x][y];
8794 InitField(newx, newy, FALSE);
8796 else if (element == EL_AMOEBA_DROPPING)
8798 Tile[x][y] = get_next_element(element);
8799 element = Tile[newx][newy] = Store[x][y];
8801 else if (element == EL_SOKOBAN_OBJECT)
8804 Tile[x][y] = Back[x][y];
8806 if (Back[newx][newy])
8807 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8809 Back[x][y] = Back[newx][newy] = 0;
8812 Store[x][y] = EL_EMPTY;
8817 MovDelay[newx][newy] = 0;
8819 if (CAN_CHANGE_OR_HAS_ACTION(element))
8821 // copy element change control values to new field
8822 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8823 ChangePage[newx][newy] = ChangePage[x][y];
8824 ChangeCount[newx][newy] = ChangeCount[x][y];
8825 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8828 CustomValue[newx][newy] = CustomValue[x][y];
8830 ChangeDelay[x][y] = 0;
8831 ChangePage[x][y] = -1;
8832 ChangeCount[x][y] = 0;
8833 ChangeEvent[x][y] = -1;
8835 CustomValue[x][y] = 0;
8837 // copy animation control values to new field
8838 GfxFrame[newx][newy] = GfxFrame[x][y];
8839 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8840 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8841 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8843 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8845 // some elements can leave other elements behind after moving
8846 if (ei->move_leave_element != EL_EMPTY &&
8847 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8848 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8850 int move_leave_element = ei->move_leave_element;
8852 // this makes it possible to leave the removed element again
8853 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8854 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8856 Tile[x][y] = move_leave_element;
8858 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8859 MovDir[x][y] = direction;
8861 InitField(x, y, FALSE);
8863 if (GFX_CRUMBLED(Tile[x][y]))
8864 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8866 if (IS_PLAYER_ELEMENT(move_leave_element))
8867 RelocatePlayer(x, y, move_leave_element);
8870 // do this after checking for left-behind element
8871 ResetGfxAnimation(x, y); // reset animation values for old field
8873 if (!CAN_MOVE(element) ||
8874 (CAN_FALL(element) && direction == MV_DOWN &&
8875 (element == EL_SPRING ||
8876 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8877 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8878 GfxDir[x][y] = MovDir[newx][newy] = 0;
8880 TEST_DrawLevelField(x, y);
8881 TEST_DrawLevelField(newx, newy);
8883 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8885 // prevent pushed element from moving on in pushed direction
8886 if (pushed_by_player && CAN_MOVE(element) &&
8887 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8888 !(element_info[element].move_pattern & direction))
8889 TurnRound(newx, newy);
8891 // prevent elements on conveyor belt from moving on in last direction
8892 if (pushed_by_conveyor && CAN_FALL(element) &&
8893 direction & MV_HORIZONTAL)
8894 MovDir[newx][newy] = 0;
8896 if (!pushed_by_player)
8898 int nextx = newx + dx, nexty = newy + dy;
8899 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8901 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8903 if (CAN_FALL(element) && direction == MV_DOWN)
8904 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8906 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8907 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8909 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8910 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8913 if (DONT_TOUCH(element)) // object may be nasty to player or others
8915 TestIfBadThingTouchesPlayer(newx, newy);
8916 TestIfBadThingTouchesFriend(newx, newy);
8918 if (!IS_CUSTOM_ELEMENT(element))
8919 TestIfBadThingTouchesOtherBadThing(newx, newy);
8921 else if (element == EL_PENGUIN)
8922 TestIfFriendTouchesBadThing(newx, newy);
8924 if (DONT_GET_HIT_BY(element))
8926 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8929 // give the player one last chance (one more frame) to move away
8930 if (CAN_FALL(element) && direction == MV_DOWN &&
8931 (last_line || (!IS_FREE(x, newy + 1) &&
8932 (!IS_PLAYER(x, newy + 1) ||
8933 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8936 if (pushed_by_player && !game.use_change_when_pushing_bug)
8938 int push_side = MV_DIR_OPPOSITE(direction);
8939 struct PlayerInfo *player = PLAYERINFO(x, y);
8941 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8942 player->index_bit, push_side);
8943 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8944 player->index_bit, push_side);
8947 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8948 MovDelay[newx][newy] = 1;
8950 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8952 TestIfElementTouchesCustomElement(x, y); // empty or new element
8953 TestIfElementHitsCustomElement(newx, newy, direction);
8954 TestIfPlayerTouchesCustomElement(newx, newy);
8955 TestIfElementTouchesCustomElement(newx, newy);
8957 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8958 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8959 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8960 MV_DIR_OPPOSITE(direction));
8963 int AmoebaNeighbourNr(int ax, int ay)
8966 int element = Tile[ax][ay];
8968 static int xy[4][2] =
8976 for (i = 0; i < NUM_DIRECTIONS; i++)
8978 int x = ax + xy[i][0];
8979 int y = ay + xy[i][1];
8981 if (!IN_LEV_FIELD(x, y))
8984 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8985 group_nr = AmoebaNr[x][y];
8991 static void AmoebaMerge(int ax, int ay)
8993 int i, x, y, xx, yy;
8994 int new_group_nr = AmoebaNr[ax][ay];
8995 static int xy[4][2] =
9003 if (new_group_nr == 0)
9006 for (i = 0; i < NUM_DIRECTIONS; i++)
9011 if (!IN_LEV_FIELD(x, y))
9014 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9015 Tile[x][y] == EL_BD_AMOEBA ||
9016 Tile[x][y] == EL_AMOEBA_DEAD) &&
9017 AmoebaNr[x][y] != new_group_nr)
9019 int old_group_nr = AmoebaNr[x][y];
9021 if (old_group_nr == 0)
9024 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9025 AmoebaCnt[old_group_nr] = 0;
9026 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9027 AmoebaCnt2[old_group_nr] = 0;
9029 SCAN_PLAYFIELD(xx, yy)
9031 if (AmoebaNr[xx][yy] == old_group_nr)
9032 AmoebaNr[xx][yy] = new_group_nr;
9038 void AmoebaToDiamond(int ax, int ay)
9042 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9044 int group_nr = AmoebaNr[ax][ay];
9049 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9050 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9056 SCAN_PLAYFIELD(x, y)
9058 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9061 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9065 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9066 SND_AMOEBA_TURNING_TO_GEM :
9067 SND_AMOEBA_TURNING_TO_ROCK));
9072 static int xy[4][2] =
9080 for (i = 0; i < NUM_DIRECTIONS; i++)
9085 if (!IN_LEV_FIELD(x, y))
9088 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9090 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9091 SND_AMOEBA_TURNING_TO_GEM :
9092 SND_AMOEBA_TURNING_TO_ROCK));
9099 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9102 int group_nr = AmoebaNr[ax][ay];
9103 boolean done = FALSE;
9108 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9109 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9115 SCAN_PLAYFIELD(x, y)
9117 if (AmoebaNr[x][y] == group_nr &&
9118 (Tile[x][y] == EL_AMOEBA_DEAD ||
9119 Tile[x][y] == EL_BD_AMOEBA ||
9120 Tile[x][y] == EL_AMOEBA_GROWING))
9123 Tile[x][y] = new_element;
9124 InitField(x, y, FALSE);
9125 TEST_DrawLevelField(x, y);
9131 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9132 SND_BD_AMOEBA_TURNING_TO_ROCK :
9133 SND_BD_AMOEBA_TURNING_TO_GEM));
9136 static void AmoebaGrowing(int x, int y)
9138 static DelayCounter sound_delay = { 0 };
9140 if (!MovDelay[x][y]) // start new growing cycle
9144 if (DelayReached(&sound_delay))
9146 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9147 sound_delay.value = 30;
9151 if (MovDelay[x][y]) // wait some time before growing bigger
9154 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9156 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9157 6 - MovDelay[x][y]);
9159 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9162 if (!MovDelay[x][y])
9164 Tile[x][y] = Store[x][y];
9166 TEST_DrawLevelField(x, y);
9171 static void AmoebaShrinking(int x, int y)
9173 static DelayCounter sound_delay = { 0 };
9175 if (!MovDelay[x][y]) // start new shrinking cycle
9179 if (DelayReached(&sound_delay))
9180 sound_delay.value = 30;
9183 if (MovDelay[x][y]) // wait some time before shrinking
9186 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9188 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9189 6 - MovDelay[x][y]);
9191 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9194 if (!MovDelay[x][y])
9196 Tile[x][y] = EL_EMPTY;
9197 TEST_DrawLevelField(x, y);
9199 // don't let mole enter this field in this cycle;
9200 // (give priority to objects falling to this field from above)
9206 static void AmoebaReproduce(int ax, int ay)
9209 int element = Tile[ax][ay];
9210 int graphic = el2img(element);
9211 int newax = ax, neway = ay;
9212 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9213 static int xy[4][2] =
9221 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9223 Tile[ax][ay] = EL_AMOEBA_DEAD;
9224 TEST_DrawLevelField(ax, ay);
9228 if (IS_ANIMATED(graphic))
9229 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9231 if (!MovDelay[ax][ay]) // start making new amoeba field
9232 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9234 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9237 if (MovDelay[ax][ay])
9241 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9244 int x = ax + xy[start][0];
9245 int y = ay + xy[start][1];
9247 if (!IN_LEV_FIELD(x, y))
9250 if (IS_FREE(x, y) ||
9251 CAN_GROW_INTO(Tile[x][y]) ||
9252 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9253 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9259 if (newax == ax && neway == ay)
9262 else // normal or "filled" (BD style) amoeba
9265 boolean waiting_for_player = FALSE;
9267 for (i = 0; i < NUM_DIRECTIONS; i++)
9269 int j = (start + i) % 4;
9270 int x = ax + xy[j][0];
9271 int y = ay + xy[j][1];
9273 if (!IN_LEV_FIELD(x, y))
9276 if (IS_FREE(x, y) ||
9277 CAN_GROW_INTO(Tile[x][y]) ||
9278 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9279 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9285 else if (IS_PLAYER(x, y))
9286 waiting_for_player = TRUE;
9289 if (newax == ax && neway == ay) // amoeba cannot grow
9291 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9293 Tile[ax][ay] = EL_AMOEBA_DEAD;
9294 TEST_DrawLevelField(ax, ay);
9295 AmoebaCnt[AmoebaNr[ax][ay]]--;
9297 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9299 if (element == EL_AMOEBA_FULL)
9300 AmoebaToDiamond(ax, ay);
9301 else if (element == EL_BD_AMOEBA)
9302 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9307 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9309 // amoeba gets larger by growing in some direction
9311 int new_group_nr = AmoebaNr[ax][ay];
9314 if (new_group_nr == 0)
9316 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9318 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9324 AmoebaNr[newax][neway] = new_group_nr;
9325 AmoebaCnt[new_group_nr]++;
9326 AmoebaCnt2[new_group_nr]++;
9328 // if amoeba touches other amoeba(s) after growing, unify them
9329 AmoebaMerge(newax, neway);
9331 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9333 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9339 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9340 (neway == lev_fieldy - 1 && newax != ax))
9342 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9343 Store[newax][neway] = element;
9345 else if (neway == ay || element == EL_EMC_DRIPPER)
9347 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9349 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9353 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9354 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9355 Store[ax][ay] = EL_AMOEBA_DROP;
9356 ContinueMoving(ax, ay);
9360 TEST_DrawLevelField(newax, neway);
9363 static void Life(int ax, int ay)
9367 int element = Tile[ax][ay];
9368 int graphic = el2img(element);
9369 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9371 boolean changed = FALSE;
9373 if (IS_ANIMATED(graphic))
9374 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9379 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9380 MovDelay[ax][ay] = life_time;
9382 if (MovDelay[ax][ay]) // wait some time before next cycle
9385 if (MovDelay[ax][ay])
9389 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9391 int xx = ax+x1, yy = ay+y1;
9392 int old_element = Tile[xx][yy];
9393 int num_neighbours = 0;
9395 if (!IN_LEV_FIELD(xx, yy))
9398 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9400 int x = xx+x2, y = yy+y2;
9402 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9405 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9406 boolean is_neighbour = FALSE;
9408 if (level.use_life_bugs)
9410 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9411 (IS_FREE(x, y) && Stop[x][y]));
9414 (Last[x][y] == element || is_player_cell);
9420 boolean is_free = FALSE;
9422 if (level.use_life_bugs)
9423 is_free = (IS_FREE(xx, yy));
9425 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9427 if (xx == ax && yy == ay) // field in the middle
9429 if (num_neighbours < life_parameter[0] ||
9430 num_neighbours > life_parameter[1])
9432 Tile[xx][yy] = EL_EMPTY;
9433 if (Tile[xx][yy] != old_element)
9434 TEST_DrawLevelField(xx, yy);
9435 Stop[xx][yy] = TRUE;
9439 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9440 { // free border field
9441 if (num_neighbours >= life_parameter[2] &&
9442 num_neighbours <= life_parameter[3])
9444 Tile[xx][yy] = element;
9445 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9446 if (Tile[xx][yy] != old_element)
9447 TEST_DrawLevelField(xx, yy);
9448 Stop[xx][yy] = TRUE;
9455 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9456 SND_GAME_OF_LIFE_GROWING);
9459 static void InitRobotWheel(int x, int y)
9461 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9464 static void RunRobotWheel(int x, int y)
9466 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9469 static void StopRobotWheel(int x, int y)
9471 if (game.robot_wheel_x == x &&
9472 game.robot_wheel_y == y)
9474 game.robot_wheel_x = -1;
9475 game.robot_wheel_y = -1;
9476 game.robot_wheel_active = FALSE;
9480 static void InitTimegateWheel(int x, int y)
9482 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9485 static void RunTimegateWheel(int x, int y)
9487 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9490 static void InitMagicBallDelay(int x, int y)
9492 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9495 static void ActivateMagicBall(int bx, int by)
9499 if (level.ball_random)
9501 int pos_border = RND(8); // select one of the eight border elements
9502 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9503 int xx = pos_content % 3;
9504 int yy = pos_content / 3;
9509 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9510 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9514 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9516 int xx = x - bx + 1;
9517 int yy = y - by + 1;
9519 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9520 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9524 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9527 static void CheckExit(int x, int y)
9529 if (game.gems_still_needed > 0 ||
9530 game.sokoban_fields_still_needed > 0 ||
9531 game.sokoban_objects_still_needed > 0 ||
9532 game.lights_still_needed > 0)
9534 int element = Tile[x][y];
9535 int graphic = el2img(element);
9537 if (IS_ANIMATED(graphic))
9538 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9543 // do not re-open exit door closed after last player
9544 if (game.all_players_gone)
9547 Tile[x][y] = EL_EXIT_OPENING;
9549 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9552 static void CheckExitEM(int x, int y)
9554 if (game.gems_still_needed > 0 ||
9555 game.sokoban_fields_still_needed > 0 ||
9556 game.sokoban_objects_still_needed > 0 ||
9557 game.lights_still_needed > 0)
9559 int element = Tile[x][y];
9560 int graphic = el2img(element);
9562 if (IS_ANIMATED(graphic))
9563 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9568 // do not re-open exit door closed after last player
9569 if (game.all_players_gone)
9572 Tile[x][y] = EL_EM_EXIT_OPENING;
9574 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9577 static void CheckExitSteel(int x, int y)
9579 if (game.gems_still_needed > 0 ||
9580 game.sokoban_fields_still_needed > 0 ||
9581 game.sokoban_objects_still_needed > 0 ||
9582 game.lights_still_needed > 0)
9584 int element = Tile[x][y];
9585 int graphic = el2img(element);
9587 if (IS_ANIMATED(graphic))
9588 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9593 // do not re-open exit door closed after last player
9594 if (game.all_players_gone)
9597 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9599 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9602 static void CheckExitSteelEM(int x, int y)
9604 if (game.gems_still_needed > 0 ||
9605 game.sokoban_fields_still_needed > 0 ||
9606 game.sokoban_objects_still_needed > 0 ||
9607 game.lights_still_needed > 0)
9609 int element = Tile[x][y];
9610 int graphic = el2img(element);
9612 if (IS_ANIMATED(graphic))
9613 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9618 // do not re-open exit door closed after last player
9619 if (game.all_players_gone)
9622 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9624 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9627 static void CheckExitSP(int x, int y)
9629 if (game.gems_still_needed > 0)
9631 int element = Tile[x][y];
9632 int graphic = el2img(element);
9634 if (IS_ANIMATED(graphic))
9635 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9640 // do not re-open exit door closed after last player
9641 if (game.all_players_gone)
9644 Tile[x][y] = EL_SP_EXIT_OPENING;
9646 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9649 static void CloseAllOpenTimegates(void)
9653 SCAN_PLAYFIELD(x, y)
9655 int element = Tile[x][y];
9657 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9659 Tile[x][y] = EL_TIMEGATE_CLOSING;
9661 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9666 static void DrawTwinkleOnField(int x, int y)
9668 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9671 if (Tile[x][y] == EL_BD_DIAMOND)
9674 if (MovDelay[x][y] == 0) // next animation frame
9675 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9677 if (MovDelay[x][y] != 0) // wait some time before next frame
9681 DrawLevelElementAnimation(x, y, Tile[x][y]);
9683 if (MovDelay[x][y] != 0)
9685 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9686 10 - MovDelay[x][y]);
9688 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9693 static void MauerWaechst(int x, int y)
9697 if (!MovDelay[x][y]) // next animation frame
9698 MovDelay[x][y] = 3 * delay;
9700 if (MovDelay[x][y]) // wait some time before next frame
9704 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9706 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9707 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9709 DrawLevelGraphic(x, y, graphic, frame);
9712 if (!MovDelay[x][y])
9714 if (MovDir[x][y] == MV_LEFT)
9716 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9717 TEST_DrawLevelField(x - 1, y);
9719 else if (MovDir[x][y] == MV_RIGHT)
9721 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9722 TEST_DrawLevelField(x + 1, y);
9724 else if (MovDir[x][y] == MV_UP)
9726 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9727 TEST_DrawLevelField(x, y - 1);
9731 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9732 TEST_DrawLevelField(x, y + 1);
9735 Tile[x][y] = Store[x][y];
9737 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9738 TEST_DrawLevelField(x, y);
9743 static void MauerAbleger(int ax, int ay)
9745 int element = Tile[ax][ay];
9746 int graphic = el2img(element);
9747 boolean oben_frei = FALSE, unten_frei = FALSE;
9748 boolean links_frei = FALSE, rechts_frei = FALSE;
9749 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9750 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9751 boolean new_wall = FALSE;
9753 if (IS_ANIMATED(graphic))
9754 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9756 if (!MovDelay[ax][ay]) // start building new wall
9757 MovDelay[ax][ay] = 6;
9759 if (MovDelay[ax][ay]) // wait some time before building new wall
9762 if (MovDelay[ax][ay])
9766 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9768 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9770 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9772 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9775 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9776 element == EL_EXPANDABLE_WALL_ANY)
9780 Tile[ax][ay - 1] = EL_EXPANDABLE_WALL_GROWING;
9781 Store[ax][ay - 1] = element;
9782 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9783 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9784 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9789 Tile[ax][ay + 1] = EL_EXPANDABLE_WALL_GROWING;
9790 Store[ax][ay + 1] = element;
9791 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9792 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9793 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9798 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9799 element == EL_EXPANDABLE_WALL_ANY ||
9800 element == EL_EXPANDABLE_WALL ||
9801 element == EL_BD_EXPANDABLE_WALL)
9805 Tile[ax - 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9806 Store[ax - 1][ay] = element;
9807 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9808 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9809 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9815 Tile[ax + 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9816 Store[ax + 1][ay] = element;
9817 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9818 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9819 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9824 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9825 TEST_DrawLevelField(ax, ay);
9827 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9829 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9830 unten_massiv = TRUE;
9831 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9832 links_massiv = TRUE;
9833 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9834 rechts_massiv = TRUE;
9836 if (((oben_massiv && unten_massiv) ||
9837 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9838 element == EL_EXPANDABLE_WALL) &&
9839 ((links_massiv && rechts_massiv) ||
9840 element == EL_EXPANDABLE_WALL_VERTICAL))
9841 Tile[ax][ay] = EL_WALL;
9844 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9847 static void MauerAblegerStahl(int ax, int ay)
9849 int element = Tile[ax][ay];
9850 int graphic = el2img(element);
9851 boolean oben_frei = FALSE, unten_frei = FALSE;
9852 boolean links_frei = FALSE, rechts_frei = FALSE;
9853 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9854 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9855 boolean new_wall = FALSE;
9857 if (IS_ANIMATED(graphic))
9858 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9860 if (!MovDelay[ax][ay]) // start building new wall
9861 MovDelay[ax][ay] = 6;
9863 if (MovDelay[ax][ay]) // wait some time before building new wall
9866 if (MovDelay[ax][ay])
9870 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9872 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9874 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9876 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9879 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9880 element == EL_EXPANDABLE_STEELWALL_ANY)
9884 Tile[ax][ay - 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9885 Store[ax][ay - 1] = element;
9886 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9887 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9888 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9893 Tile[ax][ay + 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9894 Store[ax][ay + 1] = element;
9895 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9896 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9897 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9902 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9903 element == EL_EXPANDABLE_STEELWALL_ANY)
9907 Tile[ax - 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9908 Store[ax - 1][ay] = element;
9909 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9910 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9911 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9917 Tile[ax + 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9918 Store[ax + 1][ay] = element;
9919 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9920 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9921 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9926 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9928 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9929 unten_massiv = TRUE;
9930 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9931 links_massiv = TRUE;
9932 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9933 rechts_massiv = TRUE;
9935 if (((oben_massiv && unten_massiv) ||
9936 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9937 ((links_massiv && rechts_massiv) ||
9938 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9939 Tile[ax][ay] = EL_STEELWALL;
9942 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9945 static void CheckForDragon(int x, int y)
9948 boolean dragon_found = FALSE;
9949 static int xy[4][2] =
9957 for (i = 0; i < NUM_DIRECTIONS; i++)
9959 for (j = 0; j < 4; j++)
9961 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9963 if (IN_LEV_FIELD(xx, yy) &&
9964 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9966 if (Tile[xx][yy] == EL_DRAGON)
9967 dragon_found = TRUE;
9976 for (i = 0; i < NUM_DIRECTIONS; i++)
9978 for (j = 0; j < 3; j++)
9980 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9982 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9984 Tile[xx][yy] = EL_EMPTY;
9985 TEST_DrawLevelField(xx, yy);
9994 static void InitBuggyBase(int x, int y)
9996 int element = Tile[x][y];
9997 int activating_delay = FRAMES_PER_SECOND / 4;
10000 (element == EL_SP_BUGGY_BASE ?
10001 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10002 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10004 element == EL_SP_BUGGY_BASE_ACTIVE ?
10005 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10008 static void WarnBuggyBase(int x, int y)
10011 static int xy[4][2] =
10019 for (i = 0; i < NUM_DIRECTIONS; i++)
10021 int xx = x + xy[i][0];
10022 int yy = y + xy[i][1];
10024 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10026 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10033 static void InitTrap(int x, int y)
10035 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10038 static void ActivateTrap(int x, int y)
10040 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10043 static void ChangeActiveTrap(int x, int y)
10045 int graphic = IMG_TRAP_ACTIVE;
10047 // if new animation frame was drawn, correct crumbled sand border
10048 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10049 TEST_DrawLevelFieldCrumbled(x, y);
10052 static int getSpecialActionElement(int element, int number, int base_element)
10054 return (element != EL_EMPTY ? element :
10055 number != -1 ? base_element + number - 1 :
10059 static int getModifiedActionNumber(int value_old, int operator, int operand,
10060 int value_min, int value_max)
10062 int value_new = (operator == CA_MODE_SET ? operand :
10063 operator == CA_MODE_ADD ? value_old + operand :
10064 operator == CA_MODE_SUBTRACT ? value_old - operand :
10065 operator == CA_MODE_MULTIPLY ? value_old * operand :
10066 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10067 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10070 return (value_new < value_min ? value_min :
10071 value_new > value_max ? value_max :
10075 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10077 struct ElementInfo *ei = &element_info[element];
10078 struct ElementChangeInfo *change = &ei->change_page[page];
10079 int target_element = change->target_element;
10080 int action_type = change->action_type;
10081 int action_mode = change->action_mode;
10082 int action_arg = change->action_arg;
10083 int action_element = change->action_element;
10086 if (!change->has_action)
10089 // ---------- determine action paramater values -----------------------------
10091 int level_time_value =
10092 (level.time > 0 ? TimeLeft :
10095 int action_arg_element_raw =
10096 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10097 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10098 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10099 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10100 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10101 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10102 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10104 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10106 int action_arg_direction =
10107 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10108 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10109 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10110 change->actual_trigger_side :
10111 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10112 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10115 int action_arg_number_min =
10116 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10119 int action_arg_number_max =
10120 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10121 action_type == CA_SET_LEVEL_GEMS ? 999 :
10122 action_type == CA_SET_LEVEL_TIME ? 9999 :
10123 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10124 action_type == CA_SET_CE_VALUE ? 9999 :
10125 action_type == CA_SET_CE_SCORE ? 9999 :
10128 int action_arg_number_reset =
10129 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10130 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10131 action_type == CA_SET_LEVEL_TIME ? level.time :
10132 action_type == CA_SET_LEVEL_SCORE ? 0 :
10133 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10134 action_type == CA_SET_CE_SCORE ? 0 :
10137 int action_arg_number =
10138 (action_arg <= CA_ARG_MAX ? action_arg :
10139 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10140 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10141 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10142 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10143 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10144 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10145 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10146 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10147 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10148 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10149 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10150 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10151 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10152 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10153 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10154 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10155 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10156 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10157 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10158 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10159 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10162 int action_arg_number_old =
10163 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10164 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10165 action_type == CA_SET_LEVEL_SCORE ? game.score :
10166 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10167 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10170 int action_arg_number_new =
10171 getModifiedActionNumber(action_arg_number_old,
10172 action_mode, action_arg_number,
10173 action_arg_number_min, action_arg_number_max);
10175 int trigger_player_bits =
10176 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10177 change->actual_trigger_player_bits : change->trigger_player);
10179 int action_arg_player_bits =
10180 (action_arg >= CA_ARG_PLAYER_1 &&
10181 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10182 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10183 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10186 // ---------- execute action -----------------------------------------------
10188 switch (action_type)
10195 // ---------- level actions ----------------------------------------------
10197 case CA_RESTART_LEVEL:
10199 game.restart_level = TRUE;
10204 case CA_SHOW_ENVELOPE:
10206 int element = getSpecialActionElement(action_arg_element,
10207 action_arg_number, EL_ENVELOPE_1);
10209 if (IS_ENVELOPE(element))
10210 local_player->show_envelope = element;
10215 case CA_SET_LEVEL_TIME:
10217 if (level.time > 0) // only modify limited time value
10219 TimeLeft = action_arg_number_new;
10221 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10223 DisplayGameControlValues();
10225 if (!TimeLeft && game.time_limit)
10226 for (i = 0; i < MAX_PLAYERS; i++)
10227 KillPlayer(&stored_player[i]);
10233 case CA_SET_LEVEL_SCORE:
10235 game.score = action_arg_number_new;
10237 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10239 DisplayGameControlValues();
10244 case CA_SET_LEVEL_GEMS:
10246 game.gems_still_needed = action_arg_number_new;
10248 game.snapshot.collected_item = TRUE;
10250 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10252 DisplayGameControlValues();
10257 case CA_SET_LEVEL_WIND:
10259 game.wind_direction = action_arg_direction;
10264 case CA_SET_LEVEL_RANDOM_SEED:
10266 // ensure that setting a new random seed while playing is predictable
10267 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10272 // ---------- player actions ---------------------------------------------
10274 case CA_MOVE_PLAYER:
10275 case CA_MOVE_PLAYER_NEW:
10277 // automatically move to the next field in specified direction
10278 for (i = 0; i < MAX_PLAYERS; i++)
10279 if (trigger_player_bits & (1 << i))
10280 if (action_type == CA_MOVE_PLAYER ||
10281 stored_player[i].MovPos == 0)
10282 stored_player[i].programmed_action = action_arg_direction;
10287 case CA_EXIT_PLAYER:
10289 for (i = 0; i < MAX_PLAYERS; i++)
10290 if (action_arg_player_bits & (1 << i))
10291 ExitPlayer(&stored_player[i]);
10293 if (game.players_still_needed == 0)
10299 case CA_KILL_PLAYER:
10301 for (i = 0; i < MAX_PLAYERS; i++)
10302 if (action_arg_player_bits & (1 << i))
10303 KillPlayer(&stored_player[i]);
10308 case CA_SET_PLAYER_KEYS:
10310 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10311 int element = getSpecialActionElement(action_arg_element,
10312 action_arg_number, EL_KEY_1);
10314 if (IS_KEY(element))
10316 for (i = 0; i < MAX_PLAYERS; i++)
10318 if (trigger_player_bits & (1 << i))
10320 stored_player[i].key[KEY_NR(element)] = key_state;
10322 DrawGameDoorValues();
10330 case CA_SET_PLAYER_SPEED:
10332 for (i = 0; i < MAX_PLAYERS; i++)
10334 if (trigger_player_bits & (1 << i))
10336 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10338 if (action_arg == CA_ARG_SPEED_FASTER &&
10339 stored_player[i].cannot_move)
10341 action_arg_number = STEPSIZE_VERY_SLOW;
10343 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10344 action_arg == CA_ARG_SPEED_FASTER)
10346 action_arg_number = 2;
10347 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10350 else if (action_arg == CA_ARG_NUMBER_RESET)
10352 action_arg_number = level.initial_player_stepsize[i];
10356 getModifiedActionNumber(move_stepsize,
10359 action_arg_number_min,
10360 action_arg_number_max);
10362 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10369 case CA_SET_PLAYER_SHIELD:
10371 for (i = 0; i < MAX_PLAYERS; i++)
10373 if (trigger_player_bits & (1 << i))
10375 if (action_arg == CA_ARG_SHIELD_OFF)
10377 stored_player[i].shield_normal_time_left = 0;
10378 stored_player[i].shield_deadly_time_left = 0;
10380 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10382 stored_player[i].shield_normal_time_left = 999999;
10384 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10386 stored_player[i].shield_normal_time_left = 999999;
10387 stored_player[i].shield_deadly_time_left = 999999;
10395 case CA_SET_PLAYER_GRAVITY:
10397 for (i = 0; i < MAX_PLAYERS; i++)
10399 if (trigger_player_bits & (1 << i))
10401 stored_player[i].gravity =
10402 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10403 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10404 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10405 stored_player[i].gravity);
10412 case CA_SET_PLAYER_ARTWORK:
10414 for (i = 0; i < MAX_PLAYERS; i++)
10416 if (trigger_player_bits & (1 << i))
10418 int artwork_element = action_arg_element;
10420 if (action_arg == CA_ARG_ELEMENT_RESET)
10422 (level.use_artwork_element[i] ? level.artwork_element[i] :
10423 stored_player[i].element_nr);
10425 if (stored_player[i].artwork_element != artwork_element)
10426 stored_player[i].Frame = 0;
10428 stored_player[i].artwork_element = artwork_element;
10430 SetPlayerWaiting(&stored_player[i], FALSE);
10432 // set number of special actions for bored and sleeping animation
10433 stored_player[i].num_special_action_bored =
10434 get_num_special_action(artwork_element,
10435 ACTION_BORING_1, ACTION_BORING_LAST);
10436 stored_player[i].num_special_action_sleeping =
10437 get_num_special_action(artwork_element,
10438 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10445 case CA_SET_PLAYER_INVENTORY:
10447 for (i = 0; i < MAX_PLAYERS; i++)
10449 struct PlayerInfo *player = &stored_player[i];
10452 if (trigger_player_bits & (1 << i))
10454 int inventory_element = action_arg_element;
10456 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10457 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10458 action_arg == CA_ARG_ELEMENT_ACTION)
10460 int element = inventory_element;
10461 int collect_count = element_info[element].collect_count_initial;
10463 if (!IS_CUSTOM_ELEMENT(element))
10466 if (collect_count == 0)
10467 player->inventory_infinite_element = element;
10469 for (k = 0; k < collect_count; k++)
10470 if (player->inventory_size < MAX_INVENTORY_SIZE)
10471 player->inventory_element[player->inventory_size++] =
10474 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10475 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10476 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10478 if (player->inventory_infinite_element != EL_UNDEFINED &&
10479 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10480 action_arg_element_raw))
10481 player->inventory_infinite_element = EL_UNDEFINED;
10483 for (k = 0, j = 0; j < player->inventory_size; j++)
10485 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10486 action_arg_element_raw))
10487 player->inventory_element[k++] = player->inventory_element[j];
10490 player->inventory_size = k;
10492 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10494 if (player->inventory_size > 0)
10496 for (j = 0; j < player->inventory_size - 1; j++)
10497 player->inventory_element[j] = player->inventory_element[j + 1];
10499 player->inventory_size--;
10502 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10504 if (player->inventory_size > 0)
10505 player->inventory_size--;
10507 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10509 player->inventory_infinite_element = EL_UNDEFINED;
10510 player->inventory_size = 0;
10512 else if (action_arg == CA_ARG_INVENTORY_RESET)
10514 player->inventory_infinite_element = EL_UNDEFINED;
10515 player->inventory_size = 0;
10517 if (level.use_initial_inventory[i])
10519 for (j = 0; j < level.initial_inventory_size[i]; j++)
10521 int element = level.initial_inventory_content[i][j];
10522 int collect_count = element_info[element].collect_count_initial;
10524 if (!IS_CUSTOM_ELEMENT(element))
10527 if (collect_count == 0)
10528 player->inventory_infinite_element = element;
10530 for (k = 0; k < collect_count; k++)
10531 if (player->inventory_size < MAX_INVENTORY_SIZE)
10532 player->inventory_element[player->inventory_size++] =
10543 // ---------- CE actions -------------------------------------------------
10545 case CA_SET_CE_VALUE:
10547 int last_ce_value = CustomValue[x][y];
10549 CustomValue[x][y] = action_arg_number_new;
10551 if (CustomValue[x][y] != last_ce_value)
10553 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10554 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10556 if (CustomValue[x][y] == 0)
10558 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10559 ChangeCount[x][y] = 0; // allow at least one more change
10561 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10562 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10569 case CA_SET_CE_SCORE:
10571 int last_ce_score = ei->collect_score;
10573 ei->collect_score = action_arg_number_new;
10575 if (ei->collect_score != last_ce_score)
10577 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10578 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10580 if (ei->collect_score == 0)
10584 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10585 ChangeCount[x][y] = 0; // allow at least one more change
10587 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10588 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10591 This is a very special case that seems to be a mixture between
10592 CheckElementChange() and CheckTriggeredElementChange(): while
10593 the first one only affects single elements that are triggered
10594 directly, the second one affects multiple elements in the playfield
10595 that are triggered indirectly by another element. This is a third
10596 case: Changing the CE score always affects multiple identical CEs,
10597 so every affected CE must be checked, not only the single CE for
10598 which the CE score was changed in the first place (as every instance
10599 of that CE shares the same CE score, and therefore also can change)!
10601 SCAN_PLAYFIELD(xx, yy)
10603 if (Tile[xx][yy] == element)
10604 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10605 CE_SCORE_GETS_ZERO);
10613 case CA_SET_CE_ARTWORK:
10615 int artwork_element = action_arg_element;
10616 boolean reset_frame = FALSE;
10619 if (action_arg == CA_ARG_ELEMENT_RESET)
10620 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10623 if (ei->gfx_element != artwork_element)
10624 reset_frame = TRUE;
10626 ei->gfx_element = artwork_element;
10628 SCAN_PLAYFIELD(xx, yy)
10630 if (Tile[xx][yy] == element)
10634 ResetGfxAnimation(xx, yy);
10635 ResetRandomAnimationValue(xx, yy);
10638 TEST_DrawLevelField(xx, yy);
10645 // ---------- engine actions ---------------------------------------------
10647 case CA_SET_ENGINE_SCAN_MODE:
10649 InitPlayfieldScanMode(action_arg);
10659 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10661 int old_element = Tile[x][y];
10662 int new_element = GetElementFromGroupElement(element);
10663 int previous_move_direction = MovDir[x][y];
10664 int last_ce_value = CustomValue[x][y];
10665 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10666 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10667 boolean add_player_onto_element = (new_element_is_player &&
10668 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10669 IS_WALKABLE(old_element));
10671 if (!add_player_onto_element)
10673 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10674 RemoveMovingField(x, y);
10678 Tile[x][y] = new_element;
10680 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10681 MovDir[x][y] = previous_move_direction;
10683 if (element_info[new_element].use_last_ce_value)
10684 CustomValue[x][y] = last_ce_value;
10686 InitField_WithBug1(x, y, FALSE);
10688 new_element = Tile[x][y]; // element may have changed
10690 ResetGfxAnimation(x, y);
10691 ResetRandomAnimationValue(x, y);
10693 TEST_DrawLevelField(x, y);
10695 if (GFX_CRUMBLED(new_element))
10696 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10699 // check if element under the player changes from accessible to unaccessible
10700 // (needed for special case of dropping element which then changes)
10701 // (must be checked after creating new element for walkable group elements)
10702 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10703 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10710 // "ChangeCount" not set yet to allow "entered by player" change one time
10711 if (new_element_is_player)
10712 RelocatePlayer(x, y, new_element);
10715 ChangeCount[x][y]++; // count number of changes in the same frame
10717 TestIfBadThingTouchesPlayer(x, y);
10718 TestIfPlayerTouchesCustomElement(x, y);
10719 TestIfElementTouchesCustomElement(x, y);
10722 static void CreateField(int x, int y, int element)
10724 CreateFieldExt(x, y, element, FALSE);
10727 static void CreateElementFromChange(int x, int y, int element)
10729 element = GET_VALID_RUNTIME_ELEMENT(element);
10731 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10733 int old_element = Tile[x][y];
10735 // prevent changed element from moving in same engine frame
10736 // unless both old and new element can either fall or move
10737 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10738 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10742 CreateFieldExt(x, y, element, TRUE);
10745 static boolean ChangeElement(int x, int y, int element, int page)
10747 struct ElementInfo *ei = &element_info[element];
10748 struct ElementChangeInfo *change = &ei->change_page[page];
10749 int ce_value = CustomValue[x][y];
10750 int ce_score = ei->collect_score;
10751 int target_element;
10752 int old_element = Tile[x][y];
10754 // always use default change event to prevent running into a loop
10755 if (ChangeEvent[x][y] == -1)
10756 ChangeEvent[x][y] = CE_DELAY;
10758 if (ChangeEvent[x][y] == CE_DELAY)
10760 // reset actual trigger element, trigger player and action element
10761 change->actual_trigger_element = EL_EMPTY;
10762 change->actual_trigger_player = EL_EMPTY;
10763 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10764 change->actual_trigger_side = CH_SIDE_NONE;
10765 change->actual_trigger_ce_value = 0;
10766 change->actual_trigger_ce_score = 0;
10769 // do not change elements more than a specified maximum number of changes
10770 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10773 ChangeCount[x][y]++; // count number of changes in the same frame
10775 if (change->explode)
10782 if (change->use_target_content)
10784 boolean complete_replace = TRUE;
10785 boolean can_replace[3][3];
10788 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10791 boolean is_walkable;
10792 boolean is_diggable;
10793 boolean is_collectible;
10794 boolean is_removable;
10795 boolean is_destructible;
10796 int ex = x + xx - 1;
10797 int ey = y + yy - 1;
10798 int content_element = change->target_content.e[xx][yy];
10801 can_replace[xx][yy] = TRUE;
10803 if (ex == x && ey == y) // do not check changing element itself
10806 if (content_element == EL_EMPTY_SPACE)
10808 can_replace[xx][yy] = FALSE; // do not replace border with space
10813 if (!IN_LEV_FIELD(ex, ey))
10815 can_replace[xx][yy] = FALSE;
10816 complete_replace = FALSE;
10823 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10824 e = MovingOrBlocked2Element(ex, ey);
10826 is_empty = (IS_FREE(ex, ey) ||
10827 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10829 is_walkable = (is_empty || IS_WALKABLE(e));
10830 is_diggable = (is_empty || IS_DIGGABLE(e));
10831 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10832 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10833 is_removable = (is_diggable || is_collectible);
10835 can_replace[xx][yy] =
10836 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10837 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10838 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10839 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10840 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10841 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10842 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10844 if (!can_replace[xx][yy])
10845 complete_replace = FALSE;
10848 if (!change->only_if_complete || complete_replace)
10850 boolean something_has_changed = FALSE;
10852 if (change->only_if_complete && change->use_random_replace &&
10853 RND(100) < change->random_percentage)
10856 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10858 int ex = x + xx - 1;
10859 int ey = y + yy - 1;
10860 int content_element;
10862 if (can_replace[xx][yy] && (!change->use_random_replace ||
10863 RND(100) < change->random_percentage))
10865 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10866 RemoveMovingField(ex, ey);
10868 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10870 content_element = change->target_content.e[xx][yy];
10871 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10872 ce_value, ce_score);
10874 CreateElementFromChange(ex, ey, target_element);
10876 something_has_changed = TRUE;
10878 // for symmetry reasons, freeze newly created border elements
10879 if (ex != x || ey != y)
10880 Stop[ex][ey] = TRUE; // no more moving in this frame
10884 if (something_has_changed)
10886 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10887 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10893 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10894 ce_value, ce_score);
10896 if (element == EL_DIAGONAL_GROWING ||
10897 element == EL_DIAGONAL_SHRINKING)
10899 target_element = Store[x][y];
10901 Store[x][y] = EL_EMPTY;
10904 // special case: element changes to player (and may be kept if walkable)
10905 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10906 CreateElementFromChange(x, y, EL_EMPTY);
10908 CreateElementFromChange(x, y, target_element);
10910 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10911 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10914 // this uses direct change before indirect change
10915 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10920 static void HandleElementChange(int x, int y, int page)
10922 int element = MovingOrBlocked2Element(x, y);
10923 struct ElementInfo *ei = &element_info[element];
10924 struct ElementChangeInfo *change = &ei->change_page[page];
10925 boolean handle_action_before_change = FALSE;
10928 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10929 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10931 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10932 x, y, element, element_info[element].token_name);
10933 Debug("game:playing:HandleElementChange", "This should never happen!");
10937 // this can happen with classic bombs on walkable, changing elements
10938 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10943 if (ChangeDelay[x][y] == 0) // initialize element change
10945 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10947 if (change->can_change)
10949 // !!! not clear why graphic animation should be reset at all here !!!
10950 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10951 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10954 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10956 When using an animation frame delay of 1 (this only happens with
10957 "sp_zonk.moving.left/right" in the classic graphics), the default
10958 (non-moving) animation shows wrong animation frames (while the
10959 moving animation, like "sp_zonk.moving.left/right", is correct,
10960 so this graphical bug never shows up with the classic graphics).
10961 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10962 be drawn instead of the correct frames 0,1,2,3. This is caused by
10963 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10964 an element change: First when the change delay ("ChangeDelay[][]")
10965 counter has reached zero after decrementing, then a second time in
10966 the next frame (after "GfxFrame[][]" was already incremented) when
10967 "ChangeDelay[][]" is reset to the initial delay value again.
10969 This causes frame 0 to be drawn twice, while the last frame won't
10970 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10972 As some animations may already be cleverly designed around this bug
10973 (at least the "Snake Bite" snake tail animation does this), it cannot
10974 simply be fixed here without breaking such existing animations.
10975 Unfortunately, it cannot easily be detected if a graphics set was
10976 designed "before" or "after" the bug was fixed. As a workaround,
10977 a new graphics set option "game.graphics_engine_version" was added
10978 to be able to specify the game's major release version for which the
10979 graphics set was designed, which can then be used to decide if the
10980 bugfix should be used (version 4 and above) or not (version 3 or
10981 below, or if no version was specified at all, as with old sets).
10983 (The wrong/fixed animation frames can be tested with the test level set
10984 "test_gfxframe" and level "000", which contains a specially prepared
10985 custom element at level position (x/y) == (11/9) which uses the zonk
10986 animation mentioned above. Using "game.graphics_engine_version: 4"
10987 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10988 This can also be seen from the debug output for this test element.)
10991 // when a custom element is about to change (for example by change delay),
10992 // do not reset graphic animation when the custom element is moving
10993 if (game.graphics_engine_version < 4 &&
10996 ResetGfxAnimation(x, y);
10997 ResetRandomAnimationValue(x, y);
11000 if (change->pre_change_function)
11001 change->pre_change_function(x, y);
11005 ChangeDelay[x][y]--;
11007 if (ChangeDelay[x][y] != 0) // continue element change
11009 if (change->can_change)
11011 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11013 if (IS_ANIMATED(graphic))
11014 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11016 if (change->change_function)
11017 change->change_function(x, y);
11020 else // finish element change
11022 if (ChangePage[x][y] != -1) // remember page from delayed change
11024 page = ChangePage[x][y];
11025 ChangePage[x][y] = -1;
11027 change = &ei->change_page[page];
11030 if (IS_MOVING(x, y)) // never change a running system ;-)
11032 ChangeDelay[x][y] = 1; // try change after next move step
11033 ChangePage[x][y] = page; // remember page to use for change
11038 // special case: set new level random seed before changing element
11039 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11040 handle_action_before_change = TRUE;
11042 if (change->has_action && handle_action_before_change)
11043 ExecuteCustomElementAction(x, y, element, page);
11045 if (change->can_change)
11047 if (ChangeElement(x, y, element, page))
11049 if (change->post_change_function)
11050 change->post_change_function(x, y);
11054 if (change->has_action && !handle_action_before_change)
11055 ExecuteCustomElementAction(x, y, element, page);
11059 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11060 int trigger_element,
11062 int trigger_player,
11066 boolean change_done_any = FALSE;
11067 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11070 if (!(trigger_events[trigger_element][trigger_event]))
11073 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11075 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11077 int element = EL_CUSTOM_START + i;
11078 boolean change_done = FALSE;
11081 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11082 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11085 for (p = 0; p < element_info[element].num_change_pages; p++)
11087 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11089 if (change->can_change_or_has_action &&
11090 change->has_event[trigger_event] &&
11091 change->trigger_side & trigger_side &&
11092 change->trigger_player & trigger_player &&
11093 change->trigger_page & trigger_page_bits &&
11094 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11096 change->actual_trigger_element = trigger_element;
11097 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11098 change->actual_trigger_player_bits = trigger_player;
11099 change->actual_trigger_side = trigger_side;
11100 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11101 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11103 if ((change->can_change && !change_done) || change->has_action)
11107 SCAN_PLAYFIELD(x, y)
11109 if (Tile[x][y] == element)
11111 if (change->can_change && !change_done)
11113 // if element already changed in this frame, not only prevent
11114 // another element change (checked in ChangeElement()), but
11115 // also prevent additional element actions for this element
11117 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11118 !level.use_action_after_change_bug)
11121 ChangeDelay[x][y] = 1;
11122 ChangeEvent[x][y] = trigger_event;
11124 HandleElementChange(x, y, p);
11126 else if (change->has_action)
11128 // if element already changed in this frame, not only prevent
11129 // another element change (checked in ChangeElement()), but
11130 // also prevent additional element actions for this element
11132 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11133 !level.use_action_after_change_bug)
11136 ExecuteCustomElementAction(x, y, element, p);
11137 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11142 if (change->can_change)
11144 change_done = TRUE;
11145 change_done_any = TRUE;
11152 RECURSION_LOOP_DETECTION_END();
11154 return change_done_any;
11157 static boolean CheckElementChangeExt(int x, int y,
11159 int trigger_element,
11161 int trigger_player,
11164 boolean change_done = FALSE;
11167 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11168 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11171 if (Tile[x][y] == EL_BLOCKED)
11173 Blocked2Moving(x, y, &x, &y);
11174 element = Tile[x][y];
11177 // check if element has already changed or is about to change after moving
11178 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11179 Tile[x][y] != element) ||
11181 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11182 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11183 ChangePage[x][y] != -1)))
11186 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11188 for (p = 0; p < element_info[element].num_change_pages; p++)
11190 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11192 /* check trigger element for all events where the element that is checked
11193 for changing interacts with a directly adjacent element -- this is
11194 different to element changes that affect other elements to change on the
11195 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11196 boolean check_trigger_element =
11197 (trigger_event == CE_NEXT_TO_X ||
11198 trigger_event == CE_TOUCHING_X ||
11199 trigger_event == CE_HITTING_X ||
11200 trigger_event == CE_HIT_BY_X ||
11201 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11203 if (change->can_change_or_has_action &&
11204 change->has_event[trigger_event] &&
11205 change->trigger_side & trigger_side &&
11206 change->trigger_player & trigger_player &&
11207 (!check_trigger_element ||
11208 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11210 change->actual_trigger_element = trigger_element;
11211 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11212 change->actual_trigger_player_bits = trigger_player;
11213 change->actual_trigger_side = trigger_side;
11214 change->actual_trigger_ce_value = CustomValue[x][y];
11215 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11217 // special case: trigger element not at (x,y) position for some events
11218 if (check_trigger_element)
11230 { 0, 0 }, { 0, 0 }, { 0, 0 },
11234 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11235 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11237 change->actual_trigger_ce_value = CustomValue[xx][yy];
11238 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11241 if (change->can_change && !change_done)
11243 ChangeDelay[x][y] = 1;
11244 ChangeEvent[x][y] = trigger_event;
11246 HandleElementChange(x, y, p);
11248 change_done = TRUE;
11250 else if (change->has_action)
11252 ExecuteCustomElementAction(x, y, element, p);
11253 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11258 RECURSION_LOOP_DETECTION_END();
11260 return change_done;
11263 static void PlayPlayerSound(struct PlayerInfo *player)
11265 int jx = player->jx, jy = player->jy;
11266 int sound_element = player->artwork_element;
11267 int last_action = player->last_action_waiting;
11268 int action = player->action_waiting;
11270 if (player->is_waiting)
11272 if (action != last_action)
11273 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11275 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11279 if (action != last_action)
11280 StopSound(element_info[sound_element].sound[last_action]);
11282 if (last_action == ACTION_SLEEPING)
11283 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11287 static void PlayAllPlayersSound(void)
11291 for (i = 0; i < MAX_PLAYERS; i++)
11292 if (stored_player[i].active)
11293 PlayPlayerSound(&stored_player[i]);
11296 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11298 boolean last_waiting = player->is_waiting;
11299 int move_dir = player->MovDir;
11301 player->dir_waiting = move_dir;
11302 player->last_action_waiting = player->action_waiting;
11306 if (!last_waiting) // not waiting -> waiting
11308 player->is_waiting = TRUE;
11310 player->frame_counter_bored =
11312 game.player_boring_delay_fixed +
11313 GetSimpleRandom(game.player_boring_delay_random);
11314 player->frame_counter_sleeping =
11316 game.player_sleeping_delay_fixed +
11317 GetSimpleRandom(game.player_sleeping_delay_random);
11319 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11322 if (game.player_sleeping_delay_fixed +
11323 game.player_sleeping_delay_random > 0 &&
11324 player->anim_delay_counter == 0 &&
11325 player->post_delay_counter == 0 &&
11326 FrameCounter >= player->frame_counter_sleeping)
11327 player->is_sleeping = TRUE;
11328 else if (game.player_boring_delay_fixed +
11329 game.player_boring_delay_random > 0 &&
11330 FrameCounter >= player->frame_counter_bored)
11331 player->is_bored = TRUE;
11333 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11334 player->is_bored ? ACTION_BORING :
11337 if (player->is_sleeping && player->use_murphy)
11339 // special case for sleeping Murphy when leaning against non-free tile
11341 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11342 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11343 !IS_MOVING(player->jx - 1, player->jy)))
11344 move_dir = MV_LEFT;
11345 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11346 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11347 !IS_MOVING(player->jx + 1, player->jy)))
11348 move_dir = MV_RIGHT;
11350 player->is_sleeping = FALSE;
11352 player->dir_waiting = move_dir;
11355 if (player->is_sleeping)
11357 if (player->num_special_action_sleeping > 0)
11359 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11361 int last_special_action = player->special_action_sleeping;
11362 int num_special_action = player->num_special_action_sleeping;
11363 int special_action =
11364 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11365 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11366 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11367 last_special_action + 1 : ACTION_SLEEPING);
11368 int special_graphic =
11369 el_act_dir2img(player->artwork_element, special_action, move_dir);
11371 player->anim_delay_counter =
11372 graphic_info[special_graphic].anim_delay_fixed +
11373 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11374 player->post_delay_counter =
11375 graphic_info[special_graphic].post_delay_fixed +
11376 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11378 player->special_action_sleeping = special_action;
11381 if (player->anim_delay_counter > 0)
11383 player->action_waiting = player->special_action_sleeping;
11384 player->anim_delay_counter--;
11386 else if (player->post_delay_counter > 0)
11388 player->post_delay_counter--;
11392 else if (player->is_bored)
11394 if (player->num_special_action_bored > 0)
11396 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11398 int special_action =
11399 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11400 int special_graphic =
11401 el_act_dir2img(player->artwork_element, special_action, move_dir);
11403 player->anim_delay_counter =
11404 graphic_info[special_graphic].anim_delay_fixed +
11405 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11406 player->post_delay_counter =
11407 graphic_info[special_graphic].post_delay_fixed +
11408 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11410 player->special_action_bored = special_action;
11413 if (player->anim_delay_counter > 0)
11415 player->action_waiting = player->special_action_bored;
11416 player->anim_delay_counter--;
11418 else if (player->post_delay_counter > 0)
11420 player->post_delay_counter--;
11425 else if (last_waiting) // waiting -> not waiting
11427 player->is_waiting = FALSE;
11428 player->is_bored = FALSE;
11429 player->is_sleeping = FALSE;
11431 player->frame_counter_bored = -1;
11432 player->frame_counter_sleeping = -1;
11434 player->anim_delay_counter = 0;
11435 player->post_delay_counter = 0;
11437 player->dir_waiting = player->MovDir;
11438 player->action_waiting = ACTION_DEFAULT;
11440 player->special_action_bored = ACTION_DEFAULT;
11441 player->special_action_sleeping = ACTION_DEFAULT;
11445 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11447 if ((!player->is_moving && player->was_moving) ||
11448 (player->MovPos == 0 && player->was_moving) ||
11449 (player->is_snapping && !player->was_snapping) ||
11450 (player->is_dropping && !player->was_dropping))
11452 if (!CheckSaveEngineSnapshotToList())
11455 player->was_moving = FALSE;
11456 player->was_snapping = TRUE;
11457 player->was_dropping = TRUE;
11461 if (player->is_moving)
11462 player->was_moving = TRUE;
11464 if (!player->is_snapping)
11465 player->was_snapping = FALSE;
11467 if (!player->is_dropping)
11468 player->was_dropping = FALSE;
11471 static struct MouseActionInfo mouse_action_last = { 0 };
11472 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11473 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11476 CheckSaveEngineSnapshotToList();
11478 mouse_action_last = mouse_action;
11481 static void CheckSingleStepMode(struct PlayerInfo *player)
11483 if (tape.single_step && tape.recording && !tape.pausing)
11485 // as it is called "single step mode", just return to pause mode when the
11486 // player stopped moving after one tile (or never starts moving at all)
11487 // (reverse logic needed here in case single step mode used in team mode)
11488 if (player->is_moving ||
11489 player->is_pushing ||
11490 player->is_dropping_pressed ||
11491 player->effective_mouse_action.button)
11492 game.enter_single_step_mode = FALSE;
11495 CheckSaveEngineSnapshot(player);
11498 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11500 int left = player_action & JOY_LEFT;
11501 int right = player_action & JOY_RIGHT;
11502 int up = player_action & JOY_UP;
11503 int down = player_action & JOY_DOWN;
11504 int button1 = player_action & JOY_BUTTON_1;
11505 int button2 = player_action & JOY_BUTTON_2;
11506 int dx = (left ? -1 : right ? 1 : 0);
11507 int dy = (up ? -1 : down ? 1 : 0);
11509 if (!player->active || tape.pausing)
11515 SnapField(player, dx, dy);
11519 DropElement(player);
11521 MovePlayer(player, dx, dy);
11524 CheckSingleStepMode(player);
11526 SetPlayerWaiting(player, FALSE);
11528 return player_action;
11532 // no actions for this player (no input at player's configured device)
11534 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11535 SnapField(player, 0, 0);
11536 CheckGravityMovementWhenNotMoving(player);
11538 if (player->MovPos == 0)
11539 SetPlayerWaiting(player, TRUE);
11541 if (player->MovPos == 0) // needed for tape.playing
11542 player->is_moving = FALSE;
11544 player->is_dropping = FALSE;
11545 player->is_dropping_pressed = FALSE;
11546 player->drop_pressed_delay = 0;
11548 CheckSingleStepMode(player);
11554 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11557 if (!tape.use_mouse_actions)
11560 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11561 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11562 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11565 static void SetTapeActionFromMouseAction(byte *tape_action,
11566 struct MouseActionInfo *mouse_action)
11568 if (!tape.use_mouse_actions)
11571 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11572 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11573 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11576 static void CheckLevelSolved(void)
11578 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11580 if (game_em.level_solved &&
11581 !game_em.game_over) // game won
11585 game_em.game_over = TRUE;
11587 game.all_players_gone = TRUE;
11590 if (game_em.game_over) // game lost
11591 game.all_players_gone = TRUE;
11593 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11595 if (game_sp.level_solved &&
11596 !game_sp.game_over) // game won
11600 game_sp.game_over = TRUE;
11602 game.all_players_gone = TRUE;
11605 if (game_sp.game_over) // game lost
11606 game.all_players_gone = TRUE;
11608 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11610 if (game_mm.level_solved &&
11611 !game_mm.game_over) // game won
11615 game_mm.game_over = TRUE;
11617 game.all_players_gone = TRUE;
11620 if (game_mm.game_over) // game lost
11621 game.all_players_gone = TRUE;
11625 static void CheckLevelTime_StepCounter(void)
11635 if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved)
11636 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11638 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11640 DisplayGameControlValues();
11642 if (!TimeLeft && game.time_limit && !game.LevelSolved)
11643 for (i = 0; i < MAX_PLAYERS; i++)
11644 KillPlayer(&stored_player[i]);
11646 else if (game.no_level_time_limit && !game.all_players_gone)
11648 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11650 DisplayGameControlValues();
11654 static void CheckLevelTime(void)
11658 if (TimeFrames >= FRAMES_PER_SECOND)
11663 for (i = 0; i < MAX_PLAYERS; i++)
11665 struct PlayerInfo *player = &stored_player[i];
11667 if (SHIELD_ON(player))
11669 player->shield_normal_time_left--;
11671 if (player->shield_deadly_time_left > 0)
11672 player->shield_deadly_time_left--;
11676 if (!game.LevelSolved && !level.use_step_counter)
11684 if (TimeLeft <= 10 && game.time_limit)
11685 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11687 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11688 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11690 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11692 if (!TimeLeft && game.time_limit)
11694 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11695 game_em.lev->killed_out_of_time = TRUE;
11697 for (i = 0; i < MAX_PLAYERS; i++)
11698 KillPlayer(&stored_player[i]);
11701 else if (game.no_level_time_limit && !game.all_players_gone)
11703 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11706 game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft);
11709 if (tape.recording || tape.playing)
11710 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11713 if (tape.recording || tape.playing)
11714 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11716 UpdateAndDisplayGameControlValues();
11719 void AdvanceFrameAndPlayerCounters(int player_nr)
11723 // advance frame counters (global frame counter and time frame counter)
11727 // advance player counters (counters for move delay, move animation etc.)
11728 for (i = 0; i < MAX_PLAYERS; i++)
11730 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11731 int move_delay_value = stored_player[i].move_delay_value;
11732 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11734 if (!advance_player_counters) // not all players may be affected
11737 if (move_frames == 0) // less than one move per game frame
11739 int stepsize = TILEX / move_delay_value;
11740 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11741 int count = (stored_player[i].is_moving ?
11742 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11744 if (count % delay == 0)
11748 stored_player[i].Frame += move_frames;
11750 if (stored_player[i].MovPos != 0)
11751 stored_player[i].StepFrame += move_frames;
11753 if (stored_player[i].move_delay > 0)
11754 stored_player[i].move_delay--;
11756 // due to bugs in previous versions, counter must count up, not down
11757 if (stored_player[i].push_delay != -1)
11758 stored_player[i].push_delay++;
11760 if (stored_player[i].drop_delay > 0)
11761 stored_player[i].drop_delay--;
11763 if (stored_player[i].is_dropping_pressed)
11764 stored_player[i].drop_pressed_delay++;
11768 void StartGameActions(boolean init_network_game, boolean record_tape,
11771 unsigned int new_random_seed = InitRND(random_seed);
11774 TapeStartRecording(new_random_seed);
11776 if (setup.auto_pause_on_start && !tape.pausing)
11777 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11779 if (init_network_game)
11781 SendToServer_LevelFile();
11782 SendToServer_StartPlaying();
11790 static void GameActionsExt(void)
11793 static unsigned int game_frame_delay = 0;
11795 unsigned int game_frame_delay_value;
11796 byte *recorded_player_action;
11797 byte summarized_player_action = 0;
11798 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11801 // detect endless loops, caused by custom element programming
11802 if (recursion_loop_detected && recursion_loop_depth == 0)
11804 char *message = getStringCat3("Internal Error! Element ",
11805 EL_NAME(recursion_loop_element),
11806 " caused endless loop! Quit the game?");
11808 Warn("element '%s' caused endless loop in game engine",
11809 EL_NAME(recursion_loop_element));
11811 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11813 recursion_loop_detected = FALSE; // if game should be continued
11820 if (game.restart_level)
11821 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11823 CheckLevelSolved();
11825 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11828 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11831 if (game_status != GAME_MODE_PLAYING) // status might have changed
11834 game_frame_delay_value =
11835 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11837 if (tape.playing && tape.warp_forward && !tape.pausing)
11838 game_frame_delay_value = 0;
11840 SetVideoFrameDelay(game_frame_delay_value);
11842 // (de)activate virtual buttons depending on current game status
11843 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11845 if (game.all_players_gone) // if no players there to be controlled anymore
11846 SetOverlayActive(FALSE);
11847 else if (!tape.playing) // if game continues after tape stopped playing
11848 SetOverlayActive(TRUE);
11853 // ---------- main game synchronization point ----------
11855 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11857 Debug("game:playing:skip", "skip == %d", skip);
11860 // ---------- main game synchronization point ----------
11862 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11866 if (network_playing && !network_player_action_received)
11868 // try to get network player actions in time
11870 // last chance to get network player actions without main loop delay
11871 HandleNetworking();
11873 // game was quit by network peer
11874 if (game_status != GAME_MODE_PLAYING)
11877 // check if network player actions still missing and game still running
11878 if (!network_player_action_received && !checkGameEnded())
11879 return; // failed to get network player actions in time
11881 // do not yet reset "network_player_action_received" (for tape.pausing)
11887 // at this point we know that we really continue executing the game
11889 network_player_action_received = FALSE;
11891 // when playing tape, read previously recorded player input from tape data
11892 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11894 local_player->effective_mouse_action = local_player->mouse_action;
11896 if (recorded_player_action != NULL)
11897 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11898 recorded_player_action);
11900 // TapePlayAction() may return NULL when toggling to "pause before death"
11904 if (tape.set_centered_player)
11906 game.centered_player_nr_next = tape.centered_player_nr_next;
11907 game.set_centered_player = TRUE;
11910 for (i = 0; i < MAX_PLAYERS; i++)
11912 summarized_player_action |= stored_player[i].action;
11914 if (!network_playing && (game.team_mode || tape.playing))
11915 stored_player[i].effective_action = stored_player[i].action;
11918 if (network_playing && !checkGameEnded())
11919 SendToServer_MovePlayer(summarized_player_action);
11921 // summarize all actions at local players mapped input device position
11922 // (this allows using different input devices in single player mode)
11923 if (!network.enabled && !game.team_mode)
11924 stored_player[map_player_action[local_player->index_nr]].effective_action =
11925 summarized_player_action;
11927 // summarize all actions at centered player in local team mode
11928 if (tape.recording &&
11929 setup.team_mode && !network.enabled &&
11930 setup.input_on_focus &&
11931 game.centered_player_nr != -1)
11933 for (i = 0; i < MAX_PLAYERS; i++)
11934 stored_player[map_player_action[i]].effective_action =
11935 (i == game.centered_player_nr ? summarized_player_action : 0);
11938 if (recorded_player_action != NULL)
11939 for (i = 0; i < MAX_PLAYERS; i++)
11940 stored_player[i].effective_action = recorded_player_action[i];
11942 for (i = 0; i < MAX_PLAYERS; i++)
11944 tape_action[i] = stored_player[i].effective_action;
11946 /* (this may happen in the RND game engine if a player was not present on
11947 the playfield on level start, but appeared later from a custom element */
11948 if (setup.team_mode &&
11951 !tape.player_participates[i])
11952 tape.player_participates[i] = TRUE;
11955 SetTapeActionFromMouseAction(tape_action,
11956 &local_player->effective_mouse_action);
11958 // only record actions from input devices, but not programmed actions
11959 if (tape.recording)
11960 TapeRecordAction(tape_action);
11962 // remember if game was played (especially after tape stopped playing)
11963 if (!tape.playing && summarized_player_action)
11964 game.GamePlayed = TRUE;
11966 #if USE_NEW_PLAYER_ASSIGNMENTS
11967 // !!! also map player actions in single player mode !!!
11968 // if (game.team_mode)
11971 byte mapped_action[MAX_PLAYERS];
11973 #if DEBUG_PLAYER_ACTIONS
11974 for (i = 0; i < MAX_PLAYERS; i++)
11975 DebugContinued("", "%d, ", stored_player[i].effective_action);
11978 for (i = 0; i < MAX_PLAYERS; i++)
11979 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11981 for (i = 0; i < MAX_PLAYERS; i++)
11982 stored_player[i].effective_action = mapped_action[i];
11984 #if DEBUG_PLAYER_ACTIONS
11985 DebugContinued("", "=> ");
11986 for (i = 0; i < MAX_PLAYERS; i++)
11987 DebugContinued("", "%d, ", stored_player[i].effective_action);
11988 DebugContinued("game:playing:player", "\n");
11991 #if DEBUG_PLAYER_ACTIONS
11994 for (i = 0; i < MAX_PLAYERS; i++)
11995 DebugContinued("", "%d, ", stored_player[i].effective_action);
11996 DebugContinued("game:playing:player", "\n");
12001 for (i = 0; i < MAX_PLAYERS; i++)
12003 // allow engine snapshot in case of changed movement attempt
12004 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12005 (stored_player[i].effective_action & KEY_MOTION))
12006 game.snapshot.changed_action = TRUE;
12008 // allow engine snapshot in case of snapping/dropping attempt
12009 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12010 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12011 game.snapshot.changed_action = TRUE;
12013 game.snapshot.last_action[i] = stored_player[i].effective_action;
12016 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12018 GameActions_EM_Main();
12020 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12022 GameActions_SP_Main();
12024 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12026 GameActions_MM_Main();
12030 GameActions_RND_Main();
12033 BlitScreenToBitmap(backbuffer);
12035 CheckLevelSolved();
12038 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12040 if (global.show_frames_per_second)
12042 static unsigned int fps_counter = 0;
12043 static int fps_frames = 0;
12044 unsigned int fps_delay_ms = Counter() - fps_counter;
12048 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12050 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12053 fps_counter = Counter();
12055 // always draw FPS to screen after FPS value was updated
12056 redraw_mask |= REDRAW_FPS;
12059 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12060 if (GetDrawDeactivationMask() == REDRAW_NONE)
12061 redraw_mask |= REDRAW_FPS;
12065 static void GameActions_CheckSaveEngineSnapshot(void)
12067 if (!game.snapshot.save_snapshot)
12070 // clear flag for saving snapshot _before_ saving snapshot
12071 game.snapshot.save_snapshot = FALSE;
12073 SaveEngineSnapshotToList();
12076 void GameActions(void)
12080 GameActions_CheckSaveEngineSnapshot();
12083 void GameActions_EM_Main(void)
12085 byte effective_action[MAX_PLAYERS];
12086 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12089 for (i = 0; i < MAX_PLAYERS; i++)
12090 effective_action[i] = stored_player[i].effective_action;
12092 GameActions_EM(effective_action, warp_mode);
12095 void GameActions_SP_Main(void)
12097 byte effective_action[MAX_PLAYERS];
12098 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12101 for (i = 0; i < MAX_PLAYERS; i++)
12102 effective_action[i] = stored_player[i].effective_action;
12104 GameActions_SP(effective_action, warp_mode);
12106 for (i = 0; i < MAX_PLAYERS; i++)
12108 if (stored_player[i].force_dropping)
12109 stored_player[i].action |= KEY_BUTTON_DROP;
12111 stored_player[i].force_dropping = FALSE;
12115 void GameActions_MM_Main(void)
12117 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12119 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12122 void GameActions_RND_Main(void)
12127 void GameActions_RND(void)
12129 static struct MouseActionInfo mouse_action_last = { 0 };
12130 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12131 int magic_wall_x = 0, magic_wall_y = 0;
12132 int i, x, y, element, graphic, last_gfx_frame;
12134 InitPlayfieldScanModeVars();
12136 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12138 SCAN_PLAYFIELD(x, y)
12140 ChangeCount[x][y] = 0;
12141 ChangeEvent[x][y] = -1;
12145 if (game.set_centered_player)
12147 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12149 // switching to "all players" only possible if all players fit to screen
12150 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12152 game.centered_player_nr_next = game.centered_player_nr;
12153 game.set_centered_player = FALSE;
12156 // do not switch focus to non-existing (or non-active) player
12157 if (game.centered_player_nr_next >= 0 &&
12158 !stored_player[game.centered_player_nr_next].active)
12160 game.centered_player_nr_next = game.centered_player_nr;
12161 game.set_centered_player = FALSE;
12165 if (game.set_centered_player &&
12166 ScreenMovPos == 0) // screen currently aligned at tile position
12170 if (game.centered_player_nr_next == -1)
12172 setScreenCenteredToAllPlayers(&sx, &sy);
12176 sx = stored_player[game.centered_player_nr_next].jx;
12177 sy = stored_player[game.centered_player_nr_next].jy;
12180 game.centered_player_nr = game.centered_player_nr_next;
12181 game.set_centered_player = FALSE;
12183 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12184 DrawGameDoorValues();
12187 // check single step mode (set flag and clear again if any player is active)
12188 game.enter_single_step_mode =
12189 (tape.single_step && tape.recording && !tape.pausing);
12191 for (i = 0; i < MAX_PLAYERS; i++)
12193 int actual_player_action = stored_player[i].effective_action;
12196 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12197 - rnd_equinox_tetrachloride 048
12198 - rnd_equinox_tetrachloride_ii 096
12199 - rnd_emanuel_schmieg 002
12200 - doctor_sloan_ww 001, 020
12202 if (stored_player[i].MovPos == 0)
12203 CheckGravityMovement(&stored_player[i]);
12206 // overwrite programmed action with tape action
12207 if (stored_player[i].programmed_action)
12208 actual_player_action = stored_player[i].programmed_action;
12210 PlayerActions(&stored_player[i], actual_player_action);
12212 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12215 // single step pause mode may already have been toggled by "ScrollPlayer()"
12216 if (game.enter_single_step_mode && !tape.pausing)
12217 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12219 ScrollScreen(NULL, SCROLL_GO_ON);
12221 /* for backwards compatibility, the following code emulates a fixed bug that
12222 occured when pushing elements (causing elements that just made their last
12223 pushing step to already (if possible) make their first falling step in the
12224 same game frame, which is bad); this code is also needed to use the famous
12225 "spring push bug" which is used in older levels and might be wanted to be
12226 used also in newer levels, but in this case the buggy pushing code is only
12227 affecting the "spring" element and no other elements */
12229 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12231 for (i = 0; i < MAX_PLAYERS; i++)
12233 struct PlayerInfo *player = &stored_player[i];
12234 int x = player->jx;
12235 int y = player->jy;
12237 if (player->active && player->is_pushing && player->is_moving &&
12239 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12240 Tile[x][y] == EL_SPRING))
12242 ContinueMoving(x, y);
12244 // continue moving after pushing (this is actually a bug)
12245 if (!IS_MOVING(x, y))
12246 Stop[x][y] = FALSE;
12251 SCAN_PLAYFIELD(x, y)
12253 Last[x][y] = Tile[x][y];
12255 ChangeCount[x][y] = 0;
12256 ChangeEvent[x][y] = -1;
12258 // this must be handled before main playfield loop
12259 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12262 if (MovDelay[x][y] <= 0)
12266 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12269 if (MovDelay[x][y] <= 0)
12271 int element = Store[x][y];
12272 int move_direction = MovDir[x][y];
12273 int player_index_bit = Store2[x][y];
12279 TEST_DrawLevelField(x, y);
12281 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12283 if (IS_ENVELOPE(element))
12284 local_player->show_envelope = element;
12289 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12291 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12293 Debug("game:playing:GameActions_RND", "This should never happen!");
12295 ChangePage[x][y] = -1;
12299 Stop[x][y] = FALSE;
12300 if (WasJustMoving[x][y] > 0)
12301 WasJustMoving[x][y]--;
12302 if (WasJustFalling[x][y] > 0)
12303 WasJustFalling[x][y]--;
12304 if (CheckCollision[x][y] > 0)
12305 CheckCollision[x][y]--;
12306 if (CheckImpact[x][y] > 0)
12307 CheckImpact[x][y]--;
12311 /* reset finished pushing action (not done in ContinueMoving() to allow
12312 continuous pushing animation for elements with zero push delay) */
12313 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12315 ResetGfxAnimation(x, y);
12316 TEST_DrawLevelField(x, y);
12320 if (IS_BLOCKED(x, y))
12324 Blocked2Moving(x, y, &oldx, &oldy);
12325 if (!IS_MOVING(oldx, oldy))
12327 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12328 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12329 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12330 Debug("game:playing:GameActions_RND", "This should never happen!");
12336 if (mouse_action.button)
12338 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12339 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12341 x = mouse_action.lx;
12342 y = mouse_action.ly;
12343 element = Tile[x][y];
12347 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12348 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12352 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12353 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12356 if (level.use_step_counter)
12358 boolean counted_click = FALSE;
12360 // element clicked that can change when clicked/pressed
12361 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12362 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12363 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12364 counted_click = TRUE;
12366 // element clicked that can trigger change when clicked/pressed
12367 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12368 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12369 counted_click = TRUE;
12371 if (new_button && counted_click)
12372 CheckLevelTime_StepCounter();
12376 SCAN_PLAYFIELD(x, y)
12378 element = Tile[x][y];
12379 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12380 last_gfx_frame = GfxFrame[x][y];
12382 if (element == EL_EMPTY)
12383 graphic = el2img(GfxElementEmpty[x][y]);
12385 ResetGfxFrame(x, y);
12387 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12388 DrawLevelGraphicAnimation(x, y, graphic);
12390 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12391 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12392 ResetRandomAnimationValue(x, y);
12394 SetRandomAnimationValue(x, y);
12396 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12398 if (IS_INACTIVE(element))
12400 if (IS_ANIMATED(graphic))
12401 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12406 // this may take place after moving, so 'element' may have changed
12407 if (IS_CHANGING(x, y) &&
12408 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12410 int page = element_info[element].event_page_nr[CE_DELAY];
12412 HandleElementChange(x, y, page);
12414 element = Tile[x][y];
12415 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12418 CheckNextToConditions(x, y);
12420 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12424 element = Tile[x][y];
12425 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12427 if (IS_ANIMATED(graphic) &&
12428 !IS_MOVING(x, y) &&
12430 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12432 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12433 TEST_DrawTwinkleOnField(x, y);
12435 else if (element == EL_ACID)
12438 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12440 else if ((element == EL_EXIT_OPEN ||
12441 element == EL_EM_EXIT_OPEN ||
12442 element == EL_SP_EXIT_OPEN ||
12443 element == EL_STEEL_EXIT_OPEN ||
12444 element == EL_EM_STEEL_EXIT_OPEN ||
12445 element == EL_SP_TERMINAL ||
12446 element == EL_SP_TERMINAL_ACTIVE ||
12447 element == EL_EXTRA_TIME ||
12448 element == EL_SHIELD_NORMAL ||
12449 element == EL_SHIELD_DEADLY) &&
12450 IS_ANIMATED(graphic))
12451 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12452 else if (IS_MOVING(x, y))
12453 ContinueMoving(x, y);
12454 else if (IS_ACTIVE_BOMB(element))
12455 CheckDynamite(x, y);
12456 else if (element == EL_AMOEBA_GROWING)
12457 AmoebaGrowing(x, y);
12458 else if (element == EL_AMOEBA_SHRINKING)
12459 AmoebaShrinking(x, y);
12461 #if !USE_NEW_AMOEBA_CODE
12462 else if (IS_AMOEBALIVE(element))
12463 AmoebaReproduce(x, y);
12466 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12468 else if (element == EL_EXIT_CLOSED)
12470 else if (element == EL_EM_EXIT_CLOSED)
12472 else if (element == EL_STEEL_EXIT_CLOSED)
12473 CheckExitSteel(x, y);
12474 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12475 CheckExitSteelEM(x, y);
12476 else if (element == EL_SP_EXIT_CLOSED)
12478 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12479 element == EL_EXPANDABLE_STEELWALL_GROWING)
12480 MauerWaechst(x, y);
12481 else if (element == EL_EXPANDABLE_WALL ||
12482 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12483 element == EL_EXPANDABLE_WALL_VERTICAL ||
12484 element == EL_EXPANDABLE_WALL_ANY ||
12485 element == EL_BD_EXPANDABLE_WALL)
12486 MauerAbleger(x, y);
12487 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12488 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12489 element == EL_EXPANDABLE_STEELWALL_ANY)
12490 MauerAblegerStahl(x, y);
12491 else if (element == EL_FLAMES)
12492 CheckForDragon(x, y);
12493 else if (element == EL_EXPLOSION)
12494 ; // drawing of correct explosion animation is handled separately
12495 else if (element == EL_ELEMENT_SNAPPING ||
12496 element == EL_DIAGONAL_SHRINKING ||
12497 element == EL_DIAGONAL_GROWING)
12499 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12501 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12503 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12504 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12506 if (IS_BELT_ACTIVE(element))
12507 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12509 if (game.magic_wall_active)
12511 int jx = local_player->jx, jy = local_player->jy;
12513 // play the element sound at the position nearest to the player
12514 if ((element == EL_MAGIC_WALL_FULL ||
12515 element == EL_MAGIC_WALL_ACTIVE ||
12516 element == EL_MAGIC_WALL_EMPTYING ||
12517 element == EL_BD_MAGIC_WALL_FULL ||
12518 element == EL_BD_MAGIC_WALL_ACTIVE ||
12519 element == EL_BD_MAGIC_WALL_EMPTYING ||
12520 element == EL_DC_MAGIC_WALL_FULL ||
12521 element == EL_DC_MAGIC_WALL_ACTIVE ||
12522 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12523 ABS(x - jx) + ABS(y - jy) <
12524 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12532 #if USE_NEW_AMOEBA_CODE
12533 // new experimental amoeba growth stuff
12534 if (!(FrameCounter % 8))
12536 static unsigned int random = 1684108901;
12538 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12540 x = RND(lev_fieldx);
12541 y = RND(lev_fieldy);
12542 element = Tile[x][y];
12544 if (!IS_PLAYER(x,y) &&
12545 (element == EL_EMPTY ||
12546 CAN_GROW_INTO(element) ||
12547 element == EL_QUICKSAND_EMPTY ||
12548 element == EL_QUICKSAND_FAST_EMPTY ||
12549 element == EL_ACID_SPLASH_LEFT ||
12550 element == EL_ACID_SPLASH_RIGHT))
12552 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12553 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12554 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12555 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12556 Tile[x][y] = EL_AMOEBA_DROP;
12559 random = random * 129 + 1;
12564 game.explosions_delayed = FALSE;
12566 SCAN_PLAYFIELD(x, y)
12568 element = Tile[x][y];
12570 if (ExplodeField[x][y])
12571 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12572 else if (element == EL_EXPLOSION)
12573 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12575 ExplodeField[x][y] = EX_TYPE_NONE;
12578 game.explosions_delayed = TRUE;
12580 if (game.magic_wall_active)
12582 if (!(game.magic_wall_time_left % 4))
12584 int element = Tile[magic_wall_x][magic_wall_y];
12586 if (element == EL_BD_MAGIC_WALL_FULL ||
12587 element == EL_BD_MAGIC_WALL_ACTIVE ||
12588 element == EL_BD_MAGIC_WALL_EMPTYING)
12589 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12590 else if (element == EL_DC_MAGIC_WALL_FULL ||
12591 element == EL_DC_MAGIC_WALL_ACTIVE ||
12592 element == EL_DC_MAGIC_WALL_EMPTYING)
12593 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12595 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12598 if (game.magic_wall_time_left > 0)
12600 game.magic_wall_time_left--;
12602 if (!game.magic_wall_time_left)
12604 SCAN_PLAYFIELD(x, y)
12606 element = Tile[x][y];
12608 if (element == EL_MAGIC_WALL_ACTIVE ||
12609 element == EL_MAGIC_WALL_FULL)
12611 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12612 TEST_DrawLevelField(x, y);
12614 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12615 element == EL_BD_MAGIC_WALL_FULL)
12617 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12618 TEST_DrawLevelField(x, y);
12620 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12621 element == EL_DC_MAGIC_WALL_FULL)
12623 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12624 TEST_DrawLevelField(x, y);
12628 game.magic_wall_active = FALSE;
12633 if (game.light_time_left > 0)
12635 game.light_time_left--;
12637 if (game.light_time_left == 0)
12638 RedrawAllLightSwitchesAndInvisibleElements();
12641 if (game.timegate_time_left > 0)
12643 game.timegate_time_left--;
12645 if (game.timegate_time_left == 0)
12646 CloseAllOpenTimegates();
12649 if (game.lenses_time_left > 0)
12651 game.lenses_time_left--;
12653 if (game.lenses_time_left == 0)
12654 RedrawAllInvisibleElementsForLenses();
12657 if (game.magnify_time_left > 0)
12659 game.magnify_time_left--;
12661 if (game.magnify_time_left == 0)
12662 RedrawAllInvisibleElementsForMagnifier();
12665 for (i = 0; i < MAX_PLAYERS; i++)
12667 struct PlayerInfo *player = &stored_player[i];
12669 if (SHIELD_ON(player))
12671 if (player->shield_deadly_time_left)
12672 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12673 else if (player->shield_normal_time_left)
12674 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12678 #if USE_DELAYED_GFX_REDRAW
12679 SCAN_PLAYFIELD(x, y)
12681 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12683 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12684 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12686 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12687 DrawLevelField(x, y);
12689 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12690 DrawLevelFieldCrumbled(x, y);
12692 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12693 DrawLevelFieldCrumbledNeighbours(x, y);
12695 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12696 DrawTwinkleOnField(x, y);
12699 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12704 PlayAllPlayersSound();
12706 for (i = 0; i < MAX_PLAYERS; i++)
12708 struct PlayerInfo *player = &stored_player[i];
12710 if (player->show_envelope != 0 && (!player->active ||
12711 player->MovPos == 0))
12713 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12715 player->show_envelope = 0;
12719 // use random number generator in every frame to make it less predictable
12720 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12723 mouse_action_last = mouse_action;
12726 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12728 int min_x = x, min_y = y, max_x = x, max_y = y;
12729 int scr_fieldx = getScreenFieldSizeX();
12730 int scr_fieldy = getScreenFieldSizeY();
12733 for (i = 0; i < MAX_PLAYERS; i++)
12735 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12737 if (!stored_player[i].active || &stored_player[i] == player)
12740 min_x = MIN(min_x, jx);
12741 min_y = MIN(min_y, jy);
12742 max_x = MAX(max_x, jx);
12743 max_y = MAX(max_y, jy);
12746 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12749 static boolean AllPlayersInVisibleScreen(void)
12753 for (i = 0; i < MAX_PLAYERS; i++)
12755 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12757 if (!stored_player[i].active)
12760 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12767 void ScrollLevel(int dx, int dy)
12769 int scroll_offset = 2 * TILEX_VAR;
12772 BlitBitmap(drawto_field, drawto_field,
12773 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12774 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12775 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12776 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12777 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12778 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12782 x = (dx == 1 ? BX1 : BX2);
12783 for (y = BY1; y <= BY2; y++)
12784 DrawScreenField(x, y);
12789 y = (dy == 1 ? BY1 : BY2);
12790 for (x = BX1; x <= BX2; x++)
12791 DrawScreenField(x, y);
12794 redraw_mask |= REDRAW_FIELD;
12797 static boolean canFallDown(struct PlayerInfo *player)
12799 int jx = player->jx, jy = player->jy;
12801 return (IN_LEV_FIELD(jx, jy + 1) &&
12802 (IS_FREE(jx, jy + 1) ||
12803 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12804 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12805 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12808 static boolean canPassField(int x, int y, int move_dir)
12810 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12811 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12812 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12813 int nextx = x + dx;
12814 int nexty = y + dy;
12815 int element = Tile[x][y];
12817 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12818 !CAN_MOVE(element) &&
12819 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12820 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12821 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12824 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12826 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12827 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12828 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12832 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12833 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12834 (IS_DIGGABLE(Tile[newx][newy]) ||
12835 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12836 canPassField(newx, newy, move_dir)));
12839 static void CheckGravityMovement(struct PlayerInfo *player)
12841 if (player->gravity && !player->programmed_action)
12843 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12844 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12845 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12846 int jx = player->jx, jy = player->jy;
12847 boolean player_is_moving_to_valid_field =
12848 (!player_is_snapping &&
12849 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12850 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12851 boolean player_can_fall_down = canFallDown(player);
12853 if (player_can_fall_down &&
12854 !player_is_moving_to_valid_field)
12855 player->programmed_action = MV_DOWN;
12859 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12861 return CheckGravityMovement(player);
12863 if (player->gravity && !player->programmed_action)
12865 int jx = player->jx, jy = player->jy;
12866 boolean field_under_player_is_free =
12867 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12868 boolean player_is_standing_on_valid_field =
12869 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12870 (IS_WALKABLE(Tile[jx][jy]) &&
12871 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12873 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12874 player->programmed_action = MV_DOWN;
12879 MovePlayerOneStep()
12880 -----------------------------------------------------------------------------
12881 dx, dy: direction (non-diagonal) to try to move the player to
12882 real_dx, real_dy: direction as read from input device (can be diagonal)
12885 boolean MovePlayerOneStep(struct PlayerInfo *player,
12886 int dx, int dy, int real_dx, int real_dy)
12888 int jx = player->jx, jy = player->jy;
12889 int new_jx = jx + dx, new_jy = jy + dy;
12891 boolean player_can_move = !player->cannot_move;
12893 if (!player->active || (!dx && !dy))
12894 return MP_NO_ACTION;
12896 player->MovDir = (dx < 0 ? MV_LEFT :
12897 dx > 0 ? MV_RIGHT :
12899 dy > 0 ? MV_DOWN : MV_NONE);
12901 if (!IN_LEV_FIELD(new_jx, new_jy))
12902 return MP_NO_ACTION;
12904 if (!player_can_move)
12906 if (player->MovPos == 0)
12908 player->is_moving = FALSE;
12909 player->is_digging = FALSE;
12910 player->is_collecting = FALSE;
12911 player->is_snapping = FALSE;
12912 player->is_pushing = FALSE;
12916 if (!network.enabled && game.centered_player_nr == -1 &&
12917 !AllPlayersInSight(player, new_jx, new_jy))
12918 return MP_NO_ACTION;
12920 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12921 if (can_move != MP_MOVING)
12924 // check if DigField() has caused relocation of the player
12925 if (player->jx != jx || player->jy != jy)
12926 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12928 StorePlayer[jx][jy] = 0;
12929 player->last_jx = jx;
12930 player->last_jy = jy;
12931 player->jx = new_jx;
12932 player->jy = new_jy;
12933 StorePlayer[new_jx][new_jy] = player->element_nr;
12935 if (player->move_delay_value_next != -1)
12937 player->move_delay_value = player->move_delay_value_next;
12938 player->move_delay_value_next = -1;
12942 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12944 player->step_counter++;
12946 PlayerVisit[jx][jy] = FrameCounter;
12948 player->is_moving = TRUE;
12951 // should better be called in MovePlayer(), but this breaks some tapes
12952 ScrollPlayer(player, SCROLL_INIT);
12958 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12960 int jx = player->jx, jy = player->jy;
12961 int old_jx = jx, old_jy = jy;
12962 int moved = MP_NO_ACTION;
12964 if (!player->active)
12969 if (player->MovPos == 0)
12971 player->is_moving = FALSE;
12972 player->is_digging = FALSE;
12973 player->is_collecting = FALSE;
12974 player->is_snapping = FALSE;
12975 player->is_pushing = FALSE;
12981 if (player->move_delay > 0)
12984 player->move_delay = -1; // set to "uninitialized" value
12986 // store if player is automatically moved to next field
12987 player->is_auto_moving = (player->programmed_action != MV_NONE);
12989 // remove the last programmed player action
12990 player->programmed_action = 0;
12992 if (player->MovPos)
12994 // should only happen if pre-1.2 tape recordings are played
12995 // this is only for backward compatibility
12997 int original_move_delay_value = player->move_delay_value;
13000 Debug("game:playing:MovePlayer",
13001 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13005 // scroll remaining steps with finest movement resolution
13006 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13008 while (player->MovPos)
13010 ScrollPlayer(player, SCROLL_GO_ON);
13011 ScrollScreen(NULL, SCROLL_GO_ON);
13013 AdvanceFrameAndPlayerCounters(player->index_nr);
13016 BackToFront_WithFrameDelay(0);
13019 player->move_delay_value = original_move_delay_value;
13022 player->is_active = FALSE;
13024 if (player->last_move_dir & MV_HORIZONTAL)
13026 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13027 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13031 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13032 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13035 if (!moved && !player->is_active)
13037 player->is_moving = FALSE;
13038 player->is_digging = FALSE;
13039 player->is_collecting = FALSE;
13040 player->is_snapping = FALSE;
13041 player->is_pushing = FALSE;
13047 if (moved & MP_MOVING && !ScreenMovPos &&
13048 (player->index_nr == game.centered_player_nr ||
13049 game.centered_player_nr == -1))
13051 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13053 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13055 // actual player has left the screen -- scroll in that direction
13056 if (jx != old_jx) // player has moved horizontally
13057 scroll_x += (jx - old_jx);
13058 else // player has moved vertically
13059 scroll_y += (jy - old_jy);
13063 int offset_raw = game.scroll_delay_value;
13065 if (jx != old_jx) // player has moved horizontally
13067 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13068 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13069 int new_scroll_x = jx - MIDPOSX + offset_x;
13071 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13072 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13073 scroll_x = new_scroll_x;
13075 // don't scroll over playfield boundaries
13076 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13078 // don't scroll more than one field at a time
13079 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13081 // don't scroll against the player's moving direction
13082 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13083 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13084 scroll_x = old_scroll_x;
13086 else // player has moved vertically
13088 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13089 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13090 int new_scroll_y = jy - MIDPOSY + offset_y;
13092 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13093 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13094 scroll_y = new_scroll_y;
13096 // don't scroll over playfield boundaries
13097 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13099 // don't scroll more than one field at a time
13100 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13102 // don't scroll against the player's moving direction
13103 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13104 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13105 scroll_y = old_scroll_y;
13109 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13111 if (!network.enabled && game.centered_player_nr == -1 &&
13112 !AllPlayersInVisibleScreen())
13114 scroll_x = old_scroll_x;
13115 scroll_y = old_scroll_y;
13119 ScrollScreen(player, SCROLL_INIT);
13120 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13125 player->StepFrame = 0;
13127 if (moved & MP_MOVING)
13129 if (old_jx != jx && old_jy == jy)
13130 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13131 else if (old_jx == jx && old_jy != jy)
13132 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13134 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13136 player->last_move_dir = player->MovDir;
13137 player->is_moving = TRUE;
13138 player->is_snapping = FALSE;
13139 player->is_switching = FALSE;
13140 player->is_dropping = FALSE;
13141 player->is_dropping_pressed = FALSE;
13142 player->drop_pressed_delay = 0;
13145 // should better be called here than above, but this breaks some tapes
13146 ScrollPlayer(player, SCROLL_INIT);
13151 CheckGravityMovementWhenNotMoving(player);
13153 player->is_moving = FALSE;
13155 /* at this point, the player is allowed to move, but cannot move right now
13156 (e.g. because of something blocking the way) -- ensure that the player
13157 is also allowed to move in the next frame (in old versions before 3.1.1,
13158 the player was forced to wait again for eight frames before next try) */
13160 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13161 player->move_delay = 0; // allow direct movement in the next frame
13164 if (player->move_delay == -1) // not yet initialized by DigField()
13165 player->move_delay = player->move_delay_value;
13167 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13169 TestIfPlayerTouchesBadThing(jx, jy);
13170 TestIfPlayerTouchesCustomElement(jx, jy);
13173 if (!player->active)
13174 RemovePlayer(player);
13179 void ScrollPlayer(struct PlayerInfo *player, int mode)
13181 int jx = player->jx, jy = player->jy;
13182 int last_jx = player->last_jx, last_jy = player->last_jy;
13183 int move_stepsize = TILEX / player->move_delay_value;
13185 if (!player->active)
13188 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13191 if (mode == SCROLL_INIT)
13193 player->actual_frame_counter.count = FrameCounter;
13194 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13196 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13197 Tile[last_jx][last_jy] == EL_EMPTY)
13199 int last_field_block_delay = 0; // start with no blocking at all
13200 int block_delay_adjustment = player->block_delay_adjustment;
13202 // if player blocks last field, add delay for exactly one move
13203 if (player->block_last_field)
13205 last_field_block_delay += player->move_delay_value;
13207 // when blocking enabled, prevent moving up despite gravity
13208 if (player->gravity && player->MovDir == MV_UP)
13209 block_delay_adjustment = -1;
13212 // add block delay adjustment (also possible when not blocking)
13213 last_field_block_delay += block_delay_adjustment;
13215 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13216 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13219 if (player->MovPos != 0) // player has not yet reached destination
13222 else if (!FrameReached(&player->actual_frame_counter))
13225 if (player->MovPos != 0)
13227 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13228 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13230 // before DrawPlayer() to draw correct player graphic for this case
13231 if (player->MovPos == 0)
13232 CheckGravityMovement(player);
13235 if (player->MovPos == 0) // player reached destination field
13237 if (player->move_delay_reset_counter > 0)
13239 player->move_delay_reset_counter--;
13241 if (player->move_delay_reset_counter == 0)
13243 // continue with normal speed after quickly moving through gate
13244 HALVE_PLAYER_SPEED(player);
13246 // be able to make the next move without delay
13247 player->move_delay = 0;
13251 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13252 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13253 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13254 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13255 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13256 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13257 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13258 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13260 ExitPlayer(player);
13262 if (game.players_still_needed == 0 &&
13263 (game.friends_still_needed == 0 ||
13264 IS_SP_ELEMENT(Tile[jx][jy])))
13268 player->last_jx = jx;
13269 player->last_jy = jy;
13271 // this breaks one level: "machine", level 000
13273 int move_direction = player->MovDir;
13274 int enter_side = MV_DIR_OPPOSITE(move_direction);
13275 int leave_side = move_direction;
13276 int old_jx = last_jx;
13277 int old_jy = last_jy;
13278 int old_element = Tile[old_jx][old_jy];
13279 int new_element = Tile[jx][jy];
13281 if (IS_CUSTOM_ELEMENT(old_element))
13282 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13284 player->index_bit, leave_side);
13286 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13287 CE_PLAYER_LEAVES_X,
13288 player->index_bit, leave_side);
13290 if (IS_CUSTOM_ELEMENT(new_element))
13291 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13292 player->index_bit, enter_side);
13294 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13295 CE_PLAYER_ENTERS_X,
13296 player->index_bit, enter_side);
13298 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13299 CE_MOVE_OF_X, move_direction);
13302 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13304 TestIfPlayerTouchesBadThing(jx, jy);
13305 TestIfPlayerTouchesCustomElement(jx, jy);
13307 /* needed because pushed element has not yet reached its destination,
13308 so it would trigger a change event at its previous field location */
13309 if (!player->is_pushing)
13310 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13312 if (level.finish_dig_collect &&
13313 (player->is_digging || player->is_collecting))
13315 int last_element = player->last_removed_element;
13316 int move_direction = player->MovDir;
13317 int enter_side = MV_DIR_OPPOSITE(move_direction);
13318 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13319 CE_PLAYER_COLLECTS_X);
13321 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13322 player->index_bit, enter_side);
13324 player->last_removed_element = EL_UNDEFINED;
13327 if (!player->active)
13328 RemovePlayer(player);
13331 if (level.use_step_counter)
13332 CheckLevelTime_StepCounter();
13334 if (tape.single_step && tape.recording && !tape.pausing &&
13335 !player->programmed_action)
13336 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13338 if (!player->programmed_action)
13339 CheckSaveEngineSnapshot(player);
13343 void ScrollScreen(struct PlayerInfo *player, int mode)
13345 static DelayCounter screen_frame_counter = { 0 };
13347 if (mode == SCROLL_INIT)
13349 // set scrolling step size according to actual player's moving speed
13350 ScrollStepSize = TILEX / player->move_delay_value;
13352 screen_frame_counter.count = FrameCounter;
13353 screen_frame_counter.value = 1;
13355 ScreenMovDir = player->MovDir;
13356 ScreenMovPos = player->MovPos;
13357 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13360 else if (!FrameReached(&screen_frame_counter))
13365 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13366 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13367 redraw_mask |= REDRAW_FIELD;
13370 ScreenMovDir = MV_NONE;
13373 void CheckNextToConditions(int x, int y)
13375 int element = Tile[x][y];
13377 if (IS_PLAYER(x, y))
13378 TestIfPlayerNextToCustomElement(x, y);
13380 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13381 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13382 TestIfElementNextToCustomElement(x, y);
13385 void TestIfPlayerNextToCustomElement(int x, int y)
13387 static int xy[4][2] =
13394 static int trigger_sides[4][2] =
13396 // center side border side
13397 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13398 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13399 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13400 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13404 if (!IS_PLAYER(x, y))
13407 struct PlayerInfo *player = PLAYERINFO(x, y);
13409 if (player->is_moving)
13412 for (i = 0; i < NUM_DIRECTIONS; i++)
13414 int xx = x + xy[i][0];
13415 int yy = y + xy[i][1];
13416 int border_side = trigger_sides[i][1];
13417 int border_element;
13419 if (!IN_LEV_FIELD(xx, yy))
13422 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13423 continue; // center and border element not connected
13425 border_element = Tile[xx][yy];
13427 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13428 player->index_bit, border_side);
13429 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13430 CE_PLAYER_NEXT_TO_X,
13431 player->index_bit, border_side);
13433 /* use player element that is initially defined in the level playfield,
13434 not the player element that corresponds to the runtime player number
13435 (example: a level that contains EL_PLAYER_3 as the only player would
13436 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13438 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13439 CE_NEXT_TO_X, border_side);
13443 void TestIfPlayerTouchesCustomElement(int x, int y)
13445 static int xy[4][2] =
13452 static int trigger_sides[4][2] =
13454 // center side border side
13455 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13456 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13457 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13458 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13460 static int touch_dir[4] =
13462 MV_LEFT | MV_RIGHT,
13467 int center_element = Tile[x][y]; // should always be non-moving!
13470 for (i = 0; i < NUM_DIRECTIONS; i++)
13472 int xx = x + xy[i][0];
13473 int yy = y + xy[i][1];
13474 int center_side = trigger_sides[i][0];
13475 int border_side = trigger_sides[i][1];
13476 int border_element;
13478 if (!IN_LEV_FIELD(xx, yy))
13481 if (IS_PLAYER(x, y)) // player found at center element
13483 struct PlayerInfo *player = PLAYERINFO(x, y);
13485 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13486 border_element = Tile[xx][yy]; // may be moving!
13487 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13488 border_element = Tile[xx][yy];
13489 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13490 border_element = MovingOrBlocked2Element(xx, yy);
13492 continue; // center and border element do not touch
13494 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13495 player->index_bit, border_side);
13496 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13497 CE_PLAYER_TOUCHES_X,
13498 player->index_bit, border_side);
13501 /* use player element that is initially defined in the level playfield,
13502 not the player element that corresponds to the runtime player number
13503 (example: a level that contains EL_PLAYER_3 as the only player would
13504 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13505 int player_element = PLAYERINFO(x, y)->initial_element;
13507 CheckElementChangeBySide(xx, yy, border_element, player_element,
13508 CE_TOUCHING_X, border_side);
13511 else if (IS_PLAYER(xx, yy)) // player found at border element
13513 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13515 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13517 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13518 continue; // center and border element do not touch
13521 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13522 player->index_bit, center_side);
13523 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13524 CE_PLAYER_TOUCHES_X,
13525 player->index_bit, center_side);
13528 /* use player element that is initially defined in the level playfield,
13529 not the player element that corresponds to the runtime player number
13530 (example: a level that contains EL_PLAYER_3 as the only player would
13531 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13532 int player_element = PLAYERINFO(xx, yy)->initial_element;
13534 CheckElementChangeBySide(x, y, center_element, player_element,
13535 CE_TOUCHING_X, center_side);
13543 void TestIfElementNextToCustomElement(int x, int y)
13545 static int xy[4][2] =
13552 static int trigger_sides[4][2] =
13554 // center side border side
13555 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13556 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13557 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13558 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13560 int center_element = Tile[x][y]; // should always be non-moving!
13563 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13566 for (i = 0; i < NUM_DIRECTIONS; i++)
13568 int xx = x + xy[i][0];
13569 int yy = y + xy[i][1];
13570 int border_side = trigger_sides[i][1];
13571 int border_element;
13573 if (!IN_LEV_FIELD(xx, yy))
13576 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13577 continue; // center and border element not connected
13579 border_element = Tile[xx][yy];
13581 // check for change of center element (but change it only once)
13582 if (CheckElementChangeBySide(x, y, center_element, border_element,
13583 CE_NEXT_TO_X, border_side))
13588 void TestIfElementTouchesCustomElement(int x, int y)
13590 static int xy[4][2] =
13597 static int trigger_sides[4][2] =
13599 // center side border side
13600 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13601 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13602 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13603 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13605 static int touch_dir[4] =
13607 MV_LEFT | MV_RIGHT,
13612 boolean change_center_element = FALSE;
13613 int center_element = Tile[x][y]; // should always be non-moving!
13614 int border_element_old[NUM_DIRECTIONS];
13617 for (i = 0; i < NUM_DIRECTIONS; i++)
13619 int xx = x + xy[i][0];
13620 int yy = y + xy[i][1];
13621 int border_element;
13623 border_element_old[i] = -1;
13625 if (!IN_LEV_FIELD(xx, yy))
13628 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13629 border_element = Tile[xx][yy]; // may be moving!
13630 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13631 border_element = Tile[xx][yy];
13632 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13633 border_element = MovingOrBlocked2Element(xx, yy);
13635 continue; // center and border element do not touch
13637 border_element_old[i] = border_element;
13640 for (i = 0; i < NUM_DIRECTIONS; i++)
13642 int xx = x + xy[i][0];
13643 int yy = y + xy[i][1];
13644 int center_side = trigger_sides[i][0];
13645 int border_element = border_element_old[i];
13647 if (border_element == -1)
13650 // check for change of border element
13651 CheckElementChangeBySide(xx, yy, border_element, center_element,
13652 CE_TOUCHING_X, center_side);
13654 // (center element cannot be player, so we dont have to check this here)
13657 for (i = 0; i < NUM_DIRECTIONS; i++)
13659 int xx = x + xy[i][0];
13660 int yy = y + xy[i][1];
13661 int border_side = trigger_sides[i][1];
13662 int border_element = border_element_old[i];
13664 if (border_element == -1)
13667 // check for change of center element (but change it only once)
13668 if (!change_center_element)
13669 change_center_element =
13670 CheckElementChangeBySide(x, y, center_element, border_element,
13671 CE_TOUCHING_X, border_side);
13673 if (IS_PLAYER(xx, yy))
13675 /* use player element that is initially defined in the level playfield,
13676 not the player element that corresponds to the runtime player number
13677 (example: a level that contains EL_PLAYER_3 as the only player would
13678 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13679 int player_element = PLAYERINFO(xx, yy)->initial_element;
13681 CheckElementChangeBySide(x, y, center_element, player_element,
13682 CE_TOUCHING_X, border_side);
13687 void TestIfElementHitsCustomElement(int x, int y, int direction)
13689 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13690 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13691 int hitx = x + dx, hity = y + dy;
13692 int hitting_element = Tile[x][y];
13693 int touched_element;
13695 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13698 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13699 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13701 if (IN_LEV_FIELD(hitx, hity))
13703 int opposite_direction = MV_DIR_OPPOSITE(direction);
13704 int hitting_side = direction;
13705 int touched_side = opposite_direction;
13706 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13707 MovDir[hitx][hity] != direction ||
13708 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13714 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13715 CE_HITTING_X, touched_side);
13717 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13718 CE_HIT_BY_X, hitting_side);
13720 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13721 CE_HIT_BY_SOMETHING, opposite_direction);
13723 if (IS_PLAYER(hitx, hity))
13725 /* use player element that is initially defined in the level playfield,
13726 not the player element that corresponds to the runtime player number
13727 (example: a level that contains EL_PLAYER_3 as the only player would
13728 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13729 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13731 CheckElementChangeBySide(x, y, hitting_element, player_element,
13732 CE_HITTING_X, touched_side);
13737 // "hitting something" is also true when hitting the playfield border
13738 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13739 CE_HITTING_SOMETHING, direction);
13742 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13744 int i, kill_x = -1, kill_y = -1;
13746 int bad_element = -1;
13747 static int test_xy[4][2] =
13754 static int test_dir[4] =
13762 for (i = 0; i < NUM_DIRECTIONS; i++)
13764 int test_x, test_y, test_move_dir, test_element;
13766 test_x = good_x + test_xy[i][0];
13767 test_y = good_y + test_xy[i][1];
13769 if (!IN_LEV_FIELD(test_x, test_y))
13773 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13775 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13777 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13778 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13780 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13781 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13785 bad_element = test_element;
13791 if (kill_x != -1 || kill_y != -1)
13793 if (IS_PLAYER(good_x, good_y))
13795 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13797 if (player->shield_deadly_time_left > 0 &&
13798 !IS_INDESTRUCTIBLE(bad_element))
13799 Bang(kill_x, kill_y);
13800 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13801 KillPlayer(player);
13804 Bang(good_x, good_y);
13808 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13810 int i, kill_x = -1, kill_y = -1;
13811 int bad_element = Tile[bad_x][bad_y];
13812 static int test_xy[4][2] =
13819 static int touch_dir[4] =
13821 MV_LEFT | MV_RIGHT,
13826 static int test_dir[4] =
13834 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13837 for (i = 0; i < NUM_DIRECTIONS; i++)
13839 int test_x, test_y, test_move_dir, test_element;
13841 test_x = bad_x + test_xy[i][0];
13842 test_y = bad_y + test_xy[i][1];
13844 if (!IN_LEV_FIELD(test_x, test_y))
13848 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13850 test_element = Tile[test_x][test_y];
13852 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13853 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13855 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13856 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13858 // good thing is player or penguin that does not move away
13859 if (IS_PLAYER(test_x, test_y))
13861 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13863 if (bad_element == EL_ROBOT && player->is_moving)
13864 continue; // robot does not kill player if he is moving
13866 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13868 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13869 continue; // center and border element do not touch
13877 else if (test_element == EL_PENGUIN)
13887 if (kill_x != -1 || kill_y != -1)
13889 if (IS_PLAYER(kill_x, kill_y))
13891 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13893 if (player->shield_deadly_time_left > 0 &&
13894 !IS_INDESTRUCTIBLE(bad_element))
13895 Bang(bad_x, bad_y);
13896 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13897 KillPlayer(player);
13900 Bang(kill_x, kill_y);
13904 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13906 int bad_element = Tile[bad_x][bad_y];
13907 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13908 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13909 int test_x = bad_x + dx, test_y = bad_y + dy;
13910 int test_move_dir, test_element;
13911 int kill_x = -1, kill_y = -1;
13913 if (!IN_LEV_FIELD(test_x, test_y))
13917 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13919 test_element = Tile[test_x][test_y];
13921 if (test_move_dir != bad_move_dir)
13923 // good thing can be player or penguin that does not move away
13924 if (IS_PLAYER(test_x, test_y))
13926 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13928 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13929 player as being hit when he is moving towards the bad thing, because
13930 the "get hit by" condition would be lost after the player stops) */
13931 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13932 return; // player moves away from bad thing
13937 else if (test_element == EL_PENGUIN)
13944 if (kill_x != -1 || kill_y != -1)
13946 if (IS_PLAYER(kill_x, kill_y))
13948 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13950 if (player->shield_deadly_time_left > 0 &&
13951 !IS_INDESTRUCTIBLE(bad_element))
13952 Bang(bad_x, bad_y);
13953 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13954 KillPlayer(player);
13957 Bang(kill_x, kill_y);
13961 void TestIfPlayerTouchesBadThing(int x, int y)
13963 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13966 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13968 TestIfGoodThingHitsBadThing(x, y, move_dir);
13971 void TestIfBadThingTouchesPlayer(int x, int y)
13973 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13976 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13978 TestIfBadThingHitsGoodThing(x, y, move_dir);
13981 void TestIfFriendTouchesBadThing(int x, int y)
13983 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13986 void TestIfBadThingTouchesFriend(int x, int y)
13988 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13991 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13993 int i, kill_x = bad_x, kill_y = bad_y;
13994 static int xy[4][2] =
14002 for (i = 0; i < NUM_DIRECTIONS; i++)
14006 x = bad_x + xy[i][0];
14007 y = bad_y + xy[i][1];
14008 if (!IN_LEV_FIELD(x, y))
14011 element = Tile[x][y];
14012 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14013 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14021 if (kill_x != bad_x || kill_y != bad_y)
14022 Bang(bad_x, bad_y);
14025 void KillPlayer(struct PlayerInfo *player)
14027 int jx = player->jx, jy = player->jy;
14029 if (!player->active)
14033 Debug("game:playing:KillPlayer",
14034 "0: killed == %d, active == %d, reanimated == %d",
14035 player->killed, player->active, player->reanimated);
14038 /* the following code was introduced to prevent an infinite loop when calling
14040 -> CheckTriggeredElementChangeExt()
14041 -> ExecuteCustomElementAction()
14043 -> (infinitely repeating the above sequence of function calls)
14044 which occurs when killing the player while having a CE with the setting
14045 "kill player X when explosion of <player X>"; the solution using a new
14046 field "player->killed" was chosen for backwards compatibility, although
14047 clever use of the fields "player->active" etc. would probably also work */
14049 if (player->killed)
14053 player->killed = TRUE;
14055 // remove accessible field at the player's position
14056 Tile[jx][jy] = EL_EMPTY;
14058 // deactivate shield (else Bang()/Explode() would not work right)
14059 player->shield_normal_time_left = 0;
14060 player->shield_deadly_time_left = 0;
14063 Debug("game:playing:KillPlayer",
14064 "1: killed == %d, active == %d, reanimated == %d",
14065 player->killed, player->active, player->reanimated);
14071 Debug("game:playing:KillPlayer",
14072 "2: killed == %d, active == %d, reanimated == %d",
14073 player->killed, player->active, player->reanimated);
14076 if (player->reanimated) // killed player may have been reanimated
14077 player->killed = player->reanimated = FALSE;
14079 BuryPlayer(player);
14082 static void KillPlayerUnlessEnemyProtected(int x, int y)
14084 if (!PLAYER_ENEMY_PROTECTED(x, y))
14085 KillPlayer(PLAYERINFO(x, y));
14088 static void KillPlayerUnlessExplosionProtected(int x, int y)
14090 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14091 KillPlayer(PLAYERINFO(x, y));
14094 void BuryPlayer(struct PlayerInfo *player)
14096 int jx = player->jx, jy = player->jy;
14098 if (!player->active)
14101 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14102 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14104 RemovePlayer(player);
14106 player->buried = TRUE;
14108 if (game.all_players_gone)
14109 game.GameOver = TRUE;
14112 void RemovePlayer(struct PlayerInfo *player)
14114 int jx = player->jx, jy = player->jy;
14115 int i, found = FALSE;
14117 player->present = FALSE;
14118 player->active = FALSE;
14120 // required for some CE actions (even if the player is not active anymore)
14121 player->MovPos = 0;
14123 if (!ExplodeField[jx][jy])
14124 StorePlayer[jx][jy] = 0;
14126 if (player->is_moving)
14127 TEST_DrawLevelField(player->last_jx, player->last_jy);
14129 for (i = 0; i < MAX_PLAYERS; i++)
14130 if (stored_player[i].active)
14135 game.all_players_gone = TRUE;
14136 game.GameOver = TRUE;
14139 game.exit_x = game.robot_wheel_x = jx;
14140 game.exit_y = game.robot_wheel_y = jy;
14143 void ExitPlayer(struct PlayerInfo *player)
14145 DrawPlayer(player); // needed here only to cleanup last field
14146 RemovePlayer(player);
14148 if (game.players_still_needed > 0)
14149 game.players_still_needed--;
14152 static void SetFieldForSnapping(int x, int y, int element, int direction,
14153 int player_index_bit)
14155 struct ElementInfo *ei = &element_info[element];
14156 int direction_bit = MV_DIR_TO_BIT(direction);
14157 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14158 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14159 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14161 Tile[x][y] = EL_ELEMENT_SNAPPING;
14162 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14163 MovDir[x][y] = direction;
14164 Store[x][y] = element;
14165 Store2[x][y] = player_index_bit;
14167 ResetGfxAnimation(x, y);
14169 GfxElement[x][y] = element;
14170 GfxAction[x][y] = action;
14171 GfxDir[x][y] = direction;
14172 GfxFrame[x][y] = -1;
14175 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14176 int player_index_bit)
14178 TestIfElementTouchesCustomElement(x, y); // for empty space
14180 if (level.finish_dig_collect)
14182 int dig_side = MV_DIR_OPPOSITE(direction);
14183 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14184 CE_PLAYER_COLLECTS_X);
14186 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14187 player_index_bit, dig_side);
14188 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14189 player_index_bit, dig_side);
14194 =============================================================================
14195 checkDiagonalPushing()
14196 -----------------------------------------------------------------------------
14197 check if diagonal input device direction results in pushing of object
14198 (by checking if the alternative direction is walkable, diggable, ...)
14199 =============================================================================
14202 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14203 int x, int y, int real_dx, int real_dy)
14205 int jx, jy, dx, dy, xx, yy;
14207 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14210 // diagonal direction: check alternative direction
14215 xx = jx + (dx == 0 ? real_dx : 0);
14216 yy = jy + (dy == 0 ? real_dy : 0);
14218 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14222 =============================================================================
14224 -----------------------------------------------------------------------------
14225 x, y: field next to player (non-diagonal) to try to dig to
14226 real_dx, real_dy: direction as read from input device (can be diagonal)
14227 =============================================================================
14230 static int DigField(struct PlayerInfo *player,
14231 int oldx, int oldy, int x, int y,
14232 int real_dx, int real_dy, int mode)
14234 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14235 boolean player_was_pushing = player->is_pushing;
14236 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14237 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14238 int jx = oldx, jy = oldy;
14239 int dx = x - jx, dy = y - jy;
14240 int nextx = x + dx, nexty = y + dy;
14241 int move_direction = (dx == -1 ? MV_LEFT :
14242 dx == +1 ? MV_RIGHT :
14244 dy == +1 ? MV_DOWN : MV_NONE);
14245 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14246 int dig_side = MV_DIR_OPPOSITE(move_direction);
14247 int old_element = Tile[jx][jy];
14248 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14251 if (is_player) // function can also be called by EL_PENGUIN
14253 if (player->MovPos == 0)
14255 player->is_digging = FALSE;
14256 player->is_collecting = FALSE;
14259 if (player->MovPos == 0) // last pushing move finished
14260 player->is_pushing = FALSE;
14262 if (mode == DF_NO_PUSH) // player just stopped pushing
14264 player->is_switching = FALSE;
14265 player->push_delay = -1;
14267 return MP_NO_ACTION;
14270 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14271 old_element = Back[jx][jy];
14273 // in case of element dropped at player position, check background
14274 else if (Back[jx][jy] != EL_EMPTY &&
14275 game.engine_version >= VERSION_IDENT(2,2,0,0))
14276 old_element = Back[jx][jy];
14278 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14279 return MP_NO_ACTION; // field has no opening in this direction
14281 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14282 return MP_NO_ACTION; // field has no opening in this direction
14284 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14288 Tile[jx][jy] = player->artwork_element;
14289 InitMovingField(jx, jy, MV_DOWN);
14290 Store[jx][jy] = EL_ACID;
14291 ContinueMoving(jx, jy);
14292 BuryPlayer(player);
14294 return MP_DONT_RUN_INTO;
14297 if (player_can_move && DONT_RUN_INTO(element))
14299 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14301 return MP_DONT_RUN_INTO;
14304 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14305 return MP_NO_ACTION;
14307 collect_count = element_info[element].collect_count_initial;
14309 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14310 return MP_NO_ACTION;
14312 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14313 player_can_move = player_can_move_or_snap;
14315 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14316 game.engine_version >= VERSION_IDENT(2,2,0,0))
14318 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14319 player->index_bit, dig_side);
14320 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14321 player->index_bit, dig_side);
14323 if (element == EL_DC_LANDMINE)
14326 if (Tile[x][y] != element) // field changed by snapping
14329 return MP_NO_ACTION;
14332 if (player->gravity && is_player && !player->is_auto_moving &&
14333 canFallDown(player) && move_direction != MV_DOWN &&
14334 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14335 return MP_NO_ACTION; // player cannot walk here due to gravity
14337 if (player_can_move &&
14338 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14340 int sound_element = SND_ELEMENT(element);
14341 int sound_action = ACTION_WALKING;
14343 if (IS_RND_GATE(element))
14345 if (!player->key[RND_GATE_NR(element)])
14346 return MP_NO_ACTION;
14348 else if (IS_RND_GATE_GRAY(element))
14350 if (!player->key[RND_GATE_GRAY_NR(element)])
14351 return MP_NO_ACTION;
14353 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14355 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14356 return MP_NO_ACTION;
14358 else if (element == EL_EXIT_OPEN ||
14359 element == EL_EM_EXIT_OPEN ||
14360 element == EL_EM_EXIT_OPENING ||
14361 element == EL_STEEL_EXIT_OPEN ||
14362 element == EL_EM_STEEL_EXIT_OPEN ||
14363 element == EL_EM_STEEL_EXIT_OPENING ||
14364 element == EL_SP_EXIT_OPEN ||
14365 element == EL_SP_EXIT_OPENING)
14367 sound_action = ACTION_PASSING; // player is passing exit
14369 else if (element == EL_EMPTY)
14371 sound_action = ACTION_MOVING; // nothing to walk on
14374 // play sound from background or player, whatever is available
14375 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14376 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14378 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14380 else if (player_can_move &&
14381 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14383 if (!ACCESS_FROM(element, opposite_direction))
14384 return MP_NO_ACTION; // field not accessible from this direction
14386 if (CAN_MOVE(element)) // only fixed elements can be passed!
14387 return MP_NO_ACTION;
14389 if (IS_EM_GATE(element))
14391 if (!player->key[EM_GATE_NR(element)])
14392 return MP_NO_ACTION;
14394 else if (IS_EM_GATE_GRAY(element))
14396 if (!player->key[EM_GATE_GRAY_NR(element)])
14397 return MP_NO_ACTION;
14399 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14401 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14402 return MP_NO_ACTION;
14404 else if (IS_EMC_GATE(element))
14406 if (!player->key[EMC_GATE_NR(element)])
14407 return MP_NO_ACTION;
14409 else if (IS_EMC_GATE_GRAY(element))
14411 if (!player->key[EMC_GATE_GRAY_NR(element)])
14412 return MP_NO_ACTION;
14414 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14416 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14417 return MP_NO_ACTION;
14419 else if (element == EL_DC_GATE_WHITE ||
14420 element == EL_DC_GATE_WHITE_GRAY ||
14421 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14423 if (player->num_white_keys == 0)
14424 return MP_NO_ACTION;
14426 player->num_white_keys--;
14428 else if (IS_SP_PORT(element))
14430 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14431 element == EL_SP_GRAVITY_PORT_RIGHT ||
14432 element == EL_SP_GRAVITY_PORT_UP ||
14433 element == EL_SP_GRAVITY_PORT_DOWN)
14434 player->gravity = !player->gravity;
14435 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14436 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14437 element == EL_SP_GRAVITY_ON_PORT_UP ||
14438 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14439 player->gravity = TRUE;
14440 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14441 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14442 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14443 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14444 player->gravity = FALSE;
14447 // automatically move to the next field with double speed
14448 player->programmed_action = move_direction;
14450 if (player->move_delay_reset_counter == 0)
14452 player->move_delay_reset_counter = 2; // two double speed steps
14454 DOUBLE_PLAYER_SPEED(player);
14457 PlayLevelSoundAction(x, y, ACTION_PASSING);
14459 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14463 if (mode != DF_SNAP)
14465 GfxElement[x][y] = GFX_ELEMENT(element);
14466 player->is_digging = TRUE;
14469 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14471 // use old behaviour for old levels (digging)
14472 if (!level.finish_dig_collect)
14474 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14475 player->index_bit, dig_side);
14477 // if digging triggered player relocation, finish digging tile
14478 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14479 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14482 if (mode == DF_SNAP)
14484 if (level.block_snap_field)
14485 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14487 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14489 // use old behaviour for old levels (snapping)
14490 if (!level.finish_dig_collect)
14491 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14492 player->index_bit, dig_side);
14495 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14499 if (is_player && mode != DF_SNAP)
14501 GfxElement[x][y] = element;
14502 player->is_collecting = TRUE;
14505 if (element == EL_SPEED_PILL)
14507 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14509 else if (element == EL_EXTRA_TIME && level.time > 0)
14511 TimeLeft += level.extra_time;
14513 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14515 DisplayGameControlValues();
14517 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14519 player->shield_normal_time_left += level.shield_normal_time;
14520 if (element == EL_SHIELD_DEADLY)
14521 player->shield_deadly_time_left += level.shield_deadly_time;
14523 else if (element == EL_DYNAMITE ||
14524 element == EL_EM_DYNAMITE ||
14525 element == EL_SP_DISK_RED)
14527 if (player->inventory_size < MAX_INVENTORY_SIZE)
14528 player->inventory_element[player->inventory_size++] = element;
14530 DrawGameDoorValues();
14532 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14534 player->dynabomb_count++;
14535 player->dynabombs_left++;
14537 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14539 player->dynabomb_size++;
14541 else if (element == EL_DYNABOMB_INCREASE_POWER)
14543 player->dynabomb_xl = TRUE;
14545 else if (IS_KEY(element))
14547 player->key[KEY_NR(element)] = TRUE;
14549 DrawGameDoorValues();
14551 else if (element == EL_DC_KEY_WHITE)
14553 player->num_white_keys++;
14555 // display white keys?
14556 // DrawGameDoorValues();
14558 else if (IS_ENVELOPE(element))
14560 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14562 if (!wait_for_snapping)
14563 player->show_envelope = element;
14565 else if (element == EL_EMC_LENSES)
14567 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14569 RedrawAllInvisibleElementsForLenses();
14571 else if (element == EL_EMC_MAGNIFIER)
14573 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14575 RedrawAllInvisibleElementsForMagnifier();
14577 else if (IS_DROPPABLE(element) ||
14578 IS_THROWABLE(element)) // can be collected and dropped
14582 if (collect_count == 0)
14583 player->inventory_infinite_element = element;
14585 for (i = 0; i < collect_count; i++)
14586 if (player->inventory_size < MAX_INVENTORY_SIZE)
14587 player->inventory_element[player->inventory_size++] = element;
14589 DrawGameDoorValues();
14591 else if (collect_count > 0)
14593 game.gems_still_needed -= collect_count;
14594 if (game.gems_still_needed < 0)
14595 game.gems_still_needed = 0;
14597 game.snapshot.collected_item = TRUE;
14599 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14601 DisplayGameControlValues();
14604 RaiseScoreElement(element);
14605 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14607 // use old behaviour for old levels (collecting)
14608 if (!level.finish_dig_collect && is_player)
14610 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14611 player->index_bit, dig_side);
14613 // if collecting triggered player relocation, finish collecting tile
14614 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14615 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14618 if (mode == DF_SNAP)
14620 if (level.block_snap_field)
14621 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14623 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14625 // use old behaviour for old levels (snapping)
14626 if (!level.finish_dig_collect)
14627 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14628 player->index_bit, dig_side);
14631 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14633 if (mode == DF_SNAP && element != EL_BD_ROCK)
14634 return MP_NO_ACTION;
14636 if (CAN_FALL(element) && dy)
14637 return MP_NO_ACTION;
14639 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14640 !(element == EL_SPRING && level.use_spring_bug))
14641 return MP_NO_ACTION;
14643 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14644 ((move_direction & MV_VERTICAL &&
14645 ((element_info[element].move_pattern & MV_LEFT &&
14646 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14647 (element_info[element].move_pattern & MV_RIGHT &&
14648 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14649 (move_direction & MV_HORIZONTAL &&
14650 ((element_info[element].move_pattern & MV_UP &&
14651 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14652 (element_info[element].move_pattern & MV_DOWN &&
14653 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14654 return MP_NO_ACTION;
14656 // do not push elements already moving away faster than player
14657 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14658 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14659 return MP_NO_ACTION;
14661 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14663 if (player->push_delay_value == -1 || !player_was_pushing)
14664 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14666 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14668 if (player->push_delay_value == -1)
14669 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14671 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14673 if (!player->is_pushing)
14674 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14677 player->is_pushing = TRUE;
14678 player->is_active = TRUE;
14680 if (!(IN_LEV_FIELD(nextx, nexty) &&
14681 (IS_FREE(nextx, nexty) ||
14682 (IS_SB_ELEMENT(element) &&
14683 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14684 (IS_CUSTOM_ELEMENT(element) &&
14685 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14686 return MP_NO_ACTION;
14688 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14689 return MP_NO_ACTION;
14691 if (player->push_delay == -1) // new pushing; restart delay
14692 player->push_delay = 0;
14694 if (player->push_delay < player->push_delay_value &&
14695 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14696 element != EL_SPRING && element != EL_BALLOON)
14698 // make sure that there is no move delay before next try to push
14699 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14700 player->move_delay = 0;
14702 return MP_NO_ACTION;
14705 if (IS_CUSTOM_ELEMENT(element) &&
14706 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14708 if (!DigFieldByCE(nextx, nexty, element))
14709 return MP_NO_ACTION;
14712 if (IS_SB_ELEMENT(element))
14714 boolean sokoban_task_solved = FALSE;
14716 if (element == EL_SOKOBAN_FIELD_FULL)
14718 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14720 IncrementSokobanFieldsNeeded();
14721 IncrementSokobanObjectsNeeded();
14724 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14726 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14728 DecrementSokobanFieldsNeeded();
14729 DecrementSokobanObjectsNeeded();
14731 // sokoban object was pushed from empty field to sokoban field
14732 if (Back[x][y] == EL_EMPTY)
14733 sokoban_task_solved = TRUE;
14736 Tile[x][y] = EL_SOKOBAN_OBJECT;
14738 if (Back[x][y] == Back[nextx][nexty])
14739 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14740 else if (Back[x][y] != 0)
14741 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14744 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14747 if (sokoban_task_solved &&
14748 game.sokoban_fields_still_needed == 0 &&
14749 game.sokoban_objects_still_needed == 0 &&
14750 level.auto_exit_sokoban)
14752 game.players_still_needed = 0;
14756 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14760 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14762 InitMovingField(x, y, move_direction);
14763 GfxAction[x][y] = ACTION_PUSHING;
14765 if (mode == DF_SNAP)
14766 ContinueMoving(x, y);
14768 MovPos[x][y] = (dx != 0 ? dx : dy);
14770 Pushed[x][y] = TRUE;
14771 Pushed[nextx][nexty] = TRUE;
14773 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14774 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14776 player->push_delay_value = -1; // get new value later
14778 // check for element change _after_ element has been pushed
14779 if (game.use_change_when_pushing_bug)
14781 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14782 player->index_bit, dig_side);
14783 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14784 player->index_bit, dig_side);
14787 else if (IS_SWITCHABLE(element))
14789 if (PLAYER_SWITCHING(player, x, y))
14791 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14792 player->index_bit, dig_side);
14797 player->is_switching = TRUE;
14798 player->switch_x = x;
14799 player->switch_y = y;
14801 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14803 if (element == EL_ROBOT_WHEEL)
14805 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14807 game.robot_wheel_x = x;
14808 game.robot_wheel_y = y;
14809 game.robot_wheel_active = TRUE;
14811 TEST_DrawLevelField(x, y);
14813 else if (element == EL_SP_TERMINAL)
14817 SCAN_PLAYFIELD(xx, yy)
14819 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14823 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14825 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14827 ResetGfxAnimation(xx, yy);
14828 TEST_DrawLevelField(xx, yy);
14832 else if (IS_BELT_SWITCH(element))
14834 ToggleBeltSwitch(x, y);
14836 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14837 element == EL_SWITCHGATE_SWITCH_DOWN ||
14838 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14839 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14841 ToggleSwitchgateSwitch(x, y);
14843 else if (element == EL_LIGHT_SWITCH ||
14844 element == EL_LIGHT_SWITCH_ACTIVE)
14846 ToggleLightSwitch(x, y);
14848 else if (element == EL_TIMEGATE_SWITCH ||
14849 element == EL_DC_TIMEGATE_SWITCH)
14851 ActivateTimegateSwitch(x, y);
14853 else if (element == EL_BALLOON_SWITCH_LEFT ||
14854 element == EL_BALLOON_SWITCH_RIGHT ||
14855 element == EL_BALLOON_SWITCH_UP ||
14856 element == EL_BALLOON_SWITCH_DOWN ||
14857 element == EL_BALLOON_SWITCH_NONE ||
14858 element == EL_BALLOON_SWITCH_ANY)
14860 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14861 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14862 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14863 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14864 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14867 else if (element == EL_LAMP)
14869 Tile[x][y] = EL_LAMP_ACTIVE;
14870 game.lights_still_needed--;
14872 ResetGfxAnimation(x, y);
14873 TEST_DrawLevelField(x, y);
14875 else if (element == EL_TIME_ORB_FULL)
14877 Tile[x][y] = EL_TIME_ORB_EMPTY;
14879 if (level.time > 0 || level.use_time_orb_bug)
14881 TimeLeft += level.time_orb_time;
14882 game.no_level_time_limit = FALSE;
14884 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14886 DisplayGameControlValues();
14889 ResetGfxAnimation(x, y);
14890 TEST_DrawLevelField(x, y);
14892 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14893 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14897 game.ball_active = !game.ball_active;
14899 SCAN_PLAYFIELD(xx, yy)
14901 int e = Tile[xx][yy];
14903 if (game.ball_active)
14905 if (e == EL_EMC_MAGIC_BALL)
14906 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14907 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14908 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14912 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14913 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14914 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14915 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14920 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14921 player->index_bit, dig_side);
14923 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14924 player->index_bit, dig_side);
14926 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14927 player->index_bit, dig_side);
14933 if (!PLAYER_SWITCHING(player, x, y))
14935 player->is_switching = TRUE;
14936 player->switch_x = x;
14937 player->switch_y = y;
14939 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14940 player->index_bit, dig_side);
14941 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14942 player->index_bit, dig_side);
14944 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14945 player->index_bit, dig_side);
14946 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14947 player->index_bit, dig_side);
14950 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14951 player->index_bit, dig_side);
14952 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14953 player->index_bit, dig_side);
14955 return MP_NO_ACTION;
14958 player->push_delay = -1;
14960 if (is_player) // function can also be called by EL_PENGUIN
14962 if (Tile[x][y] != element) // really digged/collected something
14964 player->is_collecting = !player->is_digging;
14965 player->is_active = TRUE;
14967 player->last_removed_element = element;
14974 static boolean DigFieldByCE(int x, int y, int digging_element)
14976 int element = Tile[x][y];
14978 if (!IS_FREE(x, y))
14980 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14981 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14984 // no element can dig solid indestructible elements
14985 if (IS_INDESTRUCTIBLE(element) &&
14986 !IS_DIGGABLE(element) &&
14987 !IS_COLLECTIBLE(element))
14990 if (AmoebaNr[x][y] &&
14991 (element == EL_AMOEBA_FULL ||
14992 element == EL_BD_AMOEBA ||
14993 element == EL_AMOEBA_GROWING))
14995 AmoebaCnt[AmoebaNr[x][y]]--;
14996 AmoebaCnt2[AmoebaNr[x][y]]--;
14999 if (IS_MOVING(x, y))
15000 RemoveMovingField(x, y);
15004 TEST_DrawLevelField(x, y);
15007 // if digged element was about to explode, prevent the explosion
15008 ExplodeField[x][y] = EX_TYPE_NONE;
15010 PlayLevelSoundAction(x, y, action);
15013 Store[x][y] = EL_EMPTY;
15015 // this makes it possible to leave the removed element again
15016 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15017 Store[x][y] = element;
15022 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15024 int jx = player->jx, jy = player->jy;
15025 int x = jx + dx, y = jy + dy;
15026 int snap_direction = (dx == -1 ? MV_LEFT :
15027 dx == +1 ? MV_RIGHT :
15029 dy == +1 ? MV_DOWN : MV_NONE);
15030 boolean can_continue_snapping = (level.continuous_snapping &&
15031 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15033 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15036 if (!player->active || !IN_LEV_FIELD(x, y))
15044 if (player->MovPos == 0)
15045 player->is_pushing = FALSE;
15047 player->is_snapping = FALSE;
15049 if (player->MovPos == 0)
15051 player->is_moving = FALSE;
15052 player->is_digging = FALSE;
15053 player->is_collecting = FALSE;
15059 // prevent snapping with already pressed snap key when not allowed
15060 if (player->is_snapping && !can_continue_snapping)
15063 player->MovDir = snap_direction;
15065 if (player->MovPos == 0)
15067 player->is_moving = FALSE;
15068 player->is_digging = FALSE;
15069 player->is_collecting = FALSE;
15072 player->is_dropping = FALSE;
15073 player->is_dropping_pressed = FALSE;
15074 player->drop_pressed_delay = 0;
15076 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15079 player->is_snapping = TRUE;
15080 player->is_active = TRUE;
15082 if (player->MovPos == 0)
15084 player->is_moving = FALSE;
15085 player->is_digging = FALSE;
15086 player->is_collecting = FALSE;
15089 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15090 TEST_DrawLevelField(player->last_jx, player->last_jy);
15092 TEST_DrawLevelField(x, y);
15097 static boolean DropElement(struct PlayerInfo *player)
15099 int old_element, new_element;
15100 int dropx = player->jx, dropy = player->jy;
15101 int drop_direction = player->MovDir;
15102 int drop_side = drop_direction;
15103 int drop_element = get_next_dropped_element(player);
15105 /* do not drop an element on top of another element; when holding drop key
15106 pressed without moving, dropped element must move away before the next
15107 element can be dropped (this is especially important if the next element
15108 is dynamite, which can be placed on background for historical reasons) */
15109 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15112 if (IS_THROWABLE(drop_element))
15114 dropx += GET_DX_FROM_DIR(drop_direction);
15115 dropy += GET_DY_FROM_DIR(drop_direction);
15117 if (!IN_LEV_FIELD(dropx, dropy))
15121 old_element = Tile[dropx][dropy]; // old element at dropping position
15122 new_element = drop_element; // default: no change when dropping
15124 // check if player is active, not moving and ready to drop
15125 if (!player->active || player->MovPos || player->drop_delay > 0)
15128 // check if player has anything that can be dropped
15129 if (new_element == EL_UNDEFINED)
15132 // only set if player has anything that can be dropped
15133 player->is_dropping_pressed = TRUE;
15135 // check if drop key was pressed long enough for EM style dynamite
15136 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15139 // check if anything can be dropped at the current position
15140 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15143 // collected custom elements can only be dropped on empty fields
15144 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15147 if (old_element != EL_EMPTY)
15148 Back[dropx][dropy] = old_element; // store old element on this field
15150 ResetGfxAnimation(dropx, dropy);
15151 ResetRandomAnimationValue(dropx, dropy);
15153 if (player->inventory_size > 0 ||
15154 player->inventory_infinite_element != EL_UNDEFINED)
15156 if (player->inventory_size > 0)
15158 player->inventory_size--;
15160 DrawGameDoorValues();
15162 if (new_element == EL_DYNAMITE)
15163 new_element = EL_DYNAMITE_ACTIVE;
15164 else if (new_element == EL_EM_DYNAMITE)
15165 new_element = EL_EM_DYNAMITE_ACTIVE;
15166 else if (new_element == EL_SP_DISK_RED)
15167 new_element = EL_SP_DISK_RED_ACTIVE;
15170 Tile[dropx][dropy] = new_element;
15172 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15173 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15174 el2img(Tile[dropx][dropy]), 0);
15176 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15178 // needed if previous element just changed to "empty" in the last frame
15179 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15181 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15182 player->index_bit, drop_side);
15183 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15185 player->index_bit, drop_side);
15187 TestIfElementTouchesCustomElement(dropx, dropy);
15189 else // player is dropping a dyna bomb
15191 player->dynabombs_left--;
15193 Tile[dropx][dropy] = new_element;
15195 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15196 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15197 el2img(Tile[dropx][dropy]), 0);
15199 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15202 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15203 InitField_WithBug1(dropx, dropy, FALSE);
15205 new_element = Tile[dropx][dropy]; // element might have changed
15207 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15208 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15210 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15211 MovDir[dropx][dropy] = drop_direction;
15213 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15215 // do not cause impact style collision by dropping elements that can fall
15216 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15219 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15220 player->is_dropping = TRUE;
15222 player->drop_pressed_delay = 0;
15223 player->is_dropping_pressed = FALSE;
15225 player->drop_x = dropx;
15226 player->drop_y = dropy;
15231 // ----------------------------------------------------------------------------
15232 // game sound playing functions
15233 // ----------------------------------------------------------------------------
15235 static int *loop_sound_frame = NULL;
15236 static int *loop_sound_volume = NULL;
15238 void InitPlayLevelSound(void)
15240 int num_sounds = getSoundListSize();
15242 checked_free(loop_sound_frame);
15243 checked_free(loop_sound_volume);
15245 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15246 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15249 static void PlayLevelSound(int x, int y, int nr)
15251 int sx = SCREENX(x), sy = SCREENY(y);
15252 int volume, stereo_position;
15253 int max_distance = 8;
15254 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15256 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15257 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15260 if (!IN_LEV_FIELD(x, y) ||
15261 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15262 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15265 volume = SOUND_MAX_VOLUME;
15267 if (!IN_SCR_FIELD(sx, sy))
15269 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15270 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15272 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15275 stereo_position = (SOUND_MAX_LEFT +
15276 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15277 (SCR_FIELDX + 2 * max_distance));
15279 if (IS_LOOP_SOUND(nr))
15281 /* This assures that quieter loop sounds do not overwrite louder ones,
15282 while restarting sound volume comparison with each new game frame. */
15284 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15287 loop_sound_volume[nr] = volume;
15288 loop_sound_frame[nr] = FrameCounter;
15291 PlaySoundExt(nr, volume, stereo_position, type);
15294 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15296 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15297 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15298 y < LEVELY(BY1) ? LEVELY(BY1) :
15299 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15303 static void PlayLevelSoundAction(int x, int y, int action)
15305 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15308 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15310 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15312 if (sound_effect != SND_UNDEFINED)
15313 PlayLevelSound(x, y, sound_effect);
15316 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15319 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15321 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15322 PlayLevelSound(x, y, sound_effect);
15325 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15327 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15329 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15330 PlayLevelSound(x, y, sound_effect);
15333 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15335 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15337 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15338 StopSound(sound_effect);
15341 static int getLevelMusicNr(void)
15343 if (levelset.music[level_nr] != MUS_UNDEFINED)
15344 return levelset.music[level_nr]; // from config file
15346 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15349 static void FadeLevelSounds(void)
15354 static void FadeLevelMusic(void)
15356 int music_nr = getLevelMusicNr();
15357 char *curr_music = getCurrentlyPlayingMusicFilename();
15358 char *next_music = getMusicInfoEntryFilename(music_nr);
15360 if (!strEqual(curr_music, next_music))
15364 void FadeLevelSoundsAndMusic(void)
15370 static void PlayLevelMusic(void)
15372 int music_nr = getLevelMusicNr();
15373 char *curr_music = getCurrentlyPlayingMusicFilename();
15374 char *next_music = getMusicInfoEntryFilename(music_nr);
15376 if (!strEqual(curr_music, next_music))
15377 PlayMusicLoop(music_nr);
15380 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15382 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15384 int x = xx - offset;
15385 int y = yy - offset;
15390 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15394 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15398 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15402 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15406 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15410 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15414 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15417 case SOUND_android_clone:
15418 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15421 case SOUND_android_move:
15422 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15426 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15430 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15434 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15437 case SOUND_eater_eat:
15438 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15442 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15445 case SOUND_collect:
15446 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15449 case SOUND_diamond:
15450 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15454 // !!! CHECK THIS !!!
15456 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15458 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15462 case SOUND_wonderfall:
15463 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15467 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15471 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15475 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15479 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15483 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15487 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15491 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15495 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15498 case SOUND_exit_open:
15499 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15502 case SOUND_exit_leave:
15503 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15506 case SOUND_dynamite:
15507 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15511 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15515 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15519 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15523 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15527 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15531 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15535 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15540 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15542 int element = map_element_SP_to_RND(element_sp);
15543 int action = map_action_SP_to_RND(action_sp);
15544 int offset = (setup.sp_show_border_elements ? 0 : 1);
15545 int x = xx - offset;
15546 int y = yy - offset;
15548 PlayLevelSoundElementAction(x, y, element, action);
15551 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15553 int element = map_element_MM_to_RND(element_mm);
15554 int action = map_action_MM_to_RND(action_mm);
15556 int x = xx - offset;
15557 int y = yy - offset;
15559 if (!IS_MM_ELEMENT(element))
15560 element = EL_MM_DEFAULT;
15562 PlayLevelSoundElementAction(x, y, element, action);
15565 void PlaySound_MM(int sound_mm)
15567 int sound = map_sound_MM_to_RND(sound_mm);
15569 if (sound == SND_UNDEFINED)
15575 void PlaySoundLoop_MM(int sound_mm)
15577 int sound = map_sound_MM_to_RND(sound_mm);
15579 if (sound == SND_UNDEFINED)
15582 PlaySoundLoop(sound);
15585 void StopSound_MM(int sound_mm)
15587 int sound = map_sound_MM_to_RND(sound_mm);
15589 if (sound == SND_UNDEFINED)
15595 void RaiseScore(int value)
15597 game.score += value;
15599 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15601 DisplayGameControlValues();
15604 void RaiseScoreElement(int element)
15609 case EL_BD_DIAMOND:
15610 case EL_EMERALD_YELLOW:
15611 case EL_EMERALD_RED:
15612 case EL_EMERALD_PURPLE:
15613 case EL_SP_INFOTRON:
15614 RaiseScore(level.score[SC_EMERALD]);
15617 RaiseScore(level.score[SC_DIAMOND]);
15620 RaiseScore(level.score[SC_CRYSTAL]);
15623 RaiseScore(level.score[SC_PEARL]);
15626 case EL_BD_BUTTERFLY:
15627 case EL_SP_ELECTRON:
15628 RaiseScore(level.score[SC_BUG]);
15631 case EL_BD_FIREFLY:
15632 case EL_SP_SNIKSNAK:
15633 RaiseScore(level.score[SC_SPACESHIP]);
15636 case EL_DARK_YAMYAM:
15637 RaiseScore(level.score[SC_YAMYAM]);
15640 RaiseScore(level.score[SC_ROBOT]);
15643 RaiseScore(level.score[SC_PACMAN]);
15646 RaiseScore(level.score[SC_NUT]);
15649 case EL_EM_DYNAMITE:
15650 case EL_SP_DISK_RED:
15651 case EL_DYNABOMB_INCREASE_NUMBER:
15652 case EL_DYNABOMB_INCREASE_SIZE:
15653 case EL_DYNABOMB_INCREASE_POWER:
15654 RaiseScore(level.score[SC_DYNAMITE]);
15656 case EL_SHIELD_NORMAL:
15657 case EL_SHIELD_DEADLY:
15658 RaiseScore(level.score[SC_SHIELD]);
15660 case EL_EXTRA_TIME:
15661 RaiseScore(level.extra_time_score);
15675 case EL_DC_KEY_WHITE:
15676 RaiseScore(level.score[SC_KEY]);
15679 RaiseScore(element_info[element].collect_score);
15684 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15686 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15690 // prevent short reactivation of overlay buttons while closing door
15691 SetOverlayActive(FALSE);
15692 UnmapGameButtons();
15694 // door may still be open due to skipped or envelope style request
15695 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15698 if (network.enabled)
15699 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15703 FadeSkipNextFadeIn();
15705 SetGameStatus(GAME_MODE_MAIN);
15710 else // continue playing the game
15712 if (tape.playing && tape.deactivate_display)
15713 TapeDeactivateDisplayOff(TRUE);
15715 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15717 if (tape.playing && tape.deactivate_display)
15718 TapeDeactivateDisplayOn();
15722 void RequestQuitGame(boolean escape_key_pressed)
15724 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15725 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15726 level_editor_test_game);
15727 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15728 quick_quit || score_info_tape_play);
15730 RequestQuitGameExt(skip_request, quick_quit,
15731 "Do you really want to quit the game?");
15734 void RequestRestartGame(char *message)
15736 game.restart_game_message = NULL;
15738 boolean has_started_game = hasStartedNetworkGame();
15739 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15741 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15743 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15747 // needed in case of envelope request to close game panel
15748 CloseDoor(DOOR_CLOSE_1);
15750 SetGameStatus(GAME_MODE_MAIN);
15756 void CheckGameOver(void)
15758 static boolean last_game_over = FALSE;
15759 static int game_over_delay = 0;
15760 int game_over_delay_value = 50;
15761 boolean game_over = checkGameFailed();
15763 // do not handle game over if request dialog is already active
15764 if (game.request_active)
15767 // do not ask to play again if game was never actually played
15768 if (!game.GamePlayed)
15773 last_game_over = FALSE;
15774 game_over_delay = game_over_delay_value;
15779 if (game_over_delay > 0)
15786 if (last_game_over != game_over)
15787 game.restart_game_message = (hasStartedNetworkGame() ?
15788 "Game over! Play it again?" :
15791 last_game_over = game_over;
15794 boolean checkGameSolved(void)
15796 // set for all game engines if level was solved
15797 return game.LevelSolved_GameEnd;
15800 boolean checkGameFailed(void)
15802 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15803 return (game_em.game_over && !game_em.level_solved);
15804 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15805 return (game_sp.game_over && !game_sp.level_solved);
15806 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15807 return (game_mm.game_over && !game_mm.level_solved);
15808 else // GAME_ENGINE_TYPE_RND
15809 return (game.GameOver && !game.LevelSolved);
15812 boolean checkGameEnded(void)
15814 return (checkGameSolved() || checkGameFailed());
15818 // ----------------------------------------------------------------------------
15819 // random generator functions
15820 // ----------------------------------------------------------------------------
15822 unsigned int InitEngineRandom_RND(int seed)
15824 game.num_random_calls = 0;
15826 return InitEngineRandom(seed);
15829 unsigned int RND(int max)
15833 game.num_random_calls++;
15835 return GetEngineRandom(max);
15842 // ----------------------------------------------------------------------------
15843 // game engine snapshot handling functions
15844 // ----------------------------------------------------------------------------
15846 struct EngineSnapshotInfo
15848 // runtime values for custom element collect score
15849 int collect_score[NUM_CUSTOM_ELEMENTS];
15851 // runtime values for group element choice position
15852 int choice_pos[NUM_GROUP_ELEMENTS];
15854 // runtime values for belt position animations
15855 int belt_graphic[4][NUM_BELT_PARTS];
15856 int belt_anim_mode[4][NUM_BELT_PARTS];
15859 static struct EngineSnapshotInfo engine_snapshot_rnd;
15860 static char *snapshot_level_identifier = NULL;
15861 static int snapshot_level_nr = -1;
15863 static void SaveEngineSnapshotValues_RND(void)
15865 static int belt_base_active_element[4] =
15867 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15868 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15869 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15870 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15874 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15876 int element = EL_CUSTOM_START + i;
15878 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15881 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15883 int element = EL_GROUP_START + i;
15885 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15888 for (i = 0; i < 4; i++)
15890 for (j = 0; j < NUM_BELT_PARTS; j++)
15892 int element = belt_base_active_element[i] + j;
15893 int graphic = el2img(element);
15894 int anim_mode = graphic_info[graphic].anim_mode;
15896 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15897 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15902 static void LoadEngineSnapshotValues_RND(void)
15904 unsigned int num_random_calls = game.num_random_calls;
15907 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15909 int element = EL_CUSTOM_START + i;
15911 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15914 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15916 int element = EL_GROUP_START + i;
15918 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15921 for (i = 0; i < 4; i++)
15923 for (j = 0; j < NUM_BELT_PARTS; j++)
15925 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15926 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15928 graphic_info[graphic].anim_mode = anim_mode;
15932 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15934 InitRND(tape.random_seed);
15935 for (i = 0; i < num_random_calls; i++)
15939 if (game.num_random_calls != num_random_calls)
15941 Error("number of random calls out of sync");
15942 Error("number of random calls should be %d", num_random_calls);
15943 Error("number of random calls is %d", game.num_random_calls);
15945 Fail("this should not happen -- please debug");
15949 void FreeEngineSnapshotSingle(void)
15951 FreeSnapshotSingle();
15953 setString(&snapshot_level_identifier, NULL);
15954 snapshot_level_nr = -1;
15957 void FreeEngineSnapshotList(void)
15959 FreeSnapshotList();
15962 static ListNode *SaveEngineSnapshotBuffers(void)
15964 ListNode *buffers = NULL;
15966 // copy some special values to a structure better suited for the snapshot
15968 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15969 SaveEngineSnapshotValues_RND();
15970 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15971 SaveEngineSnapshotValues_EM();
15972 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15973 SaveEngineSnapshotValues_SP(&buffers);
15974 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15975 SaveEngineSnapshotValues_MM(&buffers);
15977 // save values stored in special snapshot structure
15979 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15980 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15981 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15983 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15985 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15988 // save further RND engine values
15990 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15995 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16000 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16001 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16004 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16006 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16007 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16009 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16010 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16011 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16012 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16014 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16024 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16026 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16028 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16029 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16031 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16036 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16038 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16039 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16040 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16046 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16049 ListNode *node = engine_snapshot_list_rnd;
16052 while (node != NULL)
16054 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16059 Debug("game:playing:SaveEngineSnapshotBuffers",
16060 "size of engine snapshot: %d bytes", num_bytes);
16066 void SaveEngineSnapshotSingle(void)
16068 ListNode *buffers = SaveEngineSnapshotBuffers();
16070 // finally save all snapshot buffers to single snapshot
16071 SaveSnapshotSingle(buffers);
16073 // save level identification information
16074 setString(&snapshot_level_identifier, leveldir_current->identifier);
16075 snapshot_level_nr = level_nr;
16078 boolean CheckSaveEngineSnapshotToList(void)
16080 boolean save_snapshot =
16081 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16082 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16083 game.snapshot.changed_action) ||
16084 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16085 game.snapshot.collected_item));
16087 game.snapshot.changed_action = FALSE;
16088 game.snapshot.collected_item = FALSE;
16089 game.snapshot.save_snapshot = save_snapshot;
16091 return save_snapshot;
16094 void SaveEngineSnapshotToList(void)
16096 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16100 ListNode *buffers = SaveEngineSnapshotBuffers();
16102 // finally save all snapshot buffers to snapshot list
16103 SaveSnapshotToList(buffers);
16106 void SaveEngineSnapshotToListInitial(void)
16108 FreeEngineSnapshotList();
16110 SaveEngineSnapshotToList();
16113 static void LoadEngineSnapshotValues(void)
16115 // restore special values from snapshot structure
16117 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16118 LoadEngineSnapshotValues_RND();
16119 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16120 LoadEngineSnapshotValues_EM();
16121 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16122 LoadEngineSnapshotValues_SP();
16123 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16124 LoadEngineSnapshotValues_MM();
16127 void LoadEngineSnapshotSingle(void)
16129 LoadSnapshotSingle();
16131 LoadEngineSnapshotValues();
16134 static void LoadEngineSnapshot_Undo(int steps)
16136 LoadSnapshotFromList_Older(steps);
16138 LoadEngineSnapshotValues();
16141 static void LoadEngineSnapshot_Redo(int steps)
16143 LoadSnapshotFromList_Newer(steps);
16145 LoadEngineSnapshotValues();
16148 boolean CheckEngineSnapshotSingle(void)
16150 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16151 snapshot_level_nr == level_nr);
16154 boolean CheckEngineSnapshotList(void)
16156 return CheckSnapshotList();
16160 // ---------- new game button stuff -------------------------------------------
16167 boolean *setup_value;
16168 boolean allowed_on_tape;
16169 boolean is_touch_button;
16171 } gamebutton_info[NUM_GAME_BUTTONS] =
16174 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16175 GAME_CTRL_ID_STOP, NULL,
16176 TRUE, FALSE, "stop game"
16179 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16180 GAME_CTRL_ID_PAUSE, NULL,
16181 TRUE, FALSE, "pause game"
16184 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16185 GAME_CTRL_ID_PLAY, NULL,
16186 TRUE, FALSE, "play game"
16189 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16190 GAME_CTRL_ID_UNDO, NULL,
16191 TRUE, FALSE, "undo step"
16194 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16195 GAME_CTRL_ID_REDO, NULL,
16196 TRUE, FALSE, "redo step"
16199 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16200 GAME_CTRL_ID_SAVE, NULL,
16201 TRUE, FALSE, "save game"
16204 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16205 GAME_CTRL_ID_PAUSE2, NULL,
16206 TRUE, FALSE, "pause game"
16209 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16210 GAME_CTRL_ID_LOAD, NULL,
16211 TRUE, FALSE, "load game"
16214 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16215 GAME_CTRL_ID_PANEL_STOP, NULL,
16216 FALSE, FALSE, "stop game"
16219 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16220 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16221 FALSE, FALSE, "pause game"
16224 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16225 GAME_CTRL_ID_PANEL_PLAY, NULL,
16226 FALSE, FALSE, "play game"
16229 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16230 GAME_CTRL_ID_TOUCH_STOP, NULL,
16231 FALSE, TRUE, "stop game"
16234 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16235 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16236 FALSE, TRUE, "pause game"
16239 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16240 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16241 TRUE, FALSE, "background music on/off"
16244 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16245 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16246 TRUE, FALSE, "sound loops on/off"
16249 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16250 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16251 TRUE, FALSE, "normal sounds on/off"
16254 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16255 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16256 FALSE, FALSE, "background music on/off"
16259 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16260 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16261 FALSE, FALSE, "sound loops on/off"
16264 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16265 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16266 FALSE, FALSE, "normal sounds on/off"
16270 void CreateGameButtons(void)
16274 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16276 int graphic = gamebutton_info[i].graphic;
16277 struct GraphicInfo *gfx = &graphic_info[graphic];
16278 struct XY *pos = gamebutton_info[i].pos;
16279 struct GadgetInfo *gi;
16282 unsigned int event_mask;
16283 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16284 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16285 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16286 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16287 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16288 int gd_x = gfx->src_x;
16289 int gd_y = gfx->src_y;
16290 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16291 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16292 int gd_xa = gfx->src_x + gfx->active_xoffset;
16293 int gd_ya = gfx->src_y + gfx->active_yoffset;
16294 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16295 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16296 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16297 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16300 // do not use touch buttons if overlay touch buttons are disabled
16301 if (is_touch_button && !setup.touch.overlay_buttons)
16304 if (gfx->bitmap == NULL)
16306 game_gadget[id] = NULL;
16311 if (id == GAME_CTRL_ID_STOP ||
16312 id == GAME_CTRL_ID_PANEL_STOP ||
16313 id == GAME_CTRL_ID_TOUCH_STOP ||
16314 id == GAME_CTRL_ID_PLAY ||
16315 id == GAME_CTRL_ID_PANEL_PLAY ||
16316 id == GAME_CTRL_ID_SAVE ||
16317 id == GAME_CTRL_ID_LOAD)
16319 button_type = GD_TYPE_NORMAL_BUTTON;
16321 event_mask = GD_EVENT_RELEASED;
16323 else if (id == GAME_CTRL_ID_UNDO ||
16324 id == GAME_CTRL_ID_REDO)
16326 button_type = GD_TYPE_NORMAL_BUTTON;
16328 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16332 button_type = GD_TYPE_CHECK_BUTTON;
16333 checked = (gamebutton_info[i].setup_value != NULL ?
16334 *gamebutton_info[i].setup_value : FALSE);
16335 event_mask = GD_EVENT_PRESSED;
16338 gi = CreateGadget(GDI_CUSTOM_ID, id,
16339 GDI_IMAGE_ID, graphic,
16340 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16343 GDI_WIDTH, gfx->width,
16344 GDI_HEIGHT, gfx->height,
16345 GDI_TYPE, button_type,
16346 GDI_STATE, GD_BUTTON_UNPRESSED,
16347 GDI_CHECKED, checked,
16348 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16349 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16350 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16351 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16352 GDI_DIRECT_DRAW, FALSE,
16353 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16354 GDI_EVENT_MASK, event_mask,
16355 GDI_CALLBACK_ACTION, HandleGameButtons,
16359 Fail("cannot create gadget");
16361 game_gadget[id] = gi;
16365 void FreeGameButtons(void)
16369 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16370 FreeGadget(game_gadget[i]);
16373 static void UnmapGameButtonsAtSamePosition(int id)
16377 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16379 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16380 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16381 UnmapGadget(game_gadget[i]);
16384 static void UnmapGameButtonsAtSamePosition_All(void)
16386 if (setup.show_load_save_buttons)
16388 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16389 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16390 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16392 else if (setup.show_undo_redo_buttons)
16394 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16395 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16396 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16400 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16401 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16402 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16404 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16405 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16406 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16410 void MapLoadSaveButtons(void)
16412 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16413 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16415 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16416 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16419 void MapUndoRedoButtons(void)
16421 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16422 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16424 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16425 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16428 void ModifyPauseButtons(void)
16432 GAME_CTRL_ID_PAUSE,
16433 GAME_CTRL_ID_PAUSE2,
16434 GAME_CTRL_ID_PANEL_PAUSE,
16435 GAME_CTRL_ID_TOUCH_PAUSE,
16440 for (i = 0; ids[i] > -1; i++)
16441 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16444 static void MapGameButtonsExt(boolean on_tape)
16448 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16450 if ((i == GAME_CTRL_ID_UNDO ||
16451 i == GAME_CTRL_ID_REDO) &&
16452 game_status != GAME_MODE_PLAYING)
16455 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16456 MapGadget(game_gadget[i]);
16459 UnmapGameButtonsAtSamePosition_All();
16461 RedrawGameButtons();
16464 static void UnmapGameButtonsExt(boolean on_tape)
16468 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16469 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16470 UnmapGadget(game_gadget[i]);
16473 static void RedrawGameButtonsExt(boolean on_tape)
16477 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16478 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16479 RedrawGadget(game_gadget[i]);
16482 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16487 gi->checked = state;
16490 static void RedrawSoundButtonGadget(int id)
16492 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16493 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16494 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16495 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16496 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16497 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16500 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16501 RedrawGadget(game_gadget[id2]);
16504 void MapGameButtons(void)
16506 MapGameButtonsExt(FALSE);
16509 void UnmapGameButtons(void)
16511 UnmapGameButtonsExt(FALSE);
16514 void RedrawGameButtons(void)
16516 RedrawGameButtonsExt(FALSE);
16519 void MapGameButtonsOnTape(void)
16521 MapGameButtonsExt(TRUE);
16524 void UnmapGameButtonsOnTape(void)
16526 UnmapGameButtonsExt(TRUE);
16529 void RedrawGameButtonsOnTape(void)
16531 RedrawGameButtonsExt(TRUE);
16534 static void GameUndoRedoExt(void)
16536 ClearPlayerAction();
16538 tape.pausing = TRUE;
16541 UpdateAndDisplayGameControlValues();
16543 DrawCompleteVideoDisplay();
16544 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16545 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16546 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16548 ModifyPauseButtons();
16553 static void GameUndo(int steps)
16555 if (!CheckEngineSnapshotList())
16558 int tape_property_bits = tape.property_bits;
16560 LoadEngineSnapshot_Undo(steps);
16562 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16567 static void GameRedo(int steps)
16569 if (!CheckEngineSnapshotList())
16572 int tape_property_bits = tape.property_bits;
16574 LoadEngineSnapshot_Redo(steps);
16576 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16581 static void HandleGameButtonsExt(int id, int button)
16583 static boolean game_undo_executed = FALSE;
16584 int steps = BUTTON_STEPSIZE(button);
16585 boolean handle_game_buttons =
16586 (game_status == GAME_MODE_PLAYING ||
16587 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16589 if (!handle_game_buttons)
16594 case GAME_CTRL_ID_STOP:
16595 case GAME_CTRL_ID_PANEL_STOP:
16596 case GAME_CTRL_ID_TOUCH_STOP:
16601 case GAME_CTRL_ID_PAUSE:
16602 case GAME_CTRL_ID_PAUSE2:
16603 case GAME_CTRL_ID_PANEL_PAUSE:
16604 case GAME_CTRL_ID_TOUCH_PAUSE:
16605 if (network.enabled && game_status == GAME_MODE_PLAYING)
16608 SendToServer_ContinuePlaying();
16610 SendToServer_PausePlaying();
16613 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16615 game_undo_executed = FALSE;
16619 case GAME_CTRL_ID_PLAY:
16620 case GAME_CTRL_ID_PANEL_PLAY:
16621 if (game_status == GAME_MODE_MAIN)
16623 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16625 else if (tape.pausing)
16627 if (network.enabled)
16628 SendToServer_ContinuePlaying();
16630 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16634 case GAME_CTRL_ID_UNDO:
16635 // Important: When using "save snapshot when collecting an item" mode,
16636 // load last (current) snapshot for first "undo" after pressing "pause"
16637 // (else the last-but-one snapshot would be loaded, because the snapshot
16638 // pointer already points to the last snapshot when pressing "pause",
16639 // which is fine for "every step/move" mode, but not for "every collect")
16640 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16641 !game_undo_executed)
16644 game_undo_executed = TRUE;
16649 case GAME_CTRL_ID_REDO:
16653 case GAME_CTRL_ID_SAVE:
16657 case GAME_CTRL_ID_LOAD:
16661 case SOUND_CTRL_ID_MUSIC:
16662 case SOUND_CTRL_ID_PANEL_MUSIC:
16663 if (setup.sound_music)
16665 setup.sound_music = FALSE;
16669 else if (audio.music_available)
16671 setup.sound = setup.sound_music = TRUE;
16673 SetAudioMode(setup.sound);
16675 if (game_status == GAME_MODE_PLAYING)
16679 RedrawSoundButtonGadget(id);
16683 case SOUND_CTRL_ID_LOOPS:
16684 case SOUND_CTRL_ID_PANEL_LOOPS:
16685 if (setup.sound_loops)
16686 setup.sound_loops = FALSE;
16687 else if (audio.loops_available)
16689 setup.sound = setup.sound_loops = TRUE;
16691 SetAudioMode(setup.sound);
16694 RedrawSoundButtonGadget(id);
16698 case SOUND_CTRL_ID_SIMPLE:
16699 case SOUND_CTRL_ID_PANEL_SIMPLE:
16700 if (setup.sound_simple)
16701 setup.sound_simple = FALSE;
16702 else if (audio.sound_available)
16704 setup.sound = setup.sound_simple = TRUE;
16706 SetAudioMode(setup.sound);
16709 RedrawSoundButtonGadget(id);
16718 static void HandleGameButtons(struct GadgetInfo *gi)
16720 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16723 void HandleSoundButtonKeys(Key key)
16725 if (key == setup.shortcut.sound_simple)
16726 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16727 else if (key == setup.shortcut.sound_loops)
16728 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16729 else if (key == setup.shortcut.sound_music)
16730 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);