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 TestIfPlayerTouchesCustomElement(int, int);
1062 static void TestIfElementTouchesCustomElement(int, int);
1063 static void TestIfElementHitsCustomElement(int, int, int);
1065 static void HandleElementChange(int, int, int);
1066 static void ExecuteCustomElementAction(int, int, int, int);
1067 static boolean ChangeElement(int, int, int, int);
1069 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1070 #define CheckTriggeredElementChange(x, y, e, ev) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1072 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1073 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1074 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1075 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1076 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1078 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1081 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1082 #define CheckElementChange(x, y, e, te, ev) \
1083 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1084 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1085 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1086 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1087 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1088 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1089 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1091 static void PlayLevelSound(int, int, int);
1092 static void PlayLevelSoundNearest(int, int, int);
1093 static void PlayLevelSoundAction(int, int, int);
1094 static void PlayLevelSoundElementAction(int, int, int, int);
1095 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1096 static void PlayLevelSoundActionIfLoop(int, int, int);
1097 static void StopLevelSoundActionIfLoop(int, int, int);
1098 static void PlayLevelMusic(void);
1099 static void FadeLevelSoundsAndMusic(void);
1101 static void HandleGameButtons(struct GadgetInfo *);
1103 int AmoebaNeighbourNr(int, int);
1104 void AmoebaToDiamond(int, int);
1105 void ContinueMoving(int, int);
1106 void Bang(int, int);
1107 void InitMovDir(int, int);
1108 void InitAmoebaNr(int, int);
1109 void NewHighScore(int);
1111 void TestIfGoodThingHitsBadThing(int, int, int);
1112 void TestIfBadThingHitsGoodThing(int, int, int);
1113 void TestIfPlayerTouchesBadThing(int, int);
1114 void TestIfPlayerRunsIntoBadThing(int, int, int);
1115 void TestIfBadThingTouchesPlayer(int, int);
1116 void TestIfBadThingRunsIntoPlayer(int, int, int);
1117 void TestIfFriendTouchesBadThing(int, int);
1118 void TestIfBadThingTouchesFriend(int, int);
1119 void TestIfBadThingTouchesOtherBadThing(int, int);
1120 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1122 void KillPlayer(struct PlayerInfo *);
1123 void BuryPlayer(struct PlayerInfo *);
1124 void RemovePlayer(struct PlayerInfo *);
1125 void ExitPlayer(struct PlayerInfo *);
1127 static int getInvisibleActiveFromInvisibleElement(int);
1128 static int getInvisibleFromInvisibleActiveElement(int);
1130 static void TestFieldAfterSnapping(int, int, int, int, int);
1132 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1134 // for detection of endless loops, caused by custom element programming
1135 // (using maximal playfield width x 10 is just a rough approximation)
1136 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1138 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1140 if (recursion_loop_detected) \
1143 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1145 recursion_loop_detected = TRUE; \
1146 recursion_loop_element = (e); \
1149 recursion_loop_depth++; \
1152 #define RECURSION_LOOP_DETECTION_END() \
1154 recursion_loop_depth--; \
1157 static int recursion_loop_depth;
1158 static boolean recursion_loop_detected;
1159 static boolean recursion_loop_element;
1161 static int map_player_action[MAX_PLAYERS];
1164 // ----------------------------------------------------------------------------
1165 // definition of elements that automatically change to other elements after
1166 // a specified time, eventually calling a function when changing
1167 // ----------------------------------------------------------------------------
1169 // forward declaration for changer functions
1170 static void InitBuggyBase(int, int);
1171 static void WarnBuggyBase(int, int);
1173 static void InitTrap(int, int);
1174 static void ActivateTrap(int, int);
1175 static void ChangeActiveTrap(int, int);
1177 static void InitRobotWheel(int, int);
1178 static void RunRobotWheel(int, int);
1179 static void StopRobotWheel(int, int);
1181 static void InitTimegateWheel(int, int);
1182 static void RunTimegateWheel(int, int);
1184 static void InitMagicBallDelay(int, int);
1185 static void ActivateMagicBall(int, int);
1187 struct ChangingElementInfo
1192 void (*pre_change_function)(int x, int y);
1193 void (*change_function)(int x, int y);
1194 void (*post_change_function)(int x, int y);
1197 static struct ChangingElementInfo change_delay_list[] =
1232 EL_STEEL_EXIT_OPENING,
1240 EL_STEEL_EXIT_CLOSING,
1241 EL_STEEL_EXIT_CLOSED,
1264 EL_EM_STEEL_EXIT_OPENING,
1265 EL_EM_STEEL_EXIT_OPEN,
1272 EL_EM_STEEL_EXIT_CLOSING,
1296 EL_SWITCHGATE_OPENING,
1304 EL_SWITCHGATE_CLOSING,
1305 EL_SWITCHGATE_CLOSED,
1312 EL_TIMEGATE_OPENING,
1320 EL_TIMEGATE_CLOSING,
1329 EL_ACID_SPLASH_LEFT,
1337 EL_ACID_SPLASH_RIGHT,
1346 EL_SP_BUGGY_BASE_ACTIVATING,
1353 EL_SP_BUGGY_BASE_ACTIVATING,
1354 EL_SP_BUGGY_BASE_ACTIVE,
1361 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_ROBOT_WHEEL_ACTIVE,
1393 EL_TIMEGATE_SWITCH_ACTIVE,
1401 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1402 EL_DC_TIMEGATE_SWITCH,
1409 EL_EMC_MAGIC_BALL_ACTIVE,
1410 EL_EMC_MAGIC_BALL_ACTIVE,
1417 EL_EMC_SPRING_BUMPER_ACTIVE,
1418 EL_EMC_SPRING_BUMPER,
1425 EL_DIAGONAL_SHRINKING,
1433 EL_DIAGONAL_GROWING,
1454 int push_delay_fixed, push_delay_random;
1458 { EL_SPRING, 0, 0 },
1459 { EL_BALLOON, 0, 0 },
1461 { EL_SOKOBAN_OBJECT, 2, 0 },
1462 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1463 { EL_SATELLITE, 2, 0 },
1464 { EL_SP_DISK_YELLOW, 2, 0 },
1466 { EL_UNDEFINED, 0, 0 },
1474 move_stepsize_list[] =
1476 { EL_AMOEBA_DROP, 2 },
1477 { EL_AMOEBA_DROPPING, 2 },
1478 { EL_QUICKSAND_FILLING, 1 },
1479 { EL_QUICKSAND_EMPTYING, 1 },
1480 { EL_QUICKSAND_FAST_FILLING, 2 },
1481 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1482 { EL_MAGIC_WALL_FILLING, 2 },
1483 { EL_MAGIC_WALL_EMPTYING, 2 },
1484 { EL_BD_MAGIC_WALL_FILLING, 2 },
1485 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1486 { EL_DC_MAGIC_WALL_FILLING, 2 },
1487 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_UNDEFINED, 0 },
1497 collect_count_list[] =
1500 { EL_BD_DIAMOND, 1 },
1501 { EL_EMERALD_YELLOW, 1 },
1502 { EL_EMERALD_RED, 1 },
1503 { EL_EMERALD_PURPLE, 1 },
1505 { EL_SP_INFOTRON, 1 },
1509 { EL_UNDEFINED, 0 },
1517 access_direction_list[] =
1519 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1521 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1522 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1525 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1526 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1527 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1528 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1529 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1531 { EL_SP_PORT_LEFT, MV_RIGHT },
1532 { EL_SP_PORT_RIGHT, MV_LEFT },
1533 { EL_SP_PORT_UP, MV_DOWN },
1534 { EL_SP_PORT_DOWN, MV_UP },
1535 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1536 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1537 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1538 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1546 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1547 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1548 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1549 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1551 { EL_UNDEFINED, MV_NONE }
1554 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1556 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1557 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1558 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1559 IS_JUST_CHANGING(x, y))
1561 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1563 // static variables for playfield scan mode (scanning forward or backward)
1564 static int playfield_scan_start_x = 0;
1565 static int playfield_scan_start_y = 0;
1566 static int playfield_scan_delta_x = 1;
1567 static int playfield_scan_delta_y = 1;
1569 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1570 (y) >= 0 && (y) <= lev_fieldy - 1; \
1571 (y) += playfield_scan_delta_y) \
1572 for ((x) = playfield_scan_start_x; \
1573 (x) >= 0 && (x) <= lev_fieldx - 1; \
1574 (x) += playfield_scan_delta_x)
1577 void DEBUG_SetMaximumDynamite(void)
1581 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1582 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1583 local_player->inventory_element[local_player->inventory_size++] =
1588 static void InitPlayfieldScanModeVars(void)
1590 if (game.use_reverse_scan_direction)
1592 playfield_scan_start_x = lev_fieldx - 1;
1593 playfield_scan_start_y = lev_fieldy - 1;
1595 playfield_scan_delta_x = -1;
1596 playfield_scan_delta_y = -1;
1600 playfield_scan_start_x = 0;
1601 playfield_scan_start_y = 0;
1603 playfield_scan_delta_x = 1;
1604 playfield_scan_delta_y = 1;
1608 static void InitPlayfieldScanMode(int mode)
1610 game.use_reverse_scan_direction =
1611 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1613 InitPlayfieldScanModeVars();
1616 static int get_move_delay_from_stepsize(int move_stepsize)
1619 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1621 // make sure that stepsize value is always a power of 2
1622 move_stepsize = (1 << log_2(move_stepsize));
1624 return TILEX / move_stepsize;
1627 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1630 int player_nr = player->index_nr;
1631 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1632 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1634 // do no immediately change move delay -- the player might just be moving
1635 player->move_delay_value_next = move_delay;
1637 // information if player can move must be set separately
1638 player->cannot_move = cannot_move;
1642 player->move_delay = game.initial_move_delay[player_nr];
1643 player->move_delay_value = game.initial_move_delay_value[player_nr];
1645 player->move_delay_value_next = -1;
1647 player->move_delay_reset_counter = 0;
1651 void GetPlayerConfig(void)
1653 GameFrameDelay = setup.game_frame_delay;
1655 if (!audio.sound_available)
1656 setup.sound_simple = FALSE;
1658 if (!audio.loops_available)
1659 setup.sound_loops = FALSE;
1661 if (!audio.music_available)
1662 setup.sound_music = FALSE;
1664 if (!video.fullscreen_available)
1665 setup.fullscreen = FALSE;
1667 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1669 SetAudioMode(setup.sound);
1672 int GetElementFromGroupElement(int element)
1674 if (IS_GROUP_ELEMENT(element))
1676 struct ElementGroupInfo *group = element_info[element].group;
1677 int last_anim_random_frame = gfx.anim_random_frame;
1680 if (group->choice_mode == ANIM_RANDOM)
1681 gfx.anim_random_frame = RND(group->num_elements_resolved);
1683 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1684 group->choice_mode, 0,
1687 if (group->choice_mode == ANIM_RANDOM)
1688 gfx.anim_random_frame = last_anim_random_frame;
1690 group->choice_pos++;
1692 element = group->element_resolved[element_pos];
1698 static void IncrementSokobanFieldsNeeded(void)
1700 if (level.sb_fields_needed)
1701 game.sokoban_fields_still_needed++;
1704 static void IncrementSokobanObjectsNeeded(void)
1706 if (level.sb_objects_needed)
1707 game.sokoban_objects_still_needed++;
1710 static void DecrementSokobanFieldsNeeded(void)
1712 if (game.sokoban_fields_still_needed > 0)
1713 game.sokoban_fields_still_needed--;
1716 static void DecrementSokobanObjectsNeeded(void)
1718 if (game.sokoban_objects_still_needed > 0)
1719 game.sokoban_objects_still_needed--;
1722 static void InitPlayerField(int x, int y, int element, boolean init_game)
1724 if (element == EL_SP_MURPHY)
1728 if (stored_player[0].present)
1730 Tile[x][y] = EL_SP_MURPHY_CLONE;
1736 stored_player[0].initial_element = element;
1737 stored_player[0].use_murphy = TRUE;
1739 if (!level.use_artwork_element[0])
1740 stored_player[0].artwork_element = EL_SP_MURPHY;
1743 Tile[x][y] = EL_PLAYER_1;
1749 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1750 int jx = player->jx, jy = player->jy;
1752 player->present = TRUE;
1754 player->block_last_field = (element == EL_SP_MURPHY ?
1755 level.sp_block_last_field :
1756 level.block_last_field);
1758 // ---------- initialize player's last field block delay ------------------
1760 // always start with reliable default value (no adjustment needed)
1761 player->block_delay_adjustment = 0;
1763 // special case 1: in Supaplex, Murphy blocks last field one more frame
1764 if (player->block_last_field && element == EL_SP_MURPHY)
1765 player->block_delay_adjustment = 1;
1767 // special case 2: in game engines before 3.1.1, blocking was different
1768 if (game.use_block_last_field_bug)
1769 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1771 if (!network.enabled || player->connected_network)
1773 player->active = TRUE;
1775 // remove potentially duplicate players
1776 if (StorePlayer[jx][jy] == Tile[x][y])
1777 StorePlayer[jx][jy] = 0;
1779 StorePlayer[x][y] = Tile[x][y];
1781 #if DEBUG_INIT_PLAYER
1782 Debug("game:init:player", "- player element %d activated",
1783 player->element_nr);
1784 Debug("game:init:player", " (local player is %d and currently %s)",
1785 local_player->element_nr,
1786 local_player->active ? "active" : "not active");
1790 Tile[x][y] = EL_EMPTY;
1792 player->jx = player->last_jx = x;
1793 player->jy = player->last_jy = y;
1796 // always check if player was just killed and should be reanimated
1798 int player_nr = GET_PLAYER_NR(element);
1799 struct PlayerInfo *player = &stored_player[player_nr];
1801 if (player->active && player->killed)
1802 player->reanimated = TRUE; // if player was just killed, reanimate him
1806 static void InitField(int x, int y, boolean init_game)
1808 int element = Tile[x][y];
1817 InitPlayerField(x, y, element, init_game);
1820 case EL_SOKOBAN_FIELD_PLAYER:
1821 element = Tile[x][y] = EL_PLAYER_1;
1822 InitField(x, y, init_game);
1824 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1825 InitField(x, y, init_game);
1828 case EL_SOKOBAN_FIELD_EMPTY:
1829 IncrementSokobanFieldsNeeded();
1832 case EL_SOKOBAN_OBJECT:
1833 IncrementSokobanObjectsNeeded();
1837 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1838 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1839 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1843 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1844 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1845 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1846 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1855 case EL_SPACESHIP_RIGHT:
1856 case EL_SPACESHIP_UP:
1857 case EL_SPACESHIP_LEFT:
1858 case EL_SPACESHIP_DOWN:
1859 case EL_BD_BUTTERFLY:
1860 case EL_BD_BUTTERFLY_RIGHT:
1861 case EL_BD_BUTTERFLY_UP:
1862 case EL_BD_BUTTERFLY_LEFT:
1863 case EL_BD_BUTTERFLY_DOWN:
1865 case EL_BD_FIREFLY_RIGHT:
1866 case EL_BD_FIREFLY_UP:
1867 case EL_BD_FIREFLY_LEFT:
1868 case EL_BD_FIREFLY_DOWN:
1869 case EL_PACMAN_RIGHT:
1871 case EL_PACMAN_LEFT:
1872 case EL_PACMAN_DOWN:
1874 case EL_YAMYAM_LEFT:
1875 case EL_YAMYAM_RIGHT:
1877 case EL_YAMYAM_DOWN:
1878 case EL_DARK_YAMYAM:
1881 case EL_SP_SNIKSNAK:
1882 case EL_SP_ELECTRON:
1888 case EL_SPRING_LEFT:
1889 case EL_SPRING_RIGHT:
1893 case EL_AMOEBA_FULL:
1898 case EL_AMOEBA_DROP:
1899 if (y == lev_fieldy - 1)
1901 Tile[x][y] = EL_AMOEBA_GROWING;
1902 Store[x][y] = EL_AMOEBA_WET;
1906 case EL_DYNAMITE_ACTIVE:
1907 case EL_SP_DISK_RED_ACTIVE:
1908 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1909 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1910 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1912 MovDelay[x][y] = 96;
1915 case EL_EM_DYNAMITE_ACTIVE:
1916 MovDelay[x][y] = 32;
1920 game.lights_still_needed++;
1924 game.friends_still_needed++;
1929 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1932 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1933 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1934 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1935 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1946 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1947 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1948 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1950 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1952 game.belt_dir[belt_nr] = belt_dir;
1953 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1955 else // more than one switch -- set it like the first switch
1957 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1962 case EL_LIGHT_SWITCH_ACTIVE:
1964 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1967 case EL_INVISIBLE_STEELWALL:
1968 case EL_INVISIBLE_WALL:
1969 case EL_INVISIBLE_SAND:
1970 if (game.light_time_left > 0 ||
1971 game.lenses_time_left > 0)
1972 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1975 case EL_EMC_MAGIC_BALL:
1976 if (game.ball_active)
1977 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1980 case EL_EMC_MAGIC_BALL_SWITCH:
1981 if (game.ball_active)
1982 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1985 case EL_TRIGGER_PLAYER:
1986 case EL_TRIGGER_ELEMENT:
1987 case EL_TRIGGER_CE_VALUE:
1988 case EL_TRIGGER_CE_SCORE:
1990 case EL_ANY_ELEMENT:
1991 case EL_CURRENT_CE_VALUE:
1992 case EL_CURRENT_CE_SCORE:
2009 // reference elements should not be used on the playfield
2010 Tile[x][y] = EL_EMPTY;
2014 if (IS_CUSTOM_ELEMENT(element))
2016 if (CAN_MOVE(element))
2019 if (!element_info[element].use_last_ce_value || init_game)
2020 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2022 else if (IS_GROUP_ELEMENT(element))
2024 Tile[x][y] = GetElementFromGroupElement(element);
2026 InitField(x, y, init_game);
2033 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2036 static void InitField_WithBug1(int x, int y, boolean init_game)
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(Tile[x][y]))
2046 static void InitField_WithBug2(int x, int y, boolean init_game)
2048 int old_element = Tile[x][y];
2050 InitField(x, y, init_game);
2052 // not needed to call InitMovDir() -- already done by InitField()!
2053 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2054 CAN_MOVE(old_element) &&
2055 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2058 /* this case is in fact a combination of not less than three bugs:
2059 first, it calls InitMovDir() for elements that can move, although this is
2060 already done by InitField(); then, it checks the element that was at this
2061 field _before_ the call to InitField() (which can change it); lastly, it
2062 was not called for "mole with direction" elements, which were treated as
2063 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2067 static int get_key_element_from_nr(int key_nr)
2069 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2070 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2071 EL_EM_KEY_1 : EL_KEY_1);
2073 return key_base_element + key_nr;
2076 static int get_next_dropped_element(struct PlayerInfo *player)
2078 return (player->inventory_size > 0 ?
2079 player->inventory_element[player->inventory_size - 1] :
2080 player->inventory_infinite_element != EL_UNDEFINED ?
2081 player->inventory_infinite_element :
2082 player->dynabombs_left > 0 ?
2083 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2087 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2089 // pos >= 0: get element from bottom of the stack;
2090 // pos < 0: get element from top of the stack
2094 int min_inventory_size = -pos;
2095 int inventory_pos = player->inventory_size - min_inventory_size;
2096 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2098 return (player->inventory_size >= min_inventory_size ?
2099 player->inventory_element[inventory_pos] :
2100 player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 int min_dynabombs_left = pos + 1;
2109 int min_inventory_size = pos + 1 - player->dynabombs_left;
2110 int inventory_pos = pos - player->dynabombs_left;
2112 return (player->inventory_infinite_element != EL_UNDEFINED ?
2113 player->inventory_infinite_element :
2114 player->dynabombs_left >= min_dynabombs_left ?
2115 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 player->inventory_size >= min_inventory_size ?
2117 player->inventory_element[inventory_pos] :
2122 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2124 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2125 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2128 if (gpo1->sort_priority != gpo2->sort_priority)
2129 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2131 compare_result = gpo1->nr - gpo2->nr;
2133 return compare_result;
2136 int getPlayerInventorySize(int player_nr)
2138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2139 return game_em.ply[player_nr]->dynamite;
2140 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2141 return game_sp.red_disk_count;
2143 return stored_player[player_nr].inventory_size;
2146 static void InitGameControlValues(void)
2150 for (i = 0; game_panel_controls[i].nr != -1; i++)
2152 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2153 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2154 struct TextPosInfo *pos = gpc->pos;
2156 int type = gpc->type;
2160 Error("'game_panel_controls' structure corrupted at %d", i);
2162 Fail("this should not happen -- please debug");
2165 // force update of game controls after initialization
2166 gpc->value = gpc->last_value = -1;
2167 gpc->frame = gpc->last_frame = -1;
2168 gpc->gfx_frame = -1;
2170 // determine panel value width for later calculation of alignment
2171 if (type == TYPE_INTEGER || type == TYPE_STRING)
2173 pos->width = pos->size * getFontWidth(pos->font);
2174 pos->height = getFontHeight(pos->font);
2176 else if (type == TYPE_ELEMENT)
2178 pos->width = pos->size;
2179 pos->height = pos->size;
2182 // fill structure for game panel draw order
2184 gpo->sort_priority = pos->sort_priority;
2187 // sort game panel controls according to sort_priority and control number
2188 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2189 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2192 static void UpdatePlayfieldElementCount(void)
2194 boolean use_element_count = FALSE;
2197 // first check if it is needed at all to calculate playfield element count
2198 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2199 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2200 use_element_count = TRUE;
2202 if (!use_element_count)
2205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2206 element_info[i].element_count = 0;
2208 SCAN_PLAYFIELD(x, y)
2210 element_info[Tile[x][y]].element_count++;
2213 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2214 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2215 if (IS_IN_GROUP(j, i))
2216 element_info[EL_GROUP_START + i].element_count +=
2217 element_info[j].element_count;
2220 static void UpdateGameControlValues(void)
2223 int time = (game.LevelSolved ?
2224 game.LevelSolved_CountingTime :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 game_sp.time_played :
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 game_mm.energy_left :
2231 game.no_time_limit ? TimePlayed : TimeLeft);
2232 int score = (game.LevelSolved ?
2233 game.LevelSolved_CountingScore :
2234 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 game_em.lev->score :
2236 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2242 game_em.lev->gems_needed :
2243 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2244 game_sp.infotrons_still_needed :
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2246 game_mm.kettles_still_needed :
2247 game.gems_still_needed);
2248 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 game_em.lev->gems_needed > 0 :
2250 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2251 game_sp.infotrons_still_needed > 0 :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 game_mm.kettles_still_needed > 0 ||
2254 game_mm.lights_still_needed > 0 :
2255 game.gems_still_needed > 0 ||
2256 game.sokoban_fields_still_needed > 0 ||
2257 game.sokoban_objects_still_needed > 0 ||
2258 game.lights_still_needed > 0);
2259 int health = (game.LevelSolved ?
2260 game.LevelSolved_CountingHealth :
2261 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2262 MM_HEALTH(game_mm.laser_overload_value) :
2264 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2266 UpdatePlayfieldElementCount();
2268 // update game panel control values
2270 // used instead of "level_nr" (for network games)
2271 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2272 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2274 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2275 for (i = 0; i < MAX_NUM_KEYS; i++)
2276 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2277 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2278 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2280 if (game.centered_player_nr == -1)
2282 for (i = 0; i < MAX_PLAYERS; i++)
2284 // only one player in Supaplex game engine
2285 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2288 for (k = 0; k < MAX_NUM_KEYS; k++)
2290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2292 if (game_em.ply[i]->keys & (1 << k))
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2296 else if (stored_player[i].key[k])
2297 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2298 get_key_element_from_nr(k);
2301 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2302 getPlayerInventorySize(i);
2304 if (stored_player[i].num_white_keys > 0)
2305 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2308 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2309 stored_player[i].num_white_keys;
2314 int player_nr = game.centered_player_nr;
2316 for (k = 0; k < MAX_NUM_KEYS; k++)
2318 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2320 if (game_em.ply[player_nr]->keys & (1 << k))
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2324 else if (stored_player[player_nr].key[k])
2325 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2326 get_key_element_from_nr(k);
2329 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2330 getPlayerInventorySize(player_nr);
2332 if (stored_player[player_nr].num_white_keys > 0)
2333 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2336 stored_player[player_nr].num_white_keys;
2339 // re-arrange keys on game panel, if needed or if defined by style settings
2340 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2342 int nr = GAME_PANEL_KEY_1 + i;
2343 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2344 struct TextPosInfo *pos = gpc->pos;
2346 // skip check if key is not in the player's inventory
2347 if (gpc->value == EL_EMPTY)
2350 // check if keys should be arranged on panel from left to right
2351 if (pos->style == STYLE_LEFTMOST_POSITION)
2353 // check previous key positions (left from current key)
2354 for (k = 0; k < i; k++)
2356 int nr_new = GAME_PANEL_KEY_1 + k;
2358 if (game_panel_controls[nr_new].value == EL_EMPTY)
2360 game_panel_controls[nr_new].value = gpc->value;
2361 gpc->value = EL_EMPTY;
2368 // check if "undefined" keys can be placed at some other position
2369 if (pos->x == -1 && pos->y == -1)
2371 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2373 // 1st try: display key at the same position as normal or EM keys
2374 if (game_panel_controls[nr_new].value == EL_EMPTY)
2376 game_panel_controls[nr_new].value = gpc->value;
2380 // 2nd try: display key at the next free position in the key panel
2381 for (k = 0; k < STD_NUM_KEYS; k++)
2383 nr_new = GAME_PANEL_KEY_1 + k;
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2396 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2398 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, i);
2400 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2401 get_inventory_element_from_pos(local_player, -i - 1);
2404 game_panel_controls[GAME_PANEL_SCORE].value = score;
2405 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2407 game_panel_controls[GAME_PANEL_TIME].value = time;
2409 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2410 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2411 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2413 if (level.time == 0)
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2416 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2418 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2419 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2421 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2423 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2424 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2427 local_player->shield_normal_time_left;
2428 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2429 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2432 local_player->shield_deadly_time_left;
2434 game_panel_controls[GAME_PANEL_EXIT].value =
2435 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2439 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2440 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2441 EL_EMC_MAGIC_BALL_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2444 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2445 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2446 game.light_time_left;
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2449 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2450 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2451 game.timegate_time_left;
2453 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2454 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2456 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2457 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2458 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2459 game.lenses_time_left;
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2462 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2463 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2464 game.magnify_time_left;
2466 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2467 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2468 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2469 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2470 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2471 EL_BALLOON_SWITCH_NONE);
2473 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2474 local_player->dynabomb_count;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2476 local_player->dynabomb_size;
2477 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2478 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_PENGUINS].value =
2481 game.friends_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2484 game.sokoban_objects_still_needed;
2485 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2486 game.sokoban_fields_still_needed;
2488 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2489 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2491 for (i = 0; i < NUM_BELTS; i++)
2493 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2494 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2495 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2497 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2501 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2502 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2503 game.magic_wall_time_left;
2505 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2506 local_player->gravity;
2508 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2509 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2511 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2512 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2513 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2514 game.panel.element[i].id : EL_UNDEFINED);
2516 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2517 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2518 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2519 element_info[game.panel.element_count[i].id].element_count : 0);
2521 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2522 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2523 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2524 element_info[game.panel.ce_score[i].id].collect_score : 0);
2526 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2527 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2528 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2529 element_info[game.panel.ce_score_element[i].id].collect_score :
2532 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2533 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2534 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2536 // update game panel control frames
2538 for (i = 0; game_panel_controls[i].nr != -1; i++)
2540 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2542 if (gpc->type == TYPE_ELEMENT)
2544 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2546 int last_anim_random_frame = gfx.anim_random_frame;
2547 int element = gpc->value;
2548 int graphic = el2panelimg(element);
2549 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2550 sync_random_frame : INIT_GFX_RANDOM());
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = init_gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2570 gpc->gfx_frame = element_info[element].collect_score;
2572 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = last_anim_random_frame;
2578 else if (gpc->type == TYPE_GRAPHIC)
2580 if (gpc->graphic != IMG_UNDEFINED)
2582 int last_anim_random_frame = gfx.anim_random_frame;
2583 int graphic = gpc->graphic;
2584 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2585 sync_random_frame : INIT_GFX_RANDOM());
2587 if (gpc->value != gpc->last_value)
2590 gpc->gfx_random = init_gfx_random;
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2597 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2598 gpc->gfx_random = init_gfx_random;
2601 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2602 gfx.anim_random_frame = gpc->gfx_random;
2604 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2606 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2607 gfx.anim_random_frame = last_anim_random_frame;
2613 static void DisplayGameControlValues(void)
2615 boolean redraw_panel = FALSE;
2618 for (i = 0; game_panel_controls[i].nr != -1; i++)
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2622 if (PANEL_DEACTIVATED(gpc->pos))
2625 if (gpc->value == gpc->last_value &&
2626 gpc->frame == gpc->last_frame)
2629 redraw_panel = TRUE;
2635 // copy default game door content to main double buffer
2637 // !!! CHECK AGAIN !!!
2638 SetPanelBackground();
2639 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2640 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2642 // redraw game control buttons
2643 RedrawGameButtons();
2645 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2647 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2649 int nr = game_panel_order[i].nr;
2650 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2651 struct TextPosInfo *pos = gpc->pos;
2652 int type = gpc->type;
2653 int value = gpc->value;
2654 int frame = gpc->frame;
2655 int size = pos->size;
2656 int font = pos->font;
2657 boolean draw_masked = pos->draw_masked;
2658 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2660 if (PANEL_DEACTIVATED(pos))
2663 if (pos->class == get_hash_from_key("extra_panel_items") &&
2664 !setup.prefer_extra_panel_items)
2667 gpc->last_value = value;
2668 gpc->last_frame = frame;
2670 if (type == TYPE_INTEGER)
2672 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2673 nr == GAME_PANEL_TIME)
2675 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2677 if (use_dynamic_size) // use dynamic number of digits
2679 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2680 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2681 int size2 = size1 + 1;
2682 int font1 = pos->font;
2683 int font2 = pos->font_alt;
2685 size = (value < value_change ? size1 : size2);
2686 font = (value < value_change ? font1 : font2);
2690 // correct text size if "digits" is zero or less
2692 size = strlen(int2str(value, size));
2694 // dynamically correct text alignment
2695 pos->width = size * getFontWidth(font);
2697 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2698 int2str(value, size), font, mask_mode);
2700 else if (type == TYPE_ELEMENT)
2702 int element, graphic;
2706 int dst_x = PANEL_XPOS(pos);
2707 int dst_y = PANEL_YPOS(pos);
2709 if (value != EL_UNDEFINED && value != EL_EMPTY)
2712 graphic = el2panelimg(value);
2715 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2716 element, EL_NAME(element), size);
2719 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2722 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2725 width = graphic_info[graphic].width * size / TILESIZE;
2726 height = graphic_info[graphic].height * size / TILESIZE;
2729 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2732 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2736 else if (type == TYPE_GRAPHIC)
2738 int graphic = gpc->graphic;
2739 int graphic_active = gpc->graphic_active;
2743 int dst_x = PANEL_XPOS(pos);
2744 int dst_y = PANEL_YPOS(pos);
2745 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2746 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2748 if (graphic != IMG_UNDEFINED && !skip)
2750 if (pos->style == STYLE_REVERSE)
2751 value = 100 - value;
2753 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2755 if (pos->direction & MV_HORIZONTAL)
2757 width = graphic_info[graphic_active].width * value / 100;
2758 height = graphic_info[graphic_active].height;
2760 if (pos->direction == MV_LEFT)
2762 src_x += graphic_info[graphic_active].width - width;
2763 dst_x += graphic_info[graphic_active].width - width;
2768 width = graphic_info[graphic_active].width;
2769 height = graphic_info[graphic_active].height * value / 100;
2771 if (pos->direction == MV_UP)
2773 src_y += graphic_info[graphic_active].height - height;
2774 dst_y += graphic_info[graphic_active].height - height;
2779 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2782 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2785 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2787 if (pos->direction & MV_HORIZONTAL)
2789 if (pos->direction == MV_RIGHT)
2796 dst_x = PANEL_XPOS(pos);
2799 width = graphic_info[graphic].width - width;
2803 if (pos->direction == MV_DOWN)
2810 dst_y = PANEL_YPOS(pos);
2813 height = graphic_info[graphic].height - height;
2817 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2820 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2824 else if (type == TYPE_STRING)
2826 boolean active = (value != 0);
2827 char *state_normal = "off";
2828 char *state_active = "on";
2829 char *state = (active ? state_active : state_normal);
2830 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2831 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2832 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2833 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2835 if (nr == GAME_PANEL_GRAVITY_STATE)
2837 int font1 = pos->font; // (used for normal state)
2838 int font2 = pos->font_alt; // (used for active state)
2840 font = (active ? font2 : font1);
2849 // don't truncate output if "chars" is zero or less
2852 // dynamically correct text alignment
2853 pos->width = size * getFontWidth(font);
2856 s_cut = getStringCopyN(s, size);
2858 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2859 s_cut, font, mask_mode);
2865 redraw_mask |= REDRAW_DOOR_1;
2868 SetGameStatus(GAME_MODE_PLAYING);
2871 void UpdateAndDisplayGameControlValues(void)
2873 if (tape.deactivate_display)
2876 UpdateGameControlValues();
2877 DisplayGameControlValues();
2880 void UpdateGameDoorValues(void)
2882 UpdateGameControlValues();
2885 void DrawGameDoorValues(void)
2887 DisplayGameControlValues();
2891 // ============================================================================
2893 // ----------------------------------------------------------------------------
2894 // initialize game engine due to level / tape version number
2895 // ============================================================================
2897 static void InitGameEngine(void)
2899 int i, j, k, l, x, y;
2901 // set game engine from tape file when re-playing, else from level file
2902 game.engine_version = (tape.playing ? tape.engine_version :
2903 level.game_version);
2905 // set single or multi-player game mode (needed for re-playing tapes)
2906 game.team_mode = setup.team_mode;
2910 int num_players = 0;
2912 for (i = 0; i < MAX_PLAYERS; i++)
2913 if (tape.player_participates[i])
2916 // multi-player tapes contain input data for more than one player
2917 game.team_mode = (num_players > 1);
2921 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2922 level.game_version);
2923 Debug("game:init:level", " tape.file_version == %06d",
2925 Debug("game:init:level", " tape.game_version == %06d",
2927 Debug("game:init:level", " tape.engine_version == %06d",
2928 tape.engine_version);
2929 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2930 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2933 // --------------------------------------------------------------------------
2934 // set flags for bugs and changes according to active game engine version
2935 // --------------------------------------------------------------------------
2939 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2941 Bug was introduced in version:
2944 Bug was fixed in version:
2948 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2949 but the property "can fall" was missing, which caused some levels to be
2950 unsolvable. This was fixed in version 4.2.0.0.
2952 Affected levels/tapes:
2953 An example for a tape that was fixed by this bugfix is tape 029 from the
2954 level set "rnd_sam_bateman".
2955 The wrong behaviour will still be used for all levels or tapes that were
2956 created/recorded with it. An example for this is tape 023 from the level
2957 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2960 boolean use_amoeba_dropping_cannot_fall_bug =
2961 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2962 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2964 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2965 tape.game_version < VERSION_IDENT(4,2,0,0)));
2968 Summary of bugfix/change:
2969 Fixed move speed of elements entering or leaving magic wall.
2971 Fixed/changed in version:
2975 Before 2.0.1, move speed of elements entering or leaving magic wall was
2976 twice as fast as it is now.
2977 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 2.0.1, which might use the old behaviour before it was changed; known tapes
2982 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2985 above, but before it was known that this change would break tapes like the
2986 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2987 although the engine version while recording maybe was before 2.0.1. There
2988 are a lot of tapes that are affected by this exception, like tape 006 from
2989 the level set "rnd_conor_mancone".
2992 boolean use_old_move_stepsize_for_magic_wall =
2993 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2995 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2996 tape.game_version < VERSION_IDENT(4,2,0,0)));
2999 Summary of bugfix/change:
3000 Fixed handling for custom elements that change when pushed by the player.
3002 Fixed/changed in version:
3006 Before 3.1.0, custom elements that "change when pushing" changed directly
3007 after the player started pushing them (until then handled in "DigField()").
3008 Since 3.1.0, these custom elements are not changed until the "pushing"
3009 move of the element is finished (now handled in "ContinueMoving()").
3011 Affected levels/tapes:
3012 The first condition is generally needed for all levels/tapes before version
3013 3.1.0, which might use the old behaviour before it was changed; known tapes
3014 that are affected are some tapes from the level set "Walpurgis Gardens" by
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3018 above (including some development versions of 3.1.0), but before it was
3019 known that this change would break tapes like the above and was fixed in
3020 3.1.1, so that the changed behaviour was active although the engine version
3021 while recording maybe was before 3.1.0. There is at least one tape that is
3022 affected by this exception, which is the tape for the one-level set "Bug
3023 Machine" by Juergen Bonhagen.
3026 game.use_change_when_pushing_bug =
3027 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3029 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3030 tape.game_version < VERSION_IDENT(3,1,1,0)));
3033 Summary of bugfix/change:
3034 Fixed handling for blocking the field the player leaves when moving.
3036 Fixed/changed in version:
3040 Before 3.1.1, when "block last field when moving" was enabled, the field
3041 the player is leaving when moving was blocked for the time of the move,
3042 and was directly unblocked afterwards. This resulted in the last field
3043 being blocked for exactly one less than the number of frames of one player
3044 move. Additionally, even when blocking was disabled, the last field was
3045 blocked for exactly one frame.
3046 Since 3.1.1, due to changes in player movement handling, the last field
3047 is not blocked at all when blocking is disabled. When blocking is enabled,
3048 the last field is blocked for exactly the number of frames of one player
3049 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3050 last field is blocked for exactly one more than the number of frames of
3053 Affected levels/tapes:
3054 (!!! yet to be determined -- probably many !!!)
3057 game.use_block_last_field_bug =
3058 (game.engine_version < VERSION_IDENT(3,1,1,0));
3060 /* various special flags and settings for native Emerald Mine game engine */
3062 game_em.use_single_button =
3063 (game.engine_version > VERSION_IDENT(4,0,0,2));
3065 game_em.use_snap_key_bug =
3066 (game.engine_version < VERSION_IDENT(4,0,1,0));
3068 game_em.use_random_bug =
3069 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3071 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3073 game_em.use_old_explosions = use_old_em_engine;
3074 game_em.use_old_android = use_old_em_engine;
3075 game_em.use_old_push_elements = use_old_em_engine;
3076 game_em.use_old_push_into_acid = use_old_em_engine;
3078 game_em.use_wrap_around = !use_old_em_engine;
3080 // --------------------------------------------------------------------------
3082 // set maximal allowed number of custom element changes per game frame
3083 game.max_num_changes_per_frame = 1;
3085 // default scan direction: scan playfield from top/left to bottom/right
3086 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3088 // dynamically adjust element properties according to game engine version
3089 InitElementPropertiesEngine(game.engine_version);
3091 // ---------- initialize special element properties -------------------------
3093 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3094 if (use_amoeba_dropping_cannot_fall_bug)
3095 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3097 // ---------- initialize player's initial move delay ------------------------
3099 // dynamically adjust player properties according to level information
3100 for (i = 0; i < MAX_PLAYERS; i++)
3101 game.initial_move_delay_value[i] =
3102 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3104 // dynamically adjust player properties according to game engine version
3105 for (i = 0; i < MAX_PLAYERS; i++)
3106 game.initial_move_delay[i] =
3107 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3108 game.initial_move_delay_value[i] : 0);
3110 // ---------- initialize player's initial push delay ------------------------
3112 // dynamically adjust player properties according to game engine version
3113 game.initial_push_delay_value =
3114 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3116 // ---------- initialize changing elements ----------------------------------
3118 // initialize changing elements information
3119 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3121 struct ElementInfo *ei = &element_info[i];
3123 // this pointer might have been changed in the level editor
3124 ei->change = &ei->change_page[0];
3126 if (!IS_CUSTOM_ELEMENT(i))
3128 ei->change->target_element = EL_EMPTY_SPACE;
3129 ei->change->delay_fixed = 0;
3130 ei->change->delay_random = 0;
3131 ei->change->delay_frames = 1;
3134 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3136 ei->has_change_event[j] = FALSE;
3138 ei->event_page_nr[j] = 0;
3139 ei->event_page[j] = &ei->change_page[0];
3143 // add changing elements from pre-defined list
3144 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3146 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3147 struct ElementInfo *ei = &element_info[ch_delay->element];
3149 ei->change->target_element = ch_delay->target_element;
3150 ei->change->delay_fixed = ch_delay->change_delay;
3152 ei->change->pre_change_function = ch_delay->pre_change_function;
3153 ei->change->change_function = ch_delay->change_function;
3154 ei->change->post_change_function = ch_delay->post_change_function;
3156 ei->change->can_change = TRUE;
3157 ei->change->can_change_or_has_action = TRUE;
3159 ei->has_change_event[CE_DELAY] = TRUE;
3161 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3162 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3165 // ---------- initialize internal run-time variables ------------------------
3167 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3169 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3171 for (j = 0; j < ei->num_change_pages; j++)
3173 ei->change_page[j].can_change_or_has_action =
3174 (ei->change_page[j].can_change |
3175 ei->change_page[j].has_action);
3179 // add change events from custom element configuration
3180 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3182 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3184 for (j = 0; j < ei->num_change_pages; j++)
3186 if (!ei->change_page[j].can_change_or_has_action)
3189 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3191 // only add event page for the first page found with this event
3192 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3194 ei->has_change_event[k] = TRUE;
3196 ei->event_page_nr[k] = j;
3197 ei->event_page[k] = &ei->change_page[j];
3203 // ---------- initialize reference elements in change conditions ------------
3205 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3207 int element = EL_CUSTOM_START + i;
3208 struct ElementInfo *ei = &element_info[element];
3210 for (j = 0; j < ei->num_change_pages; j++)
3212 int trigger_element = ei->change_page[j].initial_trigger_element;
3214 if (trigger_element >= EL_PREV_CE_8 &&
3215 trigger_element <= EL_NEXT_CE_8)
3216 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3218 ei->change_page[j].trigger_element = trigger_element;
3222 // ---------- initialize run-time trigger player and element ----------------
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3231 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3232 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3233 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3234 ei->change_page[j].actual_trigger_ce_value = 0;
3235 ei->change_page[j].actual_trigger_ce_score = 0;
3239 // ---------- initialize trigger events -------------------------------------
3241 // initialize trigger events information
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3244 trigger_events[i][j] = FALSE;
3246 // add trigger events from element change event properties
3247 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3249 struct ElementInfo *ei = &element_info[i];
3251 for (j = 0; j < ei->num_change_pages; j++)
3253 if (!ei->change_page[j].can_change_or_has_action)
3256 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3258 int trigger_element = ei->change_page[j].trigger_element;
3260 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3262 if (ei->change_page[j].has_event[k])
3264 if (IS_GROUP_ELEMENT(trigger_element))
3266 struct ElementGroupInfo *group =
3267 element_info[trigger_element].group;
3269 for (l = 0; l < group->num_elements_resolved; l++)
3270 trigger_events[group->element_resolved[l]][k] = TRUE;
3272 else if (trigger_element == EL_ANY_ELEMENT)
3273 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3274 trigger_events[l][k] = TRUE;
3276 trigger_events[trigger_element][k] = TRUE;
3283 // ---------- initialize push delay -----------------------------------------
3285 // initialize push delay values to default
3286 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3290 // set default push delay values (corrected since version 3.0.7-1)
3291 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3293 element_info[i].push_delay_fixed = 2;
3294 element_info[i].push_delay_random = 8;
3298 element_info[i].push_delay_fixed = 8;
3299 element_info[i].push_delay_random = 8;
3304 // set push delay value for certain elements from pre-defined list
3305 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3307 int e = push_delay_list[i].element;
3309 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3310 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3313 // set push delay value for Supaplex elements for newer engine versions
3314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3316 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3318 if (IS_SP_ELEMENT(i))
3320 // set SP push delay to just enough to push under a falling zonk
3321 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3323 element_info[i].push_delay_fixed = delay;
3324 element_info[i].push_delay_random = 0;
3329 // ---------- initialize move stepsize --------------------------------------
3331 // initialize move stepsize values to default
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3336 // set move stepsize value for certain elements from pre-defined list
3337 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3339 int e = move_stepsize_list[i].element;
3341 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3343 // set move stepsize value for certain elements for older engine versions
3344 if (use_old_move_stepsize_for_magic_wall)
3346 if (e == EL_MAGIC_WALL_FILLING ||
3347 e == EL_MAGIC_WALL_EMPTYING ||
3348 e == EL_BD_MAGIC_WALL_FILLING ||
3349 e == EL_BD_MAGIC_WALL_EMPTYING)
3350 element_info[e].move_stepsize *= 2;
3354 // ---------- initialize collect score --------------------------------------
3356 // initialize collect score values for custom elements from initial value
3357 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3358 if (IS_CUSTOM_ELEMENT(i))
3359 element_info[i].collect_score = element_info[i].collect_score_initial;
3361 // ---------- initialize collect count --------------------------------------
3363 // initialize collect count values for non-custom elements
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3365 if (!IS_CUSTOM_ELEMENT(i))
3366 element_info[i].collect_count_initial = 0;
3368 // add collect count values for all elements from pre-defined list
3369 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3370 element_info[collect_count_list[i].element].collect_count_initial =
3371 collect_count_list[i].count;
3373 // ---------- initialize access direction -----------------------------------
3375 // initialize access direction values to default (access from every side)
3376 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3377 if (!IS_CUSTOM_ELEMENT(i))
3378 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3380 // set access direction value for certain elements from pre-defined list
3381 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3382 element_info[access_direction_list[i].element].access_direction =
3383 access_direction_list[i].direction;
3385 // ---------- initialize explosion content ----------------------------------
3386 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (IS_CUSTOM_ELEMENT(i))
3391 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3393 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3395 element_info[i].content.e[x][y] =
3396 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3397 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3398 i == EL_PLAYER_3 ? EL_EMERALD :
3399 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3400 i == EL_MOLE ? EL_EMERALD_RED :
3401 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3402 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3403 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3404 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3405 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3406 i == EL_WALL_EMERALD ? EL_EMERALD :
3407 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3408 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3409 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3410 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3411 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3412 i == EL_WALL_PEARL ? EL_PEARL :
3413 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3418 // ---------- initialize recursion detection --------------------------------
3419 recursion_loop_depth = 0;
3420 recursion_loop_detected = FALSE;
3421 recursion_loop_element = EL_UNDEFINED;
3423 // ---------- initialize graphics engine ------------------------------------
3424 game.scroll_delay_value =
3425 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3426 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3427 !setup.forced_scroll_delay ? 0 :
3428 setup.scroll_delay ? setup.scroll_delay_value : 0);
3429 game.scroll_delay_value =
3430 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3432 // ---------- initialize game engine snapshots ------------------------------
3433 for (i = 0; i < MAX_PLAYERS; i++)
3434 game.snapshot.last_action[i] = 0;
3435 game.snapshot.changed_action = FALSE;
3436 game.snapshot.collected_item = FALSE;
3437 game.snapshot.mode =
3438 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3439 SNAPSHOT_MODE_EVERY_STEP :
3440 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3441 SNAPSHOT_MODE_EVERY_MOVE :
3442 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3443 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3444 game.snapshot.save_snapshot = FALSE;
3446 // ---------- initialize level time for Supaplex engine ---------------------
3447 // Supaplex levels with time limit currently unsupported -- should be added
3448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3451 // ---------- initialize flags for handling game actions --------------------
3453 // set flags for game actions to default values
3454 game.use_key_actions = TRUE;
3455 game.use_mouse_actions = FALSE;
3457 // when using Mirror Magic game engine, handle mouse events only
3458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3460 game.use_key_actions = FALSE;
3461 game.use_mouse_actions = TRUE;
3464 // check for custom elements with mouse click events
3465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3469 int element = EL_CUSTOM_START + i;
3471 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3472 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3473 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3474 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3475 game.use_mouse_actions = TRUE;
3480 static int get_num_special_action(int element, int action_first,
3483 int num_special_action = 0;
3486 for (i = action_first; i <= action_last; i++)
3488 boolean found = FALSE;
3490 for (j = 0; j < NUM_DIRECTIONS; j++)
3491 if (el_act_dir2img(element, i, j) !=
3492 el_act_dir2img(element, ACTION_DEFAULT, j))
3496 num_special_action++;
3501 return num_special_action;
3505 // ============================================================================
3507 // ----------------------------------------------------------------------------
3508 // initialize and start new game
3509 // ============================================================================
3511 #if DEBUG_INIT_PLAYER
3512 static void DebugPrintPlayerStatus(char *message)
3519 Debug("game:init:player", "%s:", message);
3521 for (i = 0; i < MAX_PLAYERS; i++)
3523 struct PlayerInfo *player = &stored_player[i];
3525 Debug("game:init:player",
3526 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3530 player->connected_locally,
3531 player->connected_network,
3533 (local_player == player ? " (local player)" : ""));
3540 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3541 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3542 int fade_mask = REDRAW_FIELD;
3544 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3545 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3546 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3547 int initial_move_dir = MV_DOWN;
3550 // required here to update video display before fading (FIX THIS)
3551 DrawMaskedBorder(REDRAW_DOOR_2);
3553 if (!game.restart_level)
3554 CloseDoor(DOOR_CLOSE_1);
3556 SetGameStatus(GAME_MODE_PLAYING);
3558 if (level_editor_test_game)
3559 FadeSkipNextFadeOut();
3561 FadeSetEnterScreen();
3564 fade_mask = REDRAW_ALL;
3566 FadeLevelSoundsAndMusic();
3568 ExpireSoundLoops(TRUE);
3572 if (level_editor_test_game)
3573 FadeSkipNextFadeIn();
3575 // needed if different viewport properties defined for playing
3576 ChangeViewportPropertiesIfNeeded();
3580 DrawCompleteVideoDisplay();
3582 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3585 InitGameControlValues();
3589 // initialize tape actions from game when recording tape
3590 tape.use_key_actions = game.use_key_actions;
3591 tape.use_mouse_actions = game.use_mouse_actions;
3593 // initialize visible playfield size when recording tape (for team mode)
3594 tape.scr_fieldx = SCR_FIELDX;
3595 tape.scr_fieldy = SCR_FIELDY;
3598 // don't play tapes over network
3599 network_playing = (network.enabled && !tape.playing);
3601 for (i = 0; i < MAX_PLAYERS; i++)
3603 struct PlayerInfo *player = &stored_player[i];
3605 player->index_nr = i;
3606 player->index_bit = (1 << i);
3607 player->element_nr = EL_PLAYER_1 + i;
3609 player->present = FALSE;
3610 player->active = FALSE;
3611 player->mapped = FALSE;
3613 player->killed = FALSE;
3614 player->reanimated = FALSE;
3615 player->buried = FALSE;
3618 player->effective_action = 0;
3619 player->programmed_action = 0;
3620 player->snap_action = 0;
3622 player->mouse_action.lx = 0;
3623 player->mouse_action.ly = 0;
3624 player->mouse_action.button = 0;
3625 player->mouse_action.button_hint = 0;
3627 player->effective_mouse_action.lx = 0;
3628 player->effective_mouse_action.ly = 0;
3629 player->effective_mouse_action.button = 0;
3630 player->effective_mouse_action.button_hint = 0;
3632 for (j = 0; j < MAX_NUM_KEYS; j++)
3633 player->key[j] = FALSE;
3635 player->num_white_keys = 0;
3637 player->dynabomb_count = 0;
3638 player->dynabomb_size = 1;
3639 player->dynabombs_left = 0;
3640 player->dynabomb_xl = FALSE;
3642 player->MovDir = initial_move_dir;
3645 player->GfxDir = initial_move_dir;
3646 player->GfxAction = ACTION_DEFAULT;
3648 player->StepFrame = 0;
3650 player->initial_element = player->element_nr;
3651 player->artwork_element =
3652 (level.use_artwork_element[i] ? level.artwork_element[i] :
3653 player->element_nr);
3654 player->use_murphy = FALSE;
3656 player->block_last_field = FALSE; // initialized in InitPlayerField()
3657 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3659 player->gravity = level.initial_player_gravity[i];
3661 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3663 player->actual_frame_counter = 0;
3665 player->step_counter = 0;
3667 player->last_move_dir = initial_move_dir;
3669 player->is_active = FALSE;
3671 player->is_waiting = FALSE;
3672 player->is_moving = FALSE;
3673 player->is_auto_moving = FALSE;
3674 player->is_digging = FALSE;
3675 player->is_snapping = FALSE;
3676 player->is_collecting = FALSE;
3677 player->is_pushing = FALSE;
3678 player->is_switching = FALSE;
3679 player->is_dropping = FALSE;
3680 player->is_dropping_pressed = FALSE;
3682 player->is_bored = FALSE;
3683 player->is_sleeping = FALSE;
3685 player->was_waiting = TRUE;
3686 player->was_moving = FALSE;
3687 player->was_snapping = FALSE;
3688 player->was_dropping = FALSE;
3690 player->force_dropping = FALSE;
3692 player->frame_counter_bored = -1;
3693 player->frame_counter_sleeping = -1;
3695 player->anim_delay_counter = 0;
3696 player->post_delay_counter = 0;
3698 player->dir_waiting = initial_move_dir;
3699 player->action_waiting = ACTION_DEFAULT;
3700 player->last_action_waiting = ACTION_DEFAULT;
3701 player->special_action_bored = ACTION_DEFAULT;
3702 player->special_action_sleeping = ACTION_DEFAULT;
3704 player->switch_x = -1;
3705 player->switch_y = -1;
3707 player->drop_x = -1;
3708 player->drop_y = -1;
3710 player->show_envelope = 0;
3712 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3714 player->push_delay = -1; // initialized when pushing starts
3715 player->push_delay_value = game.initial_push_delay_value;
3717 player->drop_delay = 0;
3718 player->drop_pressed_delay = 0;
3720 player->last_jx = -1;
3721 player->last_jy = -1;
3725 player->shield_normal_time_left = 0;
3726 player->shield_deadly_time_left = 0;
3728 player->last_removed_element = EL_UNDEFINED;
3730 player->inventory_infinite_element = EL_UNDEFINED;
3731 player->inventory_size = 0;
3733 if (level.use_initial_inventory[i])
3735 for (j = 0; j < level.initial_inventory_size[i]; j++)
3737 int element = level.initial_inventory_content[i][j];
3738 int collect_count = element_info[element].collect_count_initial;
3741 if (!IS_CUSTOM_ELEMENT(element))
3744 if (collect_count == 0)
3745 player->inventory_infinite_element = element;
3747 for (k = 0; k < collect_count; k++)
3748 if (player->inventory_size < MAX_INVENTORY_SIZE)
3749 player->inventory_element[player->inventory_size++] = element;
3753 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3754 SnapField(player, 0, 0);
3756 map_player_action[i] = i;
3759 network_player_action_received = FALSE;
3761 // initial null action
3762 if (network_playing)
3763 SendToServer_MovePlayer(MV_NONE);
3768 TimeLeft = level.time;
3771 ScreenMovDir = MV_NONE;
3775 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3777 game.robot_wheel_x = -1;
3778 game.robot_wheel_y = -1;
3783 game.all_players_gone = FALSE;
3785 game.LevelSolved = FALSE;
3786 game.GameOver = FALSE;
3788 game.GamePlayed = !tape.playing;
3790 game.LevelSolved_GameWon = FALSE;
3791 game.LevelSolved_GameEnd = FALSE;
3792 game.LevelSolved_SaveTape = FALSE;
3793 game.LevelSolved_SaveScore = FALSE;
3795 game.LevelSolved_CountingTime = 0;
3796 game.LevelSolved_CountingScore = 0;
3797 game.LevelSolved_CountingHealth = 0;
3799 game.panel.active = TRUE;
3801 game.no_time_limit = (level.time == 0);
3803 game.yamyam_content_nr = 0;
3804 game.robot_wheel_active = FALSE;
3805 game.magic_wall_active = FALSE;
3806 game.magic_wall_time_left = 0;
3807 game.light_time_left = 0;
3808 game.timegate_time_left = 0;
3809 game.switchgate_pos = 0;
3810 game.wind_direction = level.wind_direction_initial;
3812 game.time_final = 0;
3813 game.score_time_final = 0;
3816 game.score_final = 0;
3818 game.health = MAX_HEALTH;
3819 game.health_final = MAX_HEALTH;
3821 game.gems_still_needed = level.gems_needed;
3822 game.sokoban_fields_still_needed = 0;
3823 game.sokoban_objects_still_needed = 0;
3824 game.lights_still_needed = 0;
3825 game.players_still_needed = 0;
3826 game.friends_still_needed = 0;
3828 game.lenses_time_left = 0;
3829 game.magnify_time_left = 0;
3831 game.ball_active = level.ball_active_initial;
3832 game.ball_content_nr = 0;
3834 game.explosions_delayed = TRUE;
3836 game.envelope_active = FALSE;
3838 for (i = 0; i < NUM_BELTS; i++)
3840 game.belt_dir[i] = MV_NONE;
3841 game.belt_dir_nr[i] = 3; // not moving, next moving left
3844 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3845 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3847 #if DEBUG_INIT_PLAYER
3848 DebugPrintPlayerStatus("Player status at level initialization");
3851 SCAN_PLAYFIELD(x, y)
3853 Tile[x][y] = Last[x][y] = level.field[x][y];
3854 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3855 ChangeDelay[x][y] = 0;
3856 ChangePage[x][y] = -1;
3857 CustomValue[x][y] = 0; // initialized in InitField()
3858 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3860 WasJustMoving[x][y] = 0;
3861 WasJustFalling[x][y] = 0;
3862 CheckCollision[x][y] = 0;
3863 CheckImpact[x][y] = 0;
3865 Pushed[x][y] = FALSE;
3867 ChangeCount[x][y] = 0;
3868 ChangeEvent[x][y] = -1;
3870 ExplodePhase[x][y] = 0;
3871 ExplodeDelay[x][y] = 0;
3872 ExplodeField[x][y] = EX_TYPE_NONE;
3874 RunnerVisit[x][y] = 0;
3875 PlayerVisit[x][y] = 0;
3878 GfxRandom[x][y] = INIT_GFX_RANDOM();
3879 GfxElement[x][y] = EL_UNDEFINED;
3880 GfxAction[x][y] = ACTION_DEFAULT;
3881 GfxDir[x][y] = MV_NONE;
3882 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3885 SCAN_PLAYFIELD(x, y)
3887 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3889 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3891 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3894 InitField(x, y, TRUE);
3896 ResetGfxAnimation(x, y);
3901 for (i = 0; i < MAX_PLAYERS; i++)
3903 struct PlayerInfo *player = &stored_player[i];
3905 // set number of special actions for bored and sleeping animation
3906 player->num_special_action_bored =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_BORING_1, ACTION_BORING_LAST);
3909 player->num_special_action_sleeping =
3910 get_num_special_action(player->artwork_element,
3911 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3914 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3915 emulate_sb ? EMU_SOKOBAN :
3916 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3918 // initialize type of slippery elements
3919 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3921 if (!IS_CUSTOM_ELEMENT(i))
3923 // default: elements slip down either to the left or right randomly
3924 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3926 // SP style elements prefer to slip down on the left side
3927 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3928 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3930 // BD style elements prefer to slip down on the left side
3931 if (game.emulation == EMU_BOULDERDASH)
3932 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3936 // initialize explosion and ignition delay
3937 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3939 if (!IS_CUSTOM_ELEMENT(i))
3942 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3943 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3944 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3945 int last_phase = (num_phase + 1) * delay;
3946 int half_phase = (num_phase / 2) * delay;
3948 element_info[i].explosion_delay = last_phase - 1;
3949 element_info[i].ignition_delay = half_phase;
3951 if (i == EL_BLACK_ORB)
3952 element_info[i].ignition_delay = 1;
3956 // correct non-moving belts to start moving left
3957 for (i = 0; i < NUM_BELTS; i++)
3958 if (game.belt_dir[i] == MV_NONE)
3959 game.belt_dir_nr[i] = 3; // not moving, next moving left
3961 #if USE_NEW_PLAYER_ASSIGNMENTS
3962 // use preferred player also in local single-player mode
3963 if (!network.enabled && !game.team_mode)
3965 int new_index_nr = setup.network_player_nr;
3967 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3969 for (i = 0; i < MAX_PLAYERS; i++)
3970 stored_player[i].connected_locally = FALSE;
3972 stored_player[new_index_nr].connected_locally = TRUE;
3976 for (i = 0; i < MAX_PLAYERS; i++)
3978 stored_player[i].connected = FALSE;
3980 // in network game mode, the local player might not be the first player
3981 if (stored_player[i].connected_locally)
3982 local_player = &stored_player[i];
3985 if (!network.enabled)
3986 local_player->connected = TRUE;
3990 for (i = 0; i < MAX_PLAYERS; i++)
3991 stored_player[i].connected = tape.player_participates[i];
3993 else if (network.enabled)
3995 // add team mode players connected over the network (needed for correct
3996 // assignment of player figures from level to locally playing players)
3998 for (i = 0; i < MAX_PLAYERS; i++)
3999 if (stored_player[i].connected_network)
4000 stored_player[i].connected = TRUE;
4002 else if (game.team_mode)
4004 // try to guess locally connected team mode players (needed for correct
4005 // assignment of player figures from level to locally playing players)
4007 for (i = 0; i < MAX_PLAYERS; i++)
4008 if (setup.input[i].use_joystick ||
4009 setup.input[i].key.left != KSYM_UNDEFINED)
4010 stored_player[i].connected = TRUE;
4013 #if DEBUG_INIT_PLAYER
4014 DebugPrintPlayerStatus("Player status after level initialization");
4017 #if DEBUG_INIT_PLAYER
4018 Debug("game:init:player", "Reassigning players ...");
4021 // check if any connected player was not found in playfield
4022 for (i = 0; i < MAX_PLAYERS; i++)
4024 struct PlayerInfo *player = &stored_player[i];
4026 if (player->connected && !player->present)
4028 struct PlayerInfo *field_player = NULL;
4030 #if DEBUG_INIT_PLAYER
4031 Debug("game:init:player",
4032 "- looking for field player for player %d ...", i + 1);
4035 // assign first free player found that is present in the playfield
4037 // first try: look for unmapped playfield player that is not connected
4038 for (j = 0; j < MAX_PLAYERS; j++)
4039 if (field_player == NULL &&
4040 stored_player[j].present &&
4041 !stored_player[j].mapped &&
4042 !stored_player[j].connected)
4043 field_player = &stored_player[j];
4045 // second try: look for *any* unmapped playfield player
4046 for (j = 0; j < MAX_PLAYERS; j++)
4047 if (field_player == NULL &&
4048 stored_player[j].present &&
4049 !stored_player[j].mapped)
4050 field_player = &stored_player[j];
4052 if (field_player != NULL)
4054 int jx = field_player->jx, jy = field_player->jy;
4056 #if DEBUG_INIT_PLAYER
4057 Debug("game:init:player", "- found player %d",
4058 field_player->index_nr + 1);
4061 player->present = FALSE;
4062 player->active = FALSE;
4064 field_player->present = TRUE;
4065 field_player->active = TRUE;
4068 player->initial_element = field_player->initial_element;
4069 player->artwork_element = field_player->artwork_element;
4071 player->block_last_field = field_player->block_last_field;
4072 player->block_delay_adjustment = field_player->block_delay_adjustment;
4075 StorePlayer[jx][jy] = field_player->element_nr;
4077 field_player->jx = field_player->last_jx = jx;
4078 field_player->jy = field_player->last_jy = jy;
4080 if (local_player == player)
4081 local_player = field_player;
4083 map_player_action[field_player->index_nr] = i;
4085 field_player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 Debug("game:init:player", "- map_player_action[%d] == %d",
4089 field_player->index_nr + 1, i + 1);
4094 if (player->connected && player->present)
4095 player->mapped = TRUE;
4098 #if DEBUG_INIT_PLAYER
4099 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4104 // check if any connected player was not found in playfield
4105 for (i = 0; i < MAX_PLAYERS; i++)
4107 struct PlayerInfo *player = &stored_player[i];
4109 if (player->connected && !player->present)
4111 for (j = 0; j < MAX_PLAYERS; j++)
4113 struct PlayerInfo *field_player = &stored_player[j];
4114 int jx = field_player->jx, jy = field_player->jy;
4116 // assign first free player found that is present in the playfield
4117 if (field_player->present && !field_player->connected)
4119 player->present = TRUE;
4120 player->active = TRUE;
4122 field_player->present = FALSE;
4123 field_player->active = FALSE;
4125 player->initial_element = field_player->initial_element;
4126 player->artwork_element = field_player->artwork_element;
4128 player->block_last_field = field_player->block_last_field;
4129 player->block_delay_adjustment = field_player->block_delay_adjustment;
4131 StorePlayer[jx][jy] = player->element_nr;
4133 player->jx = player->last_jx = jx;
4134 player->jy = player->last_jy = jy;
4144 Debug("game:init:player", "local_player->present == %d",
4145 local_player->present);
4148 // set focus to local player for network games, else to all players
4149 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4150 game.centered_player_nr_next = game.centered_player_nr;
4151 game.set_centered_player = FALSE;
4152 game.set_centered_player_wrap = FALSE;
4154 if (network_playing && tape.recording)
4156 // store client dependent player focus when recording network games
4157 tape.centered_player_nr_next = game.centered_player_nr_next;
4158 tape.set_centered_player = TRUE;
4163 // when playing a tape, eliminate all players who do not participate
4165 #if USE_NEW_PLAYER_ASSIGNMENTS
4167 if (!game.team_mode)
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 if (stored_player[i].active &&
4172 !tape.player_participates[map_player_action[i]])
4174 struct PlayerInfo *player = &stored_player[i];
4175 int jx = player->jx, jy = player->jy;
4177 #if DEBUG_INIT_PLAYER
4178 Debug("game:init:player", "Removing player %d at (%d, %d)",
4182 player->active = FALSE;
4183 StorePlayer[jx][jy] = 0;
4184 Tile[jx][jy] = EL_EMPTY;
4191 for (i = 0; i < MAX_PLAYERS; i++)
4193 if (stored_player[i].active &&
4194 !tape.player_participates[i])
4196 struct PlayerInfo *player = &stored_player[i];
4197 int jx = player->jx, jy = player->jy;
4199 player->active = FALSE;
4200 StorePlayer[jx][jy] = 0;
4201 Tile[jx][jy] = EL_EMPTY;
4206 else if (!network.enabled && !game.team_mode) // && !tape.playing
4208 // when in single player mode, eliminate all but the local player
4210 for (i = 0; i < MAX_PLAYERS; i++)
4212 struct PlayerInfo *player = &stored_player[i];
4214 if (player->active && player != local_player)
4216 int jx = player->jx, jy = player->jy;
4218 player->active = FALSE;
4219 player->present = FALSE;
4221 StorePlayer[jx][jy] = 0;
4222 Tile[jx][jy] = EL_EMPTY;
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].active)
4229 game.players_still_needed++;
4231 if (level.solved_by_one_player)
4232 game.players_still_needed = 1;
4234 // when recording the game, store which players take part in the game
4237 #if USE_NEW_PLAYER_ASSIGNMENTS
4238 for (i = 0; i < MAX_PLAYERS; i++)
4239 if (stored_player[i].connected)
4240 tape.player_participates[i] = TRUE;
4242 for (i = 0; i < MAX_PLAYERS; i++)
4243 if (stored_player[i].active)
4244 tape.player_participates[i] = TRUE;
4248 #if DEBUG_INIT_PLAYER
4249 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4252 if (BorderElement == EL_EMPTY)
4255 SBX_Right = lev_fieldx - SCR_FIELDX;
4257 SBY_Lower = lev_fieldy - SCR_FIELDY;
4262 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4264 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4267 if (full_lev_fieldx <= SCR_FIELDX)
4268 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4269 if (full_lev_fieldy <= SCR_FIELDY)
4270 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4272 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4274 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4277 // if local player not found, look for custom element that might create
4278 // the player (make some assumptions about the right custom element)
4279 if (!local_player->present)
4281 int start_x = 0, start_y = 0;
4282 int found_rating = 0;
4283 int found_element = EL_UNDEFINED;
4284 int player_nr = local_player->index_nr;
4286 SCAN_PLAYFIELD(x, y)
4288 int element = Tile[x][y];
4293 if (level.use_start_element[player_nr] &&
4294 level.start_element[player_nr] == element &&
4301 found_element = element;
4304 if (!IS_CUSTOM_ELEMENT(element))
4307 if (CAN_CHANGE(element))
4309 for (i = 0; i < element_info[element].num_change_pages; i++)
4311 // check for player created from custom element as single target
4312 content = element_info[element].change_page[i].target_element;
4313 is_player = ELEM_IS_PLAYER(content);
4315 if (is_player && (found_rating < 3 ||
4316 (found_rating == 3 && element < found_element)))
4322 found_element = element;
4327 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4329 // check for player created from custom element as explosion content
4330 content = element_info[element].content.e[xx][yy];
4331 is_player = ELEM_IS_PLAYER(content);
4333 if (is_player && (found_rating < 2 ||
4334 (found_rating == 2 && element < found_element)))
4336 start_x = x + xx - 1;
4337 start_y = y + yy - 1;
4340 found_element = element;
4343 if (!CAN_CHANGE(element))
4346 for (i = 0; i < element_info[element].num_change_pages; i++)
4348 // check for player created from custom element as extended target
4350 element_info[element].change_page[i].target_content.e[xx][yy];
4352 is_player = ELEM_IS_PLAYER(content);
4354 if (is_player && (found_rating < 1 ||
4355 (found_rating == 1 && element < found_element)))
4357 start_x = x + xx - 1;
4358 start_y = y + yy - 1;
4361 found_element = element;
4367 scroll_x = SCROLL_POSITION_X(start_x);
4368 scroll_y = SCROLL_POSITION_Y(start_y);
4372 scroll_x = SCROLL_POSITION_X(local_player->jx);
4373 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4376 // !!! FIX THIS (START) !!!
4377 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4379 InitGameEngine_EM();
4381 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4383 InitGameEngine_SP();
4385 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4387 InitGameEngine_MM();
4391 DrawLevel(REDRAW_FIELD);
4394 // after drawing the level, correct some elements
4395 if (game.timegate_time_left == 0)
4396 CloseAllOpenTimegates();
4399 // blit playfield from scroll buffer to normal back buffer for fading in
4400 BlitScreenToBitmap(backbuffer);
4401 // !!! FIX THIS (END) !!!
4403 DrawMaskedBorder(fade_mask);
4408 // full screen redraw is required at this point in the following cases:
4409 // - special editor door undrawn when game was started from level editor
4410 // - drawing area (playfield) was changed and has to be removed completely
4411 redraw_mask = REDRAW_ALL;
4415 if (!game.restart_level)
4417 // copy default game door content to main double buffer
4419 // !!! CHECK AGAIN !!!
4420 SetPanelBackground();
4421 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4422 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4425 SetPanelBackground();
4426 SetDrawBackgroundMask(REDRAW_DOOR_1);
4428 UpdateAndDisplayGameControlValues();
4430 if (!game.restart_level)
4436 CreateGameButtons();
4441 // copy actual game door content to door double buffer for OpenDoor()
4442 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4444 OpenDoor(DOOR_OPEN_ALL);
4446 KeyboardAutoRepeatOffUnlessAutoplay();
4448 #if DEBUG_INIT_PLAYER
4449 DebugPrintPlayerStatus("Player status (final)");
4458 if (!game.restart_level && !tape.playing)
4460 LevelStats_incPlayed(level_nr);
4462 SaveLevelSetup_SeriesInfo();
4465 game.restart_level = FALSE;
4466 game.restart_game_message = NULL;
4468 game.request_active = FALSE;
4469 game.request_active_or_moving = FALSE;
4471 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4472 InitGameActions_MM();
4474 SaveEngineSnapshotToListInitial();
4476 if (!game.restart_level)
4478 PlaySound(SND_GAME_STARTING);
4480 if (setup.sound_music)
4485 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4486 int actual_player_x, int actual_player_y)
4488 // this is used for non-R'n'D game engines to update certain engine values
4490 // needed to determine if sounds are played within the visible screen area
4491 scroll_x = actual_scroll_x;
4492 scroll_y = actual_scroll_y;
4494 // needed to get player position for "follow finger" playing input method
4495 local_player->jx = actual_player_x;
4496 local_player->jy = actual_player_y;
4499 void InitMovDir(int x, int y)
4501 int i, element = Tile[x][y];
4502 static int xy[4][2] =
4509 static int direction[3][4] =
4511 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4512 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4513 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4522 Tile[x][y] = EL_BUG;
4523 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4526 case EL_SPACESHIP_RIGHT:
4527 case EL_SPACESHIP_UP:
4528 case EL_SPACESHIP_LEFT:
4529 case EL_SPACESHIP_DOWN:
4530 Tile[x][y] = EL_SPACESHIP;
4531 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4534 case EL_BD_BUTTERFLY_RIGHT:
4535 case EL_BD_BUTTERFLY_UP:
4536 case EL_BD_BUTTERFLY_LEFT:
4537 case EL_BD_BUTTERFLY_DOWN:
4538 Tile[x][y] = EL_BD_BUTTERFLY;
4539 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4542 case EL_BD_FIREFLY_RIGHT:
4543 case EL_BD_FIREFLY_UP:
4544 case EL_BD_FIREFLY_LEFT:
4545 case EL_BD_FIREFLY_DOWN:
4546 Tile[x][y] = EL_BD_FIREFLY;
4547 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4550 case EL_PACMAN_RIGHT:
4552 case EL_PACMAN_LEFT:
4553 case EL_PACMAN_DOWN:
4554 Tile[x][y] = EL_PACMAN;
4555 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4558 case EL_YAMYAM_LEFT:
4559 case EL_YAMYAM_RIGHT:
4561 case EL_YAMYAM_DOWN:
4562 Tile[x][y] = EL_YAMYAM;
4563 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4566 case EL_SP_SNIKSNAK:
4567 MovDir[x][y] = MV_UP;
4570 case EL_SP_ELECTRON:
4571 MovDir[x][y] = MV_LEFT;
4578 Tile[x][y] = EL_MOLE;
4579 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4582 case EL_SPRING_LEFT:
4583 case EL_SPRING_RIGHT:
4584 Tile[x][y] = EL_SPRING;
4585 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4589 if (IS_CUSTOM_ELEMENT(element))
4591 struct ElementInfo *ei = &element_info[element];
4592 int move_direction_initial = ei->move_direction_initial;
4593 int move_pattern = ei->move_pattern;
4595 if (move_direction_initial == MV_START_PREVIOUS)
4597 if (MovDir[x][y] != MV_NONE)
4600 move_direction_initial = MV_START_AUTOMATIC;
4603 if (move_direction_initial == MV_START_RANDOM)
4604 MovDir[x][y] = 1 << RND(4);
4605 else if (move_direction_initial & MV_ANY_DIRECTION)
4606 MovDir[x][y] = move_direction_initial;
4607 else if (move_pattern == MV_ALL_DIRECTIONS ||
4608 move_pattern == MV_TURNING_LEFT ||
4609 move_pattern == MV_TURNING_RIGHT ||
4610 move_pattern == MV_TURNING_LEFT_RIGHT ||
4611 move_pattern == MV_TURNING_RIGHT_LEFT ||
4612 move_pattern == MV_TURNING_RANDOM)
4613 MovDir[x][y] = 1 << RND(4);
4614 else if (move_pattern == MV_HORIZONTAL)
4615 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4616 else if (move_pattern == MV_VERTICAL)
4617 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4618 else if (move_pattern & MV_ANY_DIRECTION)
4619 MovDir[x][y] = element_info[element].move_pattern;
4620 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4621 move_pattern == MV_ALONG_RIGHT_SIDE)
4623 // use random direction as default start direction
4624 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4625 MovDir[x][y] = 1 << RND(4);
4627 for (i = 0; i < NUM_DIRECTIONS; i++)
4629 int x1 = x + xy[i][0];
4630 int y1 = y + xy[i][1];
4632 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4634 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4635 MovDir[x][y] = direction[0][i];
4637 MovDir[x][y] = direction[1][i];
4646 MovDir[x][y] = 1 << RND(4);
4648 if (element != EL_BUG &&
4649 element != EL_SPACESHIP &&
4650 element != EL_BD_BUTTERFLY &&
4651 element != EL_BD_FIREFLY)
4654 for (i = 0; i < NUM_DIRECTIONS; i++)
4656 int x1 = x + xy[i][0];
4657 int y1 = y + xy[i][1];
4659 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4661 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4663 MovDir[x][y] = direction[0][i];
4666 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4667 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4669 MovDir[x][y] = direction[1][i];
4678 GfxDir[x][y] = MovDir[x][y];
4681 void InitAmoebaNr(int x, int y)
4684 int group_nr = AmoebaNeighbourNr(x, y);
4688 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4690 if (AmoebaCnt[i] == 0)
4698 AmoebaNr[x][y] = group_nr;
4699 AmoebaCnt[group_nr]++;
4700 AmoebaCnt2[group_nr]++;
4703 static void LevelSolved(void)
4705 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4706 game.players_still_needed > 0)
4709 game.LevelSolved = TRUE;
4710 game.GameOver = TRUE;
4715 static int time_count_steps;
4716 static int time, time_final;
4717 static float score, score_final; // needed for time score < 10 for 10 seconds
4718 static int health, health_final;
4719 static int game_over_delay_1 = 0;
4720 static int game_over_delay_2 = 0;
4721 static int game_over_delay_3 = 0;
4722 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4723 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4725 if (!game.LevelSolved_GameWon)
4729 // do not start end game actions before the player stops moving (to exit)
4730 if (local_player->active && local_player->MovPos)
4733 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4734 game.score_time_final = (level.use_step_counter ? TimePlayed :
4735 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4737 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4738 game_em.lev->score :
4739 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4743 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4744 MM_HEALTH(game_mm.laser_overload_value) :
4747 game.LevelSolved_CountingTime = game.time_final;
4748 game.LevelSolved_CountingScore = game.score_final;
4749 game.LevelSolved_CountingHealth = game.health_final;
4751 game.LevelSolved_GameWon = TRUE;
4752 game.LevelSolved_SaveTape = tape.recording;
4753 game.LevelSolved_SaveScore = !tape.playing;
4757 LevelStats_incSolved(level_nr);
4759 SaveLevelSetup_SeriesInfo();
4762 if (tape.auto_play) // tape might already be stopped here
4763 tape.auto_play_level_solved = TRUE;
4767 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4768 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4769 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4771 time = time_final = game.time_final;
4772 score = score_final = game.score_final;
4773 health = health_final = game.health_final;
4777 int time_final_max = 999;
4778 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4779 int time_frames = 0;
4780 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4781 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4786 time_frames = time_frames_left;
4788 else if (game.no_time_limit && TimePlayed < time_final_max)
4790 time_final = time_final_max;
4791 time_frames = time_frames_final_max - time_frames_played;
4794 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4796 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4798 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4801 score_final += health * time_score;
4804 game.score_final = score_final;
4805 game.health_final = health_final;
4808 if (level_editor_test_game || !setup.count_score_after_game)
4811 score = score_final;
4813 game.LevelSolved_CountingTime = time;
4814 game.LevelSolved_CountingScore = score;
4816 game_panel_controls[GAME_PANEL_TIME].value = time;
4817 game_panel_controls[GAME_PANEL_SCORE].value = score;
4819 DisplayGameControlValues();
4822 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4824 // check if last player has left the level
4825 if (game.exit_x >= 0 &&
4828 int x = game.exit_x;
4829 int y = game.exit_y;
4830 int element = Tile[x][y];
4832 // close exit door after last player
4833 if ((game.all_players_gone &&
4834 (element == EL_EXIT_OPEN ||
4835 element == EL_SP_EXIT_OPEN ||
4836 element == EL_STEEL_EXIT_OPEN)) ||
4837 element == EL_EM_EXIT_OPEN ||
4838 element == EL_EM_STEEL_EXIT_OPEN)
4842 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4843 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4844 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4845 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4846 EL_EM_STEEL_EXIT_CLOSING);
4848 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4851 // player disappears
4852 DrawLevelField(x, y);
4855 for (i = 0; i < MAX_PLAYERS; i++)
4857 struct PlayerInfo *player = &stored_player[i];
4859 if (player->present)
4861 RemovePlayer(player);
4863 // player disappears
4864 DrawLevelField(player->jx, player->jy);
4869 PlaySound(SND_GAME_WINNING);
4872 if (setup.count_score_after_game)
4874 if (time != time_final)
4876 if (game_over_delay_1 > 0)
4878 game_over_delay_1--;
4883 int time_to_go = ABS(time_final - time);
4884 int time_count_dir = (time < time_final ? +1 : -1);
4886 if (time_to_go < time_count_steps)
4887 time_count_steps = 1;
4889 time += time_count_steps * time_count_dir;
4890 score += time_count_steps * time_score;
4892 // set final score to correct rounding differences after counting score
4893 if (time == time_final)
4894 score = score_final;
4896 game.LevelSolved_CountingTime = time;
4897 game.LevelSolved_CountingScore = score;
4899 game_panel_controls[GAME_PANEL_TIME].value = time;
4900 game_panel_controls[GAME_PANEL_SCORE].value = score;
4902 DisplayGameControlValues();
4904 if (time == time_final)
4905 StopSound(SND_GAME_LEVELTIME_BONUS);
4906 else if (setup.sound_loops)
4907 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4909 PlaySound(SND_GAME_LEVELTIME_BONUS);
4914 if (health != health_final)
4916 if (game_over_delay_2 > 0)
4918 game_over_delay_2--;
4923 int health_count_dir = (health < health_final ? +1 : -1);
4925 health += health_count_dir;
4926 score += time_score;
4928 game.LevelSolved_CountingHealth = health;
4929 game.LevelSolved_CountingScore = score;
4931 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4932 game_panel_controls[GAME_PANEL_SCORE].value = score;
4934 DisplayGameControlValues();
4936 if (health == health_final)
4937 StopSound(SND_GAME_LEVELTIME_BONUS);
4938 else if (setup.sound_loops)
4939 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4941 PlaySound(SND_GAME_LEVELTIME_BONUS);
4947 game.panel.active = FALSE;
4949 if (game_over_delay_3 > 0)
4951 game_over_delay_3--;
4961 // used instead of "level_nr" (needed for network games)
4962 int last_level_nr = levelset.level_nr;
4964 game.LevelSolved_GameEnd = TRUE;
4966 if (game.LevelSolved_SaveTape)
4968 // make sure that request dialog to save tape does not open door again
4969 if (!global.use_envelope_request)
4970 CloseDoor(DOOR_CLOSE_1);
4972 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4974 // set unique basename for score tape (also saved in high score table)
4975 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4978 // if no tape is to be saved, close both doors simultaneously
4979 CloseDoor(DOOR_CLOSE_ALL);
4981 if (level_editor_test_game)
4983 SetGameStatus(GAME_MODE_MAIN);
4990 if (!game.LevelSolved_SaveScore)
4992 SetGameStatus(GAME_MODE_MAIN);
4999 if (level_nr == leveldir_current->handicap_level)
5001 leveldir_current->handicap_level++;
5003 SaveLevelSetup_SeriesInfo();
5006 // save score and score tape before potentially erasing tape below
5007 NewHighScore(last_level_nr);
5009 if (setup.increment_levels &&
5010 level_nr < leveldir_current->last_level &&
5013 level_nr++; // advance to next level
5014 TapeErase(); // start with empty tape
5016 if (setup.auto_play_next_level)
5018 LoadLevel(level_nr);
5020 SaveLevelSetup_SeriesInfo();
5024 if (scores.last_added >= 0 && setup.show_scores_after_game)
5026 SetGameStatus(GAME_MODE_SCORES);
5028 DrawHallOfFame(last_level_nr);
5030 else if (setup.auto_play_next_level && setup.increment_levels &&
5031 last_level_nr < leveldir_current->last_level &&
5034 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5038 SetGameStatus(GAME_MODE_MAIN);
5044 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5045 boolean one_score_entry_per_name)
5049 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5052 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5054 struct ScoreEntry *entry = &list->entry[i];
5055 boolean score_is_better = (new_entry->score > entry->score);
5056 boolean score_is_equal = (new_entry->score == entry->score);
5057 boolean time_is_better = (new_entry->time < entry->time);
5058 boolean time_is_equal = (new_entry->time == entry->time);
5059 boolean better_by_score = (score_is_better ||
5060 (score_is_equal && time_is_better));
5061 boolean better_by_time = (time_is_better ||
5062 (time_is_equal && score_is_better));
5063 boolean is_better = (level.rate_time_over_score ? better_by_time :
5065 boolean entry_is_empty = (entry->score == 0 &&
5068 // prevent adding server score entries if also existing in local score file
5069 // (special case: historic score entries have an empty tape basename entry)
5070 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5071 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5074 if (is_better || entry_is_empty)
5076 // player has made it to the hall of fame
5078 if (i < MAX_SCORE_ENTRIES - 1)
5080 int m = MAX_SCORE_ENTRIES - 1;
5083 if (one_score_entry_per_name)
5085 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5086 if (strEqual(list->entry[l].name, new_entry->name))
5089 if (m == i) // player's new highscore overwrites his old one
5093 for (l = m; l > i; l--)
5094 list->entry[l] = list->entry[l - 1];
5099 *entry = *new_entry;
5103 else if (one_score_entry_per_name &&
5104 strEqual(entry->name, new_entry->name))
5106 // player already in high score list with better score or time
5115 void NewHighScore(int level_nr)
5117 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5118 boolean one_per_name = !program.many_scores_per_name;
5120 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5121 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5123 new_entry.score = game.score_final;
5124 new_entry.time = game.score_time_final;
5126 LoadScore(level_nr);
5128 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5130 if (scores.last_added >= 0)
5132 SaveScore(level_nr);
5134 if (game.LevelSolved_SaveTape)
5136 SaveScoreTape(level_nr);
5137 SaveServerScore(level_nr);
5140 // store last added local score entry (before merging server scores)
5141 scores.last_added_local = scores.last_added;
5145 void MergeServerScore(void)
5147 boolean one_per_name = !program.many_scores_per_name;
5150 for (i = 0; i < server_scores.num_entries; i++)
5152 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5154 if (pos >= 0 && pos <= scores.last_added)
5155 scores.last_added++;
5158 if (scores.last_added >= MAX_SCORE_ENTRIES)
5159 scores.last_added = -1;
5162 static int getElementMoveStepsizeExt(int x, int y, int direction)
5164 int element = Tile[x][y];
5165 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5166 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5167 int horiz_move = (dx != 0);
5168 int sign = (horiz_move ? dx : dy);
5169 int step = sign * element_info[element].move_stepsize;
5171 // special values for move stepsize for spring and things on conveyor belt
5174 if (CAN_FALL(element) &&
5175 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5176 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5177 else if (element == EL_SPRING)
5178 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5184 static int getElementMoveStepsize(int x, int y)
5186 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5189 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5191 if (player->GfxAction != action || player->GfxDir != dir)
5193 player->GfxAction = action;
5194 player->GfxDir = dir;
5196 player->StepFrame = 0;
5200 static void ResetGfxFrame(int x, int y)
5202 // profiling showed that "autotest" spends 10~20% of its time in this function
5203 if (DrawingDeactivatedField())
5206 int element = Tile[x][y];
5207 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5209 if (graphic_info[graphic].anim_global_sync)
5210 GfxFrame[x][y] = FrameCounter;
5211 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5212 GfxFrame[x][y] = CustomValue[x][y];
5213 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5214 GfxFrame[x][y] = element_info[element].collect_score;
5215 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5216 GfxFrame[x][y] = ChangeDelay[x][y];
5219 static void ResetGfxAnimation(int x, int y)
5221 GfxAction[x][y] = ACTION_DEFAULT;
5222 GfxDir[x][y] = MovDir[x][y];
5225 ResetGfxFrame(x, y);
5228 static void ResetRandomAnimationValue(int x, int y)
5230 GfxRandom[x][y] = INIT_GFX_RANDOM();
5233 static void InitMovingField(int x, int y, int direction)
5235 int element = Tile[x][y];
5236 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5237 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5240 boolean is_moving_before, is_moving_after;
5242 // check if element was/is moving or being moved before/after mode change
5243 is_moving_before = (WasJustMoving[x][y] != 0);
5244 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5246 // reset animation only for moving elements which change direction of moving
5247 // or which just started or stopped moving
5248 // (else CEs with property "can move" / "not moving" are reset each frame)
5249 if (is_moving_before != is_moving_after ||
5250 direction != MovDir[x][y])
5251 ResetGfxAnimation(x, y);
5253 MovDir[x][y] = direction;
5254 GfxDir[x][y] = direction;
5256 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5257 direction == MV_DOWN && CAN_FALL(element) ?
5258 ACTION_FALLING : ACTION_MOVING);
5260 // this is needed for CEs with property "can move" / "not moving"
5262 if (is_moving_after)
5264 if (Tile[newx][newy] == EL_EMPTY)
5265 Tile[newx][newy] = EL_BLOCKED;
5267 MovDir[newx][newy] = MovDir[x][y];
5269 CustomValue[newx][newy] = CustomValue[x][y];
5271 GfxFrame[newx][newy] = GfxFrame[x][y];
5272 GfxRandom[newx][newy] = GfxRandom[x][y];
5273 GfxAction[newx][newy] = GfxAction[x][y];
5274 GfxDir[newx][newy] = GfxDir[x][y];
5278 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5280 int direction = MovDir[x][y];
5281 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5282 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5288 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5290 int oldx = x, oldy = y;
5291 int direction = MovDir[x][y];
5293 if (direction == MV_LEFT)
5295 else if (direction == MV_RIGHT)
5297 else if (direction == MV_UP)
5299 else if (direction == MV_DOWN)
5302 *comes_from_x = oldx;
5303 *comes_from_y = oldy;
5306 static int MovingOrBlocked2Element(int x, int y)
5308 int element = Tile[x][y];
5310 if (element == EL_BLOCKED)
5314 Blocked2Moving(x, y, &oldx, &oldy);
5315 return Tile[oldx][oldy];
5321 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5323 // like MovingOrBlocked2Element(), but if element is moving
5324 // and (x,y) is the field the moving element is just leaving,
5325 // return EL_BLOCKED instead of the element value
5326 int element = Tile[x][y];
5328 if (IS_MOVING(x, y))
5330 if (element == EL_BLOCKED)
5334 Blocked2Moving(x, y, &oldx, &oldy);
5335 return Tile[oldx][oldy];
5344 static void RemoveField(int x, int y)
5346 Tile[x][y] = EL_EMPTY;
5352 CustomValue[x][y] = 0;
5355 ChangeDelay[x][y] = 0;
5356 ChangePage[x][y] = -1;
5357 Pushed[x][y] = FALSE;
5359 GfxElement[x][y] = EL_UNDEFINED;
5360 GfxAction[x][y] = ACTION_DEFAULT;
5361 GfxDir[x][y] = MV_NONE;
5364 static void RemoveMovingField(int x, int y)
5366 int oldx = x, oldy = y, newx = x, newy = y;
5367 int element = Tile[x][y];
5368 int next_element = EL_UNDEFINED;
5370 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5373 if (IS_MOVING(x, y))
5375 Moving2Blocked(x, y, &newx, &newy);
5377 if (Tile[newx][newy] != EL_BLOCKED)
5379 // element is moving, but target field is not free (blocked), but
5380 // already occupied by something different (example: acid pool);
5381 // in this case, only remove the moving field, but not the target
5383 RemoveField(oldx, oldy);
5385 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5387 TEST_DrawLevelField(oldx, oldy);
5392 else if (element == EL_BLOCKED)
5394 Blocked2Moving(x, y, &oldx, &oldy);
5395 if (!IS_MOVING(oldx, oldy))
5399 if (element == EL_BLOCKED &&
5400 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5401 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5402 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5403 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5404 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5405 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5406 next_element = get_next_element(Tile[oldx][oldy]);
5408 RemoveField(oldx, oldy);
5409 RemoveField(newx, newy);
5411 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5413 if (next_element != EL_UNDEFINED)
5414 Tile[oldx][oldy] = next_element;
5416 TEST_DrawLevelField(oldx, oldy);
5417 TEST_DrawLevelField(newx, newy);
5420 void DrawDynamite(int x, int y)
5422 int sx = SCREENX(x), sy = SCREENY(y);
5423 int graphic = el2img(Tile[x][y]);
5426 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5429 if (IS_WALKABLE_INSIDE(Back[x][y]))
5433 DrawLevelElement(x, y, Back[x][y]);
5434 else if (Store[x][y])
5435 DrawLevelElement(x, y, Store[x][y]);
5436 else if (game.use_masked_elements)
5437 DrawLevelElement(x, y, EL_EMPTY);
5439 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5441 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5442 DrawGraphicThruMask(sx, sy, graphic, frame);
5444 DrawGraphic(sx, sy, graphic, frame);
5447 static void CheckDynamite(int x, int y)
5449 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5453 if (MovDelay[x][y] != 0)
5456 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5462 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5467 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5469 boolean num_checked_players = 0;
5472 for (i = 0; i < MAX_PLAYERS; i++)
5474 if (stored_player[i].active)
5476 int sx = stored_player[i].jx;
5477 int sy = stored_player[i].jy;
5479 if (num_checked_players == 0)
5486 *sx1 = MIN(*sx1, sx);
5487 *sy1 = MIN(*sy1, sy);
5488 *sx2 = MAX(*sx2, sx);
5489 *sy2 = MAX(*sy2, sy);
5492 num_checked_players++;
5497 static boolean checkIfAllPlayersFitToScreen_RND(void)
5499 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5501 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5503 return (sx2 - sx1 < SCR_FIELDX &&
5504 sy2 - sy1 < SCR_FIELDY);
5507 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5509 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5511 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5513 *sx = (sx1 + sx2) / 2;
5514 *sy = (sy1 + sy2) / 2;
5517 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5518 boolean center_screen, boolean quick_relocation)
5520 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5521 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5522 boolean no_delay = (tape.warp_forward);
5523 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5524 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5525 int new_scroll_x, new_scroll_y;
5527 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5529 // case 1: quick relocation inside visible screen (without scrolling)
5536 if (!level.shifted_relocation || center_screen)
5538 // relocation _with_ centering of screen
5540 new_scroll_x = SCROLL_POSITION_X(x);
5541 new_scroll_y = SCROLL_POSITION_Y(y);
5545 // relocation _without_ centering of screen
5547 int center_scroll_x = SCROLL_POSITION_X(old_x);
5548 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5549 int offset_x = x + (scroll_x - center_scroll_x);
5550 int offset_y = y + (scroll_y - center_scroll_y);
5552 // for new screen position, apply previous offset to center position
5553 new_scroll_x = SCROLL_POSITION_X(offset_x);
5554 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5557 if (quick_relocation)
5559 // case 2: quick relocation (redraw without visible scrolling)
5561 scroll_x = new_scroll_x;
5562 scroll_y = new_scroll_y;
5569 // case 3: visible relocation (with scrolling to new position)
5571 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5573 SetVideoFrameDelay(wait_delay_value);
5575 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5577 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5578 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5580 if (dx == 0 && dy == 0) // no scrolling needed at all
5586 // set values for horizontal/vertical screen scrolling (half tile size)
5587 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5588 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5589 int pos_x = dx * TILEX / 2;
5590 int pos_y = dy * TILEY / 2;
5591 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5592 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5594 ScrollLevel(dx, dy);
5597 // scroll in two steps of half tile size to make things smoother
5598 BlitScreenToBitmapExt_RND(window, fx, fy);
5600 // scroll second step to align at full tile size
5601 BlitScreenToBitmap(window);
5607 SetVideoFrameDelay(frame_delay_value_old);
5610 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5612 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5613 int player_nr = GET_PLAYER_NR(el_player);
5614 struct PlayerInfo *player = &stored_player[player_nr];
5615 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5616 boolean no_delay = (tape.warp_forward);
5617 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5618 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5619 int old_jx = player->jx;
5620 int old_jy = player->jy;
5621 int old_element = Tile[old_jx][old_jy];
5622 int element = Tile[jx][jy];
5623 boolean player_relocated = (old_jx != jx || old_jy != jy);
5625 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5626 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5627 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5628 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5629 int leave_side_horiz = move_dir_horiz;
5630 int leave_side_vert = move_dir_vert;
5631 int enter_side = enter_side_horiz | enter_side_vert;
5632 int leave_side = leave_side_horiz | leave_side_vert;
5634 if (player->buried) // do not reanimate dead player
5637 if (!player_relocated) // no need to relocate the player
5640 if (IS_PLAYER(jx, jy)) // player already placed at new position
5642 RemoveField(jx, jy); // temporarily remove newly placed player
5643 DrawLevelField(jx, jy);
5646 if (player->present)
5648 while (player->MovPos)
5650 ScrollPlayer(player, SCROLL_GO_ON);
5651 ScrollScreen(NULL, SCROLL_GO_ON);
5653 AdvanceFrameAndPlayerCounters(player->index_nr);
5657 BackToFront_WithFrameDelay(wait_delay_value);
5660 DrawPlayer(player); // needed here only to cleanup last field
5661 DrawLevelField(player->jx, player->jy); // remove player graphic
5663 player->is_moving = FALSE;
5666 if (IS_CUSTOM_ELEMENT(old_element))
5667 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5669 player->index_bit, leave_side);
5671 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5673 player->index_bit, leave_side);
5675 Tile[jx][jy] = el_player;
5676 InitPlayerField(jx, jy, el_player, TRUE);
5678 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5679 possible that the relocation target field did not contain a player element,
5680 but a walkable element, to which the new player was relocated -- in this
5681 case, restore that (already initialized!) element on the player field */
5682 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5684 Tile[jx][jy] = element; // restore previously existing element
5687 // only visually relocate centered player
5688 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5689 FALSE, level.instant_relocation);
5691 TestIfPlayerTouchesBadThing(jx, jy);
5692 TestIfPlayerTouchesCustomElement(jx, jy);
5694 if (IS_CUSTOM_ELEMENT(element))
5695 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5696 player->index_bit, enter_side);
5698 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5699 player->index_bit, enter_side);
5701 if (player->is_switching)
5703 /* ensure that relocation while still switching an element does not cause
5704 a new element to be treated as also switched directly after relocation
5705 (this is important for teleporter switches that teleport the player to
5706 a place where another teleporter switch is in the same direction, which
5707 would then incorrectly be treated as immediately switched before the
5708 direction key that caused the switch was released) */
5710 player->switch_x += jx - old_jx;
5711 player->switch_y += jy - old_jy;
5715 static void Explode(int ex, int ey, int phase, int mode)
5721 // !!! eliminate this variable !!!
5722 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5724 if (game.explosions_delayed)
5726 ExplodeField[ex][ey] = mode;
5730 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5732 int center_element = Tile[ex][ey];
5733 int artwork_element, explosion_element; // set these values later
5735 // remove things displayed in background while burning dynamite
5736 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5739 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5741 // put moving element to center field (and let it explode there)
5742 center_element = MovingOrBlocked2Element(ex, ey);
5743 RemoveMovingField(ex, ey);
5744 Tile[ex][ey] = center_element;
5747 // now "center_element" is finally determined -- set related values now
5748 artwork_element = center_element; // for custom player artwork
5749 explosion_element = center_element; // for custom player artwork
5751 if (IS_PLAYER(ex, ey))
5753 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5755 artwork_element = stored_player[player_nr].artwork_element;
5757 if (level.use_explosion_element[player_nr])
5759 explosion_element = level.explosion_element[player_nr];
5760 artwork_element = explosion_element;
5764 if (mode == EX_TYPE_NORMAL ||
5765 mode == EX_TYPE_CENTER ||
5766 mode == EX_TYPE_CROSS)
5767 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5769 last_phase = element_info[explosion_element].explosion_delay + 1;
5771 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5773 int xx = x - ex + 1;
5774 int yy = y - ey + 1;
5777 if (!IN_LEV_FIELD(x, y) ||
5778 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5779 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5782 element = Tile[x][y];
5784 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5786 element = MovingOrBlocked2Element(x, y);
5788 if (!IS_EXPLOSION_PROOF(element))
5789 RemoveMovingField(x, y);
5792 // indestructible elements can only explode in center (but not flames)
5793 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5794 mode == EX_TYPE_BORDER)) ||
5795 element == EL_FLAMES)
5798 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5799 behaviour, for example when touching a yamyam that explodes to rocks
5800 with active deadly shield, a rock is created under the player !!! */
5801 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5803 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5804 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5805 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5807 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5810 if (IS_ACTIVE_BOMB(element))
5812 // re-activate things under the bomb like gate or penguin
5813 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5820 // save walkable background elements while explosion on same tile
5821 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5822 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5823 Back[x][y] = element;
5825 // ignite explodable elements reached by other explosion
5826 if (element == EL_EXPLOSION)
5827 element = Store2[x][y];
5829 if (AmoebaNr[x][y] &&
5830 (element == EL_AMOEBA_FULL ||
5831 element == EL_BD_AMOEBA ||
5832 element == EL_AMOEBA_GROWING))
5834 AmoebaCnt[AmoebaNr[x][y]]--;
5835 AmoebaCnt2[AmoebaNr[x][y]]--;
5840 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5842 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5844 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5846 if (PLAYERINFO(ex, ey)->use_murphy)
5847 Store[x][y] = EL_EMPTY;
5850 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5851 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5852 else if (ELEM_IS_PLAYER(center_element))
5853 Store[x][y] = EL_EMPTY;
5854 else if (center_element == EL_YAMYAM)
5855 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5856 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5857 Store[x][y] = element_info[center_element].content.e[xx][yy];
5859 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5860 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5861 // otherwise) -- FIX THIS !!!
5862 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5863 Store[x][y] = element_info[element].content.e[1][1];
5865 else if (!CAN_EXPLODE(element))
5866 Store[x][y] = element_info[element].content.e[1][1];
5869 Store[x][y] = EL_EMPTY;
5871 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5872 center_element == EL_AMOEBA_TO_DIAMOND)
5873 Store2[x][y] = element;
5875 Tile[x][y] = EL_EXPLOSION;
5876 GfxElement[x][y] = artwork_element;
5878 ExplodePhase[x][y] = 1;
5879 ExplodeDelay[x][y] = last_phase;
5884 if (center_element == EL_YAMYAM)
5885 game.yamyam_content_nr =
5886 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5898 GfxFrame[x][y] = 0; // restart explosion animation
5900 last_phase = ExplodeDelay[x][y];
5902 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5904 // this can happen if the player leaves an explosion just in time
5905 if (GfxElement[x][y] == EL_UNDEFINED)
5906 GfxElement[x][y] = EL_EMPTY;
5908 border_element = Store2[x][y];
5909 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5910 border_element = StorePlayer[x][y];
5912 if (phase == element_info[border_element].ignition_delay ||
5913 phase == last_phase)
5915 boolean border_explosion = FALSE;
5917 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5918 !PLAYER_EXPLOSION_PROTECTED(x, y))
5920 KillPlayerUnlessExplosionProtected(x, y);
5921 border_explosion = TRUE;
5923 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5925 Tile[x][y] = Store2[x][y];
5928 border_explosion = TRUE;
5930 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5932 AmoebaToDiamond(x, y);
5934 border_explosion = TRUE;
5937 // if an element just explodes due to another explosion (chain-reaction),
5938 // do not immediately end the new explosion when it was the last frame of
5939 // the explosion (as it would be done in the following "if"-statement!)
5940 if (border_explosion && phase == last_phase)
5944 if (phase == last_phase)
5948 element = Tile[x][y] = Store[x][y];
5949 Store[x][y] = Store2[x][y] = 0;
5950 GfxElement[x][y] = EL_UNDEFINED;
5952 // player can escape from explosions and might therefore be still alive
5953 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5954 element <= EL_PLAYER_IS_EXPLODING_4)
5956 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5957 int explosion_element = EL_PLAYER_1 + player_nr;
5958 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5959 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5961 if (level.use_explosion_element[player_nr])
5962 explosion_element = level.explosion_element[player_nr];
5964 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5965 element_info[explosion_element].content.e[xx][yy]);
5968 // restore probably existing indestructible background element
5969 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5970 element = Tile[x][y] = Back[x][y];
5973 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5974 GfxDir[x][y] = MV_NONE;
5975 ChangeDelay[x][y] = 0;
5976 ChangePage[x][y] = -1;
5978 CustomValue[x][y] = 0;
5980 InitField_WithBug2(x, y, FALSE);
5982 TEST_DrawLevelField(x, y);
5984 TestIfElementTouchesCustomElement(x, y);
5986 if (GFX_CRUMBLED(element))
5987 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5989 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5990 StorePlayer[x][y] = 0;
5992 if (ELEM_IS_PLAYER(element))
5993 RelocatePlayer(x, y, element);
5995 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5997 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5998 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
6001 TEST_DrawLevelFieldCrumbled(x, y);
6003 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6005 DrawLevelElement(x, y, Back[x][y]);
6006 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6008 else if (IS_WALKABLE_UNDER(Back[x][y]))
6010 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6011 DrawLevelElementThruMask(x, y, Back[x][y]);
6013 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6014 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6018 static void DynaExplode(int ex, int ey)
6021 int dynabomb_element = Tile[ex][ey];
6022 int dynabomb_size = 1;
6023 boolean dynabomb_xl = FALSE;
6024 struct PlayerInfo *player;
6025 static int xy[4][2] =
6033 if (IS_ACTIVE_BOMB(dynabomb_element))
6035 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6036 dynabomb_size = player->dynabomb_size;
6037 dynabomb_xl = player->dynabomb_xl;
6038 player->dynabombs_left++;
6041 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6043 for (i = 0; i < NUM_DIRECTIONS; i++)
6045 for (j = 1; j <= dynabomb_size; j++)
6047 int x = ex + j * xy[i][0];
6048 int y = ey + j * xy[i][1];
6051 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6054 element = Tile[x][y];
6056 // do not restart explosions of fields with active bombs
6057 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6060 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6062 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6063 !IS_DIGGABLE(element) && !dynabomb_xl)
6069 void Bang(int x, int y)
6071 int element = MovingOrBlocked2Element(x, y);
6072 int explosion_type = EX_TYPE_NORMAL;
6074 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6076 struct PlayerInfo *player = PLAYERINFO(x, y);
6078 element = Tile[x][y] = player->initial_element;
6080 if (level.use_explosion_element[player->index_nr])
6082 int explosion_element = level.explosion_element[player->index_nr];
6084 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6085 explosion_type = EX_TYPE_CROSS;
6086 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6087 explosion_type = EX_TYPE_CENTER;
6095 case EL_BD_BUTTERFLY:
6098 case EL_DARK_YAMYAM:
6102 RaiseScoreElement(element);
6105 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6106 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6107 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6108 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6109 case EL_DYNABOMB_INCREASE_NUMBER:
6110 case EL_DYNABOMB_INCREASE_SIZE:
6111 case EL_DYNABOMB_INCREASE_POWER:
6112 explosion_type = EX_TYPE_DYNA;
6115 case EL_DC_LANDMINE:
6116 explosion_type = EX_TYPE_CENTER;
6121 case EL_LAMP_ACTIVE:
6122 case EL_AMOEBA_TO_DIAMOND:
6123 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6124 explosion_type = EX_TYPE_CENTER;
6128 if (element_info[element].explosion_type == EXPLODES_CROSS)
6129 explosion_type = EX_TYPE_CROSS;
6130 else if (element_info[element].explosion_type == EXPLODES_1X1)
6131 explosion_type = EX_TYPE_CENTER;
6135 if (explosion_type == EX_TYPE_DYNA)
6138 Explode(x, y, EX_PHASE_START, explosion_type);
6140 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6143 static void SplashAcid(int x, int y)
6145 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6146 (!IN_LEV_FIELD(x - 1, y - 2) ||
6147 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6148 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6150 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6151 (!IN_LEV_FIELD(x + 1, y - 2) ||
6152 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6153 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6155 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6158 static void InitBeltMovement(void)
6160 static int belt_base_element[4] =
6162 EL_CONVEYOR_BELT_1_LEFT,
6163 EL_CONVEYOR_BELT_2_LEFT,
6164 EL_CONVEYOR_BELT_3_LEFT,
6165 EL_CONVEYOR_BELT_4_LEFT
6167 static int belt_base_active_element[4] =
6169 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6170 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6171 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6172 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6177 // set frame order for belt animation graphic according to belt direction
6178 for (i = 0; i < NUM_BELTS; i++)
6182 for (j = 0; j < NUM_BELT_PARTS; j++)
6184 int element = belt_base_active_element[belt_nr] + j;
6185 int graphic_1 = el2img(element);
6186 int graphic_2 = el2panelimg(element);
6188 if (game.belt_dir[i] == MV_LEFT)
6190 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6191 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6195 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6196 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6201 SCAN_PLAYFIELD(x, y)
6203 int element = Tile[x][y];
6205 for (i = 0; i < NUM_BELTS; i++)
6207 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6209 int e_belt_nr = getBeltNrFromBeltElement(element);
6212 if (e_belt_nr == belt_nr)
6214 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6216 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6223 static void ToggleBeltSwitch(int x, int y)
6225 static int belt_base_element[4] =
6227 EL_CONVEYOR_BELT_1_LEFT,
6228 EL_CONVEYOR_BELT_2_LEFT,
6229 EL_CONVEYOR_BELT_3_LEFT,
6230 EL_CONVEYOR_BELT_4_LEFT
6232 static int belt_base_active_element[4] =
6234 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6235 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6236 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6237 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6239 static int belt_base_switch_element[4] =
6241 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6242 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6243 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6244 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6246 static int belt_move_dir[4] =
6254 int element = Tile[x][y];
6255 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6256 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6257 int belt_dir = belt_move_dir[belt_dir_nr];
6260 if (!IS_BELT_SWITCH(element))
6263 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6264 game.belt_dir[belt_nr] = belt_dir;
6266 if (belt_dir_nr == 3)
6269 // set frame order for belt animation graphic according to belt direction
6270 for (i = 0; i < NUM_BELT_PARTS; i++)
6272 int element = belt_base_active_element[belt_nr] + i;
6273 int graphic_1 = el2img(element);
6274 int graphic_2 = el2panelimg(element);
6276 if (belt_dir == MV_LEFT)
6278 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6279 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6283 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6284 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6288 SCAN_PLAYFIELD(xx, yy)
6290 int element = Tile[xx][yy];
6292 if (IS_BELT_SWITCH(element))
6294 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6296 if (e_belt_nr == belt_nr)
6298 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6299 TEST_DrawLevelField(xx, yy);
6302 else if (IS_BELT(element) && belt_dir != MV_NONE)
6304 int e_belt_nr = getBeltNrFromBeltElement(element);
6306 if (e_belt_nr == belt_nr)
6308 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6310 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6311 TEST_DrawLevelField(xx, yy);
6314 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6316 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6318 if (e_belt_nr == belt_nr)
6320 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6322 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6323 TEST_DrawLevelField(xx, yy);
6329 static void ToggleSwitchgateSwitch(int x, int y)
6333 game.switchgate_pos = !game.switchgate_pos;
6335 SCAN_PLAYFIELD(xx, yy)
6337 int element = Tile[xx][yy];
6339 if (element == EL_SWITCHGATE_SWITCH_UP)
6341 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6342 TEST_DrawLevelField(xx, yy);
6344 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6346 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6347 TEST_DrawLevelField(xx, yy);
6349 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6351 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6352 TEST_DrawLevelField(xx, yy);
6354 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6356 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6357 TEST_DrawLevelField(xx, yy);
6359 else if (element == EL_SWITCHGATE_OPEN ||
6360 element == EL_SWITCHGATE_OPENING)
6362 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6364 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6366 else if (element == EL_SWITCHGATE_CLOSED ||
6367 element == EL_SWITCHGATE_CLOSING)
6369 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6371 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6376 static int getInvisibleActiveFromInvisibleElement(int element)
6378 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6379 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6380 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6384 static int getInvisibleFromInvisibleActiveElement(int element)
6386 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6387 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6388 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6392 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6396 SCAN_PLAYFIELD(x, y)
6398 int element = Tile[x][y];
6400 if (element == EL_LIGHT_SWITCH &&
6401 game.light_time_left > 0)
6403 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6404 TEST_DrawLevelField(x, y);
6406 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6407 game.light_time_left == 0)
6409 Tile[x][y] = EL_LIGHT_SWITCH;
6410 TEST_DrawLevelField(x, y);
6412 else if (element == EL_EMC_DRIPPER &&
6413 game.light_time_left > 0)
6415 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6416 TEST_DrawLevelField(x, y);
6418 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6419 game.light_time_left == 0)
6421 Tile[x][y] = EL_EMC_DRIPPER;
6422 TEST_DrawLevelField(x, y);
6424 else if (element == EL_INVISIBLE_STEELWALL ||
6425 element == EL_INVISIBLE_WALL ||
6426 element == EL_INVISIBLE_SAND)
6428 if (game.light_time_left > 0)
6429 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6431 TEST_DrawLevelField(x, y);
6433 // uncrumble neighbour fields, if needed
6434 if (element == EL_INVISIBLE_SAND)
6435 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6437 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6438 element == EL_INVISIBLE_WALL_ACTIVE ||
6439 element == EL_INVISIBLE_SAND_ACTIVE)
6441 if (game.light_time_left == 0)
6442 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6444 TEST_DrawLevelField(x, y);
6446 // re-crumble neighbour fields, if needed
6447 if (element == EL_INVISIBLE_SAND)
6448 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6453 static void RedrawAllInvisibleElementsForLenses(void)
6457 SCAN_PLAYFIELD(x, y)
6459 int element = Tile[x][y];
6461 if (element == EL_EMC_DRIPPER &&
6462 game.lenses_time_left > 0)
6464 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6465 TEST_DrawLevelField(x, y);
6467 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6468 game.lenses_time_left == 0)
6470 Tile[x][y] = EL_EMC_DRIPPER;
6471 TEST_DrawLevelField(x, y);
6473 else if (element == EL_INVISIBLE_STEELWALL ||
6474 element == EL_INVISIBLE_WALL ||
6475 element == EL_INVISIBLE_SAND)
6477 if (game.lenses_time_left > 0)
6478 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6480 TEST_DrawLevelField(x, y);
6482 // uncrumble neighbour fields, if needed
6483 if (element == EL_INVISIBLE_SAND)
6484 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6486 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6487 element == EL_INVISIBLE_WALL_ACTIVE ||
6488 element == EL_INVISIBLE_SAND_ACTIVE)
6490 if (game.lenses_time_left == 0)
6491 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6493 TEST_DrawLevelField(x, y);
6495 // re-crumble neighbour fields, if needed
6496 if (element == EL_INVISIBLE_SAND)
6497 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6502 static void RedrawAllInvisibleElementsForMagnifier(void)
6506 SCAN_PLAYFIELD(x, y)
6508 int element = Tile[x][y];
6510 if (element == EL_EMC_FAKE_GRASS &&
6511 game.magnify_time_left > 0)
6513 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6514 TEST_DrawLevelField(x, y);
6516 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6517 game.magnify_time_left == 0)
6519 Tile[x][y] = EL_EMC_FAKE_GRASS;
6520 TEST_DrawLevelField(x, y);
6522 else if (IS_GATE_GRAY(element) &&
6523 game.magnify_time_left > 0)
6525 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6526 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6527 IS_EM_GATE_GRAY(element) ?
6528 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6529 IS_EMC_GATE_GRAY(element) ?
6530 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6531 IS_DC_GATE_GRAY(element) ?
6532 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6534 TEST_DrawLevelField(x, y);
6536 else if (IS_GATE_GRAY_ACTIVE(element) &&
6537 game.magnify_time_left == 0)
6539 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6540 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6541 IS_EM_GATE_GRAY_ACTIVE(element) ?
6542 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6543 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6544 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6545 IS_DC_GATE_GRAY_ACTIVE(element) ?
6546 EL_DC_GATE_WHITE_GRAY :
6548 TEST_DrawLevelField(x, y);
6553 static void ToggleLightSwitch(int x, int y)
6555 int element = Tile[x][y];
6557 game.light_time_left =
6558 (element == EL_LIGHT_SWITCH ?
6559 level.time_light * FRAMES_PER_SECOND : 0);
6561 RedrawAllLightSwitchesAndInvisibleElements();
6564 static void ActivateTimegateSwitch(int x, int y)
6568 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6570 SCAN_PLAYFIELD(xx, yy)
6572 int element = Tile[xx][yy];
6574 if (element == EL_TIMEGATE_CLOSED ||
6575 element == EL_TIMEGATE_CLOSING)
6577 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6578 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6582 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6584 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6585 TEST_DrawLevelField(xx, yy);
6591 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6592 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6595 static void Impact(int x, int y)
6597 boolean last_line = (y == lev_fieldy - 1);
6598 boolean object_hit = FALSE;
6599 boolean impact = (last_line || object_hit);
6600 int element = Tile[x][y];
6601 int smashed = EL_STEELWALL;
6603 if (!last_line) // check if element below was hit
6605 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6608 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6609 MovDir[x][y + 1] != MV_DOWN ||
6610 MovPos[x][y + 1] <= TILEY / 2));
6612 // do not smash moving elements that left the smashed field in time
6613 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6614 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6617 #if USE_QUICKSAND_IMPACT_BUGFIX
6618 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6620 RemoveMovingField(x, y + 1);
6621 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6622 Tile[x][y + 2] = EL_ROCK;
6623 TEST_DrawLevelField(x, y + 2);
6628 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6630 RemoveMovingField(x, y + 1);
6631 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6632 Tile[x][y + 2] = EL_ROCK;
6633 TEST_DrawLevelField(x, y + 2);
6640 smashed = MovingOrBlocked2Element(x, y + 1);
6642 impact = (last_line || object_hit);
6645 if (!last_line && smashed == EL_ACID) // element falls into acid
6647 SplashAcid(x, y + 1);
6651 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6652 // only reset graphic animation if graphic really changes after impact
6654 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6656 ResetGfxAnimation(x, y);
6657 TEST_DrawLevelField(x, y);
6660 if (impact && CAN_EXPLODE_IMPACT(element))
6665 else if (impact && element == EL_PEARL &&
6666 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6668 ResetGfxAnimation(x, y);
6670 Tile[x][y] = EL_PEARL_BREAKING;
6671 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6674 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6676 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6681 if (impact && element == EL_AMOEBA_DROP)
6683 if (object_hit && IS_PLAYER(x, y + 1))
6684 KillPlayerUnlessEnemyProtected(x, y + 1);
6685 else if (object_hit && smashed == EL_PENGUIN)
6689 Tile[x][y] = EL_AMOEBA_GROWING;
6690 Store[x][y] = EL_AMOEBA_WET;
6692 ResetRandomAnimationValue(x, y);
6697 if (object_hit) // check which object was hit
6699 if ((CAN_PASS_MAGIC_WALL(element) &&
6700 (smashed == EL_MAGIC_WALL ||
6701 smashed == EL_BD_MAGIC_WALL)) ||
6702 (CAN_PASS_DC_MAGIC_WALL(element) &&
6703 smashed == EL_DC_MAGIC_WALL))
6706 int activated_magic_wall =
6707 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6708 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6709 EL_DC_MAGIC_WALL_ACTIVE);
6711 // activate magic wall / mill
6712 SCAN_PLAYFIELD(xx, yy)
6714 if (Tile[xx][yy] == smashed)
6715 Tile[xx][yy] = activated_magic_wall;
6718 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6719 game.magic_wall_active = TRUE;
6721 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6722 SND_MAGIC_WALL_ACTIVATING :
6723 smashed == EL_BD_MAGIC_WALL ?
6724 SND_BD_MAGIC_WALL_ACTIVATING :
6725 SND_DC_MAGIC_WALL_ACTIVATING));
6728 if (IS_PLAYER(x, y + 1))
6730 if (CAN_SMASH_PLAYER(element))
6732 KillPlayerUnlessEnemyProtected(x, y + 1);
6736 else if (smashed == EL_PENGUIN)
6738 if (CAN_SMASH_PLAYER(element))
6744 else if (element == EL_BD_DIAMOND)
6746 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6752 else if (((element == EL_SP_INFOTRON ||
6753 element == EL_SP_ZONK) &&
6754 (smashed == EL_SP_SNIKSNAK ||
6755 smashed == EL_SP_ELECTRON ||
6756 smashed == EL_SP_DISK_ORANGE)) ||
6757 (element == EL_SP_INFOTRON &&
6758 smashed == EL_SP_DISK_YELLOW))
6763 else if (CAN_SMASH_EVERYTHING(element))
6765 if (IS_CLASSIC_ENEMY(smashed) ||
6766 CAN_EXPLODE_SMASHED(smashed))
6771 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6773 if (smashed == EL_LAMP ||
6774 smashed == EL_LAMP_ACTIVE)
6779 else if (smashed == EL_NUT)
6781 Tile[x][y + 1] = EL_NUT_BREAKING;
6782 PlayLevelSound(x, y, SND_NUT_BREAKING);
6783 RaiseScoreElement(EL_NUT);
6786 else if (smashed == EL_PEARL)
6788 ResetGfxAnimation(x, y);
6790 Tile[x][y + 1] = EL_PEARL_BREAKING;
6791 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6794 else if (smashed == EL_DIAMOND)
6796 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6797 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6800 else if (IS_BELT_SWITCH(smashed))
6802 ToggleBeltSwitch(x, y + 1);
6804 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6805 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6806 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6807 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6809 ToggleSwitchgateSwitch(x, y + 1);
6811 else if (smashed == EL_LIGHT_SWITCH ||
6812 smashed == EL_LIGHT_SWITCH_ACTIVE)
6814 ToggleLightSwitch(x, y + 1);
6818 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6820 CheckElementChangeBySide(x, y + 1, smashed, element,
6821 CE_SWITCHED, CH_SIDE_TOP);
6822 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6828 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6833 // play sound of magic wall / mill
6835 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6836 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6837 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6839 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6840 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6841 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6842 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6843 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6844 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6849 // play sound of object that hits the ground
6850 if (last_line || object_hit)
6851 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6854 static void TurnRoundExt(int x, int y)
6866 { 0, 0 }, { 0, 0 }, { 0, 0 },
6871 int left, right, back;
6875 { MV_DOWN, MV_UP, MV_RIGHT },
6876 { MV_UP, MV_DOWN, MV_LEFT },
6878 { MV_LEFT, MV_RIGHT, MV_DOWN },
6882 { MV_RIGHT, MV_LEFT, MV_UP }
6885 int element = Tile[x][y];
6886 int move_pattern = element_info[element].move_pattern;
6888 int old_move_dir = MovDir[x][y];
6889 int left_dir = turn[old_move_dir].left;
6890 int right_dir = turn[old_move_dir].right;
6891 int back_dir = turn[old_move_dir].back;
6893 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6894 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6895 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6896 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6898 int left_x = x + left_dx, left_y = y + left_dy;
6899 int right_x = x + right_dx, right_y = y + right_dy;
6900 int move_x = x + move_dx, move_y = y + move_dy;
6904 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6906 TestIfBadThingTouchesOtherBadThing(x, y);
6908 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6909 MovDir[x][y] = right_dir;
6910 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6911 MovDir[x][y] = left_dir;
6913 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6915 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6918 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6920 TestIfBadThingTouchesOtherBadThing(x, y);
6922 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6923 MovDir[x][y] = left_dir;
6924 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6925 MovDir[x][y] = right_dir;
6927 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6929 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6932 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6934 TestIfBadThingTouchesOtherBadThing(x, y);
6936 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6937 MovDir[x][y] = left_dir;
6938 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6939 MovDir[x][y] = right_dir;
6941 if (MovDir[x][y] != old_move_dir)
6944 else if (element == EL_YAMYAM)
6946 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6947 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6949 if (can_turn_left && can_turn_right)
6950 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6951 else if (can_turn_left)
6952 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6953 else if (can_turn_right)
6954 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6956 MovDir[x][y] = back_dir;
6958 MovDelay[x][y] = 16 + 16 * RND(3);
6960 else if (element == EL_DARK_YAMYAM)
6962 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6964 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6967 if (can_turn_left && can_turn_right)
6968 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6969 else if (can_turn_left)
6970 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6971 else if (can_turn_right)
6972 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6974 MovDir[x][y] = back_dir;
6976 MovDelay[x][y] = 16 + 16 * RND(3);
6978 else if (element == EL_PACMAN)
6980 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6981 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6983 if (can_turn_left && can_turn_right)
6984 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6985 else if (can_turn_left)
6986 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6987 else if (can_turn_right)
6988 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6990 MovDir[x][y] = back_dir;
6992 MovDelay[x][y] = 6 + RND(40);
6994 else if (element == EL_PIG)
6996 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6997 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6998 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6999 boolean should_turn_left, should_turn_right, should_move_on;
7001 int rnd = RND(rnd_value);
7003 should_turn_left = (can_turn_left &&
7005 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7006 y + back_dy + left_dy)));
7007 should_turn_right = (can_turn_right &&
7009 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7010 y + back_dy + right_dy)));
7011 should_move_on = (can_move_on &&
7014 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7015 y + move_dy + left_dy) ||
7016 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7017 y + move_dy + right_dy)));
7019 if (should_turn_left || should_turn_right || should_move_on)
7021 if (should_turn_left && should_turn_right && should_move_on)
7022 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7023 rnd < 2 * rnd_value / 3 ? right_dir :
7025 else if (should_turn_left && should_turn_right)
7026 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7027 else if (should_turn_left && should_move_on)
7028 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7029 else if (should_turn_right && should_move_on)
7030 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7031 else if (should_turn_left)
7032 MovDir[x][y] = left_dir;
7033 else if (should_turn_right)
7034 MovDir[x][y] = right_dir;
7035 else if (should_move_on)
7036 MovDir[x][y] = old_move_dir;
7038 else if (can_move_on && rnd > rnd_value / 8)
7039 MovDir[x][y] = old_move_dir;
7040 else if (can_turn_left && can_turn_right)
7041 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7042 else if (can_turn_left && rnd > rnd_value / 8)
7043 MovDir[x][y] = left_dir;
7044 else if (can_turn_right && rnd > rnd_value/8)
7045 MovDir[x][y] = right_dir;
7047 MovDir[x][y] = back_dir;
7049 xx = x + move_xy[MovDir[x][y]].dx;
7050 yy = y + move_xy[MovDir[x][y]].dy;
7052 if (!IN_LEV_FIELD(xx, yy) ||
7053 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7054 MovDir[x][y] = old_move_dir;
7058 else if (element == EL_DRAGON)
7060 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7061 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7062 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7064 int rnd = RND(rnd_value);
7066 if (can_move_on && rnd > rnd_value / 8)
7067 MovDir[x][y] = old_move_dir;
7068 else if (can_turn_left && can_turn_right)
7069 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7070 else if (can_turn_left && rnd > rnd_value / 8)
7071 MovDir[x][y] = left_dir;
7072 else if (can_turn_right && rnd > rnd_value / 8)
7073 MovDir[x][y] = right_dir;
7075 MovDir[x][y] = back_dir;
7077 xx = x + move_xy[MovDir[x][y]].dx;
7078 yy = y + move_xy[MovDir[x][y]].dy;
7080 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7081 MovDir[x][y] = old_move_dir;
7085 else if (element == EL_MOLE)
7087 boolean can_move_on =
7088 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7089 IS_AMOEBOID(Tile[move_x][move_y]) ||
7090 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7093 boolean can_turn_left =
7094 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7095 IS_AMOEBOID(Tile[left_x][left_y])));
7097 boolean can_turn_right =
7098 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7099 IS_AMOEBOID(Tile[right_x][right_y])));
7101 if (can_turn_left && can_turn_right)
7102 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7103 else if (can_turn_left)
7104 MovDir[x][y] = left_dir;
7106 MovDir[x][y] = right_dir;
7109 if (MovDir[x][y] != old_move_dir)
7112 else if (element == EL_BALLOON)
7114 MovDir[x][y] = game.wind_direction;
7117 else if (element == EL_SPRING)
7119 if (MovDir[x][y] & MV_HORIZONTAL)
7121 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7122 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7124 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7125 ResetGfxAnimation(move_x, move_y);
7126 TEST_DrawLevelField(move_x, move_y);
7128 MovDir[x][y] = back_dir;
7130 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7131 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7132 MovDir[x][y] = MV_NONE;
7137 else if (element == EL_ROBOT ||
7138 element == EL_SATELLITE ||
7139 element == EL_PENGUIN ||
7140 element == EL_EMC_ANDROID)
7142 int attr_x = -1, attr_y = -1;
7144 if (game.all_players_gone)
7146 attr_x = game.exit_x;
7147 attr_y = game.exit_y;
7153 for (i = 0; i < MAX_PLAYERS; i++)
7155 struct PlayerInfo *player = &stored_player[i];
7156 int jx = player->jx, jy = player->jy;
7158 if (!player->active)
7162 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7170 if (element == EL_ROBOT &&
7171 game.robot_wheel_x >= 0 &&
7172 game.robot_wheel_y >= 0 &&
7173 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7174 game.engine_version < VERSION_IDENT(3,1,0,0)))
7176 attr_x = game.robot_wheel_x;
7177 attr_y = game.robot_wheel_y;
7180 if (element == EL_PENGUIN)
7183 static int xy[4][2] =
7191 for (i = 0; i < NUM_DIRECTIONS; i++)
7193 int ex = x + xy[i][0];
7194 int ey = y + xy[i][1];
7196 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7197 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7198 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7199 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7208 MovDir[x][y] = MV_NONE;
7210 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7211 else if (attr_x > x)
7212 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7214 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7215 else if (attr_y > y)
7216 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7218 if (element == EL_ROBOT)
7222 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7223 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7224 Moving2Blocked(x, y, &newx, &newy);
7226 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7227 MovDelay[x][y] = 8 + 8 * !RND(3);
7229 MovDelay[x][y] = 16;
7231 else if (element == EL_PENGUIN)
7237 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7239 boolean first_horiz = RND(2);
7240 int new_move_dir = MovDir[x][y];
7243 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7244 Moving2Blocked(x, y, &newx, &newy);
7246 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7250 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7251 Moving2Blocked(x, y, &newx, &newy);
7253 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7256 MovDir[x][y] = old_move_dir;
7260 else if (element == EL_SATELLITE)
7266 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7268 boolean first_horiz = RND(2);
7269 int new_move_dir = MovDir[x][y];
7272 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7273 Moving2Blocked(x, y, &newx, &newy);
7275 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7279 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7280 Moving2Blocked(x, y, &newx, &newy);
7282 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7285 MovDir[x][y] = old_move_dir;
7289 else if (element == EL_EMC_ANDROID)
7291 static int check_pos[16] =
7293 -1, // 0 => (invalid)
7296 -1, // 3 => (invalid)
7298 0, // 5 => MV_LEFT | MV_UP
7299 2, // 6 => MV_RIGHT | MV_UP
7300 -1, // 7 => (invalid)
7302 6, // 9 => MV_LEFT | MV_DOWN
7303 4, // 10 => MV_RIGHT | MV_DOWN
7304 -1, // 11 => (invalid)
7305 -1, // 12 => (invalid)
7306 -1, // 13 => (invalid)
7307 -1, // 14 => (invalid)
7308 -1, // 15 => (invalid)
7316 { -1, -1, MV_LEFT | MV_UP },
7318 { +1, -1, MV_RIGHT | MV_UP },
7319 { +1, 0, MV_RIGHT },
7320 { +1, +1, MV_RIGHT | MV_DOWN },
7322 { -1, +1, MV_LEFT | MV_DOWN },
7325 int start_pos, check_order;
7326 boolean can_clone = FALSE;
7329 // check if there is any free field around current position
7330 for (i = 0; i < 8; i++)
7332 int newx = x + check_xy[i].dx;
7333 int newy = y + check_xy[i].dy;
7335 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7343 if (can_clone) // randomly find an element to clone
7347 start_pos = check_pos[RND(8)];
7348 check_order = (RND(2) ? -1 : +1);
7350 for (i = 0; i < 8; i++)
7352 int pos_raw = start_pos + i * check_order;
7353 int pos = (pos_raw + 8) % 8;
7354 int newx = x + check_xy[pos].dx;
7355 int newy = y + check_xy[pos].dy;
7357 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7359 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7360 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7362 Store[x][y] = Tile[newx][newy];
7371 if (can_clone) // randomly find a direction to move
7375 start_pos = check_pos[RND(8)];
7376 check_order = (RND(2) ? -1 : +1);
7378 for (i = 0; i < 8; i++)
7380 int pos_raw = start_pos + i * check_order;
7381 int pos = (pos_raw + 8) % 8;
7382 int newx = x + check_xy[pos].dx;
7383 int newy = y + check_xy[pos].dy;
7384 int new_move_dir = check_xy[pos].dir;
7386 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7388 MovDir[x][y] = new_move_dir;
7389 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7398 if (can_clone) // cloning and moving successful
7401 // cannot clone -- try to move towards player
7403 start_pos = check_pos[MovDir[x][y] & 0x0f];
7404 check_order = (RND(2) ? -1 : +1);
7406 for (i = 0; i < 3; i++)
7408 // first check start_pos, then previous/next or (next/previous) pos
7409 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7410 int pos = (pos_raw + 8) % 8;
7411 int newx = x + check_xy[pos].dx;
7412 int newy = y + check_xy[pos].dy;
7413 int new_move_dir = check_xy[pos].dir;
7415 if (IS_PLAYER(newx, newy))
7418 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7420 MovDir[x][y] = new_move_dir;
7421 MovDelay[x][y] = level.android_move_time * 8 + 1;
7428 else if (move_pattern == MV_TURNING_LEFT ||
7429 move_pattern == MV_TURNING_RIGHT ||
7430 move_pattern == MV_TURNING_LEFT_RIGHT ||
7431 move_pattern == MV_TURNING_RIGHT_LEFT ||
7432 move_pattern == MV_TURNING_RANDOM ||
7433 move_pattern == MV_ALL_DIRECTIONS)
7435 boolean can_turn_left =
7436 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7437 boolean can_turn_right =
7438 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7440 if (element_info[element].move_stepsize == 0) // "not moving"
7443 if (move_pattern == MV_TURNING_LEFT)
7444 MovDir[x][y] = left_dir;
7445 else if (move_pattern == MV_TURNING_RIGHT)
7446 MovDir[x][y] = right_dir;
7447 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7448 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7449 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7450 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7451 else if (move_pattern == MV_TURNING_RANDOM)
7452 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7453 can_turn_right && !can_turn_left ? right_dir :
7454 RND(2) ? left_dir : right_dir);
7455 else if (can_turn_left && can_turn_right)
7456 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7457 else if (can_turn_left)
7458 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7459 else if (can_turn_right)
7460 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7462 MovDir[x][y] = back_dir;
7464 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7466 else if (move_pattern == MV_HORIZONTAL ||
7467 move_pattern == MV_VERTICAL)
7469 if (move_pattern & old_move_dir)
7470 MovDir[x][y] = back_dir;
7471 else if (move_pattern == MV_HORIZONTAL)
7472 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7473 else if (move_pattern == MV_VERTICAL)
7474 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7476 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7478 else if (move_pattern & MV_ANY_DIRECTION)
7480 MovDir[x][y] = move_pattern;
7481 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7483 else if (move_pattern & MV_WIND_DIRECTION)
7485 MovDir[x][y] = game.wind_direction;
7486 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7488 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7490 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7491 MovDir[x][y] = left_dir;
7492 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7493 MovDir[x][y] = right_dir;
7495 if (MovDir[x][y] != old_move_dir)
7496 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7498 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7500 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7501 MovDir[x][y] = right_dir;
7502 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7503 MovDir[x][y] = left_dir;
7505 if (MovDir[x][y] != old_move_dir)
7506 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7508 else if (move_pattern == MV_TOWARDS_PLAYER ||
7509 move_pattern == MV_AWAY_FROM_PLAYER)
7511 int attr_x = -1, attr_y = -1;
7513 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7515 if (game.all_players_gone)
7517 attr_x = game.exit_x;
7518 attr_y = game.exit_y;
7524 for (i = 0; i < MAX_PLAYERS; i++)
7526 struct PlayerInfo *player = &stored_player[i];
7527 int jx = player->jx, jy = player->jy;
7529 if (!player->active)
7533 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7541 MovDir[x][y] = MV_NONE;
7543 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7544 else if (attr_x > x)
7545 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7547 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7548 else if (attr_y > y)
7549 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7551 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7553 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7555 boolean first_horiz = RND(2);
7556 int new_move_dir = MovDir[x][y];
7558 if (element_info[element].move_stepsize == 0) // "not moving"
7560 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7561 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7567 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7568 Moving2Blocked(x, y, &newx, &newy);
7570 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7574 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7575 Moving2Blocked(x, y, &newx, &newy);
7577 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7580 MovDir[x][y] = old_move_dir;
7583 else if (move_pattern == MV_WHEN_PUSHED ||
7584 move_pattern == MV_WHEN_DROPPED)
7586 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7587 MovDir[x][y] = MV_NONE;
7591 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7593 static int test_xy[7][2] =
7603 static int test_dir[7] =
7613 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7614 int move_preference = -1000000; // start with very low preference
7615 int new_move_dir = MV_NONE;
7616 int start_test = RND(4);
7619 for (i = 0; i < NUM_DIRECTIONS; i++)
7621 int move_dir = test_dir[start_test + i];
7622 int move_dir_preference;
7624 xx = x + test_xy[start_test + i][0];
7625 yy = y + test_xy[start_test + i][1];
7627 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7628 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7630 new_move_dir = move_dir;
7635 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7638 move_dir_preference = -1 * RunnerVisit[xx][yy];
7639 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7640 move_dir_preference = PlayerVisit[xx][yy];
7642 if (move_dir_preference > move_preference)
7644 // prefer field that has not been visited for the longest time
7645 move_preference = move_dir_preference;
7646 new_move_dir = move_dir;
7648 else if (move_dir_preference == move_preference &&
7649 move_dir == old_move_dir)
7651 // prefer last direction when all directions are preferred equally
7652 move_preference = move_dir_preference;
7653 new_move_dir = move_dir;
7657 MovDir[x][y] = new_move_dir;
7658 if (old_move_dir != new_move_dir)
7659 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7663 static void TurnRound(int x, int y)
7665 int direction = MovDir[x][y];
7669 GfxDir[x][y] = MovDir[x][y];
7671 if (direction != MovDir[x][y])
7675 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7677 ResetGfxFrame(x, y);
7680 static boolean JustBeingPushed(int x, int y)
7684 for (i = 0; i < MAX_PLAYERS; i++)
7686 struct PlayerInfo *player = &stored_player[i];
7688 if (player->active && player->is_pushing && player->MovPos)
7690 int next_jx = player->jx + (player->jx - player->last_jx);
7691 int next_jy = player->jy + (player->jy - player->last_jy);
7693 if (x == next_jx && y == next_jy)
7701 static void StartMoving(int x, int y)
7703 boolean started_moving = FALSE; // some elements can fall _and_ move
7704 int element = Tile[x][y];
7709 if (MovDelay[x][y] == 0)
7710 GfxAction[x][y] = ACTION_DEFAULT;
7712 if (CAN_FALL(element) && y < lev_fieldy - 1)
7714 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7715 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7716 if (JustBeingPushed(x, y))
7719 if (element == EL_QUICKSAND_FULL)
7721 if (IS_FREE(x, y + 1))
7723 InitMovingField(x, y, MV_DOWN);
7724 started_moving = TRUE;
7726 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7727 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7728 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7729 Store[x][y] = EL_ROCK;
7731 Store[x][y] = EL_ROCK;
7734 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7736 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7738 if (!MovDelay[x][y])
7740 MovDelay[x][y] = TILEY + 1;
7742 ResetGfxAnimation(x, y);
7743 ResetGfxAnimation(x, y + 1);
7748 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7749 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7756 Tile[x][y] = EL_QUICKSAND_EMPTY;
7757 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7758 Store[x][y + 1] = Store[x][y];
7761 PlayLevelSoundAction(x, y, ACTION_FILLING);
7763 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7765 if (!MovDelay[x][y])
7767 MovDelay[x][y] = TILEY + 1;
7769 ResetGfxAnimation(x, y);
7770 ResetGfxAnimation(x, y + 1);
7775 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7776 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7783 Tile[x][y] = EL_QUICKSAND_EMPTY;
7784 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7785 Store[x][y + 1] = Store[x][y];
7788 PlayLevelSoundAction(x, y, ACTION_FILLING);
7791 else if (element == EL_QUICKSAND_FAST_FULL)
7793 if (IS_FREE(x, y + 1))
7795 InitMovingField(x, y, MV_DOWN);
7796 started_moving = TRUE;
7798 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7799 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7800 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7801 Store[x][y] = EL_ROCK;
7803 Store[x][y] = EL_ROCK;
7806 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7808 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7810 if (!MovDelay[x][y])
7812 MovDelay[x][y] = TILEY + 1;
7814 ResetGfxAnimation(x, y);
7815 ResetGfxAnimation(x, y + 1);
7820 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7821 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7828 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7829 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7830 Store[x][y + 1] = Store[x][y];
7833 PlayLevelSoundAction(x, y, ACTION_FILLING);
7835 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7837 if (!MovDelay[x][y])
7839 MovDelay[x][y] = TILEY + 1;
7841 ResetGfxAnimation(x, y);
7842 ResetGfxAnimation(x, y + 1);
7847 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7848 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7855 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7856 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7857 Store[x][y + 1] = Store[x][y];
7860 PlayLevelSoundAction(x, y, ACTION_FILLING);
7863 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7864 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7866 InitMovingField(x, y, MV_DOWN);
7867 started_moving = TRUE;
7869 Tile[x][y] = EL_QUICKSAND_FILLING;
7870 Store[x][y] = element;
7872 PlayLevelSoundAction(x, y, ACTION_FILLING);
7874 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7875 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7877 InitMovingField(x, y, MV_DOWN);
7878 started_moving = TRUE;
7880 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7881 Store[x][y] = element;
7883 PlayLevelSoundAction(x, y, ACTION_FILLING);
7885 else if (element == EL_MAGIC_WALL_FULL)
7887 if (IS_FREE(x, y + 1))
7889 InitMovingField(x, y, MV_DOWN);
7890 started_moving = TRUE;
7892 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7893 Store[x][y] = EL_CHANGED(Store[x][y]);
7895 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7897 if (!MovDelay[x][y])
7898 MovDelay[x][y] = TILEY / 4 + 1;
7907 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7908 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7909 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7913 else if (element == EL_BD_MAGIC_WALL_FULL)
7915 if (IS_FREE(x, y + 1))
7917 InitMovingField(x, y, MV_DOWN);
7918 started_moving = TRUE;
7920 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7921 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7923 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7925 if (!MovDelay[x][y])
7926 MovDelay[x][y] = TILEY / 4 + 1;
7935 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7936 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7937 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7941 else if (element == EL_DC_MAGIC_WALL_FULL)
7943 if (IS_FREE(x, y + 1))
7945 InitMovingField(x, y, MV_DOWN);
7946 started_moving = TRUE;
7948 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7949 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7951 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7953 if (!MovDelay[x][y])
7954 MovDelay[x][y] = TILEY / 4 + 1;
7963 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7964 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7965 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7969 else if ((CAN_PASS_MAGIC_WALL(element) &&
7970 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7971 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7972 (CAN_PASS_DC_MAGIC_WALL(element) &&
7973 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7976 InitMovingField(x, y, MV_DOWN);
7977 started_moving = TRUE;
7980 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7981 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7982 EL_DC_MAGIC_WALL_FILLING);
7983 Store[x][y] = element;
7985 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7987 SplashAcid(x, y + 1);
7989 InitMovingField(x, y, MV_DOWN);
7990 started_moving = TRUE;
7992 Store[x][y] = EL_ACID;
7995 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7996 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7997 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7998 CAN_FALL(element) && WasJustFalling[x][y] &&
7999 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8001 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8002 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8003 (Tile[x][y + 1] == EL_BLOCKED)))
8005 /* this is needed for a special case not covered by calling "Impact()"
8006 from "ContinueMoving()": if an element moves to a tile directly below
8007 another element which was just falling on that tile (which was empty
8008 in the previous frame), the falling element above would just stop
8009 instead of smashing the element below (in previous version, the above
8010 element was just checked for "moving" instead of "falling", resulting
8011 in incorrect smashes caused by horizontal movement of the above
8012 element; also, the case of the player being the element to smash was
8013 simply not covered here... :-/ ) */
8015 CheckCollision[x][y] = 0;
8016 CheckImpact[x][y] = 0;
8020 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8022 if (MovDir[x][y] == MV_NONE)
8024 InitMovingField(x, y, MV_DOWN);
8025 started_moving = TRUE;
8028 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8030 if (WasJustFalling[x][y]) // prevent animation from being restarted
8031 MovDir[x][y] = MV_DOWN;
8033 InitMovingField(x, y, MV_DOWN);
8034 started_moving = TRUE;
8036 else if (element == EL_AMOEBA_DROP)
8038 Tile[x][y] = EL_AMOEBA_GROWING;
8039 Store[x][y] = EL_AMOEBA_WET;
8041 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8042 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8043 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8044 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8046 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8047 (IS_FREE(x - 1, y + 1) ||
8048 Tile[x - 1][y + 1] == EL_ACID));
8049 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8050 (IS_FREE(x + 1, y + 1) ||
8051 Tile[x + 1][y + 1] == EL_ACID));
8052 boolean can_fall_any = (can_fall_left || can_fall_right);
8053 boolean can_fall_both = (can_fall_left && can_fall_right);
8054 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8056 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8058 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8059 can_fall_right = FALSE;
8060 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8061 can_fall_left = FALSE;
8062 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8063 can_fall_right = FALSE;
8064 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8065 can_fall_left = FALSE;
8067 can_fall_any = (can_fall_left || can_fall_right);
8068 can_fall_both = FALSE;
8073 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8074 can_fall_right = FALSE; // slip down on left side
8076 can_fall_left = !(can_fall_right = RND(2));
8078 can_fall_both = FALSE;
8083 // if not determined otherwise, prefer left side for slipping down
8084 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8085 started_moving = TRUE;
8088 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8090 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8091 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8092 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8093 int belt_dir = game.belt_dir[belt_nr];
8095 if ((belt_dir == MV_LEFT && left_is_free) ||
8096 (belt_dir == MV_RIGHT && right_is_free))
8098 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8100 InitMovingField(x, y, belt_dir);
8101 started_moving = TRUE;
8103 Pushed[x][y] = TRUE;
8104 Pushed[nextx][y] = TRUE;
8106 GfxAction[x][y] = ACTION_DEFAULT;
8110 MovDir[x][y] = 0; // if element was moving, stop it
8115 // not "else if" because of elements that can fall and move (EL_SPRING)
8116 if (CAN_MOVE(element) && !started_moving)
8118 int move_pattern = element_info[element].move_pattern;
8121 Moving2Blocked(x, y, &newx, &newy);
8123 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8126 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8127 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8129 WasJustMoving[x][y] = 0;
8130 CheckCollision[x][y] = 0;
8132 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8134 if (Tile[x][y] != element) // element has changed
8138 if (!MovDelay[x][y]) // start new movement phase
8140 // all objects that can change their move direction after each step
8141 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8143 if (element != EL_YAMYAM &&
8144 element != EL_DARK_YAMYAM &&
8145 element != EL_PACMAN &&
8146 !(move_pattern & MV_ANY_DIRECTION) &&
8147 move_pattern != MV_TURNING_LEFT &&
8148 move_pattern != MV_TURNING_RIGHT &&
8149 move_pattern != MV_TURNING_LEFT_RIGHT &&
8150 move_pattern != MV_TURNING_RIGHT_LEFT &&
8151 move_pattern != MV_TURNING_RANDOM)
8155 if (MovDelay[x][y] && (element == EL_BUG ||
8156 element == EL_SPACESHIP ||
8157 element == EL_SP_SNIKSNAK ||
8158 element == EL_SP_ELECTRON ||
8159 element == EL_MOLE))
8160 TEST_DrawLevelField(x, y);
8164 if (MovDelay[x][y]) // wait some time before next movement
8168 if (element == EL_ROBOT ||
8169 element == EL_YAMYAM ||
8170 element == EL_DARK_YAMYAM)
8172 DrawLevelElementAnimationIfNeeded(x, y, element);
8173 PlayLevelSoundAction(x, y, ACTION_WAITING);
8175 else if (element == EL_SP_ELECTRON)
8176 DrawLevelElementAnimationIfNeeded(x, y, element);
8177 else if (element == EL_DRAGON)
8180 int dir = MovDir[x][y];
8181 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8182 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8183 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8184 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8185 dir == MV_UP ? IMG_FLAMES_1_UP :
8186 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8187 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8189 GfxAction[x][y] = ACTION_ATTACKING;
8191 if (IS_PLAYER(x, y))
8192 DrawPlayerField(x, y);
8194 TEST_DrawLevelField(x, y);
8196 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8198 for (i = 1; i <= 3; i++)
8200 int xx = x + i * dx;
8201 int yy = y + i * dy;
8202 int sx = SCREENX(xx);
8203 int sy = SCREENY(yy);
8204 int flame_graphic = graphic + (i - 1);
8206 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8211 int flamed = MovingOrBlocked2Element(xx, yy);
8213 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8216 RemoveMovingField(xx, yy);
8218 ChangeDelay[xx][yy] = 0;
8220 Tile[xx][yy] = EL_FLAMES;
8222 if (IN_SCR_FIELD(sx, sy))
8224 TEST_DrawLevelFieldCrumbled(xx, yy);
8225 DrawGraphic(sx, sy, flame_graphic, frame);
8230 if (Tile[xx][yy] == EL_FLAMES)
8231 Tile[xx][yy] = EL_EMPTY;
8232 TEST_DrawLevelField(xx, yy);
8237 if (MovDelay[x][y]) // element still has to wait some time
8239 PlayLevelSoundAction(x, y, ACTION_WAITING);
8245 // now make next step
8247 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8249 if (DONT_COLLIDE_WITH(element) &&
8250 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8251 !PLAYER_ENEMY_PROTECTED(newx, newy))
8253 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8258 else if (CAN_MOVE_INTO_ACID(element) &&
8259 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8260 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8261 (MovDir[x][y] == MV_DOWN ||
8262 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8264 SplashAcid(newx, newy);
8265 Store[x][y] = EL_ACID;
8267 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8269 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8270 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8271 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8272 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8275 TEST_DrawLevelField(x, y);
8277 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8278 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8279 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8281 game.friends_still_needed--;
8282 if (!game.friends_still_needed &&
8284 game.all_players_gone)
8289 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8291 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8292 TEST_DrawLevelField(newx, newy);
8294 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8296 else if (!IS_FREE(newx, newy))
8298 GfxAction[x][y] = ACTION_WAITING;
8300 if (IS_PLAYER(x, y))
8301 DrawPlayerField(x, y);
8303 TEST_DrawLevelField(x, y);
8308 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8310 if (IS_FOOD_PIG(Tile[newx][newy]))
8312 if (IS_MOVING(newx, newy))
8313 RemoveMovingField(newx, newy);
8316 Tile[newx][newy] = EL_EMPTY;
8317 TEST_DrawLevelField(newx, newy);
8320 PlayLevelSound(x, y, SND_PIG_DIGGING);
8322 else if (!IS_FREE(newx, newy))
8324 if (IS_PLAYER(x, y))
8325 DrawPlayerField(x, y);
8327 TEST_DrawLevelField(x, y);
8332 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8334 if (Store[x][y] != EL_EMPTY)
8336 boolean can_clone = FALSE;
8339 // check if element to clone is still there
8340 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8342 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8350 // cannot clone or target field not free anymore -- do not clone
8351 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8352 Store[x][y] = EL_EMPTY;
8355 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8357 if (IS_MV_DIAGONAL(MovDir[x][y]))
8359 int diagonal_move_dir = MovDir[x][y];
8360 int stored = Store[x][y];
8361 int change_delay = 8;
8364 // android is moving diagonally
8366 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8368 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8369 GfxElement[x][y] = EL_EMC_ANDROID;
8370 GfxAction[x][y] = ACTION_SHRINKING;
8371 GfxDir[x][y] = diagonal_move_dir;
8372 ChangeDelay[x][y] = change_delay;
8374 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8377 DrawLevelGraphicAnimation(x, y, graphic);
8378 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8380 if (Tile[newx][newy] == EL_ACID)
8382 SplashAcid(newx, newy);
8387 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8389 Store[newx][newy] = EL_EMC_ANDROID;
8390 GfxElement[newx][newy] = EL_EMC_ANDROID;
8391 GfxAction[newx][newy] = ACTION_GROWING;
8392 GfxDir[newx][newy] = diagonal_move_dir;
8393 ChangeDelay[newx][newy] = change_delay;
8395 graphic = el_act_dir2img(GfxElement[newx][newy],
8396 GfxAction[newx][newy], GfxDir[newx][newy]);
8398 DrawLevelGraphicAnimation(newx, newy, graphic);
8399 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8405 Tile[newx][newy] = EL_EMPTY;
8406 TEST_DrawLevelField(newx, newy);
8408 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8411 else if (!IS_FREE(newx, newy))
8416 else if (IS_CUSTOM_ELEMENT(element) &&
8417 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8419 if (!DigFieldByCE(newx, newy, element))
8422 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8424 RunnerVisit[x][y] = FrameCounter;
8425 PlayerVisit[x][y] /= 8; // expire player visit path
8428 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8430 if (!IS_FREE(newx, newy))
8432 if (IS_PLAYER(x, y))
8433 DrawPlayerField(x, y);
8435 TEST_DrawLevelField(x, y);
8441 boolean wanna_flame = !RND(10);
8442 int dx = newx - x, dy = newy - y;
8443 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8444 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8445 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8446 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8447 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8448 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8451 IS_CLASSIC_ENEMY(element1) ||
8452 IS_CLASSIC_ENEMY(element2)) &&
8453 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8454 element1 != EL_FLAMES && element2 != EL_FLAMES)
8456 ResetGfxAnimation(x, y);
8457 GfxAction[x][y] = ACTION_ATTACKING;
8459 if (IS_PLAYER(x, y))
8460 DrawPlayerField(x, y);
8462 TEST_DrawLevelField(x, y);
8464 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8466 MovDelay[x][y] = 50;
8468 Tile[newx][newy] = EL_FLAMES;
8469 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8470 Tile[newx1][newy1] = EL_FLAMES;
8471 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8472 Tile[newx2][newy2] = EL_FLAMES;
8478 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8479 Tile[newx][newy] == EL_DIAMOND)
8481 if (IS_MOVING(newx, newy))
8482 RemoveMovingField(newx, newy);
8485 Tile[newx][newy] = EL_EMPTY;
8486 TEST_DrawLevelField(newx, newy);
8489 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8491 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8492 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8494 if (AmoebaNr[newx][newy])
8496 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8497 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8498 Tile[newx][newy] == EL_BD_AMOEBA)
8499 AmoebaCnt[AmoebaNr[newx][newy]]--;
8502 if (IS_MOVING(newx, newy))
8504 RemoveMovingField(newx, newy);
8508 Tile[newx][newy] = EL_EMPTY;
8509 TEST_DrawLevelField(newx, newy);
8512 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8514 else if ((element == EL_PACMAN || element == EL_MOLE)
8515 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8517 if (AmoebaNr[newx][newy])
8519 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8520 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8521 Tile[newx][newy] == EL_BD_AMOEBA)
8522 AmoebaCnt[AmoebaNr[newx][newy]]--;
8525 if (element == EL_MOLE)
8527 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8528 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8530 ResetGfxAnimation(x, y);
8531 GfxAction[x][y] = ACTION_DIGGING;
8532 TEST_DrawLevelField(x, y);
8534 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8536 return; // wait for shrinking amoeba
8538 else // element == EL_PACMAN
8540 Tile[newx][newy] = EL_EMPTY;
8541 TEST_DrawLevelField(newx, newy);
8542 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8545 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8546 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8547 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8549 // wait for shrinking amoeba to completely disappear
8552 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8554 // object was running against a wall
8558 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8559 DrawLevelElementAnimation(x, y, element);
8561 if (DONT_TOUCH(element))
8562 TestIfBadThingTouchesPlayer(x, y);
8567 InitMovingField(x, y, MovDir[x][y]);
8569 PlayLevelSoundAction(x, y, ACTION_MOVING);
8573 ContinueMoving(x, y);
8576 void ContinueMoving(int x, int y)
8578 int element = Tile[x][y];
8579 struct ElementInfo *ei = &element_info[element];
8580 int direction = MovDir[x][y];
8581 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8582 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8583 int newx = x + dx, newy = y + dy;
8584 int stored = Store[x][y];
8585 int stored_new = Store[newx][newy];
8586 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8587 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8588 boolean last_line = (newy == lev_fieldy - 1);
8589 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8591 if (pushed_by_player) // special case: moving object pushed by player
8593 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8595 else if (use_step_delay) // special case: moving object has step delay
8597 if (!MovDelay[x][y])
8598 MovPos[x][y] += getElementMoveStepsize(x, y);
8603 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8607 TEST_DrawLevelField(x, y);
8609 return; // element is still waiting
8612 else // normal case: generically moving object
8614 MovPos[x][y] += getElementMoveStepsize(x, y);
8617 if (ABS(MovPos[x][y]) < TILEX)
8619 TEST_DrawLevelField(x, y);
8621 return; // element is still moving
8624 // element reached destination field
8626 Tile[x][y] = EL_EMPTY;
8627 Tile[newx][newy] = element;
8628 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8630 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8632 element = Tile[newx][newy] = EL_ACID;
8634 else if (element == EL_MOLE)
8636 Tile[x][y] = EL_SAND;
8638 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8640 else if (element == EL_QUICKSAND_FILLING)
8642 element = Tile[newx][newy] = get_next_element(element);
8643 Store[newx][newy] = Store[x][y];
8645 else if (element == EL_QUICKSAND_EMPTYING)
8647 Tile[x][y] = get_next_element(element);
8648 element = Tile[newx][newy] = Store[x][y];
8650 else if (element == EL_QUICKSAND_FAST_FILLING)
8652 element = Tile[newx][newy] = get_next_element(element);
8653 Store[newx][newy] = Store[x][y];
8655 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8657 Tile[x][y] = get_next_element(element);
8658 element = Tile[newx][newy] = Store[x][y];
8660 else if (element == EL_MAGIC_WALL_FILLING)
8662 element = Tile[newx][newy] = get_next_element(element);
8663 if (!game.magic_wall_active)
8664 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8665 Store[newx][newy] = Store[x][y];
8667 else if (element == EL_MAGIC_WALL_EMPTYING)
8669 Tile[x][y] = get_next_element(element);
8670 if (!game.magic_wall_active)
8671 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8672 element = Tile[newx][newy] = Store[x][y];
8674 InitField(newx, newy, FALSE);
8676 else if (element == EL_BD_MAGIC_WALL_FILLING)
8678 element = Tile[newx][newy] = get_next_element(element);
8679 if (!game.magic_wall_active)
8680 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8681 Store[newx][newy] = Store[x][y];
8683 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8685 Tile[x][y] = get_next_element(element);
8686 if (!game.magic_wall_active)
8687 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8688 element = Tile[newx][newy] = Store[x][y];
8690 InitField(newx, newy, FALSE);
8692 else if (element == EL_DC_MAGIC_WALL_FILLING)
8694 element = Tile[newx][newy] = get_next_element(element);
8695 if (!game.magic_wall_active)
8696 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8697 Store[newx][newy] = Store[x][y];
8699 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8701 Tile[x][y] = get_next_element(element);
8702 if (!game.magic_wall_active)
8703 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8704 element = Tile[newx][newy] = Store[x][y];
8706 InitField(newx, newy, FALSE);
8708 else if (element == EL_AMOEBA_DROPPING)
8710 Tile[x][y] = get_next_element(element);
8711 element = Tile[newx][newy] = Store[x][y];
8713 else if (element == EL_SOKOBAN_OBJECT)
8716 Tile[x][y] = Back[x][y];
8718 if (Back[newx][newy])
8719 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8721 Back[x][y] = Back[newx][newy] = 0;
8724 Store[x][y] = EL_EMPTY;
8729 MovDelay[newx][newy] = 0;
8731 if (CAN_CHANGE_OR_HAS_ACTION(element))
8733 // copy element change control values to new field
8734 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8735 ChangePage[newx][newy] = ChangePage[x][y];
8736 ChangeCount[newx][newy] = ChangeCount[x][y];
8737 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8740 CustomValue[newx][newy] = CustomValue[x][y];
8742 ChangeDelay[x][y] = 0;
8743 ChangePage[x][y] = -1;
8744 ChangeCount[x][y] = 0;
8745 ChangeEvent[x][y] = -1;
8747 CustomValue[x][y] = 0;
8749 // copy animation control values to new field
8750 GfxFrame[newx][newy] = GfxFrame[x][y];
8751 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8752 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8753 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8755 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8757 // some elements can leave other elements behind after moving
8758 if (ei->move_leave_element != EL_EMPTY &&
8759 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8760 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8762 int move_leave_element = ei->move_leave_element;
8764 // this makes it possible to leave the removed element again
8765 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8766 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8768 Tile[x][y] = move_leave_element;
8770 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8771 MovDir[x][y] = direction;
8773 InitField(x, y, FALSE);
8775 if (GFX_CRUMBLED(Tile[x][y]))
8776 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8778 if (ELEM_IS_PLAYER(move_leave_element))
8779 RelocatePlayer(x, y, move_leave_element);
8782 // do this after checking for left-behind element
8783 ResetGfxAnimation(x, y); // reset animation values for old field
8785 if (!CAN_MOVE(element) ||
8786 (CAN_FALL(element) && direction == MV_DOWN &&
8787 (element == EL_SPRING ||
8788 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8789 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8790 GfxDir[x][y] = MovDir[newx][newy] = 0;
8792 TEST_DrawLevelField(x, y);
8793 TEST_DrawLevelField(newx, newy);
8795 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8797 // prevent pushed element from moving on in pushed direction
8798 if (pushed_by_player && CAN_MOVE(element) &&
8799 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8800 !(element_info[element].move_pattern & direction))
8801 TurnRound(newx, newy);
8803 // prevent elements on conveyor belt from moving on in last direction
8804 if (pushed_by_conveyor && CAN_FALL(element) &&
8805 direction & MV_HORIZONTAL)
8806 MovDir[newx][newy] = 0;
8808 if (!pushed_by_player)
8810 int nextx = newx + dx, nexty = newy + dy;
8811 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8813 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8815 if (CAN_FALL(element) && direction == MV_DOWN)
8816 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8818 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8819 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8821 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8822 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8825 if (DONT_TOUCH(element)) // object may be nasty to player or others
8827 TestIfBadThingTouchesPlayer(newx, newy);
8828 TestIfBadThingTouchesFriend(newx, newy);
8830 if (!IS_CUSTOM_ELEMENT(element))
8831 TestIfBadThingTouchesOtherBadThing(newx, newy);
8833 else if (element == EL_PENGUIN)
8834 TestIfFriendTouchesBadThing(newx, newy);
8836 if (DONT_GET_HIT_BY(element))
8838 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8841 // give the player one last chance (one more frame) to move away
8842 if (CAN_FALL(element) && direction == MV_DOWN &&
8843 (last_line || (!IS_FREE(x, newy + 1) &&
8844 (!IS_PLAYER(x, newy + 1) ||
8845 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8848 if (pushed_by_player && !game.use_change_when_pushing_bug)
8850 int push_side = MV_DIR_OPPOSITE(direction);
8851 struct PlayerInfo *player = PLAYERINFO(x, y);
8853 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8854 player->index_bit, push_side);
8855 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8856 player->index_bit, push_side);
8859 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8860 MovDelay[newx][newy] = 1;
8862 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8864 TestIfElementTouchesCustomElement(x, y); // empty or new element
8865 TestIfElementHitsCustomElement(newx, newy, direction);
8866 TestIfPlayerTouchesCustomElement(newx, newy);
8867 TestIfElementTouchesCustomElement(newx, newy);
8869 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8870 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8871 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8872 MV_DIR_OPPOSITE(direction));
8875 int AmoebaNeighbourNr(int ax, int ay)
8878 int element = Tile[ax][ay];
8880 static int xy[4][2] =
8888 for (i = 0; i < NUM_DIRECTIONS; i++)
8890 int x = ax + xy[i][0];
8891 int y = ay + xy[i][1];
8893 if (!IN_LEV_FIELD(x, y))
8896 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8897 group_nr = AmoebaNr[x][y];
8903 static void AmoebaMerge(int ax, int ay)
8905 int i, x, y, xx, yy;
8906 int new_group_nr = AmoebaNr[ax][ay];
8907 static int xy[4][2] =
8915 if (new_group_nr == 0)
8918 for (i = 0; i < NUM_DIRECTIONS; i++)
8923 if (!IN_LEV_FIELD(x, y))
8926 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8927 Tile[x][y] == EL_BD_AMOEBA ||
8928 Tile[x][y] == EL_AMOEBA_DEAD) &&
8929 AmoebaNr[x][y] != new_group_nr)
8931 int old_group_nr = AmoebaNr[x][y];
8933 if (old_group_nr == 0)
8936 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8937 AmoebaCnt[old_group_nr] = 0;
8938 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8939 AmoebaCnt2[old_group_nr] = 0;
8941 SCAN_PLAYFIELD(xx, yy)
8943 if (AmoebaNr[xx][yy] == old_group_nr)
8944 AmoebaNr[xx][yy] = new_group_nr;
8950 void AmoebaToDiamond(int ax, int ay)
8954 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8956 int group_nr = AmoebaNr[ax][ay];
8961 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8962 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8968 SCAN_PLAYFIELD(x, y)
8970 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8973 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8977 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8978 SND_AMOEBA_TURNING_TO_GEM :
8979 SND_AMOEBA_TURNING_TO_ROCK));
8984 static int xy[4][2] =
8992 for (i = 0; i < NUM_DIRECTIONS; i++)
8997 if (!IN_LEV_FIELD(x, y))
9000 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9002 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9003 SND_AMOEBA_TURNING_TO_GEM :
9004 SND_AMOEBA_TURNING_TO_ROCK));
9011 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9014 int group_nr = AmoebaNr[ax][ay];
9015 boolean done = FALSE;
9020 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9021 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9027 SCAN_PLAYFIELD(x, y)
9029 if (AmoebaNr[x][y] == group_nr &&
9030 (Tile[x][y] == EL_AMOEBA_DEAD ||
9031 Tile[x][y] == EL_BD_AMOEBA ||
9032 Tile[x][y] == EL_AMOEBA_GROWING))
9035 Tile[x][y] = new_element;
9036 InitField(x, y, FALSE);
9037 TEST_DrawLevelField(x, y);
9043 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9044 SND_BD_AMOEBA_TURNING_TO_ROCK :
9045 SND_BD_AMOEBA_TURNING_TO_GEM));
9048 static void AmoebaGrowing(int x, int y)
9050 static unsigned int sound_delay = 0;
9051 static unsigned int sound_delay_value = 0;
9053 if (!MovDelay[x][y]) // start new growing cycle
9057 if (DelayReached(&sound_delay, sound_delay_value))
9059 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9060 sound_delay_value = 30;
9064 if (MovDelay[x][y]) // wait some time before growing bigger
9067 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9069 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9070 6 - MovDelay[x][y]);
9072 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9075 if (!MovDelay[x][y])
9077 Tile[x][y] = Store[x][y];
9079 TEST_DrawLevelField(x, y);
9084 static void AmoebaShrinking(int x, int y)
9086 static unsigned int sound_delay = 0;
9087 static unsigned int sound_delay_value = 0;
9089 if (!MovDelay[x][y]) // start new shrinking cycle
9093 if (DelayReached(&sound_delay, sound_delay_value))
9094 sound_delay_value = 30;
9097 if (MovDelay[x][y]) // wait some time before shrinking
9100 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9102 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9103 6 - MovDelay[x][y]);
9105 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9108 if (!MovDelay[x][y])
9110 Tile[x][y] = EL_EMPTY;
9111 TEST_DrawLevelField(x, y);
9113 // don't let mole enter this field in this cycle;
9114 // (give priority to objects falling to this field from above)
9120 static void AmoebaReproduce(int ax, int ay)
9123 int element = Tile[ax][ay];
9124 int graphic = el2img(element);
9125 int newax = ax, neway = ay;
9126 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9127 static int xy[4][2] =
9135 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9137 Tile[ax][ay] = EL_AMOEBA_DEAD;
9138 TEST_DrawLevelField(ax, ay);
9142 if (IS_ANIMATED(graphic))
9143 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9145 if (!MovDelay[ax][ay]) // start making new amoeba field
9146 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9148 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9151 if (MovDelay[ax][ay])
9155 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9158 int x = ax + xy[start][0];
9159 int y = ay + xy[start][1];
9161 if (!IN_LEV_FIELD(x, y))
9164 if (IS_FREE(x, y) ||
9165 CAN_GROW_INTO(Tile[x][y]) ||
9166 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9167 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9173 if (newax == ax && neway == ay)
9176 else // normal or "filled" (BD style) amoeba
9179 boolean waiting_for_player = FALSE;
9181 for (i = 0; i < NUM_DIRECTIONS; i++)
9183 int j = (start + i) % 4;
9184 int x = ax + xy[j][0];
9185 int y = ay + xy[j][1];
9187 if (!IN_LEV_FIELD(x, y))
9190 if (IS_FREE(x, y) ||
9191 CAN_GROW_INTO(Tile[x][y]) ||
9192 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9193 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9199 else if (IS_PLAYER(x, y))
9200 waiting_for_player = TRUE;
9203 if (newax == ax && neway == ay) // amoeba cannot grow
9205 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9207 Tile[ax][ay] = EL_AMOEBA_DEAD;
9208 TEST_DrawLevelField(ax, ay);
9209 AmoebaCnt[AmoebaNr[ax][ay]]--;
9211 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9213 if (element == EL_AMOEBA_FULL)
9214 AmoebaToDiamond(ax, ay);
9215 else if (element == EL_BD_AMOEBA)
9216 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9221 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9223 // amoeba gets larger by growing in some direction
9225 int new_group_nr = AmoebaNr[ax][ay];
9228 if (new_group_nr == 0)
9230 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9232 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9238 AmoebaNr[newax][neway] = new_group_nr;
9239 AmoebaCnt[new_group_nr]++;
9240 AmoebaCnt2[new_group_nr]++;
9242 // if amoeba touches other amoeba(s) after growing, unify them
9243 AmoebaMerge(newax, neway);
9245 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9247 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9253 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9254 (neway == lev_fieldy - 1 && newax != ax))
9256 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9257 Store[newax][neway] = element;
9259 else if (neway == ay || element == EL_EMC_DRIPPER)
9261 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9263 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9267 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9268 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9269 Store[ax][ay] = EL_AMOEBA_DROP;
9270 ContinueMoving(ax, ay);
9274 TEST_DrawLevelField(newax, neway);
9277 static void Life(int ax, int ay)
9281 int element = Tile[ax][ay];
9282 int graphic = el2img(element);
9283 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9285 boolean changed = FALSE;
9287 if (IS_ANIMATED(graphic))
9288 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9293 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9294 MovDelay[ax][ay] = life_time;
9296 if (MovDelay[ax][ay]) // wait some time before next cycle
9299 if (MovDelay[ax][ay])
9303 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9305 int xx = ax+x1, yy = ay+y1;
9306 int old_element = Tile[xx][yy];
9307 int num_neighbours = 0;
9309 if (!IN_LEV_FIELD(xx, yy))
9312 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9314 int x = xx+x2, y = yy+y2;
9316 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9319 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9320 boolean is_neighbour = FALSE;
9322 if (level.use_life_bugs)
9324 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9325 (IS_FREE(x, y) && Stop[x][y]));
9328 (Last[x][y] == element || is_player_cell);
9334 boolean is_free = FALSE;
9336 if (level.use_life_bugs)
9337 is_free = (IS_FREE(xx, yy));
9339 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9341 if (xx == ax && yy == ay) // field in the middle
9343 if (num_neighbours < life_parameter[0] ||
9344 num_neighbours > life_parameter[1])
9346 Tile[xx][yy] = EL_EMPTY;
9347 if (Tile[xx][yy] != old_element)
9348 TEST_DrawLevelField(xx, yy);
9349 Stop[xx][yy] = TRUE;
9353 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9354 { // free border field
9355 if (num_neighbours >= life_parameter[2] &&
9356 num_neighbours <= life_parameter[3])
9358 Tile[xx][yy] = element;
9359 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9360 if (Tile[xx][yy] != old_element)
9361 TEST_DrawLevelField(xx, yy);
9362 Stop[xx][yy] = TRUE;
9369 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9370 SND_GAME_OF_LIFE_GROWING);
9373 static void InitRobotWheel(int x, int y)
9375 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9378 static void RunRobotWheel(int x, int y)
9380 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9383 static void StopRobotWheel(int x, int y)
9385 if (game.robot_wheel_x == x &&
9386 game.robot_wheel_y == y)
9388 game.robot_wheel_x = -1;
9389 game.robot_wheel_y = -1;
9390 game.robot_wheel_active = FALSE;
9394 static void InitTimegateWheel(int x, int y)
9396 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9399 static void RunTimegateWheel(int x, int y)
9401 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9404 static void InitMagicBallDelay(int x, int y)
9406 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9409 static void ActivateMagicBall(int bx, int by)
9413 if (level.ball_random)
9415 int pos_border = RND(8); // select one of the eight border elements
9416 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9417 int xx = pos_content % 3;
9418 int yy = pos_content / 3;
9423 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9424 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9428 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9430 int xx = x - bx + 1;
9431 int yy = y - by + 1;
9433 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9434 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9438 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9441 static void CheckExit(int x, int y)
9443 if (game.gems_still_needed > 0 ||
9444 game.sokoban_fields_still_needed > 0 ||
9445 game.sokoban_objects_still_needed > 0 ||
9446 game.lights_still_needed > 0)
9448 int element = Tile[x][y];
9449 int graphic = el2img(element);
9451 if (IS_ANIMATED(graphic))
9452 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9457 // do not re-open exit door closed after last player
9458 if (game.all_players_gone)
9461 Tile[x][y] = EL_EXIT_OPENING;
9463 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9466 static void CheckExitEM(int x, int y)
9468 if (game.gems_still_needed > 0 ||
9469 game.sokoban_fields_still_needed > 0 ||
9470 game.sokoban_objects_still_needed > 0 ||
9471 game.lights_still_needed > 0)
9473 int element = Tile[x][y];
9474 int graphic = el2img(element);
9476 if (IS_ANIMATED(graphic))
9477 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9482 // do not re-open exit door closed after last player
9483 if (game.all_players_gone)
9486 Tile[x][y] = EL_EM_EXIT_OPENING;
9488 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9491 static void CheckExitSteel(int x, int y)
9493 if (game.gems_still_needed > 0 ||
9494 game.sokoban_fields_still_needed > 0 ||
9495 game.sokoban_objects_still_needed > 0 ||
9496 game.lights_still_needed > 0)
9498 int element = Tile[x][y];
9499 int graphic = el2img(element);
9501 if (IS_ANIMATED(graphic))
9502 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9507 // do not re-open exit door closed after last player
9508 if (game.all_players_gone)
9511 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9513 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9516 static void CheckExitSteelEM(int x, int y)
9518 if (game.gems_still_needed > 0 ||
9519 game.sokoban_fields_still_needed > 0 ||
9520 game.sokoban_objects_still_needed > 0 ||
9521 game.lights_still_needed > 0)
9523 int element = Tile[x][y];
9524 int graphic = el2img(element);
9526 if (IS_ANIMATED(graphic))
9527 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9532 // do not re-open exit door closed after last player
9533 if (game.all_players_gone)
9536 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9538 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9541 static void CheckExitSP(int x, int y)
9543 if (game.gems_still_needed > 0)
9545 int element = Tile[x][y];
9546 int graphic = el2img(element);
9548 if (IS_ANIMATED(graphic))
9549 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9554 // do not re-open exit door closed after last player
9555 if (game.all_players_gone)
9558 Tile[x][y] = EL_SP_EXIT_OPENING;
9560 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9563 static void CloseAllOpenTimegates(void)
9567 SCAN_PLAYFIELD(x, y)
9569 int element = Tile[x][y];
9571 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9573 Tile[x][y] = EL_TIMEGATE_CLOSING;
9575 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9580 static void DrawTwinkleOnField(int x, int y)
9582 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9585 if (Tile[x][y] == EL_BD_DIAMOND)
9588 if (MovDelay[x][y] == 0) // next animation frame
9589 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9591 if (MovDelay[x][y] != 0) // wait some time before next frame
9595 DrawLevelElementAnimation(x, y, Tile[x][y]);
9597 if (MovDelay[x][y] != 0)
9599 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9600 10 - MovDelay[x][y]);
9602 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9607 static void MauerWaechst(int x, int y)
9611 if (!MovDelay[x][y]) // next animation frame
9612 MovDelay[x][y] = 3 * delay;
9614 if (MovDelay[x][y]) // wait some time before next frame
9618 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9620 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9621 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9623 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9626 if (!MovDelay[x][y])
9628 if (MovDir[x][y] == MV_LEFT)
9630 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9631 TEST_DrawLevelField(x - 1, y);
9633 else if (MovDir[x][y] == MV_RIGHT)
9635 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9636 TEST_DrawLevelField(x + 1, y);
9638 else if (MovDir[x][y] == MV_UP)
9640 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9641 TEST_DrawLevelField(x, y - 1);
9645 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9646 TEST_DrawLevelField(x, y + 1);
9649 Tile[x][y] = Store[x][y];
9651 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9652 TEST_DrawLevelField(x, y);
9657 static void MauerAbleger(int ax, int ay)
9659 int element = Tile[ax][ay];
9660 int graphic = el2img(element);
9661 boolean oben_frei = FALSE, unten_frei = FALSE;
9662 boolean links_frei = FALSE, rechts_frei = FALSE;
9663 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9664 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9665 boolean new_wall = FALSE;
9667 if (IS_ANIMATED(graphic))
9668 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9670 if (!MovDelay[ax][ay]) // start building new wall
9671 MovDelay[ax][ay] = 6;
9673 if (MovDelay[ax][ay]) // wait some time before building new wall
9676 if (MovDelay[ax][ay])
9680 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9682 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9684 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9686 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9689 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9690 element == EL_EXPANDABLE_WALL_ANY)
9694 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9695 Store[ax][ay-1] = element;
9696 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9697 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9698 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9699 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9704 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9705 Store[ax][ay+1] = element;
9706 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9707 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9708 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9709 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9714 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9715 element == EL_EXPANDABLE_WALL_ANY ||
9716 element == EL_EXPANDABLE_WALL ||
9717 element == EL_BD_EXPANDABLE_WALL)
9721 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9722 Store[ax-1][ay] = element;
9723 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9724 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9725 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9726 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9732 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9733 Store[ax+1][ay] = element;
9734 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9735 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9736 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9737 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9742 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9743 TEST_DrawLevelField(ax, ay);
9745 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9747 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9748 unten_massiv = TRUE;
9749 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9750 links_massiv = TRUE;
9751 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9752 rechts_massiv = TRUE;
9754 if (((oben_massiv && unten_massiv) ||
9755 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9756 element == EL_EXPANDABLE_WALL) &&
9757 ((links_massiv && rechts_massiv) ||
9758 element == EL_EXPANDABLE_WALL_VERTICAL))
9759 Tile[ax][ay] = EL_WALL;
9762 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9765 static void MauerAblegerStahl(int ax, int ay)
9767 int element = Tile[ax][ay];
9768 int graphic = el2img(element);
9769 boolean oben_frei = FALSE, unten_frei = FALSE;
9770 boolean links_frei = FALSE, rechts_frei = FALSE;
9771 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9772 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9773 boolean new_wall = FALSE;
9775 if (IS_ANIMATED(graphic))
9776 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9778 if (!MovDelay[ax][ay]) // start building new wall
9779 MovDelay[ax][ay] = 6;
9781 if (MovDelay[ax][ay]) // wait some time before building new wall
9784 if (MovDelay[ax][ay])
9788 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9790 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9792 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9794 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9797 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9798 element == EL_EXPANDABLE_STEELWALL_ANY)
9802 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9803 Store[ax][ay-1] = element;
9804 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9805 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9806 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9807 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9812 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9813 Store[ax][ay+1] = element;
9814 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9815 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9816 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9817 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9822 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9823 element == EL_EXPANDABLE_STEELWALL_ANY)
9827 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9828 Store[ax-1][ay] = element;
9829 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9830 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9831 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9832 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9838 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9839 Store[ax+1][ay] = element;
9840 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9841 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9842 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9843 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9848 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9850 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9851 unten_massiv = TRUE;
9852 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9853 links_massiv = TRUE;
9854 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9855 rechts_massiv = TRUE;
9857 if (((oben_massiv && unten_massiv) ||
9858 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9859 ((links_massiv && rechts_massiv) ||
9860 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9861 Tile[ax][ay] = EL_STEELWALL;
9864 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9867 static void CheckForDragon(int x, int y)
9870 boolean dragon_found = FALSE;
9871 static int xy[4][2] =
9879 for (i = 0; i < NUM_DIRECTIONS; i++)
9881 for (j = 0; j < 4; j++)
9883 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9885 if (IN_LEV_FIELD(xx, yy) &&
9886 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9888 if (Tile[xx][yy] == EL_DRAGON)
9889 dragon_found = TRUE;
9898 for (i = 0; i < NUM_DIRECTIONS; i++)
9900 for (j = 0; j < 3; j++)
9902 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9904 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9906 Tile[xx][yy] = EL_EMPTY;
9907 TEST_DrawLevelField(xx, yy);
9916 static void InitBuggyBase(int x, int y)
9918 int element = Tile[x][y];
9919 int activating_delay = FRAMES_PER_SECOND / 4;
9922 (element == EL_SP_BUGGY_BASE ?
9923 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9924 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9926 element == EL_SP_BUGGY_BASE_ACTIVE ?
9927 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9930 static void WarnBuggyBase(int x, int y)
9933 static int xy[4][2] =
9941 for (i = 0; i < NUM_DIRECTIONS; i++)
9943 int xx = x + xy[i][0];
9944 int yy = y + xy[i][1];
9946 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9948 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9955 static void InitTrap(int x, int y)
9957 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9960 static void ActivateTrap(int x, int y)
9962 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9965 static void ChangeActiveTrap(int x, int y)
9967 int graphic = IMG_TRAP_ACTIVE;
9969 // if new animation frame was drawn, correct crumbled sand border
9970 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9971 TEST_DrawLevelFieldCrumbled(x, y);
9974 static int getSpecialActionElement(int element, int number, int base_element)
9976 return (element != EL_EMPTY ? element :
9977 number != -1 ? base_element + number - 1 :
9981 static int getModifiedActionNumber(int value_old, int operator, int operand,
9982 int value_min, int value_max)
9984 int value_new = (operator == CA_MODE_SET ? operand :
9985 operator == CA_MODE_ADD ? value_old + operand :
9986 operator == CA_MODE_SUBTRACT ? value_old - operand :
9987 operator == CA_MODE_MULTIPLY ? value_old * operand :
9988 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9989 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9992 return (value_new < value_min ? value_min :
9993 value_new > value_max ? value_max :
9997 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9999 struct ElementInfo *ei = &element_info[element];
10000 struct ElementChangeInfo *change = &ei->change_page[page];
10001 int target_element = change->target_element;
10002 int action_type = change->action_type;
10003 int action_mode = change->action_mode;
10004 int action_arg = change->action_arg;
10005 int action_element = change->action_element;
10008 if (!change->has_action)
10011 // ---------- determine action paramater values -----------------------------
10013 int level_time_value =
10014 (level.time > 0 ? TimeLeft :
10017 int action_arg_element_raw =
10018 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10019 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10020 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10021 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10022 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10023 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10024 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10026 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10028 int action_arg_direction =
10029 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10030 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10031 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10032 change->actual_trigger_side :
10033 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10034 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10037 int action_arg_number_min =
10038 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10041 int action_arg_number_max =
10042 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10043 action_type == CA_SET_LEVEL_GEMS ? 999 :
10044 action_type == CA_SET_LEVEL_TIME ? 9999 :
10045 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10046 action_type == CA_SET_CE_VALUE ? 9999 :
10047 action_type == CA_SET_CE_SCORE ? 9999 :
10050 int action_arg_number_reset =
10051 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10052 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10053 action_type == CA_SET_LEVEL_TIME ? level.time :
10054 action_type == CA_SET_LEVEL_SCORE ? 0 :
10055 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10056 action_type == CA_SET_CE_SCORE ? 0 :
10059 int action_arg_number =
10060 (action_arg <= CA_ARG_MAX ? action_arg :
10061 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10062 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10063 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10064 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10065 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10066 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10067 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10068 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10069 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10070 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10071 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10072 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10073 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10074 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10075 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10076 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10077 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10078 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10079 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10080 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10081 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10084 int action_arg_number_old =
10085 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10086 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10087 action_type == CA_SET_LEVEL_SCORE ? game.score :
10088 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10089 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10092 int action_arg_number_new =
10093 getModifiedActionNumber(action_arg_number_old,
10094 action_mode, action_arg_number,
10095 action_arg_number_min, action_arg_number_max);
10097 int trigger_player_bits =
10098 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10099 change->actual_trigger_player_bits : change->trigger_player);
10101 int action_arg_player_bits =
10102 (action_arg >= CA_ARG_PLAYER_1 &&
10103 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10104 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10105 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10108 // ---------- execute action -----------------------------------------------
10110 switch (action_type)
10117 // ---------- level actions ----------------------------------------------
10119 case CA_RESTART_LEVEL:
10121 game.restart_level = TRUE;
10126 case CA_SHOW_ENVELOPE:
10128 int element = getSpecialActionElement(action_arg_element,
10129 action_arg_number, EL_ENVELOPE_1);
10131 if (IS_ENVELOPE(element))
10132 local_player->show_envelope = element;
10137 case CA_SET_LEVEL_TIME:
10139 if (level.time > 0) // only modify limited time value
10141 TimeLeft = action_arg_number_new;
10143 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10145 DisplayGameControlValues();
10147 if (!TimeLeft && setup.time_limit)
10148 for (i = 0; i < MAX_PLAYERS; i++)
10149 KillPlayer(&stored_player[i]);
10155 case CA_SET_LEVEL_SCORE:
10157 game.score = action_arg_number_new;
10159 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10161 DisplayGameControlValues();
10166 case CA_SET_LEVEL_GEMS:
10168 game.gems_still_needed = action_arg_number_new;
10170 game.snapshot.collected_item = TRUE;
10172 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10174 DisplayGameControlValues();
10179 case CA_SET_LEVEL_WIND:
10181 game.wind_direction = action_arg_direction;
10186 case CA_SET_LEVEL_RANDOM_SEED:
10188 // ensure that setting a new random seed while playing is predictable
10189 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10194 // ---------- player actions ---------------------------------------------
10196 case CA_MOVE_PLAYER:
10197 case CA_MOVE_PLAYER_NEW:
10199 // automatically move to the next field in specified direction
10200 for (i = 0; i < MAX_PLAYERS; i++)
10201 if (trigger_player_bits & (1 << i))
10202 if (action_type == CA_MOVE_PLAYER ||
10203 stored_player[i].MovPos == 0)
10204 stored_player[i].programmed_action = action_arg_direction;
10209 case CA_EXIT_PLAYER:
10211 for (i = 0; i < MAX_PLAYERS; i++)
10212 if (action_arg_player_bits & (1 << i))
10213 ExitPlayer(&stored_player[i]);
10215 if (game.players_still_needed == 0)
10221 case CA_KILL_PLAYER:
10223 for (i = 0; i < MAX_PLAYERS; i++)
10224 if (action_arg_player_bits & (1 << i))
10225 KillPlayer(&stored_player[i]);
10230 case CA_SET_PLAYER_KEYS:
10232 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10233 int element = getSpecialActionElement(action_arg_element,
10234 action_arg_number, EL_KEY_1);
10236 if (IS_KEY(element))
10238 for (i = 0; i < MAX_PLAYERS; i++)
10240 if (trigger_player_bits & (1 << i))
10242 stored_player[i].key[KEY_NR(element)] = key_state;
10244 DrawGameDoorValues();
10252 case CA_SET_PLAYER_SPEED:
10254 for (i = 0; i < MAX_PLAYERS; i++)
10256 if (trigger_player_bits & (1 << i))
10258 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10260 if (action_arg == CA_ARG_SPEED_FASTER &&
10261 stored_player[i].cannot_move)
10263 action_arg_number = STEPSIZE_VERY_SLOW;
10265 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10266 action_arg == CA_ARG_SPEED_FASTER)
10268 action_arg_number = 2;
10269 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10272 else if (action_arg == CA_ARG_NUMBER_RESET)
10274 action_arg_number = level.initial_player_stepsize[i];
10278 getModifiedActionNumber(move_stepsize,
10281 action_arg_number_min,
10282 action_arg_number_max);
10284 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10291 case CA_SET_PLAYER_SHIELD:
10293 for (i = 0; i < MAX_PLAYERS; i++)
10295 if (trigger_player_bits & (1 << i))
10297 if (action_arg == CA_ARG_SHIELD_OFF)
10299 stored_player[i].shield_normal_time_left = 0;
10300 stored_player[i].shield_deadly_time_left = 0;
10302 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10304 stored_player[i].shield_normal_time_left = 999999;
10306 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10308 stored_player[i].shield_normal_time_left = 999999;
10309 stored_player[i].shield_deadly_time_left = 999999;
10317 case CA_SET_PLAYER_GRAVITY:
10319 for (i = 0; i < MAX_PLAYERS; i++)
10321 if (trigger_player_bits & (1 << i))
10323 stored_player[i].gravity =
10324 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10325 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10326 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10327 stored_player[i].gravity);
10334 case CA_SET_PLAYER_ARTWORK:
10336 for (i = 0; i < MAX_PLAYERS; i++)
10338 if (trigger_player_bits & (1 << i))
10340 int artwork_element = action_arg_element;
10342 if (action_arg == CA_ARG_ELEMENT_RESET)
10344 (level.use_artwork_element[i] ? level.artwork_element[i] :
10345 stored_player[i].element_nr);
10347 if (stored_player[i].artwork_element != artwork_element)
10348 stored_player[i].Frame = 0;
10350 stored_player[i].artwork_element = artwork_element;
10352 SetPlayerWaiting(&stored_player[i], FALSE);
10354 // set number of special actions for bored and sleeping animation
10355 stored_player[i].num_special_action_bored =
10356 get_num_special_action(artwork_element,
10357 ACTION_BORING_1, ACTION_BORING_LAST);
10358 stored_player[i].num_special_action_sleeping =
10359 get_num_special_action(artwork_element,
10360 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10367 case CA_SET_PLAYER_INVENTORY:
10369 for (i = 0; i < MAX_PLAYERS; i++)
10371 struct PlayerInfo *player = &stored_player[i];
10374 if (trigger_player_bits & (1 << i))
10376 int inventory_element = action_arg_element;
10378 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10379 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10380 action_arg == CA_ARG_ELEMENT_ACTION)
10382 int element = inventory_element;
10383 int collect_count = element_info[element].collect_count_initial;
10385 if (!IS_CUSTOM_ELEMENT(element))
10388 if (collect_count == 0)
10389 player->inventory_infinite_element = element;
10391 for (k = 0; k < collect_count; k++)
10392 if (player->inventory_size < MAX_INVENTORY_SIZE)
10393 player->inventory_element[player->inventory_size++] =
10396 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10397 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10398 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10400 if (player->inventory_infinite_element != EL_UNDEFINED &&
10401 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10402 action_arg_element_raw))
10403 player->inventory_infinite_element = EL_UNDEFINED;
10405 for (k = 0, j = 0; j < player->inventory_size; j++)
10407 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10408 action_arg_element_raw))
10409 player->inventory_element[k++] = player->inventory_element[j];
10412 player->inventory_size = k;
10414 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10416 if (player->inventory_size > 0)
10418 for (j = 0; j < player->inventory_size - 1; j++)
10419 player->inventory_element[j] = player->inventory_element[j + 1];
10421 player->inventory_size--;
10424 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10426 if (player->inventory_size > 0)
10427 player->inventory_size--;
10429 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10431 player->inventory_infinite_element = EL_UNDEFINED;
10432 player->inventory_size = 0;
10434 else if (action_arg == CA_ARG_INVENTORY_RESET)
10436 player->inventory_infinite_element = EL_UNDEFINED;
10437 player->inventory_size = 0;
10439 if (level.use_initial_inventory[i])
10441 for (j = 0; j < level.initial_inventory_size[i]; j++)
10443 int element = level.initial_inventory_content[i][j];
10444 int collect_count = element_info[element].collect_count_initial;
10446 if (!IS_CUSTOM_ELEMENT(element))
10449 if (collect_count == 0)
10450 player->inventory_infinite_element = element;
10452 for (k = 0; k < collect_count; k++)
10453 if (player->inventory_size < MAX_INVENTORY_SIZE)
10454 player->inventory_element[player->inventory_size++] =
10465 // ---------- CE actions -------------------------------------------------
10467 case CA_SET_CE_VALUE:
10469 int last_ce_value = CustomValue[x][y];
10471 CustomValue[x][y] = action_arg_number_new;
10473 if (CustomValue[x][y] != last_ce_value)
10475 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10476 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10478 if (CustomValue[x][y] == 0)
10480 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10481 ChangeCount[x][y] = 0; // allow at least one more change
10483 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10484 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10491 case CA_SET_CE_SCORE:
10493 int last_ce_score = ei->collect_score;
10495 ei->collect_score = action_arg_number_new;
10497 if (ei->collect_score != last_ce_score)
10499 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10500 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10502 if (ei->collect_score == 0)
10506 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10507 ChangeCount[x][y] = 0; // allow at least one more change
10509 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10510 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10513 This is a very special case that seems to be a mixture between
10514 CheckElementChange() and CheckTriggeredElementChange(): while
10515 the first one only affects single elements that are triggered
10516 directly, the second one affects multiple elements in the playfield
10517 that are triggered indirectly by another element. This is a third
10518 case: Changing the CE score always affects multiple identical CEs,
10519 so every affected CE must be checked, not only the single CE for
10520 which the CE score was changed in the first place (as every instance
10521 of that CE shares the same CE score, and therefore also can change)!
10523 SCAN_PLAYFIELD(xx, yy)
10525 if (Tile[xx][yy] == element)
10526 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10527 CE_SCORE_GETS_ZERO);
10535 case CA_SET_CE_ARTWORK:
10537 int artwork_element = action_arg_element;
10538 boolean reset_frame = FALSE;
10541 if (action_arg == CA_ARG_ELEMENT_RESET)
10542 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10545 if (ei->gfx_element != artwork_element)
10546 reset_frame = TRUE;
10548 ei->gfx_element = artwork_element;
10550 SCAN_PLAYFIELD(xx, yy)
10552 if (Tile[xx][yy] == element)
10556 ResetGfxAnimation(xx, yy);
10557 ResetRandomAnimationValue(xx, yy);
10560 TEST_DrawLevelField(xx, yy);
10567 // ---------- engine actions ---------------------------------------------
10569 case CA_SET_ENGINE_SCAN_MODE:
10571 InitPlayfieldScanMode(action_arg);
10581 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10583 int old_element = Tile[x][y];
10584 int new_element = GetElementFromGroupElement(element);
10585 int previous_move_direction = MovDir[x][y];
10586 int last_ce_value = CustomValue[x][y];
10587 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10588 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10589 boolean add_player_onto_element = (new_element_is_player &&
10590 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10591 IS_WALKABLE(old_element));
10593 if (!add_player_onto_element)
10595 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10596 RemoveMovingField(x, y);
10600 Tile[x][y] = new_element;
10602 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10603 MovDir[x][y] = previous_move_direction;
10605 if (element_info[new_element].use_last_ce_value)
10606 CustomValue[x][y] = last_ce_value;
10608 InitField_WithBug1(x, y, FALSE);
10610 new_element = Tile[x][y]; // element may have changed
10612 ResetGfxAnimation(x, y);
10613 ResetRandomAnimationValue(x, y);
10615 TEST_DrawLevelField(x, y);
10617 if (GFX_CRUMBLED(new_element))
10618 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10621 // check if element under the player changes from accessible to unaccessible
10622 // (needed for special case of dropping element which then changes)
10623 // (must be checked after creating new element for walkable group elements)
10624 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10625 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10632 // "ChangeCount" not set yet to allow "entered by player" change one time
10633 if (new_element_is_player)
10634 RelocatePlayer(x, y, new_element);
10637 ChangeCount[x][y]++; // count number of changes in the same frame
10639 TestIfBadThingTouchesPlayer(x, y);
10640 TestIfPlayerTouchesCustomElement(x, y);
10641 TestIfElementTouchesCustomElement(x, y);
10644 static void CreateField(int x, int y, int element)
10646 CreateFieldExt(x, y, element, FALSE);
10649 static void CreateElementFromChange(int x, int y, int element)
10651 element = GET_VALID_RUNTIME_ELEMENT(element);
10653 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10655 int old_element = Tile[x][y];
10657 // prevent changed element from moving in same engine frame
10658 // unless both old and new element can either fall or move
10659 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10660 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10664 CreateFieldExt(x, y, element, TRUE);
10667 static boolean ChangeElement(int x, int y, int element, int page)
10669 struct ElementInfo *ei = &element_info[element];
10670 struct ElementChangeInfo *change = &ei->change_page[page];
10671 int ce_value = CustomValue[x][y];
10672 int ce_score = ei->collect_score;
10673 int target_element;
10674 int old_element = Tile[x][y];
10676 // always use default change event to prevent running into a loop
10677 if (ChangeEvent[x][y] == -1)
10678 ChangeEvent[x][y] = CE_DELAY;
10680 if (ChangeEvent[x][y] == CE_DELAY)
10682 // reset actual trigger element, trigger player and action element
10683 change->actual_trigger_element = EL_EMPTY;
10684 change->actual_trigger_player = EL_EMPTY;
10685 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10686 change->actual_trigger_side = CH_SIDE_NONE;
10687 change->actual_trigger_ce_value = 0;
10688 change->actual_trigger_ce_score = 0;
10691 // do not change elements more than a specified maximum number of changes
10692 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10695 ChangeCount[x][y]++; // count number of changes in the same frame
10697 if (change->explode)
10704 if (change->use_target_content)
10706 boolean complete_replace = TRUE;
10707 boolean can_replace[3][3];
10710 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10713 boolean is_walkable;
10714 boolean is_diggable;
10715 boolean is_collectible;
10716 boolean is_removable;
10717 boolean is_destructible;
10718 int ex = x + xx - 1;
10719 int ey = y + yy - 1;
10720 int content_element = change->target_content.e[xx][yy];
10723 can_replace[xx][yy] = TRUE;
10725 if (ex == x && ey == y) // do not check changing element itself
10728 if (content_element == EL_EMPTY_SPACE)
10730 can_replace[xx][yy] = FALSE; // do not replace border with space
10735 if (!IN_LEV_FIELD(ex, ey))
10737 can_replace[xx][yy] = FALSE;
10738 complete_replace = FALSE;
10745 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10746 e = MovingOrBlocked2Element(ex, ey);
10748 is_empty = (IS_FREE(ex, ey) ||
10749 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10751 is_walkable = (is_empty || IS_WALKABLE(e));
10752 is_diggable = (is_empty || IS_DIGGABLE(e));
10753 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10754 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10755 is_removable = (is_diggable || is_collectible);
10757 can_replace[xx][yy] =
10758 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10759 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10760 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10761 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10762 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10763 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10764 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10766 if (!can_replace[xx][yy])
10767 complete_replace = FALSE;
10770 if (!change->only_if_complete || complete_replace)
10772 boolean something_has_changed = FALSE;
10774 if (change->only_if_complete && change->use_random_replace &&
10775 RND(100) < change->random_percentage)
10778 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10780 int ex = x + xx - 1;
10781 int ey = y + yy - 1;
10782 int content_element;
10784 if (can_replace[xx][yy] && (!change->use_random_replace ||
10785 RND(100) < change->random_percentage))
10787 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10788 RemoveMovingField(ex, ey);
10790 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10792 content_element = change->target_content.e[xx][yy];
10793 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10794 ce_value, ce_score);
10796 CreateElementFromChange(ex, ey, target_element);
10798 something_has_changed = TRUE;
10800 // for symmetry reasons, freeze newly created border elements
10801 if (ex != x || ey != y)
10802 Stop[ex][ey] = TRUE; // no more moving in this frame
10806 if (something_has_changed)
10808 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10809 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10815 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10816 ce_value, ce_score);
10818 if (element == EL_DIAGONAL_GROWING ||
10819 element == EL_DIAGONAL_SHRINKING)
10821 target_element = Store[x][y];
10823 Store[x][y] = EL_EMPTY;
10826 CreateElementFromChange(x, y, target_element);
10828 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10829 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10832 // this uses direct change before indirect change
10833 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10838 static void HandleElementChange(int x, int y, int page)
10840 int element = MovingOrBlocked2Element(x, y);
10841 struct ElementInfo *ei = &element_info[element];
10842 struct ElementChangeInfo *change = &ei->change_page[page];
10843 boolean handle_action_before_change = FALSE;
10846 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10847 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10849 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10850 x, y, element, element_info[element].token_name);
10851 Debug("game:playing:HandleElementChange", "This should never happen!");
10855 // this can happen with classic bombs on walkable, changing elements
10856 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10861 if (ChangeDelay[x][y] == 0) // initialize element change
10863 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10865 if (change->can_change)
10867 // !!! not clear why graphic animation should be reset at all here !!!
10868 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10869 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10872 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10874 When using an animation frame delay of 1 (this only happens with
10875 "sp_zonk.moving.left/right" in the classic graphics), the default
10876 (non-moving) animation shows wrong animation frames (while the
10877 moving animation, like "sp_zonk.moving.left/right", is correct,
10878 so this graphical bug never shows up with the classic graphics).
10879 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10880 be drawn instead of the correct frames 0,1,2,3. This is caused by
10881 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10882 an element change: First when the change delay ("ChangeDelay[][]")
10883 counter has reached zero after decrementing, then a second time in
10884 the next frame (after "GfxFrame[][]" was already incremented) when
10885 "ChangeDelay[][]" is reset to the initial delay value again.
10887 This causes frame 0 to be drawn twice, while the last frame won't
10888 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10890 As some animations may already be cleverly designed around this bug
10891 (at least the "Snake Bite" snake tail animation does this), it cannot
10892 simply be fixed here without breaking such existing animations.
10893 Unfortunately, it cannot easily be detected if a graphics set was
10894 designed "before" or "after" the bug was fixed. As a workaround,
10895 a new graphics set option "game.graphics_engine_version" was added
10896 to be able to specify the game's major release version for which the
10897 graphics set was designed, which can then be used to decide if the
10898 bugfix should be used (version 4 and above) or not (version 3 or
10899 below, or if no version was specified at all, as with old sets).
10901 (The wrong/fixed animation frames can be tested with the test level set
10902 "test_gfxframe" and level "000", which contains a specially prepared
10903 custom element at level position (x/y) == (11/9) which uses the zonk
10904 animation mentioned above. Using "game.graphics_engine_version: 4"
10905 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10906 This can also be seen from the debug output for this test element.)
10909 // when a custom element is about to change (for example by change delay),
10910 // do not reset graphic animation when the custom element is moving
10911 if (game.graphics_engine_version < 4 &&
10914 ResetGfxAnimation(x, y);
10915 ResetRandomAnimationValue(x, y);
10918 if (change->pre_change_function)
10919 change->pre_change_function(x, y);
10923 ChangeDelay[x][y]--;
10925 if (ChangeDelay[x][y] != 0) // continue element change
10927 if (change->can_change)
10929 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10931 if (IS_ANIMATED(graphic))
10932 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10934 if (change->change_function)
10935 change->change_function(x, y);
10938 else // finish element change
10940 if (ChangePage[x][y] != -1) // remember page from delayed change
10942 page = ChangePage[x][y];
10943 ChangePage[x][y] = -1;
10945 change = &ei->change_page[page];
10948 if (IS_MOVING(x, y)) // never change a running system ;-)
10950 ChangeDelay[x][y] = 1; // try change after next move step
10951 ChangePage[x][y] = page; // remember page to use for change
10956 // special case: set new level random seed before changing element
10957 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10958 handle_action_before_change = TRUE;
10960 if (change->has_action && handle_action_before_change)
10961 ExecuteCustomElementAction(x, y, element, page);
10963 if (change->can_change)
10965 if (ChangeElement(x, y, element, page))
10967 if (change->post_change_function)
10968 change->post_change_function(x, y);
10972 if (change->has_action && !handle_action_before_change)
10973 ExecuteCustomElementAction(x, y, element, page);
10977 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10978 int trigger_element,
10980 int trigger_player,
10984 boolean change_done_any = FALSE;
10985 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10988 if (!(trigger_events[trigger_element][trigger_event]))
10991 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10993 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10995 int element = EL_CUSTOM_START + i;
10996 boolean change_done = FALSE;
10999 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11000 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11003 for (p = 0; p < element_info[element].num_change_pages; p++)
11005 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11007 if (change->can_change_or_has_action &&
11008 change->has_event[trigger_event] &&
11009 change->trigger_side & trigger_side &&
11010 change->trigger_player & trigger_player &&
11011 change->trigger_page & trigger_page_bits &&
11012 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11014 change->actual_trigger_element = trigger_element;
11015 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11016 change->actual_trigger_player_bits = trigger_player;
11017 change->actual_trigger_side = trigger_side;
11018 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11019 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11021 if ((change->can_change && !change_done) || change->has_action)
11025 SCAN_PLAYFIELD(x, y)
11027 if (Tile[x][y] == element)
11029 if (change->can_change && !change_done)
11031 // if element already changed in this frame, not only prevent
11032 // another element change (checked in ChangeElement()), but
11033 // also prevent additional element actions for this element
11035 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11036 !level.use_action_after_change_bug)
11039 ChangeDelay[x][y] = 1;
11040 ChangeEvent[x][y] = trigger_event;
11042 HandleElementChange(x, y, p);
11044 else if (change->has_action)
11046 // if element already changed in this frame, not only prevent
11047 // another element change (checked in ChangeElement()), but
11048 // also prevent additional element actions for this element
11050 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11051 !level.use_action_after_change_bug)
11054 ExecuteCustomElementAction(x, y, element, p);
11055 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11060 if (change->can_change)
11062 change_done = TRUE;
11063 change_done_any = TRUE;
11070 RECURSION_LOOP_DETECTION_END();
11072 return change_done_any;
11075 static boolean CheckElementChangeExt(int x, int y,
11077 int trigger_element,
11079 int trigger_player,
11082 boolean change_done = FALSE;
11085 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11086 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11089 if (Tile[x][y] == EL_BLOCKED)
11091 Blocked2Moving(x, y, &x, &y);
11092 element = Tile[x][y];
11095 // check if element has already changed or is about to change after moving
11096 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11097 Tile[x][y] != element) ||
11099 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11100 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11101 ChangePage[x][y] != -1)))
11104 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11106 for (p = 0; p < element_info[element].num_change_pages; p++)
11108 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11110 /* check trigger element for all events where the element that is checked
11111 for changing interacts with a directly adjacent element -- this is
11112 different to element changes that affect other elements to change on the
11113 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11114 boolean check_trigger_element =
11115 (trigger_event == CE_TOUCHING_X ||
11116 trigger_event == CE_HITTING_X ||
11117 trigger_event == CE_HIT_BY_X ||
11118 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11120 if (change->can_change_or_has_action &&
11121 change->has_event[trigger_event] &&
11122 change->trigger_side & trigger_side &&
11123 change->trigger_player & trigger_player &&
11124 (!check_trigger_element ||
11125 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11127 change->actual_trigger_element = trigger_element;
11128 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11129 change->actual_trigger_player_bits = trigger_player;
11130 change->actual_trigger_side = trigger_side;
11131 change->actual_trigger_ce_value = CustomValue[x][y];
11132 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11134 // special case: trigger element not at (x,y) position for some events
11135 if (check_trigger_element)
11147 { 0, 0 }, { 0, 0 }, { 0, 0 },
11151 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11152 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11154 change->actual_trigger_ce_value = CustomValue[xx][yy];
11155 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11158 if (change->can_change && !change_done)
11160 ChangeDelay[x][y] = 1;
11161 ChangeEvent[x][y] = trigger_event;
11163 HandleElementChange(x, y, p);
11165 change_done = TRUE;
11167 else if (change->has_action)
11169 ExecuteCustomElementAction(x, y, element, p);
11170 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11175 RECURSION_LOOP_DETECTION_END();
11177 return change_done;
11180 static void PlayPlayerSound(struct PlayerInfo *player)
11182 int jx = player->jx, jy = player->jy;
11183 int sound_element = player->artwork_element;
11184 int last_action = player->last_action_waiting;
11185 int action = player->action_waiting;
11187 if (player->is_waiting)
11189 if (action != last_action)
11190 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11192 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11196 if (action != last_action)
11197 StopSound(element_info[sound_element].sound[last_action]);
11199 if (last_action == ACTION_SLEEPING)
11200 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11204 static void PlayAllPlayersSound(void)
11208 for (i = 0; i < MAX_PLAYERS; i++)
11209 if (stored_player[i].active)
11210 PlayPlayerSound(&stored_player[i]);
11213 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11215 boolean last_waiting = player->is_waiting;
11216 int move_dir = player->MovDir;
11218 player->dir_waiting = move_dir;
11219 player->last_action_waiting = player->action_waiting;
11223 if (!last_waiting) // not waiting -> waiting
11225 player->is_waiting = TRUE;
11227 player->frame_counter_bored =
11229 game.player_boring_delay_fixed +
11230 GetSimpleRandom(game.player_boring_delay_random);
11231 player->frame_counter_sleeping =
11233 game.player_sleeping_delay_fixed +
11234 GetSimpleRandom(game.player_sleeping_delay_random);
11236 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11239 if (game.player_sleeping_delay_fixed +
11240 game.player_sleeping_delay_random > 0 &&
11241 player->anim_delay_counter == 0 &&
11242 player->post_delay_counter == 0 &&
11243 FrameCounter >= player->frame_counter_sleeping)
11244 player->is_sleeping = TRUE;
11245 else if (game.player_boring_delay_fixed +
11246 game.player_boring_delay_random > 0 &&
11247 FrameCounter >= player->frame_counter_bored)
11248 player->is_bored = TRUE;
11250 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11251 player->is_bored ? ACTION_BORING :
11254 if (player->is_sleeping && player->use_murphy)
11256 // special case for sleeping Murphy when leaning against non-free tile
11258 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11259 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11260 !IS_MOVING(player->jx - 1, player->jy)))
11261 move_dir = MV_LEFT;
11262 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11263 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11264 !IS_MOVING(player->jx + 1, player->jy)))
11265 move_dir = MV_RIGHT;
11267 player->is_sleeping = FALSE;
11269 player->dir_waiting = move_dir;
11272 if (player->is_sleeping)
11274 if (player->num_special_action_sleeping > 0)
11276 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11278 int last_special_action = player->special_action_sleeping;
11279 int num_special_action = player->num_special_action_sleeping;
11280 int special_action =
11281 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11282 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11283 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11284 last_special_action + 1 : ACTION_SLEEPING);
11285 int special_graphic =
11286 el_act_dir2img(player->artwork_element, special_action, move_dir);
11288 player->anim_delay_counter =
11289 graphic_info[special_graphic].anim_delay_fixed +
11290 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11291 player->post_delay_counter =
11292 graphic_info[special_graphic].post_delay_fixed +
11293 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11295 player->special_action_sleeping = special_action;
11298 if (player->anim_delay_counter > 0)
11300 player->action_waiting = player->special_action_sleeping;
11301 player->anim_delay_counter--;
11303 else if (player->post_delay_counter > 0)
11305 player->post_delay_counter--;
11309 else if (player->is_bored)
11311 if (player->num_special_action_bored > 0)
11313 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11315 int special_action =
11316 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11317 int special_graphic =
11318 el_act_dir2img(player->artwork_element, special_action, move_dir);
11320 player->anim_delay_counter =
11321 graphic_info[special_graphic].anim_delay_fixed +
11322 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11323 player->post_delay_counter =
11324 graphic_info[special_graphic].post_delay_fixed +
11325 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11327 player->special_action_bored = special_action;
11330 if (player->anim_delay_counter > 0)
11332 player->action_waiting = player->special_action_bored;
11333 player->anim_delay_counter--;
11335 else if (player->post_delay_counter > 0)
11337 player->post_delay_counter--;
11342 else if (last_waiting) // waiting -> not waiting
11344 player->is_waiting = FALSE;
11345 player->is_bored = FALSE;
11346 player->is_sleeping = FALSE;
11348 player->frame_counter_bored = -1;
11349 player->frame_counter_sleeping = -1;
11351 player->anim_delay_counter = 0;
11352 player->post_delay_counter = 0;
11354 player->dir_waiting = player->MovDir;
11355 player->action_waiting = ACTION_DEFAULT;
11357 player->special_action_bored = ACTION_DEFAULT;
11358 player->special_action_sleeping = ACTION_DEFAULT;
11362 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11364 if ((!player->is_moving && player->was_moving) ||
11365 (player->MovPos == 0 && player->was_moving) ||
11366 (player->is_snapping && !player->was_snapping) ||
11367 (player->is_dropping && !player->was_dropping))
11369 if (!CheckSaveEngineSnapshotToList())
11372 player->was_moving = FALSE;
11373 player->was_snapping = TRUE;
11374 player->was_dropping = TRUE;
11378 if (player->is_moving)
11379 player->was_moving = TRUE;
11381 if (!player->is_snapping)
11382 player->was_snapping = FALSE;
11384 if (!player->is_dropping)
11385 player->was_dropping = FALSE;
11388 static struct MouseActionInfo mouse_action_last = { 0 };
11389 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11390 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11393 CheckSaveEngineSnapshotToList();
11395 mouse_action_last = mouse_action;
11398 static void CheckSingleStepMode(struct PlayerInfo *player)
11400 if (tape.single_step && tape.recording && !tape.pausing)
11402 // as it is called "single step mode", just return to pause mode when the
11403 // player stopped moving after one tile (or never starts moving at all)
11404 // (reverse logic needed here in case single step mode used in team mode)
11405 if (player->is_moving ||
11406 player->is_pushing ||
11407 player->is_dropping_pressed ||
11408 player->effective_mouse_action.button)
11409 game.enter_single_step_mode = FALSE;
11412 CheckSaveEngineSnapshot(player);
11415 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11417 int left = player_action & JOY_LEFT;
11418 int right = player_action & JOY_RIGHT;
11419 int up = player_action & JOY_UP;
11420 int down = player_action & JOY_DOWN;
11421 int button1 = player_action & JOY_BUTTON_1;
11422 int button2 = player_action & JOY_BUTTON_2;
11423 int dx = (left ? -1 : right ? 1 : 0);
11424 int dy = (up ? -1 : down ? 1 : 0);
11426 if (!player->active || tape.pausing)
11432 SnapField(player, dx, dy);
11436 DropElement(player);
11438 MovePlayer(player, dx, dy);
11441 CheckSingleStepMode(player);
11443 SetPlayerWaiting(player, FALSE);
11445 return player_action;
11449 // no actions for this player (no input at player's configured device)
11451 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11452 SnapField(player, 0, 0);
11453 CheckGravityMovementWhenNotMoving(player);
11455 if (player->MovPos == 0)
11456 SetPlayerWaiting(player, TRUE);
11458 if (player->MovPos == 0) // needed for tape.playing
11459 player->is_moving = FALSE;
11461 player->is_dropping = FALSE;
11462 player->is_dropping_pressed = FALSE;
11463 player->drop_pressed_delay = 0;
11465 CheckSingleStepMode(player);
11471 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11474 if (!tape.use_mouse_actions)
11477 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11478 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11479 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11482 static void SetTapeActionFromMouseAction(byte *tape_action,
11483 struct MouseActionInfo *mouse_action)
11485 if (!tape.use_mouse_actions)
11488 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11489 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11490 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11493 static void CheckLevelSolved(void)
11495 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11497 if (game_em.level_solved &&
11498 !game_em.game_over) // game won
11502 game_em.game_over = TRUE;
11504 game.all_players_gone = TRUE;
11507 if (game_em.game_over) // game lost
11508 game.all_players_gone = TRUE;
11510 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11512 if (game_sp.level_solved &&
11513 !game_sp.game_over) // game won
11517 game_sp.game_over = TRUE;
11519 game.all_players_gone = TRUE;
11522 if (game_sp.game_over) // game lost
11523 game.all_players_gone = TRUE;
11525 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11527 if (game_mm.level_solved &&
11528 !game_mm.game_over) // game won
11532 game_mm.game_over = TRUE;
11534 game.all_players_gone = TRUE;
11537 if (game_mm.game_over) // game lost
11538 game.all_players_gone = TRUE;
11542 static void CheckLevelTime(void)
11546 if (TimeFrames >= FRAMES_PER_SECOND)
11551 for (i = 0; i < MAX_PLAYERS; i++)
11553 struct PlayerInfo *player = &stored_player[i];
11555 if (SHIELD_ON(player))
11557 player->shield_normal_time_left--;
11559 if (player->shield_deadly_time_left > 0)
11560 player->shield_deadly_time_left--;
11564 if (!game.LevelSolved && !level.use_step_counter)
11572 if (TimeLeft <= 10 && setup.time_limit)
11573 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11575 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11576 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11578 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11580 if (!TimeLeft && setup.time_limit)
11582 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11583 game_em.lev->killed_out_of_time = TRUE;
11585 for (i = 0; i < MAX_PLAYERS; i++)
11586 KillPlayer(&stored_player[i]);
11589 else if (game.no_time_limit && !game.all_players_gone)
11591 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11594 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11597 if (tape.recording || tape.playing)
11598 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11601 if (tape.recording || tape.playing)
11602 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11604 UpdateAndDisplayGameControlValues();
11607 void AdvanceFrameAndPlayerCounters(int player_nr)
11611 // advance frame counters (global frame counter and time frame counter)
11615 // advance player counters (counters for move delay, move animation etc.)
11616 for (i = 0; i < MAX_PLAYERS; i++)
11618 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11619 int move_delay_value = stored_player[i].move_delay_value;
11620 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11622 if (!advance_player_counters) // not all players may be affected
11625 if (move_frames == 0) // less than one move per game frame
11627 int stepsize = TILEX / move_delay_value;
11628 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11629 int count = (stored_player[i].is_moving ?
11630 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11632 if (count % delay == 0)
11636 stored_player[i].Frame += move_frames;
11638 if (stored_player[i].MovPos != 0)
11639 stored_player[i].StepFrame += move_frames;
11641 if (stored_player[i].move_delay > 0)
11642 stored_player[i].move_delay--;
11644 // due to bugs in previous versions, counter must count up, not down
11645 if (stored_player[i].push_delay != -1)
11646 stored_player[i].push_delay++;
11648 if (stored_player[i].drop_delay > 0)
11649 stored_player[i].drop_delay--;
11651 if (stored_player[i].is_dropping_pressed)
11652 stored_player[i].drop_pressed_delay++;
11656 void StartGameActions(boolean init_network_game, boolean record_tape,
11659 unsigned int new_random_seed = InitRND(random_seed);
11662 TapeStartRecording(new_random_seed);
11664 if (init_network_game)
11666 SendToServer_LevelFile();
11667 SendToServer_StartPlaying();
11675 static void GameActionsExt(void)
11678 static unsigned int game_frame_delay = 0;
11680 unsigned int game_frame_delay_value;
11681 byte *recorded_player_action;
11682 byte summarized_player_action = 0;
11683 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11686 // detect endless loops, caused by custom element programming
11687 if (recursion_loop_detected && recursion_loop_depth == 0)
11689 char *message = getStringCat3("Internal Error! Element ",
11690 EL_NAME(recursion_loop_element),
11691 " caused endless loop! Quit the game?");
11693 Warn("element '%s' caused endless loop in game engine",
11694 EL_NAME(recursion_loop_element));
11696 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11698 recursion_loop_detected = FALSE; // if game should be continued
11705 if (game.restart_level)
11706 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11708 CheckLevelSolved();
11710 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11713 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11716 if (game_status != GAME_MODE_PLAYING) // status might have changed
11719 game_frame_delay_value =
11720 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11722 if (tape.playing && tape.warp_forward && !tape.pausing)
11723 game_frame_delay_value = 0;
11725 SetVideoFrameDelay(game_frame_delay_value);
11727 // (de)activate virtual buttons depending on current game status
11728 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11730 if (game.all_players_gone) // if no players there to be controlled anymore
11731 SetOverlayActive(FALSE);
11732 else if (!tape.playing) // if game continues after tape stopped playing
11733 SetOverlayActive(TRUE);
11738 // ---------- main game synchronization point ----------
11740 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11742 Debug("game:playing:skip", "skip == %d", skip);
11745 // ---------- main game synchronization point ----------
11747 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11751 if (network_playing && !network_player_action_received)
11753 // try to get network player actions in time
11755 // last chance to get network player actions without main loop delay
11756 HandleNetworking();
11758 // game was quit by network peer
11759 if (game_status != GAME_MODE_PLAYING)
11762 // check if network player actions still missing and game still running
11763 if (!network_player_action_received && !checkGameEnded())
11764 return; // failed to get network player actions in time
11766 // do not yet reset "network_player_action_received" (for tape.pausing)
11772 // at this point we know that we really continue executing the game
11774 network_player_action_received = FALSE;
11776 // when playing tape, read previously recorded player input from tape data
11777 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11779 local_player->effective_mouse_action = local_player->mouse_action;
11781 if (recorded_player_action != NULL)
11782 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11783 recorded_player_action);
11785 // TapePlayAction() may return NULL when toggling to "pause before death"
11789 if (tape.set_centered_player)
11791 game.centered_player_nr_next = tape.centered_player_nr_next;
11792 game.set_centered_player = TRUE;
11795 for (i = 0; i < MAX_PLAYERS; i++)
11797 summarized_player_action |= stored_player[i].action;
11799 if (!network_playing && (game.team_mode || tape.playing))
11800 stored_player[i].effective_action = stored_player[i].action;
11803 if (network_playing && !checkGameEnded())
11804 SendToServer_MovePlayer(summarized_player_action);
11806 // summarize all actions at local players mapped input device position
11807 // (this allows using different input devices in single player mode)
11808 if (!network.enabled && !game.team_mode)
11809 stored_player[map_player_action[local_player->index_nr]].effective_action =
11810 summarized_player_action;
11812 // summarize all actions at centered player in local team mode
11813 if (tape.recording &&
11814 setup.team_mode && !network.enabled &&
11815 setup.input_on_focus &&
11816 game.centered_player_nr != -1)
11818 for (i = 0; i < MAX_PLAYERS; i++)
11819 stored_player[map_player_action[i]].effective_action =
11820 (i == game.centered_player_nr ? summarized_player_action : 0);
11823 if (recorded_player_action != NULL)
11824 for (i = 0; i < MAX_PLAYERS; i++)
11825 stored_player[i].effective_action = recorded_player_action[i];
11827 for (i = 0; i < MAX_PLAYERS; i++)
11829 tape_action[i] = stored_player[i].effective_action;
11831 /* (this may happen in the RND game engine if a player was not present on
11832 the playfield on level start, but appeared later from a custom element */
11833 if (setup.team_mode &&
11836 !tape.player_participates[i])
11837 tape.player_participates[i] = TRUE;
11840 SetTapeActionFromMouseAction(tape_action,
11841 &local_player->effective_mouse_action);
11843 // only record actions from input devices, but not programmed actions
11844 if (tape.recording)
11845 TapeRecordAction(tape_action);
11847 // remember if game was played (especially after tape stopped playing)
11848 if (!tape.playing && summarized_player_action)
11849 game.GamePlayed = TRUE;
11851 #if USE_NEW_PLAYER_ASSIGNMENTS
11852 // !!! also map player actions in single player mode !!!
11853 // if (game.team_mode)
11856 byte mapped_action[MAX_PLAYERS];
11858 #if DEBUG_PLAYER_ACTIONS
11859 for (i = 0; i < MAX_PLAYERS; i++)
11860 DebugContinued("", "%d, ", stored_player[i].effective_action);
11863 for (i = 0; i < MAX_PLAYERS; i++)
11864 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11866 for (i = 0; i < MAX_PLAYERS; i++)
11867 stored_player[i].effective_action = mapped_action[i];
11869 #if DEBUG_PLAYER_ACTIONS
11870 DebugContinued("", "=> ");
11871 for (i = 0; i < MAX_PLAYERS; i++)
11872 DebugContinued("", "%d, ", stored_player[i].effective_action);
11873 DebugContinued("game:playing:player", "\n");
11876 #if DEBUG_PLAYER_ACTIONS
11879 for (i = 0; i < MAX_PLAYERS; i++)
11880 DebugContinued("", "%d, ", stored_player[i].effective_action);
11881 DebugContinued("game:playing:player", "\n");
11886 for (i = 0; i < MAX_PLAYERS; i++)
11888 // allow engine snapshot in case of changed movement attempt
11889 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11890 (stored_player[i].effective_action & KEY_MOTION))
11891 game.snapshot.changed_action = TRUE;
11893 // allow engine snapshot in case of snapping/dropping attempt
11894 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11895 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11896 game.snapshot.changed_action = TRUE;
11898 game.snapshot.last_action[i] = stored_player[i].effective_action;
11901 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11903 GameActions_EM_Main();
11905 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11907 GameActions_SP_Main();
11909 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11911 GameActions_MM_Main();
11915 GameActions_RND_Main();
11918 BlitScreenToBitmap(backbuffer);
11920 CheckLevelSolved();
11923 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11925 if (global.show_frames_per_second)
11927 static unsigned int fps_counter = 0;
11928 static int fps_frames = 0;
11929 unsigned int fps_delay_ms = Counter() - fps_counter;
11933 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11935 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11938 fps_counter = Counter();
11940 // always draw FPS to screen after FPS value was updated
11941 redraw_mask |= REDRAW_FPS;
11944 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11945 if (GetDrawDeactivationMask() == REDRAW_NONE)
11946 redraw_mask |= REDRAW_FPS;
11950 static void GameActions_CheckSaveEngineSnapshot(void)
11952 if (!game.snapshot.save_snapshot)
11955 // clear flag for saving snapshot _before_ saving snapshot
11956 game.snapshot.save_snapshot = FALSE;
11958 SaveEngineSnapshotToList();
11961 void GameActions(void)
11965 GameActions_CheckSaveEngineSnapshot();
11968 void GameActions_EM_Main(void)
11970 byte effective_action[MAX_PLAYERS];
11971 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11974 for (i = 0; i < MAX_PLAYERS; i++)
11975 effective_action[i] = stored_player[i].effective_action;
11977 GameActions_EM(effective_action, warp_mode);
11980 void GameActions_SP_Main(void)
11982 byte effective_action[MAX_PLAYERS];
11983 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11986 for (i = 0; i < MAX_PLAYERS; i++)
11987 effective_action[i] = stored_player[i].effective_action;
11989 GameActions_SP(effective_action, warp_mode);
11991 for (i = 0; i < MAX_PLAYERS; i++)
11993 if (stored_player[i].force_dropping)
11994 stored_player[i].action |= KEY_BUTTON_DROP;
11996 stored_player[i].force_dropping = FALSE;
12000 void GameActions_MM_Main(void)
12002 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12004 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12007 void GameActions_RND_Main(void)
12012 void GameActions_RND(void)
12014 static struct MouseActionInfo mouse_action_last = { 0 };
12015 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12016 int magic_wall_x = 0, magic_wall_y = 0;
12017 int i, x, y, element, graphic, last_gfx_frame;
12019 InitPlayfieldScanModeVars();
12021 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12023 SCAN_PLAYFIELD(x, y)
12025 ChangeCount[x][y] = 0;
12026 ChangeEvent[x][y] = -1;
12030 if (game.set_centered_player)
12032 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12034 // switching to "all players" only possible if all players fit to screen
12035 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12037 game.centered_player_nr_next = game.centered_player_nr;
12038 game.set_centered_player = FALSE;
12041 // do not switch focus to non-existing (or non-active) player
12042 if (game.centered_player_nr_next >= 0 &&
12043 !stored_player[game.centered_player_nr_next].active)
12045 game.centered_player_nr_next = game.centered_player_nr;
12046 game.set_centered_player = FALSE;
12050 if (game.set_centered_player &&
12051 ScreenMovPos == 0) // screen currently aligned at tile position
12055 if (game.centered_player_nr_next == -1)
12057 setScreenCenteredToAllPlayers(&sx, &sy);
12061 sx = stored_player[game.centered_player_nr_next].jx;
12062 sy = stored_player[game.centered_player_nr_next].jy;
12065 game.centered_player_nr = game.centered_player_nr_next;
12066 game.set_centered_player = FALSE;
12068 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12069 DrawGameDoorValues();
12072 // check single step mode (set flag and clear again if any player is active)
12073 game.enter_single_step_mode =
12074 (tape.single_step && tape.recording && !tape.pausing);
12076 for (i = 0; i < MAX_PLAYERS; i++)
12078 int actual_player_action = stored_player[i].effective_action;
12081 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12082 - rnd_equinox_tetrachloride 048
12083 - rnd_equinox_tetrachloride_ii 096
12084 - rnd_emanuel_schmieg 002
12085 - doctor_sloan_ww 001, 020
12087 if (stored_player[i].MovPos == 0)
12088 CheckGravityMovement(&stored_player[i]);
12091 // overwrite programmed action with tape action
12092 if (stored_player[i].programmed_action)
12093 actual_player_action = stored_player[i].programmed_action;
12095 PlayerActions(&stored_player[i], actual_player_action);
12097 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12100 // single step pause mode may already have been toggled by "ScrollPlayer()"
12101 if (game.enter_single_step_mode && !tape.pausing)
12102 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12104 ScrollScreen(NULL, SCROLL_GO_ON);
12106 /* for backwards compatibility, the following code emulates a fixed bug that
12107 occured when pushing elements (causing elements that just made their last
12108 pushing step to already (if possible) make their first falling step in the
12109 same game frame, which is bad); this code is also needed to use the famous
12110 "spring push bug" which is used in older levels and might be wanted to be
12111 used also in newer levels, but in this case the buggy pushing code is only
12112 affecting the "spring" element and no other elements */
12114 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12116 for (i = 0; i < MAX_PLAYERS; i++)
12118 struct PlayerInfo *player = &stored_player[i];
12119 int x = player->jx;
12120 int y = player->jy;
12122 if (player->active && player->is_pushing && player->is_moving &&
12124 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12125 Tile[x][y] == EL_SPRING))
12127 ContinueMoving(x, y);
12129 // continue moving after pushing (this is actually a bug)
12130 if (!IS_MOVING(x, y))
12131 Stop[x][y] = FALSE;
12136 SCAN_PLAYFIELD(x, y)
12138 Last[x][y] = Tile[x][y];
12140 ChangeCount[x][y] = 0;
12141 ChangeEvent[x][y] = -1;
12143 // this must be handled before main playfield loop
12144 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12147 if (MovDelay[x][y] <= 0)
12151 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12154 if (MovDelay[x][y] <= 0)
12156 int element = Store[x][y];
12157 int move_direction = MovDir[x][y];
12158 int player_index_bit = Store2[x][y];
12164 TEST_DrawLevelField(x, y);
12166 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12171 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12173 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12175 Debug("game:playing:GameActions_RND", "This should never happen!");
12177 ChangePage[x][y] = -1;
12181 Stop[x][y] = FALSE;
12182 if (WasJustMoving[x][y] > 0)
12183 WasJustMoving[x][y]--;
12184 if (WasJustFalling[x][y] > 0)
12185 WasJustFalling[x][y]--;
12186 if (CheckCollision[x][y] > 0)
12187 CheckCollision[x][y]--;
12188 if (CheckImpact[x][y] > 0)
12189 CheckImpact[x][y]--;
12193 /* reset finished pushing action (not done in ContinueMoving() to allow
12194 continuous pushing animation for elements with zero push delay) */
12195 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12197 ResetGfxAnimation(x, y);
12198 TEST_DrawLevelField(x, y);
12202 if (IS_BLOCKED(x, y))
12206 Blocked2Moving(x, y, &oldx, &oldy);
12207 if (!IS_MOVING(oldx, oldy))
12209 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12210 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12211 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12212 Debug("game:playing:GameActions_RND", "This should never happen!");
12218 if (mouse_action.button)
12220 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12221 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12223 x = mouse_action.lx;
12224 y = mouse_action.ly;
12225 element = Tile[x][y];
12229 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12230 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12234 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12235 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12239 SCAN_PLAYFIELD(x, y)
12241 element = Tile[x][y];
12242 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12243 last_gfx_frame = GfxFrame[x][y];
12245 ResetGfxFrame(x, y);
12247 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12248 DrawLevelGraphicAnimation(x, y, graphic);
12250 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12251 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12252 ResetRandomAnimationValue(x, y);
12254 SetRandomAnimationValue(x, y);
12256 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12258 if (IS_INACTIVE(element))
12260 if (IS_ANIMATED(graphic))
12261 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12266 // this may take place after moving, so 'element' may have changed
12267 if (IS_CHANGING(x, y) &&
12268 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12270 int page = element_info[element].event_page_nr[CE_DELAY];
12272 HandleElementChange(x, y, page);
12274 element = Tile[x][y];
12275 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12278 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12282 element = Tile[x][y];
12283 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12285 if (IS_ANIMATED(graphic) &&
12286 !IS_MOVING(x, y) &&
12288 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12290 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12291 TEST_DrawTwinkleOnField(x, y);
12293 else if (element == EL_ACID)
12296 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12298 else if ((element == EL_EXIT_OPEN ||
12299 element == EL_EM_EXIT_OPEN ||
12300 element == EL_SP_EXIT_OPEN ||
12301 element == EL_STEEL_EXIT_OPEN ||
12302 element == EL_EM_STEEL_EXIT_OPEN ||
12303 element == EL_SP_TERMINAL ||
12304 element == EL_SP_TERMINAL_ACTIVE ||
12305 element == EL_EXTRA_TIME ||
12306 element == EL_SHIELD_NORMAL ||
12307 element == EL_SHIELD_DEADLY) &&
12308 IS_ANIMATED(graphic))
12309 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12310 else if (IS_MOVING(x, y))
12311 ContinueMoving(x, y);
12312 else if (IS_ACTIVE_BOMB(element))
12313 CheckDynamite(x, y);
12314 else if (element == EL_AMOEBA_GROWING)
12315 AmoebaGrowing(x, y);
12316 else if (element == EL_AMOEBA_SHRINKING)
12317 AmoebaShrinking(x, y);
12319 #if !USE_NEW_AMOEBA_CODE
12320 else if (IS_AMOEBALIVE(element))
12321 AmoebaReproduce(x, y);
12324 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12326 else if (element == EL_EXIT_CLOSED)
12328 else if (element == EL_EM_EXIT_CLOSED)
12330 else if (element == EL_STEEL_EXIT_CLOSED)
12331 CheckExitSteel(x, y);
12332 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12333 CheckExitSteelEM(x, y);
12334 else if (element == EL_SP_EXIT_CLOSED)
12336 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12337 element == EL_EXPANDABLE_STEELWALL_GROWING)
12338 MauerWaechst(x, y);
12339 else if (element == EL_EXPANDABLE_WALL ||
12340 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12341 element == EL_EXPANDABLE_WALL_VERTICAL ||
12342 element == EL_EXPANDABLE_WALL_ANY ||
12343 element == EL_BD_EXPANDABLE_WALL)
12344 MauerAbleger(x, y);
12345 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12346 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12347 element == EL_EXPANDABLE_STEELWALL_ANY)
12348 MauerAblegerStahl(x, y);
12349 else if (element == EL_FLAMES)
12350 CheckForDragon(x, y);
12351 else if (element == EL_EXPLOSION)
12352 ; // drawing of correct explosion animation is handled separately
12353 else if (element == EL_ELEMENT_SNAPPING ||
12354 element == EL_DIAGONAL_SHRINKING ||
12355 element == EL_DIAGONAL_GROWING)
12357 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12359 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12361 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12362 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12364 if (IS_BELT_ACTIVE(element))
12365 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12367 if (game.magic_wall_active)
12369 int jx = local_player->jx, jy = local_player->jy;
12371 // play the element sound at the position nearest to the player
12372 if ((element == EL_MAGIC_WALL_FULL ||
12373 element == EL_MAGIC_WALL_ACTIVE ||
12374 element == EL_MAGIC_WALL_EMPTYING ||
12375 element == EL_BD_MAGIC_WALL_FULL ||
12376 element == EL_BD_MAGIC_WALL_ACTIVE ||
12377 element == EL_BD_MAGIC_WALL_EMPTYING ||
12378 element == EL_DC_MAGIC_WALL_FULL ||
12379 element == EL_DC_MAGIC_WALL_ACTIVE ||
12380 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12381 ABS(x - jx) + ABS(y - jy) <
12382 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12390 #if USE_NEW_AMOEBA_CODE
12391 // new experimental amoeba growth stuff
12392 if (!(FrameCounter % 8))
12394 static unsigned int random = 1684108901;
12396 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12398 x = RND(lev_fieldx);
12399 y = RND(lev_fieldy);
12400 element = Tile[x][y];
12402 if (!IS_PLAYER(x,y) &&
12403 (element == EL_EMPTY ||
12404 CAN_GROW_INTO(element) ||
12405 element == EL_QUICKSAND_EMPTY ||
12406 element == EL_QUICKSAND_FAST_EMPTY ||
12407 element == EL_ACID_SPLASH_LEFT ||
12408 element == EL_ACID_SPLASH_RIGHT))
12410 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12411 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12412 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12413 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12414 Tile[x][y] = EL_AMOEBA_DROP;
12417 random = random * 129 + 1;
12422 game.explosions_delayed = FALSE;
12424 SCAN_PLAYFIELD(x, y)
12426 element = Tile[x][y];
12428 if (ExplodeField[x][y])
12429 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12430 else if (element == EL_EXPLOSION)
12431 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12433 ExplodeField[x][y] = EX_TYPE_NONE;
12436 game.explosions_delayed = TRUE;
12438 if (game.magic_wall_active)
12440 if (!(game.magic_wall_time_left % 4))
12442 int element = Tile[magic_wall_x][magic_wall_y];
12444 if (element == EL_BD_MAGIC_WALL_FULL ||
12445 element == EL_BD_MAGIC_WALL_ACTIVE ||
12446 element == EL_BD_MAGIC_WALL_EMPTYING)
12447 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12448 else if (element == EL_DC_MAGIC_WALL_FULL ||
12449 element == EL_DC_MAGIC_WALL_ACTIVE ||
12450 element == EL_DC_MAGIC_WALL_EMPTYING)
12451 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12453 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12456 if (game.magic_wall_time_left > 0)
12458 game.magic_wall_time_left--;
12460 if (!game.magic_wall_time_left)
12462 SCAN_PLAYFIELD(x, y)
12464 element = Tile[x][y];
12466 if (element == EL_MAGIC_WALL_ACTIVE ||
12467 element == EL_MAGIC_WALL_FULL)
12469 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12470 TEST_DrawLevelField(x, y);
12472 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12473 element == EL_BD_MAGIC_WALL_FULL)
12475 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12476 TEST_DrawLevelField(x, y);
12478 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12479 element == EL_DC_MAGIC_WALL_FULL)
12481 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12482 TEST_DrawLevelField(x, y);
12486 game.magic_wall_active = FALSE;
12491 if (game.light_time_left > 0)
12493 game.light_time_left--;
12495 if (game.light_time_left == 0)
12496 RedrawAllLightSwitchesAndInvisibleElements();
12499 if (game.timegate_time_left > 0)
12501 game.timegate_time_left--;
12503 if (game.timegate_time_left == 0)
12504 CloseAllOpenTimegates();
12507 if (game.lenses_time_left > 0)
12509 game.lenses_time_left--;
12511 if (game.lenses_time_left == 0)
12512 RedrawAllInvisibleElementsForLenses();
12515 if (game.magnify_time_left > 0)
12517 game.magnify_time_left--;
12519 if (game.magnify_time_left == 0)
12520 RedrawAllInvisibleElementsForMagnifier();
12523 for (i = 0; i < MAX_PLAYERS; i++)
12525 struct PlayerInfo *player = &stored_player[i];
12527 if (SHIELD_ON(player))
12529 if (player->shield_deadly_time_left)
12530 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12531 else if (player->shield_normal_time_left)
12532 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12536 #if USE_DELAYED_GFX_REDRAW
12537 SCAN_PLAYFIELD(x, y)
12539 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12541 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12542 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12544 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12545 DrawLevelField(x, y);
12547 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12548 DrawLevelFieldCrumbled(x, y);
12550 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12551 DrawLevelFieldCrumbledNeighbours(x, y);
12553 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12554 DrawTwinkleOnField(x, y);
12557 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12562 PlayAllPlayersSound();
12564 for (i = 0; i < MAX_PLAYERS; i++)
12566 struct PlayerInfo *player = &stored_player[i];
12568 if (player->show_envelope != 0 && (!player->active ||
12569 player->MovPos == 0))
12571 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12573 player->show_envelope = 0;
12577 // use random number generator in every frame to make it less predictable
12578 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12581 mouse_action_last = mouse_action;
12584 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12586 int min_x = x, min_y = y, max_x = x, max_y = y;
12587 int scr_fieldx = getScreenFieldSizeX();
12588 int scr_fieldy = getScreenFieldSizeY();
12591 for (i = 0; i < MAX_PLAYERS; i++)
12593 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12595 if (!stored_player[i].active || &stored_player[i] == player)
12598 min_x = MIN(min_x, jx);
12599 min_y = MIN(min_y, jy);
12600 max_x = MAX(max_x, jx);
12601 max_y = MAX(max_y, jy);
12604 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12607 static boolean AllPlayersInVisibleScreen(void)
12611 for (i = 0; i < MAX_PLAYERS; i++)
12613 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12615 if (!stored_player[i].active)
12618 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12625 void ScrollLevel(int dx, int dy)
12627 int scroll_offset = 2 * TILEX_VAR;
12630 BlitBitmap(drawto_field, drawto_field,
12631 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12632 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12633 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12634 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12635 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12636 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12640 x = (dx == 1 ? BX1 : BX2);
12641 for (y = BY1; y <= BY2; y++)
12642 DrawScreenField(x, y);
12647 y = (dy == 1 ? BY1 : BY2);
12648 for (x = BX1; x <= BX2; x++)
12649 DrawScreenField(x, y);
12652 redraw_mask |= REDRAW_FIELD;
12655 static boolean canFallDown(struct PlayerInfo *player)
12657 int jx = player->jx, jy = player->jy;
12659 return (IN_LEV_FIELD(jx, jy + 1) &&
12660 (IS_FREE(jx, jy + 1) ||
12661 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12662 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12663 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12666 static boolean canPassField(int x, int y, int move_dir)
12668 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12669 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12670 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12671 int nextx = x + dx;
12672 int nexty = y + dy;
12673 int element = Tile[x][y];
12675 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12676 !CAN_MOVE(element) &&
12677 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12678 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12679 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12682 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12684 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12685 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12686 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12690 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12691 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12692 (IS_DIGGABLE(Tile[newx][newy]) ||
12693 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12694 canPassField(newx, newy, move_dir)));
12697 static void CheckGravityMovement(struct PlayerInfo *player)
12699 if (player->gravity && !player->programmed_action)
12701 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12702 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12703 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12704 int jx = player->jx, jy = player->jy;
12705 boolean player_is_moving_to_valid_field =
12706 (!player_is_snapping &&
12707 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12708 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12709 boolean player_can_fall_down = canFallDown(player);
12711 if (player_can_fall_down &&
12712 !player_is_moving_to_valid_field)
12713 player->programmed_action = MV_DOWN;
12717 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12719 return CheckGravityMovement(player);
12721 if (player->gravity && !player->programmed_action)
12723 int jx = player->jx, jy = player->jy;
12724 boolean field_under_player_is_free =
12725 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12726 boolean player_is_standing_on_valid_field =
12727 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12728 (IS_WALKABLE(Tile[jx][jy]) &&
12729 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12731 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12732 player->programmed_action = MV_DOWN;
12737 MovePlayerOneStep()
12738 -----------------------------------------------------------------------------
12739 dx, dy: direction (non-diagonal) to try to move the player to
12740 real_dx, real_dy: direction as read from input device (can be diagonal)
12743 boolean MovePlayerOneStep(struct PlayerInfo *player,
12744 int dx, int dy, int real_dx, int real_dy)
12746 int jx = player->jx, jy = player->jy;
12747 int new_jx = jx + dx, new_jy = jy + dy;
12749 boolean player_can_move = !player->cannot_move;
12751 if (!player->active || (!dx && !dy))
12752 return MP_NO_ACTION;
12754 player->MovDir = (dx < 0 ? MV_LEFT :
12755 dx > 0 ? MV_RIGHT :
12757 dy > 0 ? MV_DOWN : MV_NONE);
12759 if (!IN_LEV_FIELD(new_jx, new_jy))
12760 return MP_NO_ACTION;
12762 if (!player_can_move)
12764 if (player->MovPos == 0)
12766 player->is_moving = FALSE;
12767 player->is_digging = FALSE;
12768 player->is_collecting = FALSE;
12769 player->is_snapping = FALSE;
12770 player->is_pushing = FALSE;
12774 if (!network.enabled && game.centered_player_nr == -1 &&
12775 !AllPlayersInSight(player, new_jx, new_jy))
12776 return MP_NO_ACTION;
12778 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12779 if (can_move != MP_MOVING)
12782 // check if DigField() has caused relocation of the player
12783 if (player->jx != jx || player->jy != jy)
12784 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12786 StorePlayer[jx][jy] = 0;
12787 player->last_jx = jx;
12788 player->last_jy = jy;
12789 player->jx = new_jx;
12790 player->jy = new_jy;
12791 StorePlayer[new_jx][new_jy] = player->element_nr;
12793 if (player->move_delay_value_next != -1)
12795 player->move_delay_value = player->move_delay_value_next;
12796 player->move_delay_value_next = -1;
12800 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12802 player->step_counter++;
12804 PlayerVisit[jx][jy] = FrameCounter;
12806 player->is_moving = TRUE;
12809 // should better be called in MovePlayer(), but this breaks some tapes
12810 ScrollPlayer(player, SCROLL_INIT);
12816 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12818 int jx = player->jx, jy = player->jy;
12819 int old_jx = jx, old_jy = jy;
12820 int moved = MP_NO_ACTION;
12822 if (!player->active)
12827 if (player->MovPos == 0)
12829 player->is_moving = FALSE;
12830 player->is_digging = FALSE;
12831 player->is_collecting = FALSE;
12832 player->is_snapping = FALSE;
12833 player->is_pushing = FALSE;
12839 if (player->move_delay > 0)
12842 player->move_delay = -1; // set to "uninitialized" value
12844 // store if player is automatically moved to next field
12845 player->is_auto_moving = (player->programmed_action != MV_NONE);
12847 // remove the last programmed player action
12848 player->programmed_action = 0;
12850 if (player->MovPos)
12852 // should only happen if pre-1.2 tape recordings are played
12853 // this is only for backward compatibility
12855 int original_move_delay_value = player->move_delay_value;
12858 Debug("game:playing:MovePlayer",
12859 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12863 // scroll remaining steps with finest movement resolution
12864 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12866 while (player->MovPos)
12868 ScrollPlayer(player, SCROLL_GO_ON);
12869 ScrollScreen(NULL, SCROLL_GO_ON);
12871 AdvanceFrameAndPlayerCounters(player->index_nr);
12874 BackToFront_WithFrameDelay(0);
12877 player->move_delay_value = original_move_delay_value;
12880 player->is_active = FALSE;
12882 if (player->last_move_dir & MV_HORIZONTAL)
12884 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12885 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12889 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12890 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12893 if (!moved && !player->is_active)
12895 player->is_moving = FALSE;
12896 player->is_digging = FALSE;
12897 player->is_collecting = FALSE;
12898 player->is_snapping = FALSE;
12899 player->is_pushing = FALSE;
12905 if (moved & MP_MOVING && !ScreenMovPos &&
12906 (player->index_nr == game.centered_player_nr ||
12907 game.centered_player_nr == -1))
12909 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12911 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12913 // actual player has left the screen -- scroll in that direction
12914 if (jx != old_jx) // player has moved horizontally
12915 scroll_x += (jx - old_jx);
12916 else // player has moved vertically
12917 scroll_y += (jy - old_jy);
12921 int offset_raw = game.scroll_delay_value;
12923 if (jx != old_jx) // player has moved horizontally
12925 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12926 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12927 int new_scroll_x = jx - MIDPOSX + offset_x;
12929 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12930 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12931 scroll_x = new_scroll_x;
12933 // don't scroll over playfield boundaries
12934 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12936 // don't scroll more than one field at a time
12937 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12939 // don't scroll against the player's moving direction
12940 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12941 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12942 scroll_x = old_scroll_x;
12944 else // player has moved vertically
12946 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12947 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12948 int new_scroll_y = jy - MIDPOSY + offset_y;
12950 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12951 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12952 scroll_y = new_scroll_y;
12954 // don't scroll over playfield boundaries
12955 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12957 // don't scroll more than one field at a time
12958 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12960 // don't scroll against the player's moving direction
12961 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12962 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12963 scroll_y = old_scroll_y;
12967 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12969 if (!network.enabled && game.centered_player_nr == -1 &&
12970 !AllPlayersInVisibleScreen())
12972 scroll_x = old_scroll_x;
12973 scroll_y = old_scroll_y;
12977 ScrollScreen(player, SCROLL_INIT);
12978 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12983 player->StepFrame = 0;
12985 if (moved & MP_MOVING)
12987 if (old_jx != jx && old_jy == jy)
12988 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12989 else if (old_jx == jx && old_jy != jy)
12990 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12992 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12994 player->last_move_dir = player->MovDir;
12995 player->is_moving = TRUE;
12996 player->is_snapping = FALSE;
12997 player->is_switching = FALSE;
12998 player->is_dropping = FALSE;
12999 player->is_dropping_pressed = FALSE;
13000 player->drop_pressed_delay = 0;
13003 // should better be called here than above, but this breaks some tapes
13004 ScrollPlayer(player, SCROLL_INIT);
13009 CheckGravityMovementWhenNotMoving(player);
13011 player->is_moving = FALSE;
13013 /* at this point, the player is allowed to move, but cannot move right now
13014 (e.g. because of something blocking the way) -- ensure that the player
13015 is also allowed to move in the next frame (in old versions before 3.1.1,
13016 the player was forced to wait again for eight frames before next try) */
13018 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13019 player->move_delay = 0; // allow direct movement in the next frame
13022 if (player->move_delay == -1) // not yet initialized by DigField()
13023 player->move_delay = player->move_delay_value;
13025 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13027 TestIfPlayerTouchesBadThing(jx, jy);
13028 TestIfPlayerTouchesCustomElement(jx, jy);
13031 if (!player->active)
13032 RemovePlayer(player);
13037 void ScrollPlayer(struct PlayerInfo *player, int mode)
13039 int jx = player->jx, jy = player->jy;
13040 int last_jx = player->last_jx, last_jy = player->last_jy;
13041 int move_stepsize = TILEX / player->move_delay_value;
13043 if (!player->active)
13046 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13049 if (mode == SCROLL_INIT)
13051 player->actual_frame_counter = FrameCounter;
13052 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13054 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13055 Tile[last_jx][last_jy] == EL_EMPTY)
13057 int last_field_block_delay = 0; // start with no blocking at all
13058 int block_delay_adjustment = player->block_delay_adjustment;
13060 // if player blocks last field, add delay for exactly one move
13061 if (player->block_last_field)
13063 last_field_block_delay += player->move_delay_value;
13065 // when blocking enabled, prevent moving up despite gravity
13066 if (player->gravity && player->MovDir == MV_UP)
13067 block_delay_adjustment = -1;
13070 // add block delay adjustment (also possible when not blocking)
13071 last_field_block_delay += block_delay_adjustment;
13073 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13074 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13077 if (player->MovPos != 0) // player has not yet reached destination
13080 else if (!FrameReached(&player->actual_frame_counter, 1))
13083 if (player->MovPos != 0)
13085 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13086 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13088 // before DrawPlayer() to draw correct player graphic for this case
13089 if (player->MovPos == 0)
13090 CheckGravityMovement(player);
13093 if (player->MovPos == 0) // player reached destination field
13095 if (player->move_delay_reset_counter > 0)
13097 player->move_delay_reset_counter--;
13099 if (player->move_delay_reset_counter == 0)
13101 // continue with normal speed after quickly moving through gate
13102 HALVE_PLAYER_SPEED(player);
13104 // be able to make the next move without delay
13105 player->move_delay = 0;
13109 player->last_jx = jx;
13110 player->last_jy = jy;
13112 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13113 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13114 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13115 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13116 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13117 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13118 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13119 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13121 ExitPlayer(player);
13123 if (game.players_still_needed == 0 &&
13124 (game.friends_still_needed == 0 ||
13125 IS_SP_ELEMENT(Tile[jx][jy])))
13129 // this breaks one level: "machine", level 000
13131 int move_direction = player->MovDir;
13132 int enter_side = MV_DIR_OPPOSITE(move_direction);
13133 int leave_side = move_direction;
13134 int old_jx = last_jx;
13135 int old_jy = last_jy;
13136 int old_element = Tile[old_jx][old_jy];
13137 int new_element = Tile[jx][jy];
13139 if (IS_CUSTOM_ELEMENT(old_element))
13140 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13142 player->index_bit, leave_side);
13144 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13145 CE_PLAYER_LEAVES_X,
13146 player->index_bit, leave_side);
13148 if (IS_CUSTOM_ELEMENT(new_element))
13149 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13150 player->index_bit, enter_side);
13152 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13153 CE_PLAYER_ENTERS_X,
13154 player->index_bit, enter_side);
13156 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13157 CE_MOVE_OF_X, move_direction);
13160 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13162 TestIfPlayerTouchesBadThing(jx, jy);
13163 TestIfPlayerTouchesCustomElement(jx, jy);
13165 /* needed because pushed element has not yet reached its destination,
13166 so it would trigger a change event at its previous field location */
13167 if (!player->is_pushing)
13168 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13170 if (level.finish_dig_collect &&
13171 (player->is_digging || player->is_collecting))
13173 int last_element = player->last_removed_element;
13174 int move_direction = player->MovDir;
13175 int enter_side = MV_DIR_OPPOSITE(move_direction);
13176 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13177 CE_PLAYER_COLLECTS_X);
13179 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13180 player->index_bit, enter_side);
13182 player->last_removed_element = EL_UNDEFINED;
13185 if (!player->active)
13186 RemovePlayer(player);
13189 if (level.use_step_counter)
13199 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13200 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13202 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13204 DisplayGameControlValues();
13206 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13207 for (i = 0; i < MAX_PLAYERS; i++)
13208 KillPlayer(&stored_player[i]);
13210 else if (game.no_time_limit && !game.all_players_gone)
13212 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13214 DisplayGameControlValues();
13218 if (tape.single_step && tape.recording && !tape.pausing &&
13219 !player->programmed_action)
13220 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13222 if (!player->programmed_action)
13223 CheckSaveEngineSnapshot(player);
13227 void ScrollScreen(struct PlayerInfo *player, int mode)
13229 static unsigned int screen_frame_counter = 0;
13231 if (mode == SCROLL_INIT)
13233 // set scrolling step size according to actual player's moving speed
13234 ScrollStepSize = TILEX / player->move_delay_value;
13236 screen_frame_counter = FrameCounter;
13237 ScreenMovDir = player->MovDir;
13238 ScreenMovPos = player->MovPos;
13239 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13242 else if (!FrameReached(&screen_frame_counter, 1))
13247 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13248 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13249 redraw_mask |= REDRAW_FIELD;
13252 ScreenMovDir = MV_NONE;
13255 void TestIfPlayerTouchesCustomElement(int x, int y)
13257 static int xy[4][2] =
13264 static int trigger_sides[4][2] =
13266 // center side border side
13267 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13268 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13269 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13270 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13272 static int touch_dir[4] =
13274 MV_LEFT | MV_RIGHT,
13279 int center_element = Tile[x][y]; // should always be non-moving!
13282 for (i = 0; i < NUM_DIRECTIONS; i++)
13284 int xx = x + xy[i][0];
13285 int yy = y + xy[i][1];
13286 int center_side = trigger_sides[i][0];
13287 int border_side = trigger_sides[i][1];
13288 int border_element;
13290 if (!IN_LEV_FIELD(xx, yy))
13293 if (IS_PLAYER(x, y)) // player found at center element
13295 struct PlayerInfo *player = PLAYERINFO(x, y);
13297 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13298 border_element = Tile[xx][yy]; // may be moving!
13299 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13300 border_element = Tile[xx][yy];
13301 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13302 border_element = MovingOrBlocked2Element(xx, yy);
13304 continue; // center and border element do not touch
13306 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13307 player->index_bit, border_side);
13308 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13309 CE_PLAYER_TOUCHES_X,
13310 player->index_bit, border_side);
13313 /* use player element that is initially defined in the level playfield,
13314 not the player element that corresponds to the runtime player number
13315 (example: a level that contains EL_PLAYER_3 as the only player would
13316 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13317 int player_element = PLAYERINFO(x, y)->initial_element;
13319 CheckElementChangeBySide(xx, yy, border_element, player_element,
13320 CE_TOUCHING_X, border_side);
13323 else if (IS_PLAYER(xx, yy)) // player found at border element
13325 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13327 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13329 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13330 continue; // center and border element do not touch
13333 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13334 player->index_bit, center_side);
13335 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13336 CE_PLAYER_TOUCHES_X,
13337 player->index_bit, center_side);
13340 /* use player element that is initially defined in the level playfield,
13341 not the player element that corresponds to the runtime player number
13342 (example: a level that contains EL_PLAYER_3 as the only player would
13343 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13344 int player_element = PLAYERINFO(xx, yy)->initial_element;
13346 CheckElementChangeBySide(x, y, center_element, player_element,
13347 CE_TOUCHING_X, center_side);
13355 void TestIfElementTouchesCustomElement(int x, int y)
13357 static int xy[4][2] =
13364 static int trigger_sides[4][2] =
13366 // center side border side
13367 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13368 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13369 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13370 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13372 static int touch_dir[4] =
13374 MV_LEFT | MV_RIGHT,
13379 boolean change_center_element = FALSE;
13380 int center_element = Tile[x][y]; // should always be non-moving!
13381 int border_element_old[NUM_DIRECTIONS];
13384 for (i = 0; i < NUM_DIRECTIONS; i++)
13386 int xx = x + xy[i][0];
13387 int yy = y + xy[i][1];
13388 int border_element;
13390 border_element_old[i] = -1;
13392 if (!IN_LEV_FIELD(xx, yy))
13395 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13396 border_element = Tile[xx][yy]; // may be moving!
13397 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13398 border_element = Tile[xx][yy];
13399 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13400 border_element = MovingOrBlocked2Element(xx, yy);
13402 continue; // center and border element do not touch
13404 border_element_old[i] = border_element;
13407 for (i = 0; i < NUM_DIRECTIONS; i++)
13409 int xx = x + xy[i][0];
13410 int yy = y + xy[i][1];
13411 int center_side = trigger_sides[i][0];
13412 int border_element = border_element_old[i];
13414 if (border_element == -1)
13417 // check for change of border element
13418 CheckElementChangeBySide(xx, yy, border_element, center_element,
13419 CE_TOUCHING_X, center_side);
13421 // (center element cannot be player, so we dont have to check this here)
13424 for (i = 0; i < NUM_DIRECTIONS; i++)
13426 int xx = x + xy[i][0];
13427 int yy = y + xy[i][1];
13428 int border_side = trigger_sides[i][1];
13429 int border_element = border_element_old[i];
13431 if (border_element == -1)
13434 // check for change of center element (but change it only once)
13435 if (!change_center_element)
13436 change_center_element =
13437 CheckElementChangeBySide(x, y, center_element, border_element,
13438 CE_TOUCHING_X, border_side);
13440 if (IS_PLAYER(xx, yy))
13442 /* use player element that is initially defined in the level playfield,
13443 not the player element that corresponds to the runtime player number
13444 (example: a level that contains EL_PLAYER_3 as the only player would
13445 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13446 int player_element = PLAYERINFO(xx, yy)->initial_element;
13448 CheckElementChangeBySide(x, y, center_element, player_element,
13449 CE_TOUCHING_X, border_side);
13454 void TestIfElementHitsCustomElement(int x, int y, int direction)
13456 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13457 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13458 int hitx = x + dx, hity = y + dy;
13459 int hitting_element = Tile[x][y];
13460 int touched_element;
13462 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13465 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13466 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13468 if (IN_LEV_FIELD(hitx, hity))
13470 int opposite_direction = MV_DIR_OPPOSITE(direction);
13471 int hitting_side = direction;
13472 int touched_side = opposite_direction;
13473 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13474 MovDir[hitx][hity] != direction ||
13475 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13481 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13482 CE_HITTING_X, touched_side);
13484 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13485 CE_HIT_BY_X, hitting_side);
13487 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13488 CE_HIT_BY_SOMETHING, opposite_direction);
13490 if (IS_PLAYER(hitx, hity))
13492 /* use player element that is initially defined in the level playfield,
13493 not the player element that corresponds to the runtime player number
13494 (example: a level that contains EL_PLAYER_3 as the only player would
13495 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13496 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13498 CheckElementChangeBySide(x, y, hitting_element, player_element,
13499 CE_HITTING_X, touched_side);
13504 // "hitting something" is also true when hitting the playfield border
13505 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13506 CE_HITTING_SOMETHING, direction);
13509 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13511 int i, kill_x = -1, kill_y = -1;
13513 int bad_element = -1;
13514 static int test_xy[4][2] =
13521 static int test_dir[4] =
13529 for (i = 0; i < NUM_DIRECTIONS; i++)
13531 int test_x, test_y, test_move_dir, test_element;
13533 test_x = good_x + test_xy[i][0];
13534 test_y = good_y + test_xy[i][1];
13536 if (!IN_LEV_FIELD(test_x, test_y))
13540 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13542 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13544 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13545 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13547 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13548 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13552 bad_element = test_element;
13558 if (kill_x != -1 || kill_y != -1)
13560 if (IS_PLAYER(good_x, good_y))
13562 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13564 if (player->shield_deadly_time_left > 0 &&
13565 !IS_INDESTRUCTIBLE(bad_element))
13566 Bang(kill_x, kill_y);
13567 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13568 KillPlayer(player);
13571 Bang(good_x, good_y);
13575 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13577 int i, kill_x = -1, kill_y = -1;
13578 int bad_element = Tile[bad_x][bad_y];
13579 static int test_xy[4][2] =
13586 static int touch_dir[4] =
13588 MV_LEFT | MV_RIGHT,
13593 static int test_dir[4] =
13601 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13604 for (i = 0; i < NUM_DIRECTIONS; i++)
13606 int test_x, test_y, test_move_dir, test_element;
13608 test_x = bad_x + test_xy[i][0];
13609 test_y = bad_y + test_xy[i][1];
13611 if (!IN_LEV_FIELD(test_x, test_y))
13615 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13617 test_element = Tile[test_x][test_y];
13619 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13620 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13622 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13623 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13625 // good thing is player or penguin that does not move away
13626 if (IS_PLAYER(test_x, test_y))
13628 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13630 if (bad_element == EL_ROBOT && player->is_moving)
13631 continue; // robot does not kill player if he is moving
13633 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13635 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13636 continue; // center and border element do not touch
13644 else if (test_element == EL_PENGUIN)
13654 if (kill_x != -1 || kill_y != -1)
13656 if (IS_PLAYER(kill_x, kill_y))
13658 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13660 if (player->shield_deadly_time_left > 0 &&
13661 !IS_INDESTRUCTIBLE(bad_element))
13662 Bang(bad_x, bad_y);
13663 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13664 KillPlayer(player);
13667 Bang(kill_x, kill_y);
13671 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13673 int bad_element = Tile[bad_x][bad_y];
13674 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13675 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13676 int test_x = bad_x + dx, test_y = bad_y + dy;
13677 int test_move_dir, test_element;
13678 int kill_x = -1, kill_y = -1;
13680 if (!IN_LEV_FIELD(test_x, test_y))
13684 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13686 test_element = Tile[test_x][test_y];
13688 if (test_move_dir != bad_move_dir)
13690 // good thing can be player or penguin that does not move away
13691 if (IS_PLAYER(test_x, test_y))
13693 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13695 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13696 player as being hit when he is moving towards the bad thing, because
13697 the "get hit by" condition would be lost after the player stops) */
13698 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13699 return; // player moves away from bad thing
13704 else if (test_element == EL_PENGUIN)
13711 if (kill_x != -1 || kill_y != -1)
13713 if (IS_PLAYER(kill_x, kill_y))
13715 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13717 if (player->shield_deadly_time_left > 0 &&
13718 !IS_INDESTRUCTIBLE(bad_element))
13719 Bang(bad_x, bad_y);
13720 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13721 KillPlayer(player);
13724 Bang(kill_x, kill_y);
13728 void TestIfPlayerTouchesBadThing(int x, int y)
13730 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13733 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13735 TestIfGoodThingHitsBadThing(x, y, move_dir);
13738 void TestIfBadThingTouchesPlayer(int x, int y)
13740 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13743 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13745 TestIfBadThingHitsGoodThing(x, y, move_dir);
13748 void TestIfFriendTouchesBadThing(int x, int y)
13750 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13753 void TestIfBadThingTouchesFriend(int x, int y)
13755 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13758 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13760 int i, kill_x = bad_x, kill_y = bad_y;
13761 static int xy[4][2] =
13769 for (i = 0; i < NUM_DIRECTIONS; i++)
13773 x = bad_x + xy[i][0];
13774 y = bad_y + xy[i][1];
13775 if (!IN_LEV_FIELD(x, y))
13778 element = Tile[x][y];
13779 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13780 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13788 if (kill_x != bad_x || kill_y != bad_y)
13789 Bang(bad_x, bad_y);
13792 void KillPlayer(struct PlayerInfo *player)
13794 int jx = player->jx, jy = player->jy;
13796 if (!player->active)
13800 Debug("game:playing:KillPlayer",
13801 "0: killed == %d, active == %d, reanimated == %d",
13802 player->killed, player->active, player->reanimated);
13805 /* the following code was introduced to prevent an infinite loop when calling
13807 -> CheckTriggeredElementChangeExt()
13808 -> ExecuteCustomElementAction()
13810 -> (infinitely repeating the above sequence of function calls)
13811 which occurs when killing the player while having a CE with the setting
13812 "kill player X when explosion of <player X>"; the solution using a new
13813 field "player->killed" was chosen for backwards compatibility, although
13814 clever use of the fields "player->active" etc. would probably also work */
13816 if (player->killed)
13820 player->killed = TRUE;
13822 // remove accessible field at the player's position
13823 Tile[jx][jy] = EL_EMPTY;
13825 // deactivate shield (else Bang()/Explode() would not work right)
13826 player->shield_normal_time_left = 0;
13827 player->shield_deadly_time_left = 0;
13830 Debug("game:playing:KillPlayer",
13831 "1: killed == %d, active == %d, reanimated == %d",
13832 player->killed, player->active, player->reanimated);
13838 Debug("game:playing:KillPlayer",
13839 "2: killed == %d, active == %d, reanimated == %d",
13840 player->killed, player->active, player->reanimated);
13843 if (player->reanimated) // killed player may have been reanimated
13844 player->killed = player->reanimated = FALSE;
13846 BuryPlayer(player);
13849 static void KillPlayerUnlessEnemyProtected(int x, int y)
13851 if (!PLAYER_ENEMY_PROTECTED(x, y))
13852 KillPlayer(PLAYERINFO(x, y));
13855 static void KillPlayerUnlessExplosionProtected(int x, int y)
13857 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13858 KillPlayer(PLAYERINFO(x, y));
13861 void BuryPlayer(struct PlayerInfo *player)
13863 int jx = player->jx, jy = player->jy;
13865 if (!player->active)
13868 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13869 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13871 RemovePlayer(player);
13873 player->buried = TRUE;
13875 if (game.all_players_gone)
13876 game.GameOver = TRUE;
13879 void RemovePlayer(struct PlayerInfo *player)
13881 int jx = player->jx, jy = player->jy;
13882 int i, found = FALSE;
13884 player->present = FALSE;
13885 player->active = FALSE;
13887 // required for some CE actions (even if the player is not active anymore)
13888 player->MovPos = 0;
13890 if (!ExplodeField[jx][jy])
13891 StorePlayer[jx][jy] = 0;
13893 if (player->is_moving)
13894 TEST_DrawLevelField(player->last_jx, player->last_jy);
13896 for (i = 0; i < MAX_PLAYERS; i++)
13897 if (stored_player[i].active)
13902 game.all_players_gone = TRUE;
13903 game.GameOver = TRUE;
13906 game.exit_x = game.robot_wheel_x = jx;
13907 game.exit_y = game.robot_wheel_y = jy;
13910 void ExitPlayer(struct PlayerInfo *player)
13912 DrawPlayer(player); // needed here only to cleanup last field
13913 RemovePlayer(player);
13915 if (game.players_still_needed > 0)
13916 game.players_still_needed--;
13919 static void SetFieldForSnapping(int x, int y, int element, int direction,
13920 int player_index_bit)
13922 struct ElementInfo *ei = &element_info[element];
13923 int direction_bit = MV_DIR_TO_BIT(direction);
13924 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13925 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13926 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13928 Tile[x][y] = EL_ELEMENT_SNAPPING;
13929 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13930 MovDir[x][y] = direction;
13931 Store[x][y] = element;
13932 Store2[x][y] = player_index_bit;
13934 ResetGfxAnimation(x, y);
13936 GfxElement[x][y] = element;
13937 GfxAction[x][y] = action;
13938 GfxDir[x][y] = direction;
13939 GfxFrame[x][y] = -1;
13942 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13943 int player_index_bit)
13945 TestIfElementTouchesCustomElement(x, y); // for empty space
13947 if (level.finish_dig_collect)
13949 int dig_side = MV_DIR_OPPOSITE(direction);
13951 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13952 player_index_bit, dig_side);
13957 =============================================================================
13958 checkDiagonalPushing()
13959 -----------------------------------------------------------------------------
13960 check if diagonal input device direction results in pushing of object
13961 (by checking if the alternative direction is walkable, diggable, ...)
13962 =============================================================================
13965 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13966 int x, int y, int real_dx, int real_dy)
13968 int jx, jy, dx, dy, xx, yy;
13970 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13973 // diagonal direction: check alternative direction
13978 xx = jx + (dx == 0 ? real_dx : 0);
13979 yy = jy + (dy == 0 ? real_dy : 0);
13981 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13985 =============================================================================
13987 -----------------------------------------------------------------------------
13988 x, y: field next to player (non-diagonal) to try to dig to
13989 real_dx, real_dy: direction as read from input device (can be diagonal)
13990 =============================================================================
13993 static int DigField(struct PlayerInfo *player,
13994 int oldx, int oldy, int x, int y,
13995 int real_dx, int real_dy, int mode)
13997 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13998 boolean player_was_pushing = player->is_pushing;
13999 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14000 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14001 int jx = oldx, jy = oldy;
14002 int dx = x - jx, dy = y - jy;
14003 int nextx = x + dx, nexty = y + dy;
14004 int move_direction = (dx == -1 ? MV_LEFT :
14005 dx == +1 ? MV_RIGHT :
14007 dy == +1 ? MV_DOWN : MV_NONE);
14008 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14009 int dig_side = MV_DIR_OPPOSITE(move_direction);
14010 int old_element = Tile[jx][jy];
14011 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14014 if (is_player) // function can also be called by EL_PENGUIN
14016 if (player->MovPos == 0)
14018 player->is_digging = FALSE;
14019 player->is_collecting = FALSE;
14022 if (player->MovPos == 0) // last pushing move finished
14023 player->is_pushing = FALSE;
14025 if (mode == DF_NO_PUSH) // player just stopped pushing
14027 player->is_switching = FALSE;
14028 player->push_delay = -1;
14030 return MP_NO_ACTION;
14034 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14035 old_element = Back[jx][jy];
14037 // in case of element dropped at player position, check background
14038 else if (Back[jx][jy] != EL_EMPTY &&
14039 game.engine_version >= VERSION_IDENT(2,2,0,0))
14040 old_element = Back[jx][jy];
14042 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14043 return MP_NO_ACTION; // field has no opening in this direction
14045 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14046 return MP_NO_ACTION; // field has no opening in this direction
14048 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14052 Tile[jx][jy] = player->artwork_element;
14053 InitMovingField(jx, jy, MV_DOWN);
14054 Store[jx][jy] = EL_ACID;
14055 ContinueMoving(jx, jy);
14056 BuryPlayer(player);
14058 return MP_DONT_RUN_INTO;
14061 if (player_can_move && DONT_RUN_INTO(element))
14063 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14065 return MP_DONT_RUN_INTO;
14068 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14069 return MP_NO_ACTION;
14071 collect_count = element_info[element].collect_count_initial;
14073 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14074 return MP_NO_ACTION;
14076 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14077 player_can_move = player_can_move_or_snap;
14079 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14080 game.engine_version >= VERSION_IDENT(2,2,0,0))
14082 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14083 player->index_bit, dig_side);
14084 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14085 player->index_bit, dig_side);
14087 if (element == EL_DC_LANDMINE)
14090 if (Tile[x][y] != element) // field changed by snapping
14093 return MP_NO_ACTION;
14096 if (player->gravity && is_player && !player->is_auto_moving &&
14097 canFallDown(player) && move_direction != MV_DOWN &&
14098 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14099 return MP_NO_ACTION; // player cannot walk here due to gravity
14101 if (player_can_move &&
14102 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14104 int sound_element = SND_ELEMENT(element);
14105 int sound_action = ACTION_WALKING;
14107 if (IS_RND_GATE(element))
14109 if (!player->key[RND_GATE_NR(element)])
14110 return MP_NO_ACTION;
14112 else if (IS_RND_GATE_GRAY(element))
14114 if (!player->key[RND_GATE_GRAY_NR(element)])
14115 return MP_NO_ACTION;
14117 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14119 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14120 return MP_NO_ACTION;
14122 else if (element == EL_EXIT_OPEN ||
14123 element == EL_EM_EXIT_OPEN ||
14124 element == EL_EM_EXIT_OPENING ||
14125 element == EL_STEEL_EXIT_OPEN ||
14126 element == EL_EM_STEEL_EXIT_OPEN ||
14127 element == EL_EM_STEEL_EXIT_OPENING ||
14128 element == EL_SP_EXIT_OPEN ||
14129 element == EL_SP_EXIT_OPENING)
14131 sound_action = ACTION_PASSING; // player is passing exit
14133 else if (element == EL_EMPTY)
14135 sound_action = ACTION_MOVING; // nothing to walk on
14138 // play sound from background or player, whatever is available
14139 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14140 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14142 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14144 else if (player_can_move &&
14145 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14147 if (!ACCESS_FROM(element, opposite_direction))
14148 return MP_NO_ACTION; // field not accessible from this direction
14150 if (CAN_MOVE(element)) // only fixed elements can be passed!
14151 return MP_NO_ACTION;
14153 if (IS_EM_GATE(element))
14155 if (!player->key[EM_GATE_NR(element)])
14156 return MP_NO_ACTION;
14158 else if (IS_EM_GATE_GRAY(element))
14160 if (!player->key[EM_GATE_GRAY_NR(element)])
14161 return MP_NO_ACTION;
14163 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14165 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14166 return MP_NO_ACTION;
14168 else if (IS_EMC_GATE(element))
14170 if (!player->key[EMC_GATE_NR(element)])
14171 return MP_NO_ACTION;
14173 else if (IS_EMC_GATE_GRAY(element))
14175 if (!player->key[EMC_GATE_GRAY_NR(element)])
14176 return MP_NO_ACTION;
14178 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14180 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14181 return MP_NO_ACTION;
14183 else if (element == EL_DC_GATE_WHITE ||
14184 element == EL_DC_GATE_WHITE_GRAY ||
14185 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14187 if (player->num_white_keys == 0)
14188 return MP_NO_ACTION;
14190 player->num_white_keys--;
14192 else if (IS_SP_PORT(element))
14194 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14195 element == EL_SP_GRAVITY_PORT_RIGHT ||
14196 element == EL_SP_GRAVITY_PORT_UP ||
14197 element == EL_SP_GRAVITY_PORT_DOWN)
14198 player->gravity = !player->gravity;
14199 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14200 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14201 element == EL_SP_GRAVITY_ON_PORT_UP ||
14202 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14203 player->gravity = TRUE;
14204 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14205 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14206 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14207 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14208 player->gravity = FALSE;
14211 // automatically move to the next field with double speed
14212 player->programmed_action = move_direction;
14214 if (player->move_delay_reset_counter == 0)
14216 player->move_delay_reset_counter = 2; // two double speed steps
14218 DOUBLE_PLAYER_SPEED(player);
14221 PlayLevelSoundAction(x, y, ACTION_PASSING);
14223 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14227 if (mode != DF_SNAP)
14229 GfxElement[x][y] = GFX_ELEMENT(element);
14230 player->is_digging = TRUE;
14233 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14235 // use old behaviour for old levels (digging)
14236 if (!level.finish_dig_collect)
14238 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14239 player->index_bit, dig_side);
14241 // if digging triggered player relocation, finish digging tile
14242 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14243 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14246 if (mode == DF_SNAP)
14248 if (level.block_snap_field)
14249 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14251 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14253 // use old behaviour for old levels (snapping)
14254 if (!level.finish_dig_collect)
14255 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14256 player->index_bit, dig_side);
14259 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14263 if (is_player && mode != DF_SNAP)
14265 GfxElement[x][y] = element;
14266 player->is_collecting = TRUE;
14269 if (element == EL_SPEED_PILL)
14271 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14273 else if (element == EL_EXTRA_TIME && level.time > 0)
14275 TimeLeft += level.extra_time;
14277 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14279 DisplayGameControlValues();
14281 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14283 player->shield_normal_time_left += level.shield_normal_time;
14284 if (element == EL_SHIELD_DEADLY)
14285 player->shield_deadly_time_left += level.shield_deadly_time;
14287 else if (element == EL_DYNAMITE ||
14288 element == EL_EM_DYNAMITE ||
14289 element == EL_SP_DISK_RED)
14291 if (player->inventory_size < MAX_INVENTORY_SIZE)
14292 player->inventory_element[player->inventory_size++] = element;
14294 DrawGameDoorValues();
14296 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14298 player->dynabomb_count++;
14299 player->dynabombs_left++;
14301 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14303 player->dynabomb_size++;
14305 else if (element == EL_DYNABOMB_INCREASE_POWER)
14307 player->dynabomb_xl = TRUE;
14309 else if (IS_KEY(element))
14311 player->key[KEY_NR(element)] = TRUE;
14313 DrawGameDoorValues();
14315 else if (element == EL_DC_KEY_WHITE)
14317 player->num_white_keys++;
14319 // display white keys?
14320 // DrawGameDoorValues();
14322 else if (IS_ENVELOPE(element))
14324 player->show_envelope = element;
14326 else if (element == EL_EMC_LENSES)
14328 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14330 RedrawAllInvisibleElementsForLenses();
14332 else if (element == EL_EMC_MAGNIFIER)
14334 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14336 RedrawAllInvisibleElementsForMagnifier();
14338 else if (IS_DROPPABLE(element) ||
14339 IS_THROWABLE(element)) // can be collected and dropped
14343 if (collect_count == 0)
14344 player->inventory_infinite_element = element;
14346 for (i = 0; i < collect_count; i++)
14347 if (player->inventory_size < MAX_INVENTORY_SIZE)
14348 player->inventory_element[player->inventory_size++] = element;
14350 DrawGameDoorValues();
14352 else if (collect_count > 0)
14354 game.gems_still_needed -= collect_count;
14355 if (game.gems_still_needed < 0)
14356 game.gems_still_needed = 0;
14358 game.snapshot.collected_item = TRUE;
14360 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14362 DisplayGameControlValues();
14365 RaiseScoreElement(element);
14366 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14368 // use old behaviour for old levels (collecting)
14369 if (!level.finish_dig_collect && is_player)
14371 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14372 player->index_bit, dig_side);
14374 // if collecting triggered player relocation, finish collecting tile
14375 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14376 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14379 if (mode == DF_SNAP)
14381 if (level.block_snap_field)
14382 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14384 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14386 // use old behaviour for old levels (snapping)
14387 if (!level.finish_dig_collect)
14388 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14389 player->index_bit, dig_side);
14392 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14394 if (mode == DF_SNAP && element != EL_BD_ROCK)
14395 return MP_NO_ACTION;
14397 if (CAN_FALL(element) && dy)
14398 return MP_NO_ACTION;
14400 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14401 !(element == EL_SPRING && level.use_spring_bug))
14402 return MP_NO_ACTION;
14404 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14405 ((move_direction & MV_VERTICAL &&
14406 ((element_info[element].move_pattern & MV_LEFT &&
14407 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14408 (element_info[element].move_pattern & MV_RIGHT &&
14409 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14410 (move_direction & MV_HORIZONTAL &&
14411 ((element_info[element].move_pattern & MV_UP &&
14412 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14413 (element_info[element].move_pattern & MV_DOWN &&
14414 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14415 return MP_NO_ACTION;
14417 // do not push elements already moving away faster than player
14418 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14419 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14420 return MP_NO_ACTION;
14422 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14424 if (player->push_delay_value == -1 || !player_was_pushing)
14425 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14427 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14429 if (player->push_delay_value == -1)
14430 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14432 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14434 if (!player->is_pushing)
14435 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14438 player->is_pushing = TRUE;
14439 player->is_active = TRUE;
14441 if (!(IN_LEV_FIELD(nextx, nexty) &&
14442 (IS_FREE(nextx, nexty) ||
14443 (IS_SB_ELEMENT(element) &&
14444 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14445 (IS_CUSTOM_ELEMENT(element) &&
14446 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14447 return MP_NO_ACTION;
14449 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14450 return MP_NO_ACTION;
14452 if (player->push_delay == -1) // new pushing; restart delay
14453 player->push_delay = 0;
14455 if (player->push_delay < player->push_delay_value &&
14456 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14457 element != EL_SPRING && element != EL_BALLOON)
14459 // make sure that there is no move delay before next try to push
14460 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14461 player->move_delay = 0;
14463 return MP_NO_ACTION;
14466 if (IS_CUSTOM_ELEMENT(element) &&
14467 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14469 if (!DigFieldByCE(nextx, nexty, element))
14470 return MP_NO_ACTION;
14473 if (IS_SB_ELEMENT(element))
14475 boolean sokoban_task_solved = FALSE;
14477 if (element == EL_SOKOBAN_FIELD_FULL)
14479 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14481 IncrementSokobanFieldsNeeded();
14482 IncrementSokobanObjectsNeeded();
14485 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14487 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14489 DecrementSokobanFieldsNeeded();
14490 DecrementSokobanObjectsNeeded();
14492 // sokoban object was pushed from empty field to sokoban field
14493 if (Back[x][y] == EL_EMPTY)
14494 sokoban_task_solved = TRUE;
14497 Tile[x][y] = EL_SOKOBAN_OBJECT;
14499 if (Back[x][y] == Back[nextx][nexty])
14500 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14501 else if (Back[x][y] != 0)
14502 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14505 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14508 if (sokoban_task_solved &&
14509 game.sokoban_fields_still_needed == 0 &&
14510 game.sokoban_objects_still_needed == 0 &&
14511 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14513 game.players_still_needed = 0;
14517 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14521 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14523 InitMovingField(x, y, move_direction);
14524 GfxAction[x][y] = ACTION_PUSHING;
14526 if (mode == DF_SNAP)
14527 ContinueMoving(x, y);
14529 MovPos[x][y] = (dx != 0 ? dx : dy);
14531 Pushed[x][y] = TRUE;
14532 Pushed[nextx][nexty] = TRUE;
14534 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14535 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14537 player->push_delay_value = -1; // get new value later
14539 // check for element change _after_ element has been pushed
14540 if (game.use_change_when_pushing_bug)
14542 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14543 player->index_bit, dig_side);
14544 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14545 player->index_bit, dig_side);
14548 else if (IS_SWITCHABLE(element))
14550 if (PLAYER_SWITCHING(player, x, y))
14552 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14553 player->index_bit, dig_side);
14558 player->is_switching = TRUE;
14559 player->switch_x = x;
14560 player->switch_y = y;
14562 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14564 if (element == EL_ROBOT_WHEEL)
14566 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14568 game.robot_wheel_x = x;
14569 game.robot_wheel_y = y;
14570 game.robot_wheel_active = TRUE;
14572 TEST_DrawLevelField(x, y);
14574 else if (element == EL_SP_TERMINAL)
14578 SCAN_PLAYFIELD(xx, yy)
14580 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14584 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14586 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14588 ResetGfxAnimation(xx, yy);
14589 TEST_DrawLevelField(xx, yy);
14593 else if (IS_BELT_SWITCH(element))
14595 ToggleBeltSwitch(x, y);
14597 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14598 element == EL_SWITCHGATE_SWITCH_DOWN ||
14599 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14600 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14602 ToggleSwitchgateSwitch(x, y);
14604 else if (element == EL_LIGHT_SWITCH ||
14605 element == EL_LIGHT_SWITCH_ACTIVE)
14607 ToggleLightSwitch(x, y);
14609 else if (element == EL_TIMEGATE_SWITCH ||
14610 element == EL_DC_TIMEGATE_SWITCH)
14612 ActivateTimegateSwitch(x, y);
14614 else if (element == EL_BALLOON_SWITCH_LEFT ||
14615 element == EL_BALLOON_SWITCH_RIGHT ||
14616 element == EL_BALLOON_SWITCH_UP ||
14617 element == EL_BALLOON_SWITCH_DOWN ||
14618 element == EL_BALLOON_SWITCH_NONE ||
14619 element == EL_BALLOON_SWITCH_ANY)
14621 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14622 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14623 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14624 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14625 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14628 else if (element == EL_LAMP)
14630 Tile[x][y] = EL_LAMP_ACTIVE;
14631 game.lights_still_needed--;
14633 ResetGfxAnimation(x, y);
14634 TEST_DrawLevelField(x, y);
14636 else if (element == EL_TIME_ORB_FULL)
14638 Tile[x][y] = EL_TIME_ORB_EMPTY;
14640 if (level.time > 0 || level.use_time_orb_bug)
14642 TimeLeft += level.time_orb_time;
14643 game.no_time_limit = FALSE;
14645 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14647 DisplayGameControlValues();
14650 ResetGfxAnimation(x, y);
14651 TEST_DrawLevelField(x, y);
14653 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14654 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14658 game.ball_active = !game.ball_active;
14660 SCAN_PLAYFIELD(xx, yy)
14662 int e = Tile[xx][yy];
14664 if (game.ball_active)
14666 if (e == EL_EMC_MAGIC_BALL)
14667 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14668 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14669 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14673 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14674 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14675 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14676 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14681 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14682 player->index_bit, dig_side);
14684 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14685 player->index_bit, dig_side);
14687 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14688 player->index_bit, dig_side);
14694 if (!PLAYER_SWITCHING(player, x, y))
14696 player->is_switching = TRUE;
14697 player->switch_x = x;
14698 player->switch_y = y;
14700 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14701 player->index_bit, dig_side);
14702 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14703 player->index_bit, dig_side);
14705 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14706 player->index_bit, dig_side);
14707 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14708 player->index_bit, dig_side);
14711 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14712 player->index_bit, dig_side);
14713 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14714 player->index_bit, dig_side);
14716 return MP_NO_ACTION;
14719 player->push_delay = -1;
14721 if (is_player) // function can also be called by EL_PENGUIN
14723 if (Tile[x][y] != element) // really digged/collected something
14725 player->is_collecting = !player->is_digging;
14726 player->is_active = TRUE;
14728 player->last_removed_element = element;
14735 static boolean DigFieldByCE(int x, int y, int digging_element)
14737 int element = Tile[x][y];
14739 if (!IS_FREE(x, y))
14741 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14742 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14745 // no element can dig solid indestructible elements
14746 if (IS_INDESTRUCTIBLE(element) &&
14747 !IS_DIGGABLE(element) &&
14748 !IS_COLLECTIBLE(element))
14751 if (AmoebaNr[x][y] &&
14752 (element == EL_AMOEBA_FULL ||
14753 element == EL_BD_AMOEBA ||
14754 element == EL_AMOEBA_GROWING))
14756 AmoebaCnt[AmoebaNr[x][y]]--;
14757 AmoebaCnt2[AmoebaNr[x][y]]--;
14760 if (IS_MOVING(x, y))
14761 RemoveMovingField(x, y);
14765 TEST_DrawLevelField(x, y);
14768 // if digged element was about to explode, prevent the explosion
14769 ExplodeField[x][y] = EX_TYPE_NONE;
14771 PlayLevelSoundAction(x, y, action);
14774 Store[x][y] = EL_EMPTY;
14776 // this makes it possible to leave the removed element again
14777 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14778 Store[x][y] = element;
14783 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14785 int jx = player->jx, jy = player->jy;
14786 int x = jx + dx, y = jy + dy;
14787 int snap_direction = (dx == -1 ? MV_LEFT :
14788 dx == +1 ? MV_RIGHT :
14790 dy == +1 ? MV_DOWN : MV_NONE);
14791 boolean can_continue_snapping = (level.continuous_snapping &&
14792 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14794 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14797 if (!player->active || !IN_LEV_FIELD(x, y))
14805 if (player->MovPos == 0)
14806 player->is_pushing = FALSE;
14808 player->is_snapping = FALSE;
14810 if (player->MovPos == 0)
14812 player->is_moving = FALSE;
14813 player->is_digging = FALSE;
14814 player->is_collecting = FALSE;
14820 // prevent snapping with already pressed snap key when not allowed
14821 if (player->is_snapping && !can_continue_snapping)
14824 player->MovDir = snap_direction;
14826 if (player->MovPos == 0)
14828 player->is_moving = FALSE;
14829 player->is_digging = FALSE;
14830 player->is_collecting = FALSE;
14833 player->is_dropping = FALSE;
14834 player->is_dropping_pressed = FALSE;
14835 player->drop_pressed_delay = 0;
14837 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14840 player->is_snapping = TRUE;
14841 player->is_active = TRUE;
14843 if (player->MovPos == 0)
14845 player->is_moving = FALSE;
14846 player->is_digging = FALSE;
14847 player->is_collecting = FALSE;
14850 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14851 TEST_DrawLevelField(player->last_jx, player->last_jy);
14853 TEST_DrawLevelField(x, y);
14858 static boolean DropElement(struct PlayerInfo *player)
14860 int old_element, new_element;
14861 int dropx = player->jx, dropy = player->jy;
14862 int drop_direction = player->MovDir;
14863 int drop_side = drop_direction;
14864 int drop_element = get_next_dropped_element(player);
14866 /* do not drop an element on top of another element; when holding drop key
14867 pressed without moving, dropped element must move away before the next
14868 element can be dropped (this is especially important if the next element
14869 is dynamite, which can be placed on background for historical reasons) */
14870 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14873 if (IS_THROWABLE(drop_element))
14875 dropx += GET_DX_FROM_DIR(drop_direction);
14876 dropy += GET_DY_FROM_DIR(drop_direction);
14878 if (!IN_LEV_FIELD(dropx, dropy))
14882 old_element = Tile[dropx][dropy]; // old element at dropping position
14883 new_element = drop_element; // default: no change when dropping
14885 // check if player is active, not moving and ready to drop
14886 if (!player->active || player->MovPos || player->drop_delay > 0)
14889 // check if player has anything that can be dropped
14890 if (new_element == EL_UNDEFINED)
14893 // only set if player has anything that can be dropped
14894 player->is_dropping_pressed = TRUE;
14896 // check if drop key was pressed long enough for EM style dynamite
14897 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14900 // check if anything can be dropped at the current position
14901 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14904 // collected custom elements can only be dropped on empty fields
14905 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14908 if (old_element != EL_EMPTY)
14909 Back[dropx][dropy] = old_element; // store old element on this field
14911 ResetGfxAnimation(dropx, dropy);
14912 ResetRandomAnimationValue(dropx, dropy);
14914 if (player->inventory_size > 0 ||
14915 player->inventory_infinite_element != EL_UNDEFINED)
14917 if (player->inventory_size > 0)
14919 player->inventory_size--;
14921 DrawGameDoorValues();
14923 if (new_element == EL_DYNAMITE)
14924 new_element = EL_DYNAMITE_ACTIVE;
14925 else if (new_element == EL_EM_DYNAMITE)
14926 new_element = EL_EM_DYNAMITE_ACTIVE;
14927 else if (new_element == EL_SP_DISK_RED)
14928 new_element = EL_SP_DISK_RED_ACTIVE;
14931 Tile[dropx][dropy] = new_element;
14933 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14934 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14935 el2img(Tile[dropx][dropy]), 0);
14937 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14939 // needed if previous element just changed to "empty" in the last frame
14940 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14942 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14943 player->index_bit, drop_side);
14944 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14946 player->index_bit, drop_side);
14948 TestIfElementTouchesCustomElement(dropx, dropy);
14950 else // player is dropping a dyna bomb
14952 player->dynabombs_left--;
14954 Tile[dropx][dropy] = new_element;
14956 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14957 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14958 el2img(Tile[dropx][dropy]), 0);
14960 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14963 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14964 InitField_WithBug1(dropx, dropy, FALSE);
14966 new_element = Tile[dropx][dropy]; // element might have changed
14968 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14969 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14971 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14972 MovDir[dropx][dropy] = drop_direction;
14974 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14976 // do not cause impact style collision by dropping elements that can fall
14977 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14980 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14981 player->is_dropping = TRUE;
14983 player->drop_pressed_delay = 0;
14984 player->is_dropping_pressed = FALSE;
14986 player->drop_x = dropx;
14987 player->drop_y = dropy;
14992 // ----------------------------------------------------------------------------
14993 // game sound playing functions
14994 // ----------------------------------------------------------------------------
14996 static int *loop_sound_frame = NULL;
14997 static int *loop_sound_volume = NULL;
14999 void InitPlayLevelSound(void)
15001 int num_sounds = getSoundListSize();
15003 checked_free(loop_sound_frame);
15004 checked_free(loop_sound_volume);
15006 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15007 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15010 static void PlayLevelSound(int x, int y, int nr)
15012 int sx = SCREENX(x), sy = SCREENY(y);
15013 int volume, stereo_position;
15014 int max_distance = 8;
15015 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15017 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15018 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15021 if (!IN_LEV_FIELD(x, y) ||
15022 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15023 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15026 volume = SOUND_MAX_VOLUME;
15028 if (!IN_SCR_FIELD(sx, sy))
15030 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15031 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15033 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15036 stereo_position = (SOUND_MAX_LEFT +
15037 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15038 (SCR_FIELDX + 2 * max_distance));
15040 if (IS_LOOP_SOUND(nr))
15042 /* This assures that quieter loop sounds do not overwrite louder ones,
15043 while restarting sound volume comparison with each new game frame. */
15045 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15048 loop_sound_volume[nr] = volume;
15049 loop_sound_frame[nr] = FrameCounter;
15052 PlaySoundExt(nr, volume, stereo_position, type);
15055 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15057 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15058 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15059 y < LEVELY(BY1) ? LEVELY(BY1) :
15060 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15064 static void PlayLevelSoundAction(int x, int y, int action)
15066 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15069 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15071 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15073 if (sound_effect != SND_UNDEFINED)
15074 PlayLevelSound(x, y, sound_effect);
15077 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15080 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15082 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15083 PlayLevelSound(x, y, sound_effect);
15086 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15088 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15090 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15091 PlayLevelSound(x, y, sound_effect);
15094 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15096 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15098 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15099 StopSound(sound_effect);
15102 static int getLevelMusicNr(void)
15104 if (levelset.music[level_nr] != MUS_UNDEFINED)
15105 return levelset.music[level_nr]; // from config file
15107 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15110 static void FadeLevelSounds(void)
15115 static void FadeLevelMusic(void)
15117 int music_nr = getLevelMusicNr();
15118 char *curr_music = getCurrentlyPlayingMusicFilename();
15119 char *next_music = getMusicInfoEntryFilename(music_nr);
15121 if (!strEqual(curr_music, next_music))
15125 void FadeLevelSoundsAndMusic(void)
15131 static void PlayLevelMusic(void)
15133 int music_nr = getLevelMusicNr();
15134 char *curr_music = getCurrentlyPlayingMusicFilename();
15135 char *next_music = getMusicInfoEntryFilename(music_nr);
15137 if (!strEqual(curr_music, next_music))
15138 PlayMusicLoop(music_nr);
15141 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15143 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15145 int x = xx - offset;
15146 int y = yy - offset;
15151 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15155 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15159 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15163 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15167 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15171 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15175 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15178 case SOUND_android_clone:
15179 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15182 case SOUND_android_move:
15183 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15187 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15191 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15195 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15198 case SOUND_eater_eat:
15199 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15203 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15206 case SOUND_collect:
15207 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15210 case SOUND_diamond:
15211 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15215 // !!! CHECK THIS !!!
15217 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15219 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15223 case SOUND_wonderfall:
15224 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15228 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15232 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15236 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15240 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15244 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15248 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15252 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15256 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15259 case SOUND_exit_open:
15260 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15263 case SOUND_exit_leave:
15264 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15267 case SOUND_dynamite:
15268 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15272 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15276 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15280 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15284 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15288 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15292 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15296 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15301 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15303 int element = map_element_SP_to_RND(element_sp);
15304 int action = map_action_SP_to_RND(action_sp);
15305 int offset = (setup.sp_show_border_elements ? 0 : 1);
15306 int x = xx - offset;
15307 int y = yy - offset;
15309 PlayLevelSoundElementAction(x, y, element, action);
15312 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15314 int element = map_element_MM_to_RND(element_mm);
15315 int action = map_action_MM_to_RND(action_mm);
15317 int x = xx - offset;
15318 int y = yy - offset;
15320 if (!IS_MM_ELEMENT(element))
15321 element = EL_MM_DEFAULT;
15323 PlayLevelSoundElementAction(x, y, element, action);
15326 void PlaySound_MM(int sound_mm)
15328 int sound = map_sound_MM_to_RND(sound_mm);
15330 if (sound == SND_UNDEFINED)
15336 void PlaySoundLoop_MM(int sound_mm)
15338 int sound = map_sound_MM_to_RND(sound_mm);
15340 if (sound == SND_UNDEFINED)
15343 PlaySoundLoop(sound);
15346 void StopSound_MM(int sound_mm)
15348 int sound = map_sound_MM_to_RND(sound_mm);
15350 if (sound == SND_UNDEFINED)
15356 void RaiseScore(int value)
15358 game.score += value;
15360 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15362 DisplayGameControlValues();
15365 void RaiseScoreElement(int element)
15370 case EL_BD_DIAMOND:
15371 case EL_EMERALD_YELLOW:
15372 case EL_EMERALD_RED:
15373 case EL_EMERALD_PURPLE:
15374 case EL_SP_INFOTRON:
15375 RaiseScore(level.score[SC_EMERALD]);
15378 RaiseScore(level.score[SC_DIAMOND]);
15381 RaiseScore(level.score[SC_CRYSTAL]);
15384 RaiseScore(level.score[SC_PEARL]);
15387 case EL_BD_BUTTERFLY:
15388 case EL_SP_ELECTRON:
15389 RaiseScore(level.score[SC_BUG]);
15392 case EL_BD_FIREFLY:
15393 case EL_SP_SNIKSNAK:
15394 RaiseScore(level.score[SC_SPACESHIP]);
15397 case EL_DARK_YAMYAM:
15398 RaiseScore(level.score[SC_YAMYAM]);
15401 RaiseScore(level.score[SC_ROBOT]);
15404 RaiseScore(level.score[SC_PACMAN]);
15407 RaiseScore(level.score[SC_NUT]);
15410 case EL_EM_DYNAMITE:
15411 case EL_SP_DISK_RED:
15412 case EL_DYNABOMB_INCREASE_NUMBER:
15413 case EL_DYNABOMB_INCREASE_SIZE:
15414 case EL_DYNABOMB_INCREASE_POWER:
15415 RaiseScore(level.score[SC_DYNAMITE]);
15417 case EL_SHIELD_NORMAL:
15418 case EL_SHIELD_DEADLY:
15419 RaiseScore(level.score[SC_SHIELD]);
15421 case EL_EXTRA_TIME:
15422 RaiseScore(level.extra_time_score);
15436 case EL_DC_KEY_WHITE:
15437 RaiseScore(level.score[SC_KEY]);
15440 RaiseScore(element_info[element].collect_score);
15445 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15447 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15451 // prevent short reactivation of overlay buttons while closing door
15452 SetOverlayActive(FALSE);
15454 // door may still be open due to skipped or envelope style request
15455 CloseDoor(DOOR_CLOSE_1);
15458 if (network.enabled)
15459 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15463 FadeSkipNextFadeIn();
15465 SetGameStatus(GAME_MODE_MAIN);
15470 else // continue playing the game
15472 if (tape.playing && tape.deactivate_display)
15473 TapeDeactivateDisplayOff(TRUE);
15475 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15477 if (tape.playing && tape.deactivate_display)
15478 TapeDeactivateDisplayOn();
15482 void RequestQuitGame(boolean escape_key_pressed)
15484 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15485 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15486 level_editor_test_game);
15487 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15490 RequestQuitGameExt(skip_request, quick_quit,
15491 "Do you really want to quit the game?");
15494 void RequestRestartGame(char *message)
15496 game.restart_game_message = NULL;
15498 boolean has_started_game = hasStartedNetworkGame();
15499 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15501 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15503 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15507 // needed in case of envelope request to close game panel
15508 CloseDoor(DOOR_CLOSE_1);
15510 SetGameStatus(GAME_MODE_MAIN);
15516 void CheckGameOver(void)
15518 static boolean last_game_over = FALSE;
15519 static int game_over_delay = 0;
15520 int game_over_delay_value = 50;
15521 boolean game_over = checkGameFailed();
15523 // do not handle game over if request dialog is already active
15524 if (game.request_active)
15527 // do not ask to play again if game was never actually played
15528 if (!game.GamePlayed)
15533 last_game_over = FALSE;
15534 game_over_delay = game_over_delay_value;
15539 if (game_over_delay > 0)
15546 if (last_game_over != game_over)
15547 game.restart_game_message = (hasStartedNetworkGame() ?
15548 "Game over! Play it again?" :
15551 last_game_over = game_over;
15554 boolean checkGameSolved(void)
15556 // set for all game engines if level was solved
15557 return game.LevelSolved_GameEnd;
15560 boolean checkGameFailed(void)
15562 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15563 return (game_em.game_over && !game_em.level_solved);
15564 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15565 return (game_sp.game_over && !game_sp.level_solved);
15566 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15567 return (game_mm.game_over && !game_mm.level_solved);
15568 else // GAME_ENGINE_TYPE_RND
15569 return (game.GameOver && !game.LevelSolved);
15572 boolean checkGameEnded(void)
15574 return (checkGameSolved() || checkGameFailed());
15578 // ----------------------------------------------------------------------------
15579 // random generator functions
15580 // ----------------------------------------------------------------------------
15582 unsigned int InitEngineRandom_RND(int seed)
15584 game.num_random_calls = 0;
15586 return InitEngineRandom(seed);
15589 unsigned int RND(int max)
15593 game.num_random_calls++;
15595 return GetEngineRandom(max);
15602 // ----------------------------------------------------------------------------
15603 // game engine snapshot handling functions
15604 // ----------------------------------------------------------------------------
15606 struct EngineSnapshotInfo
15608 // runtime values for custom element collect score
15609 int collect_score[NUM_CUSTOM_ELEMENTS];
15611 // runtime values for group element choice position
15612 int choice_pos[NUM_GROUP_ELEMENTS];
15614 // runtime values for belt position animations
15615 int belt_graphic[4][NUM_BELT_PARTS];
15616 int belt_anim_mode[4][NUM_BELT_PARTS];
15619 static struct EngineSnapshotInfo engine_snapshot_rnd;
15620 static char *snapshot_level_identifier = NULL;
15621 static int snapshot_level_nr = -1;
15623 static void SaveEngineSnapshotValues_RND(void)
15625 static int belt_base_active_element[4] =
15627 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15628 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15629 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15630 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15634 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15636 int element = EL_CUSTOM_START + i;
15638 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15641 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15643 int element = EL_GROUP_START + i;
15645 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15648 for (i = 0; i < 4; i++)
15650 for (j = 0; j < NUM_BELT_PARTS; j++)
15652 int element = belt_base_active_element[i] + j;
15653 int graphic = el2img(element);
15654 int anim_mode = graphic_info[graphic].anim_mode;
15656 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15657 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15662 static void LoadEngineSnapshotValues_RND(void)
15664 unsigned int num_random_calls = game.num_random_calls;
15667 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15669 int element = EL_CUSTOM_START + i;
15671 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15674 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15676 int element = EL_GROUP_START + i;
15678 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15681 for (i = 0; i < 4; i++)
15683 for (j = 0; j < NUM_BELT_PARTS; j++)
15685 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15686 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15688 graphic_info[graphic].anim_mode = anim_mode;
15692 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15694 InitRND(tape.random_seed);
15695 for (i = 0; i < num_random_calls; i++)
15699 if (game.num_random_calls != num_random_calls)
15701 Error("number of random calls out of sync");
15702 Error("number of random calls should be %d", num_random_calls);
15703 Error("number of random calls is %d", game.num_random_calls);
15705 Fail("this should not happen -- please debug");
15709 void FreeEngineSnapshotSingle(void)
15711 FreeSnapshotSingle();
15713 setString(&snapshot_level_identifier, NULL);
15714 snapshot_level_nr = -1;
15717 void FreeEngineSnapshotList(void)
15719 FreeSnapshotList();
15722 static ListNode *SaveEngineSnapshotBuffers(void)
15724 ListNode *buffers = NULL;
15726 // copy some special values to a structure better suited for the snapshot
15728 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15729 SaveEngineSnapshotValues_RND();
15730 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15731 SaveEngineSnapshotValues_EM();
15732 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15733 SaveEngineSnapshotValues_SP(&buffers);
15734 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15735 SaveEngineSnapshotValues_MM(&buffers);
15737 // save values stored in special snapshot structure
15739 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15740 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15741 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15742 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15743 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15744 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15745 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15746 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15748 // save further RND engine values
15750 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15751 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15752 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15754 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15755 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15756 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15757 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15758 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15760 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15761 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15762 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15764 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15766 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15767 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15769 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15770 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15771 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15772 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15773 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15774 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15775 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15776 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15777 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15778 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15779 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15780 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15781 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15782 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15783 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15784 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15785 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15786 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15788 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15789 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15791 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15792 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15793 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15795 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15796 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15798 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15799 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15800 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15801 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15802 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15804 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15805 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15808 ListNode *node = engine_snapshot_list_rnd;
15811 while (node != NULL)
15813 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15818 Debug("game:playing:SaveEngineSnapshotBuffers",
15819 "size of engine snapshot: %d bytes", num_bytes);
15825 void SaveEngineSnapshotSingle(void)
15827 ListNode *buffers = SaveEngineSnapshotBuffers();
15829 // finally save all snapshot buffers to single snapshot
15830 SaveSnapshotSingle(buffers);
15832 // save level identification information
15833 setString(&snapshot_level_identifier, leveldir_current->identifier);
15834 snapshot_level_nr = level_nr;
15837 boolean CheckSaveEngineSnapshotToList(void)
15839 boolean save_snapshot =
15840 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15841 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15842 game.snapshot.changed_action) ||
15843 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15844 game.snapshot.collected_item));
15846 game.snapshot.changed_action = FALSE;
15847 game.snapshot.collected_item = FALSE;
15848 game.snapshot.save_snapshot = save_snapshot;
15850 return save_snapshot;
15853 void SaveEngineSnapshotToList(void)
15855 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15859 ListNode *buffers = SaveEngineSnapshotBuffers();
15861 // finally save all snapshot buffers to snapshot list
15862 SaveSnapshotToList(buffers);
15865 void SaveEngineSnapshotToListInitial(void)
15867 FreeEngineSnapshotList();
15869 SaveEngineSnapshotToList();
15872 static void LoadEngineSnapshotValues(void)
15874 // restore special values from snapshot structure
15876 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15877 LoadEngineSnapshotValues_RND();
15878 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15879 LoadEngineSnapshotValues_EM();
15880 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15881 LoadEngineSnapshotValues_SP();
15882 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15883 LoadEngineSnapshotValues_MM();
15886 void LoadEngineSnapshotSingle(void)
15888 LoadSnapshotSingle();
15890 LoadEngineSnapshotValues();
15893 static void LoadEngineSnapshot_Undo(int steps)
15895 LoadSnapshotFromList_Older(steps);
15897 LoadEngineSnapshotValues();
15900 static void LoadEngineSnapshot_Redo(int steps)
15902 LoadSnapshotFromList_Newer(steps);
15904 LoadEngineSnapshotValues();
15907 boolean CheckEngineSnapshotSingle(void)
15909 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15910 snapshot_level_nr == level_nr);
15913 boolean CheckEngineSnapshotList(void)
15915 return CheckSnapshotList();
15919 // ---------- new game button stuff -------------------------------------------
15926 boolean *setup_value;
15927 boolean allowed_on_tape;
15928 boolean is_touch_button;
15930 } gamebutton_info[NUM_GAME_BUTTONS] =
15933 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15934 GAME_CTRL_ID_STOP, NULL,
15935 TRUE, FALSE, "stop game"
15938 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15939 GAME_CTRL_ID_PAUSE, NULL,
15940 TRUE, FALSE, "pause game"
15943 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15944 GAME_CTRL_ID_PLAY, NULL,
15945 TRUE, FALSE, "play game"
15948 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15949 GAME_CTRL_ID_UNDO, NULL,
15950 TRUE, FALSE, "undo step"
15953 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15954 GAME_CTRL_ID_REDO, NULL,
15955 TRUE, FALSE, "redo step"
15958 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15959 GAME_CTRL_ID_SAVE, NULL,
15960 TRUE, FALSE, "save game"
15963 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15964 GAME_CTRL_ID_PAUSE2, NULL,
15965 TRUE, FALSE, "pause game"
15968 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15969 GAME_CTRL_ID_LOAD, NULL,
15970 TRUE, FALSE, "load game"
15973 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15974 GAME_CTRL_ID_PANEL_STOP, NULL,
15975 FALSE, FALSE, "stop game"
15978 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15979 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15980 FALSE, FALSE, "pause game"
15983 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15984 GAME_CTRL_ID_PANEL_PLAY, NULL,
15985 FALSE, FALSE, "play game"
15988 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15989 GAME_CTRL_ID_TOUCH_STOP, NULL,
15990 FALSE, TRUE, "stop game"
15993 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15994 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15995 FALSE, TRUE, "pause game"
15998 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15999 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16000 TRUE, FALSE, "background music on/off"
16003 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16004 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16005 TRUE, FALSE, "sound loops on/off"
16008 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16009 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16010 TRUE, FALSE, "normal sounds on/off"
16013 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16014 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16015 FALSE, FALSE, "background music on/off"
16018 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16019 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16020 FALSE, FALSE, "sound loops on/off"
16023 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16024 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16025 FALSE, FALSE, "normal sounds on/off"
16029 void CreateGameButtons(void)
16033 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16035 int graphic = gamebutton_info[i].graphic;
16036 struct GraphicInfo *gfx = &graphic_info[graphic];
16037 struct XY *pos = gamebutton_info[i].pos;
16038 struct GadgetInfo *gi;
16041 unsigned int event_mask;
16042 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16043 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16044 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16045 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16046 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16047 int gd_x = gfx->src_x;
16048 int gd_y = gfx->src_y;
16049 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16050 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16051 int gd_xa = gfx->src_x + gfx->active_xoffset;
16052 int gd_ya = gfx->src_y + gfx->active_yoffset;
16053 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16054 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16055 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16056 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16059 if (gfx->bitmap == NULL)
16061 game_gadget[id] = NULL;
16066 if (id == GAME_CTRL_ID_STOP ||
16067 id == GAME_CTRL_ID_PANEL_STOP ||
16068 id == GAME_CTRL_ID_TOUCH_STOP ||
16069 id == GAME_CTRL_ID_PLAY ||
16070 id == GAME_CTRL_ID_PANEL_PLAY ||
16071 id == GAME_CTRL_ID_SAVE ||
16072 id == GAME_CTRL_ID_LOAD)
16074 button_type = GD_TYPE_NORMAL_BUTTON;
16076 event_mask = GD_EVENT_RELEASED;
16078 else if (id == GAME_CTRL_ID_UNDO ||
16079 id == GAME_CTRL_ID_REDO)
16081 button_type = GD_TYPE_NORMAL_BUTTON;
16083 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16087 button_type = GD_TYPE_CHECK_BUTTON;
16088 checked = (gamebutton_info[i].setup_value != NULL ?
16089 *gamebutton_info[i].setup_value : FALSE);
16090 event_mask = GD_EVENT_PRESSED;
16093 gi = CreateGadget(GDI_CUSTOM_ID, id,
16094 GDI_IMAGE_ID, graphic,
16095 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16098 GDI_WIDTH, gfx->width,
16099 GDI_HEIGHT, gfx->height,
16100 GDI_TYPE, button_type,
16101 GDI_STATE, GD_BUTTON_UNPRESSED,
16102 GDI_CHECKED, checked,
16103 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16104 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16105 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16106 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16107 GDI_DIRECT_DRAW, FALSE,
16108 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16109 GDI_EVENT_MASK, event_mask,
16110 GDI_CALLBACK_ACTION, HandleGameButtons,
16114 Fail("cannot create gadget");
16116 game_gadget[id] = gi;
16120 void FreeGameButtons(void)
16124 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16125 FreeGadget(game_gadget[i]);
16128 static void UnmapGameButtonsAtSamePosition(int id)
16132 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16134 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16135 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16136 UnmapGadget(game_gadget[i]);
16139 static void UnmapGameButtonsAtSamePosition_All(void)
16141 if (setup.show_snapshot_buttons)
16143 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16144 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16145 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16149 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16150 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16151 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16153 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16154 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16155 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16159 static void MapGameButtonsAtSamePosition(int id)
16163 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16165 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16166 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16167 MapGadget(game_gadget[i]);
16169 UnmapGameButtonsAtSamePosition_All();
16172 void MapUndoRedoButtons(void)
16174 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16175 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16177 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16178 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16181 void UnmapUndoRedoButtons(void)
16183 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16184 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16186 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16187 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16190 void ModifyPauseButtons(void)
16194 GAME_CTRL_ID_PAUSE,
16195 GAME_CTRL_ID_PAUSE2,
16196 GAME_CTRL_ID_PANEL_PAUSE,
16197 GAME_CTRL_ID_TOUCH_PAUSE,
16202 for (i = 0; ids[i] > -1; i++)
16203 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16206 static void MapGameButtonsExt(boolean on_tape)
16210 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16211 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16212 i != GAME_CTRL_ID_UNDO &&
16213 i != GAME_CTRL_ID_REDO)
16214 MapGadget(game_gadget[i]);
16216 UnmapGameButtonsAtSamePosition_All();
16218 RedrawGameButtons();
16221 static void UnmapGameButtonsExt(boolean on_tape)
16225 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16226 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16227 UnmapGadget(game_gadget[i]);
16230 static void RedrawGameButtonsExt(boolean on_tape)
16234 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16235 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16236 RedrawGadget(game_gadget[i]);
16239 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16244 gi->checked = state;
16247 static void RedrawSoundButtonGadget(int id)
16249 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16250 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16251 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16252 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16253 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16254 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16257 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16258 RedrawGadget(game_gadget[id2]);
16261 void MapGameButtons(void)
16263 MapGameButtonsExt(FALSE);
16266 void UnmapGameButtons(void)
16268 UnmapGameButtonsExt(FALSE);
16271 void RedrawGameButtons(void)
16273 RedrawGameButtonsExt(FALSE);
16276 void MapGameButtonsOnTape(void)
16278 MapGameButtonsExt(TRUE);
16281 void UnmapGameButtonsOnTape(void)
16283 UnmapGameButtonsExt(TRUE);
16286 void RedrawGameButtonsOnTape(void)
16288 RedrawGameButtonsExt(TRUE);
16291 static void GameUndoRedoExt(void)
16293 ClearPlayerAction();
16295 tape.pausing = TRUE;
16298 UpdateAndDisplayGameControlValues();
16300 DrawCompleteVideoDisplay();
16301 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16302 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16303 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16308 static void GameUndo(int steps)
16310 if (!CheckEngineSnapshotList())
16313 LoadEngineSnapshot_Undo(steps);
16318 static void GameRedo(int steps)
16320 if (!CheckEngineSnapshotList())
16323 LoadEngineSnapshot_Redo(steps);
16328 static void HandleGameButtonsExt(int id, int button)
16330 static boolean game_undo_executed = FALSE;
16331 int steps = BUTTON_STEPSIZE(button);
16332 boolean handle_game_buttons =
16333 (game_status == GAME_MODE_PLAYING ||
16334 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16336 if (!handle_game_buttons)
16341 case GAME_CTRL_ID_STOP:
16342 case GAME_CTRL_ID_PANEL_STOP:
16343 case GAME_CTRL_ID_TOUCH_STOP:
16344 if (game_status == GAME_MODE_MAIN)
16350 RequestQuitGame(FALSE);
16354 case GAME_CTRL_ID_PAUSE:
16355 case GAME_CTRL_ID_PAUSE2:
16356 case GAME_CTRL_ID_PANEL_PAUSE:
16357 case GAME_CTRL_ID_TOUCH_PAUSE:
16358 if (network.enabled && game_status == GAME_MODE_PLAYING)
16361 SendToServer_ContinuePlaying();
16363 SendToServer_PausePlaying();
16366 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16368 game_undo_executed = FALSE;
16372 case GAME_CTRL_ID_PLAY:
16373 case GAME_CTRL_ID_PANEL_PLAY:
16374 if (game_status == GAME_MODE_MAIN)
16376 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16378 else if (tape.pausing)
16380 if (network.enabled)
16381 SendToServer_ContinuePlaying();
16383 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16387 case GAME_CTRL_ID_UNDO:
16388 // Important: When using "save snapshot when collecting an item" mode,
16389 // load last (current) snapshot for first "undo" after pressing "pause"
16390 // (else the last-but-one snapshot would be loaded, because the snapshot
16391 // pointer already points to the last snapshot when pressing "pause",
16392 // which is fine for "every step/move" mode, but not for "every collect")
16393 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16394 !game_undo_executed)
16397 game_undo_executed = TRUE;
16402 case GAME_CTRL_ID_REDO:
16406 case GAME_CTRL_ID_SAVE:
16410 case GAME_CTRL_ID_LOAD:
16414 case SOUND_CTRL_ID_MUSIC:
16415 case SOUND_CTRL_ID_PANEL_MUSIC:
16416 if (setup.sound_music)
16418 setup.sound_music = FALSE;
16422 else if (audio.music_available)
16424 setup.sound = setup.sound_music = TRUE;
16426 SetAudioMode(setup.sound);
16428 if (game_status == GAME_MODE_PLAYING)
16432 RedrawSoundButtonGadget(id);
16436 case SOUND_CTRL_ID_LOOPS:
16437 case SOUND_CTRL_ID_PANEL_LOOPS:
16438 if (setup.sound_loops)
16439 setup.sound_loops = FALSE;
16440 else if (audio.loops_available)
16442 setup.sound = setup.sound_loops = TRUE;
16444 SetAudioMode(setup.sound);
16447 RedrawSoundButtonGadget(id);
16451 case SOUND_CTRL_ID_SIMPLE:
16452 case SOUND_CTRL_ID_PANEL_SIMPLE:
16453 if (setup.sound_simple)
16454 setup.sound_simple = FALSE;
16455 else if (audio.sound_available)
16457 setup.sound = setup.sound_simple = TRUE;
16459 SetAudioMode(setup.sound);
16462 RedrawSoundButtonGadget(id);
16471 static void HandleGameButtons(struct GadgetInfo *gi)
16473 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16476 void HandleSoundButtonKeys(Key key)
16478 if (key == setup.shortcut.sound_simple)
16479 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16480 else if (key == setup.shortcut.sound_loops)
16481 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16482 else if (key == setup.shortcut.sound_music)
16483 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);