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)
1079 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1080 #define CheckElementChange(x, y, e, te, ev) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1082 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1083 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1084 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1085 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1087 static void PlayLevelSound(int, int, int);
1088 static void PlayLevelSoundNearest(int, int, int);
1089 static void PlayLevelSoundAction(int, int, int);
1090 static void PlayLevelSoundElementAction(int, int, int, int);
1091 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1092 static void PlayLevelSoundActionIfLoop(int, int, int);
1093 static void StopLevelSoundActionIfLoop(int, int, int);
1094 static void PlayLevelMusic(void);
1095 static void FadeLevelSoundsAndMusic(void);
1097 static void HandleGameButtons(struct GadgetInfo *);
1099 int AmoebaNeighbourNr(int, int);
1100 void AmoebaToDiamond(int, int);
1101 void ContinueMoving(int, int);
1102 void Bang(int, int);
1103 void InitMovDir(int, int);
1104 void InitAmoebaNr(int, int);
1105 int NewHiScore(int);
1107 void TestIfGoodThingHitsBadThing(int, int, int);
1108 void TestIfBadThingHitsGoodThing(int, int, int);
1109 void TestIfPlayerTouchesBadThing(int, int);
1110 void TestIfPlayerRunsIntoBadThing(int, int, int);
1111 void TestIfBadThingTouchesPlayer(int, int);
1112 void TestIfBadThingRunsIntoPlayer(int, int, int);
1113 void TestIfFriendTouchesBadThing(int, int);
1114 void TestIfBadThingTouchesFriend(int, int);
1115 void TestIfBadThingTouchesOtherBadThing(int, int);
1116 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1118 void KillPlayer(struct PlayerInfo *);
1119 void BuryPlayer(struct PlayerInfo *);
1120 void RemovePlayer(struct PlayerInfo *);
1121 void ExitPlayer(struct PlayerInfo *);
1123 static int getInvisibleActiveFromInvisibleElement(int);
1124 static int getInvisibleFromInvisibleActiveElement(int);
1126 static void TestFieldAfterSnapping(int, int, int, int, int);
1128 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1130 // for detection of endless loops, caused by custom element programming
1131 // (using maximal playfield width x 10 is just a rough approximation)
1132 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1134 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1136 if (recursion_loop_detected) \
1139 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1141 recursion_loop_detected = TRUE; \
1142 recursion_loop_element = (e); \
1145 recursion_loop_depth++; \
1148 #define RECURSION_LOOP_DETECTION_END() \
1150 recursion_loop_depth--; \
1153 static int recursion_loop_depth;
1154 static boolean recursion_loop_detected;
1155 static boolean recursion_loop_element;
1157 static int map_player_action[MAX_PLAYERS];
1160 // ----------------------------------------------------------------------------
1161 // definition of elements that automatically change to other elements after
1162 // a specified time, eventually calling a function when changing
1163 // ----------------------------------------------------------------------------
1165 // forward declaration for changer functions
1166 static void InitBuggyBase(int, int);
1167 static void WarnBuggyBase(int, int);
1169 static void InitTrap(int, int);
1170 static void ActivateTrap(int, int);
1171 static void ChangeActiveTrap(int, int);
1173 static void InitRobotWheel(int, int);
1174 static void RunRobotWheel(int, int);
1175 static void StopRobotWheel(int, int);
1177 static void InitTimegateWheel(int, int);
1178 static void RunTimegateWheel(int, int);
1180 static void InitMagicBallDelay(int, int);
1181 static void ActivateMagicBall(int, int);
1183 struct ChangingElementInfo
1188 void (*pre_change_function)(int x, int y);
1189 void (*change_function)(int x, int y);
1190 void (*post_change_function)(int x, int y);
1193 static struct ChangingElementInfo change_delay_list[] =
1228 EL_STEEL_EXIT_OPENING,
1236 EL_STEEL_EXIT_CLOSING,
1237 EL_STEEL_EXIT_CLOSED,
1260 EL_EM_STEEL_EXIT_OPENING,
1261 EL_EM_STEEL_EXIT_OPEN,
1268 EL_EM_STEEL_EXIT_CLOSING,
1292 EL_SWITCHGATE_OPENING,
1300 EL_SWITCHGATE_CLOSING,
1301 EL_SWITCHGATE_CLOSED,
1308 EL_TIMEGATE_OPENING,
1316 EL_TIMEGATE_CLOSING,
1325 EL_ACID_SPLASH_LEFT,
1333 EL_ACID_SPLASH_RIGHT,
1342 EL_SP_BUGGY_BASE_ACTIVATING,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1350 EL_SP_BUGGY_BASE_ACTIVE,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1381 EL_ROBOT_WHEEL_ACTIVE,
1389 EL_TIMEGATE_SWITCH_ACTIVE,
1397 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1398 EL_DC_TIMEGATE_SWITCH,
1405 EL_EMC_MAGIC_BALL_ACTIVE,
1406 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_SPRING_BUMPER_ACTIVE,
1414 EL_EMC_SPRING_BUMPER,
1421 EL_DIAGONAL_SHRINKING,
1429 EL_DIAGONAL_GROWING,
1450 int push_delay_fixed, push_delay_random;
1454 { EL_SPRING, 0, 0 },
1455 { EL_BALLOON, 0, 0 },
1457 { EL_SOKOBAN_OBJECT, 2, 0 },
1458 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1459 { EL_SATELLITE, 2, 0 },
1460 { EL_SP_DISK_YELLOW, 2, 0 },
1462 { EL_UNDEFINED, 0, 0 },
1470 move_stepsize_list[] =
1472 { EL_AMOEBA_DROP, 2 },
1473 { EL_AMOEBA_DROPPING, 2 },
1474 { EL_QUICKSAND_FILLING, 1 },
1475 { EL_QUICKSAND_EMPTYING, 1 },
1476 { EL_QUICKSAND_FAST_FILLING, 2 },
1477 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1478 { EL_MAGIC_WALL_FILLING, 2 },
1479 { EL_MAGIC_WALL_EMPTYING, 2 },
1480 { EL_BD_MAGIC_WALL_FILLING, 2 },
1481 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1482 { EL_DC_MAGIC_WALL_FILLING, 2 },
1483 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1485 { EL_UNDEFINED, 0 },
1493 collect_count_list[] =
1496 { EL_BD_DIAMOND, 1 },
1497 { EL_EMERALD_YELLOW, 1 },
1498 { EL_EMERALD_RED, 1 },
1499 { EL_EMERALD_PURPLE, 1 },
1501 { EL_SP_INFOTRON, 1 },
1505 { EL_UNDEFINED, 0 },
1513 access_direction_list[] =
1515 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1516 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1517 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1518 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1519 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1521 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1522 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1523 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1524 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1525 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1527 { EL_SP_PORT_LEFT, MV_RIGHT },
1528 { EL_SP_PORT_RIGHT, MV_LEFT },
1529 { EL_SP_PORT_UP, MV_DOWN },
1530 { EL_SP_PORT_DOWN, MV_UP },
1531 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1532 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1533 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1534 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1538 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1547 { EL_UNDEFINED, MV_NONE }
1550 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1552 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1553 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1554 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1555 IS_JUST_CHANGING(x, y))
1557 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1559 // static variables for playfield scan mode (scanning forward or backward)
1560 static int playfield_scan_start_x = 0;
1561 static int playfield_scan_start_y = 0;
1562 static int playfield_scan_delta_x = 1;
1563 static int playfield_scan_delta_y = 1;
1565 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1566 (y) >= 0 && (y) <= lev_fieldy - 1; \
1567 (y) += playfield_scan_delta_y) \
1568 for ((x) = playfield_scan_start_x; \
1569 (x) >= 0 && (x) <= lev_fieldx - 1; \
1570 (x) += playfield_scan_delta_x)
1573 void DEBUG_SetMaximumDynamite(void)
1577 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1578 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1579 local_player->inventory_element[local_player->inventory_size++] =
1584 static void InitPlayfieldScanModeVars(void)
1586 if (game.use_reverse_scan_direction)
1588 playfield_scan_start_x = lev_fieldx - 1;
1589 playfield_scan_start_y = lev_fieldy - 1;
1591 playfield_scan_delta_x = -1;
1592 playfield_scan_delta_y = -1;
1596 playfield_scan_start_x = 0;
1597 playfield_scan_start_y = 0;
1599 playfield_scan_delta_x = 1;
1600 playfield_scan_delta_y = 1;
1604 static void InitPlayfieldScanMode(int mode)
1606 game.use_reverse_scan_direction =
1607 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1609 InitPlayfieldScanModeVars();
1612 static int get_move_delay_from_stepsize(int move_stepsize)
1615 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1617 // make sure that stepsize value is always a power of 2
1618 move_stepsize = (1 << log_2(move_stepsize));
1620 return TILEX / move_stepsize;
1623 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1626 int player_nr = player->index_nr;
1627 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1628 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1630 // do no immediately change move delay -- the player might just be moving
1631 player->move_delay_value_next = move_delay;
1633 // information if player can move must be set separately
1634 player->cannot_move = cannot_move;
1638 player->move_delay = game.initial_move_delay[player_nr];
1639 player->move_delay_value = game.initial_move_delay_value[player_nr];
1641 player->move_delay_value_next = -1;
1643 player->move_delay_reset_counter = 0;
1647 void GetPlayerConfig(void)
1649 GameFrameDelay = setup.game_frame_delay;
1651 if (!audio.sound_available)
1652 setup.sound_simple = FALSE;
1654 if (!audio.loops_available)
1655 setup.sound_loops = FALSE;
1657 if (!audio.music_available)
1658 setup.sound_music = FALSE;
1660 if (!video.fullscreen_available)
1661 setup.fullscreen = FALSE;
1663 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1665 SetAudioMode(setup.sound);
1668 int GetElementFromGroupElement(int element)
1670 if (IS_GROUP_ELEMENT(element))
1672 struct ElementGroupInfo *group = element_info[element].group;
1673 int last_anim_random_frame = gfx.anim_random_frame;
1676 if (group->choice_mode == ANIM_RANDOM)
1677 gfx.anim_random_frame = RND(group->num_elements_resolved);
1679 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1680 group->choice_mode, 0,
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = last_anim_random_frame;
1686 group->choice_pos++;
1688 element = group->element_resolved[element_pos];
1694 static void IncrementSokobanFieldsNeeded(void)
1696 if (level.sb_fields_needed)
1697 game.sokoban_fields_still_needed++;
1700 static void IncrementSokobanObjectsNeeded(void)
1702 if (level.sb_objects_needed)
1703 game.sokoban_objects_still_needed++;
1706 static void DecrementSokobanFieldsNeeded(void)
1708 if (game.sokoban_fields_still_needed > 0)
1709 game.sokoban_fields_still_needed--;
1712 static void DecrementSokobanObjectsNeeded(void)
1714 if (game.sokoban_objects_still_needed > 0)
1715 game.sokoban_objects_still_needed--;
1718 static void InitPlayerField(int x, int y, int element, boolean init_game)
1720 if (element == EL_SP_MURPHY)
1724 if (stored_player[0].present)
1726 Tile[x][y] = EL_SP_MURPHY_CLONE;
1732 stored_player[0].initial_element = element;
1733 stored_player[0].use_murphy = TRUE;
1735 if (!level.use_artwork_element[0])
1736 stored_player[0].artwork_element = EL_SP_MURPHY;
1739 Tile[x][y] = EL_PLAYER_1;
1745 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1746 int jx = player->jx, jy = player->jy;
1748 player->present = TRUE;
1750 player->block_last_field = (element == EL_SP_MURPHY ?
1751 level.sp_block_last_field :
1752 level.block_last_field);
1754 // ---------- initialize player's last field block delay ------------------
1756 // always start with reliable default value (no adjustment needed)
1757 player->block_delay_adjustment = 0;
1759 // special case 1: in Supaplex, Murphy blocks last field one more frame
1760 if (player->block_last_field && element == EL_SP_MURPHY)
1761 player->block_delay_adjustment = 1;
1763 // special case 2: in game engines before 3.1.1, blocking was different
1764 if (game.use_block_last_field_bug)
1765 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1767 if (!network.enabled || player->connected_network)
1769 player->active = TRUE;
1771 // remove potentially duplicate players
1772 if (StorePlayer[jx][jy] == Tile[x][y])
1773 StorePlayer[jx][jy] = 0;
1775 StorePlayer[x][y] = Tile[x][y];
1777 #if DEBUG_INIT_PLAYER
1778 Debug("game:init:player", "- player element %d activated",
1779 player->element_nr);
1780 Debug("game:init:player", " (local player is %d and currently %s)",
1781 local_player->element_nr,
1782 local_player->active ? "active" : "not active");
1786 Tile[x][y] = EL_EMPTY;
1788 player->jx = player->last_jx = x;
1789 player->jy = player->last_jy = y;
1792 // always check if player was just killed and should be reanimated
1794 int player_nr = GET_PLAYER_NR(element);
1795 struct PlayerInfo *player = &stored_player[player_nr];
1797 if (player->active && player->killed)
1798 player->reanimated = TRUE; // if player was just killed, reanimate him
1802 static void InitField(int x, int y, boolean init_game)
1804 int element = Tile[x][y];
1813 InitPlayerField(x, y, element, init_game);
1816 case EL_SOKOBAN_FIELD_PLAYER:
1817 element = Tile[x][y] = EL_PLAYER_1;
1818 InitField(x, y, init_game);
1820 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1821 InitField(x, y, init_game);
1824 case EL_SOKOBAN_FIELD_EMPTY:
1825 IncrementSokobanFieldsNeeded();
1828 case EL_SOKOBAN_OBJECT:
1829 IncrementSokobanObjectsNeeded();
1833 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1834 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1835 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1836 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1837 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1838 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1839 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1851 case EL_SPACESHIP_RIGHT:
1852 case EL_SPACESHIP_UP:
1853 case EL_SPACESHIP_LEFT:
1854 case EL_SPACESHIP_DOWN:
1855 case EL_BD_BUTTERFLY:
1856 case EL_BD_BUTTERFLY_RIGHT:
1857 case EL_BD_BUTTERFLY_UP:
1858 case EL_BD_BUTTERFLY_LEFT:
1859 case EL_BD_BUTTERFLY_DOWN:
1861 case EL_BD_FIREFLY_RIGHT:
1862 case EL_BD_FIREFLY_UP:
1863 case EL_BD_FIREFLY_LEFT:
1864 case EL_BD_FIREFLY_DOWN:
1865 case EL_PACMAN_RIGHT:
1867 case EL_PACMAN_LEFT:
1868 case EL_PACMAN_DOWN:
1870 case EL_YAMYAM_LEFT:
1871 case EL_YAMYAM_RIGHT:
1873 case EL_YAMYAM_DOWN:
1874 case EL_DARK_YAMYAM:
1877 case EL_SP_SNIKSNAK:
1878 case EL_SP_ELECTRON:
1884 case EL_SPRING_LEFT:
1885 case EL_SPRING_RIGHT:
1889 case EL_AMOEBA_FULL:
1894 case EL_AMOEBA_DROP:
1895 if (y == lev_fieldy - 1)
1897 Tile[x][y] = EL_AMOEBA_GROWING;
1898 Store[x][y] = EL_AMOEBA_WET;
1902 case EL_DYNAMITE_ACTIVE:
1903 case EL_SP_DISK_RED_ACTIVE:
1904 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1905 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1906 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1907 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1908 MovDelay[x][y] = 96;
1911 case EL_EM_DYNAMITE_ACTIVE:
1912 MovDelay[x][y] = 32;
1916 game.lights_still_needed++;
1920 game.friends_still_needed++;
1925 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1928 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1934 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1935 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1936 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1937 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1938 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1939 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1942 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1943 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1944 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1946 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1948 game.belt_dir[belt_nr] = belt_dir;
1949 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1951 else // more than one switch -- set it like the first switch
1953 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1958 case EL_LIGHT_SWITCH_ACTIVE:
1960 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1963 case EL_INVISIBLE_STEELWALL:
1964 case EL_INVISIBLE_WALL:
1965 case EL_INVISIBLE_SAND:
1966 if (game.light_time_left > 0 ||
1967 game.lenses_time_left > 0)
1968 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1971 case EL_EMC_MAGIC_BALL:
1972 if (game.ball_active)
1973 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1976 case EL_EMC_MAGIC_BALL_SWITCH:
1977 if (game.ball_active)
1978 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1981 case EL_TRIGGER_PLAYER:
1982 case EL_TRIGGER_ELEMENT:
1983 case EL_TRIGGER_CE_VALUE:
1984 case EL_TRIGGER_CE_SCORE:
1986 case EL_ANY_ELEMENT:
1987 case EL_CURRENT_CE_VALUE:
1988 case EL_CURRENT_CE_SCORE:
2005 // reference elements should not be used on the playfield
2006 Tile[x][y] = EL_EMPTY;
2010 if (IS_CUSTOM_ELEMENT(element))
2012 if (CAN_MOVE(element))
2015 if (!element_info[element].use_last_ce_value || init_game)
2016 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2018 else if (IS_GROUP_ELEMENT(element))
2020 Tile[x][y] = GetElementFromGroupElement(element);
2022 InitField(x, y, init_game);
2029 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2032 static void InitField_WithBug1(int x, int y, boolean init_game)
2034 InitField(x, y, init_game);
2036 // not needed to call InitMovDir() -- already done by InitField()!
2037 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2038 CAN_MOVE(Tile[x][y]))
2042 static void InitField_WithBug2(int x, int y, boolean init_game)
2044 int old_element = Tile[x][y];
2046 InitField(x, y, init_game);
2048 // not needed to call InitMovDir() -- already done by InitField()!
2049 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2050 CAN_MOVE(old_element) &&
2051 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2054 /* this case is in fact a combination of not less than three bugs:
2055 first, it calls InitMovDir() for elements that can move, although this is
2056 already done by InitField(); then, it checks the element that was at this
2057 field _before_ the call to InitField() (which can change it); lastly, it
2058 was not called for "mole with direction" elements, which were treated as
2059 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2063 static int get_key_element_from_nr(int key_nr)
2065 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2066 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2067 EL_EM_KEY_1 : EL_KEY_1);
2069 return key_base_element + key_nr;
2072 static int get_next_dropped_element(struct PlayerInfo *player)
2074 return (player->inventory_size > 0 ?
2075 player->inventory_element[player->inventory_size - 1] :
2076 player->inventory_infinite_element != EL_UNDEFINED ?
2077 player->inventory_infinite_element :
2078 player->dynabombs_left > 0 ?
2079 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2083 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2085 // pos >= 0: get element from bottom of the stack;
2086 // pos < 0: get element from top of the stack
2090 int min_inventory_size = -pos;
2091 int inventory_pos = player->inventory_size - min_inventory_size;
2092 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2094 return (player->inventory_size >= min_inventory_size ?
2095 player->inventory_element[inventory_pos] :
2096 player->inventory_infinite_element != EL_UNDEFINED ?
2097 player->inventory_infinite_element :
2098 player->dynabombs_left >= min_dynabombs_left ?
2099 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2104 int min_dynabombs_left = pos + 1;
2105 int min_inventory_size = pos + 1 - player->dynabombs_left;
2106 int inventory_pos = pos - player->dynabombs_left;
2108 return (player->inventory_infinite_element != EL_UNDEFINED ?
2109 player->inventory_infinite_element :
2110 player->dynabombs_left >= min_dynabombs_left ?
2111 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2112 player->inventory_size >= min_inventory_size ?
2113 player->inventory_element[inventory_pos] :
2118 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2120 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2121 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2124 if (gpo1->sort_priority != gpo2->sort_priority)
2125 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2127 compare_result = gpo1->nr - gpo2->nr;
2129 return compare_result;
2132 int getPlayerInventorySize(int player_nr)
2134 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2135 return game_em.ply[player_nr]->dynamite;
2136 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2137 return game_sp.red_disk_count;
2139 return stored_player[player_nr].inventory_size;
2142 static void InitGameControlValues(void)
2146 for (i = 0; game_panel_controls[i].nr != -1; i++)
2148 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2149 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2150 struct TextPosInfo *pos = gpc->pos;
2152 int type = gpc->type;
2156 Error("'game_panel_controls' structure corrupted at %d", i);
2158 Fail("this should not happen -- please debug");
2161 // force update of game controls after initialization
2162 gpc->value = gpc->last_value = -1;
2163 gpc->frame = gpc->last_frame = -1;
2164 gpc->gfx_frame = -1;
2166 // determine panel value width for later calculation of alignment
2167 if (type == TYPE_INTEGER || type == TYPE_STRING)
2169 pos->width = pos->size * getFontWidth(pos->font);
2170 pos->height = getFontHeight(pos->font);
2172 else if (type == TYPE_ELEMENT)
2174 pos->width = pos->size;
2175 pos->height = pos->size;
2178 // fill structure for game panel draw order
2180 gpo->sort_priority = pos->sort_priority;
2183 // sort game panel controls according to sort_priority and control number
2184 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2185 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2188 static void UpdatePlayfieldElementCount(void)
2190 boolean use_element_count = FALSE;
2193 // first check if it is needed at all to calculate playfield element count
2194 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2195 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2196 use_element_count = TRUE;
2198 if (!use_element_count)
2201 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2202 element_info[i].element_count = 0;
2204 SCAN_PLAYFIELD(x, y)
2206 element_info[Tile[x][y]].element_count++;
2209 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2210 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2211 if (IS_IN_GROUP(j, i))
2212 element_info[EL_GROUP_START + i].element_count +=
2213 element_info[j].element_count;
2216 static void UpdateGameControlValues(void)
2219 int time = (game.LevelSolved ?
2220 game.LevelSolved_CountingTime :
2221 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2223 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2224 game_sp.time_played :
2225 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2226 game_mm.energy_left :
2227 game.no_time_limit ? TimePlayed : TimeLeft);
2228 int score = (game.LevelSolved ?
2229 game.LevelSolved_CountingScore :
2230 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2231 game_em.lev->score :
2232 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2234 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2237 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 game_em.lev->gems_needed :
2239 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2240 game_sp.infotrons_still_needed :
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2242 game_mm.kettles_still_needed :
2243 game.gems_still_needed);
2244 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2245 game_em.lev->gems_needed > 0 :
2246 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2247 game_sp.infotrons_still_needed > 0 :
2248 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2249 game_mm.kettles_still_needed > 0 ||
2250 game_mm.lights_still_needed > 0 :
2251 game.gems_still_needed > 0 ||
2252 game.sokoban_fields_still_needed > 0 ||
2253 game.sokoban_objects_still_needed > 0 ||
2254 game.lights_still_needed > 0);
2255 int health = (game.LevelSolved ?
2256 game.LevelSolved_CountingHealth :
2257 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2258 MM_HEALTH(game_mm.laser_overload_value) :
2260 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2262 UpdatePlayfieldElementCount();
2264 // update game panel control values
2266 // used instead of "level_nr" (for network games)
2267 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2268 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2270 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2271 for (i = 0; i < MAX_NUM_KEYS; i++)
2272 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2273 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2274 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2276 if (game.centered_player_nr == -1)
2278 for (i = 0; i < MAX_PLAYERS; i++)
2280 // only one player in Supaplex game engine
2281 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2284 for (k = 0; k < MAX_NUM_KEYS; k++)
2286 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2288 if (game_em.ply[i]->keys & (1 << k))
2289 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2290 get_key_element_from_nr(k);
2292 else if (stored_player[i].key[k])
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2297 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2298 getPlayerInventorySize(i);
2300 if (stored_player[i].num_white_keys > 0)
2301 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2304 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2305 stored_player[i].num_white_keys;
2310 int player_nr = game.centered_player_nr;
2312 for (k = 0; k < MAX_NUM_KEYS; k++)
2314 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2316 if (game_em.ply[player_nr]->keys & (1 << k))
2317 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2318 get_key_element_from_nr(k);
2320 else if (stored_player[player_nr].key[k])
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2325 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2326 getPlayerInventorySize(player_nr);
2328 if (stored_player[player_nr].num_white_keys > 0)
2329 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2331 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2332 stored_player[player_nr].num_white_keys;
2335 // re-arrange keys on game panel, if needed or if defined by style settings
2336 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2338 int nr = GAME_PANEL_KEY_1 + i;
2339 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2340 struct TextPosInfo *pos = gpc->pos;
2342 // skip check if key is not in the player's inventory
2343 if (gpc->value == EL_EMPTY)
2346 // check if keys should be arranged on panel from left to right
2347 if (pos->style == STYLE_LEFTMOST_POSITION)
2349 // check previous key positions (left from current key)
2350 for (k = 0; k < i; k++)
2352 int nr_new = GAME_PANEL_KEY_1 + k;
2354 if (game_panel_controls[nr_new].value == EL_EMPTY)
2356 game_panel_controls[nr_new].value = gpc->value;
2357 gpc->value = EL_EMPTY;
2364 // check if "undefined" keys can be placed at some other position
2365 if (pos->x == -1 && pos->y == -1)
2367 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2369 // 1st try: display key at the same position as normal or EM keys
2370 if (game_panel_controls[nr_new].value == EL_EMPTY)
2372 game_panel_controls[nr_new].value = gpc->value;
2376 // 2nd try: display key at the next free position in the key panel
2377 for (k = 0; k < STD_NUM_KEYS; k++)
2379 nr_new = GAME_PANEL_KEY_1 + k;
2381 if (game_panel_controls[nr_new].value == EL_EMPTY)
2383 game_panel_controls[nr_new].value = gpc->value;
2392 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2394 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2395 get_inventory_element_from_pos(local_player, i);
2396 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2397 get_inventory_element_from_pos(local_player, -i - 1);
2400 game_panel_controls[GAME_PANEL_SCORE].value = score;
2401 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2403 game_panel_controls[GAME_PANEL_TIME].value = time;
2405 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2406 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2407 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2409 if (level.time == 0)
2410 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2412 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2414 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2415 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2417 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2419 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2420 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2422 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2423 local_player->shield_normal_time_left;
2424 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2425 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2427 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2428 local_player->shield_deadly_time_left;
2430 game_panel_controls[GAME_PANEL_EXIT].value =
2431 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2433 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2434 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2435 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2436 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2437 EL_EMC_MAGIC_BALL_SWITCH);
2439 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2440 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2441 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2442 game.light_time_left;
2444 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2445 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2446 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2447 game.timegate_time_left;
2449 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2450 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2452 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2453 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2454 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2455 game.lenses_time_left;
2457 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2458 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2459 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2460 game.magnify_time_left;
2462 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2463 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2464 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2465 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2466 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2467 EL_BALLOON_SWITCH_NONE);
2469 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2470 local_player->dynabomb_count;
2471 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2472 local_player->dynabomb_size;
2473 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2474 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2476 game_panel_controls[GAME_PANEL_PENGUINS].value =
2477 game.friends_still_needed;
2479 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2480 game.sokoban_objects_still_needed;
2481 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2482 game.sokoban_fields_still_needed;
2484 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2485 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2487 for (i = 0; i < NUM_BELTS; i++)
2489 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2490 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2491 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2492 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2493 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2496 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2497 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2498 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2499 game.magic_wall_time_left;
2501 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2502 local_player->gravity;
2504 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2505 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2507 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2508 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2509 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2510 game.panel.element[i].id : EL_UNDEFINED);
2512 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2513 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2514 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2515 element_info[game.panel.element_count[i].id].element_count : 0);
2517 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2518 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2519 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2520 element_info[game.panel.ce_score[i].id].collect_score : 0);
2522 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2523 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2524 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2525 element_info[game.panel.ce_score_element[i].id].collect_score :
2528 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2529 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2530 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2532 // update game panel control frames
2534 for (i = 0; game_panel_controls[i].nr != -1; i++)
2536 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2538 if (gpc->type == TYPE_ELEMENT)
2540 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2542 int last_anim_random_frame = gfx.anim_random_frame;
2543 int element = gpc->value;
2544 int graphic = el2panelimg(element);
2545 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2546 sync_random_frame : INIT_GFX_RANDOM());
2548 if (gpc->value != gpc->last_value)
2551 gpc->gfx_random = init_gfx_random;
2557 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2558 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2559 gpc->gfx_random = init_gfx_random;
2562 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2563 gfx.anim_random_frame = gpc->gfx_random;
2565 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2566 gpc->gfx_frame = element_info[element].collect_score;
2568 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2570 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2571 gfx.anim_random_frame = last_anim_random_frame;
2574 else if (gpc->type == TYPE_GRAPHIC)
2576 if (gpc->graphic != IMG_UNDEFINED)
2578 int last_anim_random_frame = gfx.anim_random_frame;
2579 int graphic = gpc->graphic;
2580 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2581 sync_random_frame : INIT_GFX_RANDOM());
2583 if (gpc->value != gpc->last_value)
2586 gpc->gfx_random = init_gfx_random;
2592 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2593 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2594 gpc->gfx_random = init_gfx_random;
2597 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2598 gfx.anim_random_frame = gpc->gfx_random;
2600 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2602 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2603 gfx.anim_random_frame = last_anim_random_frame;
2609 static void DisplayGameControlValues(void)
2611 boolean redraw_panel = FALSE;
2614 for (i = 0; game_panel_controls[i].nr != -1; i++)
2616 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2618 if (PANEL_DEACTIVATED(gpc->pos))
2621 if (gpc->value == gpc->last_value &&
2622 gpc->frame == gpc->last_frame)
2625 redraw_panel = TRUE;
2631 // copy default game door content to main double buffer
2633 // !!! CHECK AGAIN !!!
2634 SetPanelBackground();
2635 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2636 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2638 // redraw game control buttons
2639 RedrawGameButtons();
2641 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2643 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2645 int nr = game_panel_order[i].nr;
2646 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2647 struct TextPosInfo *pos = gpc->pos;
2648 int type = gpc->type;
2649 int value = gpc->value;
2650 int frame = gpc->frame;
2651 int size = pos->size;
2652 int font = pos->font;
2653 boolean draw_masked = pos->draw_masked;
2654 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2656 if (PANEL_DEACTIVATED(pos))
2659 if (pos->class == get_hash_from_key("extra_panel_items") &&
2660 !setup.prefer_extra_panel_items)
2663 gpc->last_value = value;
2664 gpc->last_frame = frame;
2666 if (type == TYPE_INTEGER)
2668 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2669 nr == GAME_PANEL_TIME)
2671 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2673 if (use_dynamic_size) // use dynamic number of digits
2675 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2676 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2677 int size2 = size1 + 1;
2678 int font1 = pos->font;
2679 int font2 = pos->font_alt;
2681 size = (value < value_change ? size1 : size2);
2682 font = (value < value_change ? font1 : font2);
2686 // correct text size if "digits" is zero or less
2688 size = strlen(int2str(value, size));
2690 // dynamically correct text alignment
2691 pos->width = size * getFontWidth(font);
2693 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2694 int2str(value, size), font, mask_mode);
2696 else if (type == TYPE_ELEMENT)
2698 int element, graphic;
2702 int dst_x = PANEL_XPOS(pos);
2703 int dst_y = PANEL_YPOS(pos);
2705 if (value != EL_UNDEFINED && value != EL_EMPTY)
2708 graphic = el2panelimg(value);
2711 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2712 element, EL_NAME(element), size);
2715 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2718 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2721 width = graphic_info[graphic].width * size / TILESIZE;
2722 height = graphic_info[graphic].height * size / TILESIZE;
2725 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2728 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2732 else if (type == TYPE_GRAPHIC)
2734 int graphic = gpc->graphic;
2735 int graphic_active = gpc->graphic_active;
2739 int dst_x = PANEL_XPOS(pos);
2740 int dst_y = PANEL_YPOS(pos);
2741 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2742 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2744 if (graphic != IMG_UNDEFINED && !skip)
2746 if (pos->style == STYLE_REVERSE)
2747 value = 100 - value;
2749 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2751 if (pos->direction & MV_HORIZONTAL)
2753 width = graphic_info[graphic_active].width * value / 100;
2754 height = graphic_info[graphic_active].height;
2756 if (pos->direction == MV_LEFT)
2758 src_x += graphic_info[graphic_active].width - width;
2759 dst_x += graphic_info[graphic_active].width - width;
2764 width = graphic_info[graphic_active].width;
2765 height = graphic_info[graphic_active].height * value / 100;
2767 if (pos->direction == MV_UP)
2769 src_y += graphic_info[graphic_active].height - height;
2770 dst_y += graphic_info[graphic_active].height - height;
2775 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2778 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2781 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2783 if (pos->direction & MV_HORIZONTAL)
2785 if (pos->direction == MV_RIGHT)
2792 dst_x = PANEL_XPOS(pos);
2795 width = graphic_info[graphic].width - width;
2799 if (pos->direction == MV_DOWN)
2806 dst_y = PANEL_YPOS(pos);
2809 height = graphic_info[graphic].height - height;
2813 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2816 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2820 else if (type == TYPE_STRING)
2822 boolean active = (value != 0);
2823 char *state_normal = "off";
2824 char *state_active = "on";
2825 char *state = (active ? state_active : state_normal);
2826 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2827 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2828 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2829 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2831 if (nr == GAME_PANEL_GRAVITY_STATE)
2833 int font1 = pos->font; // (used for normal state)
2834 int font2 = pos->font_alt; // (used for active state)
2836 font = (active ? font2 : font1);
2845 // don't truncate output if "chars" is zero or less
2848 // dynamically correct text alignment
2849 pos->width = size * getFontWidth(font);
2852 s_cut = getStringCopyN(s, size);
2854 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2855 s_cut, font, mask_mode);
2861 redraw_mask |= REDRAW_DOOR_1;
2864 SetGameStatus(GAME_MODE_PLAYING);
2867 void UpdateAndDisplayGameControlValues(void)
2869 if (tape.deactivate_display)
2872 UpdateGameControlValues();
2873 DisplayGameControlValues();
2876 void UpdateGameDoorValues(void)
2878 UpdateGameControlValues();
2881 void DrawGameDoorValues(void)
2883 DisplayGameControlValues();
2887 // ============================================================================
2889 // ----------------------------------------------------------------------------
2890 // initialize game engine due to level / tape version number
2891 // ============================================================================
2893 static void InitGameEngine(void)
2895 int i, j, k, l, x, y;
2897 // set game engine from tape file when re-playing, else from level file
2898 game.engine_version = (tape.playing ? tape.engine_version :
2899 level.game_version);
2901 // set single or multi-player game mode (needed for re-playing tapes)
2902 game.team_mode = setup.team_mode;
2906 int num_players = 0;
2908 for (i = 0; i < MAX_PLAYERS; i++)
2909 if (tape.player_participates[i])
2912 // multi-player tapes contain input data for more than one player
2913 game.team_mode = (num_players > 1);
2917 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2918 level.game_version);
2919 Debug("game:init:level", " tape.file_version == %06d",
2921 Debug("game:init:level", " tape.game_version == %06d",
2923 Debug("game:init:level", " tape.engine_version == %06d",
2924 tape.engine_version);
2925 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2926 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2929 // --------------------------------------------------------------------------
2930 // set flags for bugs and changes according to active game engine version
2931 // --------------------------------------------------------------------------
2935 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2937 Bug was introduced in version:
2940 Bug was fixed in version:
2944 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2945 but the property "can fall" was missing, which caused some levels to be
2946 unsolvable. This was fixed in version 4.2.0.0.
2948 Affected levels/tapes:
2949 An example for a tape that was fixed by this bugfix is tape 029 from the
2950 level set "rnd_sam_bateman".
2951 The wrong behaviour will still be used for all levels or tapes that were
2952 created/recorded with it. An example for this is tape 023 from the level
2953 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2956 boolean use_amoeba_dropping_cannot_fall_bug =
2957 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2958 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2960 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2961 tape.game_version < VERSION_IDENT(4,2,0,0)));
2964 Summary of bugfix/change:
2965 Fixed move speed of elements entering or leaving magic wall.
2967 Fixed/changed in version:
2971 Before 2.0.1, move speed of elements entering or leaving magic wall was
2972 twice as fast as it is now.
2973 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2975 Affected levels/tapes:
2976 The first condition is generally needed for all levels/tapes before version
2977 2.0.1, which might use the old behaviour before it was changed; known tapes
2978 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2979 The second condition is an exception from the above case and is needed for
2980 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2981 above, but before it was known that this change would break tapes like the
2982 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2983 although the engine version while recording maybe was before 2.0.1. There
2984 are a lot of tapes that are affected by this exception, like tape 006 from
2985 the level set "rnd_conor_mancone".
2988 boolean use_old_move_stepsize_for_magic_wall =
2989 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2991 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2992 tape.game_version < VERSION_IDENT(4,2,0,0)));
2995 Summary of bugfix/change:
2996 Fixed handling for custom elements that change when pushed by the player.
2998 Fixed/changed in version:
3002 Before 3.1.0, custom elements that "change when pushing" changed directly
3003 after the player started pushing them (until then handled in "DigField()").
3004 Since 3.1.0, these custom elements are not changed until the "pushing"
3005 move of the element is finished (now handled in "ContinueMoving()").
3007 Affected levels/tapes:
3008 The first condition is generally needed for all levels/tapes before version
3009 3.1.0, which might use the old behaviour before it was changed; known tapes
3010 that are affected are some tapes from the level set "Walpurgis Gardens" by
3012 The second condition is an exception from the above case and is needed for
3013 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3014 above (including some development versions of 3.1.0), but before it was
3015 known that this change would break tapes like the above and was fixed in
3016 3.1.1, so that the changed behaviour was active although the engine version
3017 while recording maybe was before 3.1.0. There is at least one tape that is
3018 affected by this exception, which is the tape for the one-level set "Bug
3019 Machine" by Juergen Bonhagen.
3022 game.use_change_when_pushing_bug =
3023 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3025 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3026 tape.game_version < VERSION_IDENT(3,1,1,0)));
3029 Summary of bugfix/change:
3030 Fixed handling for blocking the field the player leaves when moving.
3032 Fixed/changed in version:
3036 Before 3.1.1, when "block last field when moving" was enabled, the field
3037 the player is leaving when moving was blocked for the time of the move,
3038 and was directly unblocked afterwards. This resulted in the last field
3039 being blocked for exactly one less than the number of frames of one player
3040 move. Additionally, even when blocking was disabled, the last field was
3041 blocked for exactly one frame.
3042 Since 3.1.1, due to changes in player movement handling, the last field
3043 is not blocked at all when blocking is disabled. When blocking is enabled,
3044 the last field is blocked for exactly the number of frames of one player
3045 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3046 last field is blocked for exactly one more than the number of frames of
3049 Affected levels/tapes:
3050 (!!! yet to be determined -- probably many !!!)
3053 game.use_block_last_field_bug =
3054 (game.engine_version < VERSION_IDENT(3,1,1,0));
3056 /* various special flags and settings for native Emerald Mine game engine */
3058 game_em.use_single_button =
3059 (game.engine_version > VERSION_IDENT(4,0,0,2));
3061 game_em.use_snap_key_bug =
3062 (game.engine_version < VERSION_IDENT(4,0,1,0));
3064 game_em.use_random_bug =
3065 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3067 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3069 game_em.use_old_explosions = use_old_em_engine;
3070 game_em.use_old_android = use_old_em_engine;
3071 game_em.use_old_push_elements = use_old_em_engine;
3072 game_em.use_old_push_into_acid = use_old_em_engine;
3074 game_em.use_wrap_around = !use_old_em_engine;
3076 // --------------------------------------------------------------------------
3078 // set maximal allowed number of custom element changes per game frame
3079 game.max_num_changes_per_frame = 1;
3081 // default scan direction: scan playfield from top/left to bottom/right
3082 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3084 // dynamically adjust element properties according to game engine version
3085 InitElementPropertiesEngine(game.engine_version);
3087 // ---------- initialize special element properties -------------------------
3089 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3090 if (use_amoeba_dropping_cannot_fall_bug)
3091 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3093 // ---------- initialize player's initial move delay ------------------------
3095 // dynamically adjust player properties according to level information
3096 for (i = 0; i < MAX_PLAYERS; i++)
3097 game.initial_move_delay_value[i] =
3098 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3100 // dynamically adjust player properties according to game engine version
3101 for (i = 0; i < MAX_PLAYERS; i++)
3102 game.initial_move_delay[i] =
3103 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3104 game.initial_move_delay_value[i] : 0);
3106 // ---------- initialize player's initial push delay ------------------------
3108 // dynamically adjust player properties according to game engine version
3109 game.initial_push_delay_value =
3110 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3112 // ---------- initialize changing elements ----------------------------------
3114 // initialize changing elements information
3115 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3117 struct ElementInfo *ei = &element_info[i];
3119 // this pointer might have been changed in the level editor
3120 ei->change = &ei->change_page[0];
3122 if (!IS_CUSTOM_ELEMENT(i))
3124 ei->change->target_element = EL_EMPTY_SPACE;
3125 ei->change->delay_fixed = 0;
3126 ei->change->delay_random = 0;
3127 ei->change->delay_frames = 1;
3130 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3132 ei->has_change_event[j] = FALSE;
3134 ei->event_page_nr[j] = 0;
3135 ei->event_page[j] = &ei->change_page[0];
3139 // add changing elements from pre-defined list
3140 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3142 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3143 struct ElementInfo *ei = &element_info[ch_delay->element];
3145 ei->change->target_element = ch_delay->target_element;
3146 ei->change->delay_fixed = ch_delay->change_delay;
3148 ei->change->pre_change_function = ch_delay->pre_change_function;
3149 ei->change->change_function = ch_delay->change_function;
3150 ei->change->post_change_function = ch_delay->post_change_function;
3152 ei->change->can_change = TRUE;
3153 ei->change->can_change_or_has_action = TRUE;
3155 ei->has_change_event[CE_DELAY] = TRUE;
3157 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3158 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3161 // ---------- initialize internal run-time variables ------------------------
3163 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3165 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3167 for (j = 0; j < ei->num_change_pages; j++)
3169 ei->change_page[j].can_change_or_has_action =
3170 (ei->change_page[j].can_change |
3171 ei->change_page[j].has_action);
3175 // add change events from custom element configuration
3176 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3178 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3180 for (j = 0; j < ei->num_change_pages; j++)
3182 if (!ei->change_page[j].can_change_or_has_action)
3185 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3187 // only add event page for the first page found with this event
3188 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3190 ei->has_change_event[k] = TRUE;
3192 ei->event_page_nr[k] = j;
3193 ei->event_page[k] = &ei->change_page[j];
3199 // ---------- initialize reference elements in change conditions ------------
3201 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3203 int element = EL_CUSTOM_START + i;
3204 struct ElementInfo *ei = &element_info[element];
3206 for (j = 0; j < ei->num_change_pages; j++)
3208 int trigger_element = ei->change_page[j].initial_trigger_element;
3210 if (trigger_element >= EL_PREV_CE_8 &&
3211 trigger_element <= EL_NEXT_CE_8)
3212 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3214 ei->change_page[j].trigger_element = trigger_element;
3218 // ---------- initialize run-time trigger player and element ----------------
3220 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3222 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3224 for (j = 0; j < ei->num_change_pages; j++)
3226 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3227 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3228 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3229 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3230 ei->change_page[j].actual_trigger_ce_value = 0;
3231 ei->change_page[j].actual_trigger_ce_score = 0;
3235 // ---------- initialize trigger events -------------------------------------
3237 // initialize trigger events information
3238 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3239 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3240 trigger_events[i][j] = FALSE;
3242 // add trigger events from element change event properties
3243 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3245 struct ElementInfo *ei = &element_info[i];
3247 for (j = 0; j < ei->num_change_pages; j++)
3249 if (!ei->change_page[j].can_change_or_has_action)
3252 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3254 int trigger_element = ei->change_page[j].trigger_element;
3256 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3258 if (ei->change_page[j].has_event[k])
3260 if (IS_GROUP_ELEMENT(trigger_element))
3262 struct ElementGroupInfo *group =
3263 element_info[trigger_element].group;
3265 for (l = 0; l < group->num_elements_resolved; l++)
3266 trigger_events[group->element_resolved[l]][k] = TRUE;
3268 else if (trigger_element == EL_ANY_ELEMENT)
3269 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3270 trigger_events[l][k] = TRUE;
3272 trigger_events[trigger_element][k] = TRUE;
3279 // ---------- initialize push delay -----------------------------------------
3281 // initialize push delay values to default
3282 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3284 if (!IS_CUSTOM_ELEMENT(i))
3286 // set default push delay values (corrected since version 3.0.7-1)
3287 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3289 element_info[i].push_delay_fixed = 2;
3290 element_info[i].push_delay_random = 8;
3294 element_info[i].push_delay_fixed = 8;
3295 element_info[i].push_delay_random = 8;
3300 // set push delay value for certain elements from pre-defined list
3301 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3303 int e = push_delay_list[i].element;
3305 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3306 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3309 // set push delay value for Supaplex elements for newer engine versions
3310 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3312 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3314 if (IS_SP_ELEMENT(i))
3316 // set SP push delay to just enough to push under a falling zonk
3317 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3319 element_info[i].push_delay_fixed = delay;
3320 element_info[i].push_delay_random = 0;
3325 // ---------- initialize move stepsize --------------------------------------
3327 // initialize move stepsize values to default
3328 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3329 if (!IS_CUSTOM_ELEMENT(i))
3330 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3332 // set move stepsize value for certain elements from pre-defined list
3333 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3335 int e = move_stepsize_list[i].element;
3337 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3339 // set move stepsize value for certain elements for older engine versions
3340 if (use_old_move_stepsize_for_magic_wall)
3342 if (e == EL_MAGIC_WALL_FILLING ||
3343 e == EL_MAGIC_WALL_EMPTYING ||
3344 e == EL_BD_MAGIC_WALL_FILLING ||
3345 e == EL_BD_MAGIC_WALL_EMPTYING)
3346 element_info[e].move_stepsize *= 2;
3350 // ---------- initialize collect score --------------------------------------
3352 // initialize collect score values for custom elements from initial value
3353 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3354 if (IS_CUSTOM_ELEMENT(i))
3355 element_info[i].collect_score = element_info[i].collect_score_initial;
3357 // ---------- initialize collect count --------------------------------------
3359 // initialize collect count values for non-custom elements
3360 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3361 if (!IS_CUSTOM_ELEMENT(i))
3362 element_info[i].collect_count_initial = 0;
3364 // add collect count values for all elements from pre-defined list
3365 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3366 element_info[collect_count_list[i].element].collect_count_initial =
3367 collect_count_list[i].count;
3369 // ---------- initialize access direction -----------------------------------
3371 // initialize access direction values to default (access from every side)
3372 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3373 if (!IS_CUSTOM_ELEMENT(i))
3374 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3376 // set access direction value for certain elements from pre-defined list
3377 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3378 element_info[access_direction_list[i].element].access_direction =
3379 access_direction_list[i].direction;
3381 // ---------- initialize explosion content ----------------------------------
3382 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3384 if (IS_CUSTOM_ELEMENT(i))
3387 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3389 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3391 element_info[i].content.e[x][y] =
3392 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3393 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3394 i == EL_PLAYER_3 ? EL_EMERALD :
3395 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3396 i == EL_MOLE ? EL_EMERALD_RED :
3397 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3398 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3399 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3400 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3401 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3402 i == EL_WALL_EMERALD ? EL_EMERALD :
3403 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3404 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3405 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3406 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3407 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3408 i == EL_WALL_PEARL ? EL_PEARL :
3409 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3414 // ---------- initialize recursion detection --------------------------------
3415 recursion_loop_depth = 0;
3416 recursion_loop_detected = FALSE;
3417 recursion_loop_element = EL_UNDEFINED;
3419 // ---------- initialize graphics engine ------------------------------------
3420 game.scroll_delay_value =
3421 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3422 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3423 !setup.forced_scroll_delay ? 0 :
3424 setup.scroll_delay ? setup.scroll_delay_value : 0);
3425 game.scroll_delay_value =
3426 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3428 // ---------- initialize game engine snapshots ------------------------------
3429 for (i = 0; i < MAX_PLAYERS; i++)
3430 game.snapshot.last_action[i] = 0;
3431 game.snapshot.changed_action = FALSE;
3432 game.snapshot.collected_item = FALSE;
3433 game.snapshot.mode =
3434 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3435 SNAPSHOT_MODE_EVERY_STEP :
3436 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3437 SNAPSHOT_MODE_EVERY_MOVE :
3438 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3439 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3440 game.snapshot.save_snapshot = FALSE;
3442 // ---------- initialize level time for Supaplex engine ---------------------
3443 // Supaplex levels with time limit currently unsupported -- should be added
3444 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3447 // ---------- initialize flags for handling game actions --------------------
3449 // set flags for game actions to default values
3450 game.use_key_actions = TRUE;
3451 game.use_mouse_actions = FALSE;
3453 // when using Mirror Magic game engine, handle mouse events only
3454 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3456 game.use_key_actions = FALSE;
3457 game.use_mouse_actions = TRUE;
3460 // check for custom elements with mouse click events
3461 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3463 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3465 int element = EL_CUSTOM_START + i;
3467 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3468 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3469 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3470 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3471 game.use_mouse_actions = TRUE;
3476 static int get_num_special_action(int element, int action_first,
3479 int num_special_action = 0;
3482 for (i = action_first; i <= action_last; i++)
3484 boolean found = FALSE;
3486 for (j = 0; j < NUM_DIRECTIONS; j++)
3487 if (el_act_dir2img(element, i, j) !=
3488 el_act_dir2img(element, ACTION_DEFAULT, j))
3492 num_special_action++;
3497 return num_special_action;
3501 // ============================================================================
3503 // ----------------------------------------------------------------------------
3504 // initialize and start new game
3505 // ============================================================================
3507 #if DEBUG_INIT_PLAYER
3508 static void DebugPrintPlayerStatus(char *message)
3515 Debug("game:init:player", "%s:", message);
3517 for (i = 0; i < MAX_PLAYERS; i++)
3519 struct PlayerInfo *player = &stored_player[i];
3521 Debug("game:init:player",
3522 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3526 player->connected_locally,
3527 player->connected_network,
3529 (local_player == player ? " (local player)" : ""));
3536 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3537 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3538 int fade_mask = REDRAW_FIELD;
3540 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3541 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3542 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3543 int initial_move_dir = MV_DOWN;
3546 // required here to update video display before fading (FIX THIS)
3547 DrawMaskedBorder(REDRAW_DOOR_2);
3549 if (!game.restart_level)
3550 CloseDoor(DOOR_CLOSE_1);
3552 SetGameStatus(GAME_MODE_PLAYING);
3554 if (level_editor_test_game)
3555 FadeSkipNextFadeOut();
3557 FadeSetEnterScreen();
3560 fade_mask = REDRAW_ALL;
3562 FadeLevelSoundsAndMusic();
3564 ExpireSoundLoops(TRUE);
3568 if (level_editor_test_game)
3569 FadeSkipNextFadeIn();
3571 // needed if different viewport properties defined for playing
3572 ChangeViewportPropertiesIfNeeded();
3576 DrawCompleteVideoDisplay();
3578 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3581 InitGameControlValues();
3585 // initialize tape actions from game when recording tape
3586 tape.use_key_actions = game.use_key_actions;
3587 tape.use_mouse_actions = game.use_mouse_actions;
3589 // initialize visible playfield size when recording tape (for team mode)
3590 tape.scr_fieldx = SCR_FIELDX;
3591 tape.scr_fieldy = SCR_FIELDY;
3594 // don't play tapes over network
3595 network_playing = (network.enabled && !tape.playing);
3597 for (i = 0; i < MAX_PLAYERS; i++)
3599 struct PlayerInfo *player = &stored_player[i];
3601 player->index_nr = i;
3602 player->index_bit = (1 << i);
3603 player->element_nr = EL_PLAYER_1 + i;
3605 player->present = FALSE;
3606 player->active = FALSE;
3607 player->mapped = FALSE;
3609 player->killed = FALSE;
3610 player->reanimated = FALSE;
3611 player->buried = FALSE;
3614 player->effective_action = 0;
3615 player->programmed_action = 0;
3616 player->snap_action = 0;
3618 player->mouse_action.lx = 0;
3619 player->mouse_action.ly = 0;
3620 player->mouse_action.button = 0;
3621 player->mouse_action.button_hint = 0;
3623 player->effective_mouse_action.lx = 0;
3624 player->effective_mouse_action.ly = 0;
3625 player->effective_mouse_action.button = 0;
3626 player->effective_mouse_action.button_hint = 0;
3628 for (j = 0; j < MAX_NUM_KEYS; j++)
3629 player->key[j] = FALSE;
3631 player->num_white_keys = 0;
3633 player->dynabomb_count = 0;
3634 player->dynabomb_size = 1;
3635 player->dynabombs_left = 0;
3636 player->dynabomb_xl = FALSE;
3638 player->MovDir = initial_move_dir;
3641 player->GfxDir = initial_move_dir;
3642 player->GfxAction = ACTION_DEFAULT;
3644 player->StepFrame = 0;
3646 player->initial_element = player->element_nr;
3647 player->artwork_element =
3648 (level.use_artwork_element[i] ? level.artwork_element[i] :
3649 player->element_nr);
3650 player->use_murphy = FALSE;
3652 player->block_last_field = FALSE; // initialized in InitPlayerField()
3653 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3655 player->gravity = level.initial_player_gravity[i];
3657 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3659 player->actual_frame_counter = 0;
3661 player->step_counter = 0;
3663 player->last_move_dir = initial_move_dir;
3665 player->is_active = FALSE;
3667 player->is_waiting = FALSE;
3668 player->is_moving = FALSE;
3669 player->is_auto_moving = FALSE;
3670 player->is_digging = FALSE;
3671 player->is_snapping = FALSE;
3672 player->is_collecting = FALSE;
3673 player->is_pushing = FALSE;
3674 player->is_switching = FALSE;
3675 player->is_dropping = FALSE;
3676 player->is_dropping_pressed = FALSE;
3678 player->is_bored = FALSE;
3679 player->is_sleeping = FALSE;
3681 player->was_waiting = TRUE;
3682 player->was_moving = FALSE;
3683 player->was_snapping = FALSE;
3684 player->was_dropping = FALSE;
3686 player->force_dropping = FALSE;
3688 player->frame_counter_bored = -1;
3689 player->frame_counter_sleeping = -1;
3691 player->anim_delay_counter = 0;
3692 player->post_delay_counter = 0;
3694 player->dir_waiting = initial_move_dir;
3695 player->action_waiting = ACTION_DEFAULT;
3696 player->last_action_waiting = ACTION_DEFAULT;
3697 player->special_action_bored = ACTION_DEFAULT;
3698 player->special_action_sleeping = ACTION_DEFAULT;
3700 player->switch_x = -1;
3701 player->switch_y = -1;
3703 player->drop_x = -1;
3704 player->drop_y = -1;
3706 player->show_envelope = 0;
3708 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3710 player->push_delay = -1; // initialized when pushing starts
3711 player->push_delay_value = game.initial_push_delay_value;
3713 player->drop_delay = 0;
3714 player->drop_pressed_delay = 0;
3716 player->last_jx = -1;
3717 player->last_jy = -1;
3721 player->shield_normal_time_left = 0;
3722 player->shield_deadly_time_left = 0;
3724 player->last_removed_element = EL_UNDEFINED;
3726 player->inventory_infinite_element = EL_UNDEFINED;
3727 player->inventory_size = 0;
3729 if (level.use_initial_inventory[i])
3731 for (j = 0; j < level.initial_inventory_size[i]; j++)
3733 int element = level.initial_inventory_content[i][j];
3734 int collect_count = element_info[element].collect_count_initial;
3737 if (!IS_CUSTOM_ELEMENT(element))
3740 if (collect_count == 0)
3741 player->inventory_infinite_element = element;
3743 for (k = 0; k < collect_count; k++)
3744 if (player->inventory_size < MAX_INVENTORY_SIZE)
3745 player->inventory_element[player->inventory_size++] = element;
3749 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3750 SnapField(player, 0, 0);
3752 map_player_action[i] = i;
3755 network_player_action_received = FALSE;
3757 // initial null action
3758 if (network_playing)
3759 SendToServer_MovePlayer(MV_NONE);
3764 TimeLeft = level.time;
3767 ScreenMovDir = MV_NONE;
3771 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3773 game.robot_wheel_x = -1;
3774 game.robot_wheel_y = -1;
3779 game.all_players_gone = FALSE;
3781 game.LevelSolved = FALSE;
3782 game.GameOver = FALSE;
3784 game.GamePlayed = !tape.playing;
3786 game.LevelSolved_GameWon = FALSE;
3787 game.LevelSolved_GameEnd = FALSE;
3788 game.LevelSolved_SaveTape = FALSE;
3789 game.LevelSolved_SaveScore = FALSE;
3791 game.LevelSolved_CountingTime = 0;
3792 game.LevelSolved_CountingScore = 0;
3793 game.LevelSolved_CountingHealth = 0;
3795 game.panel.active = TRUE;
3797 game.no_time_limit = (level.time == 0);
3799 game.yamyam_content_nr = 0;
3800 game.robot_wheel_active = FALSE;
3801 game.magic_wall_active = FALSE;
3802 game.magic_wall_time_left = 0;
3803 game.light_time_left = 0;
3804 game.timegate_time_left = 0;
3805 game.switchgate_pos = 0;
3806 game.wind_direction = level.wind_direction_initial;
3809 game.score_final = 0;
3811 game.health = MAX_HEALTH;
3812 game.health_final = MAX_HEALTH;
3814 game.gems_still_needed = level.gems_needed;
3815 game.sokoban_fields_still_needed = 0;
3816 game.sokoban_objects_still_needed = 0;
3817 game.lights_still_needed = 0;
3818 game.players_still_needed = 0;
3819 game.friends_still_needed = 0;
3821 game.lenses_time_left = 0;
3822 game.magnify_time_left = 0;
3824 game.ball_active = level.ball_active_initial;
3825 game.ball_content_nr = 0;
3827 game.explosions_delayed = TRUE;
3829 game.envelope_active = FALSE;
3831 for (i = 0; i < NUM_BELTS; i++)
3833 game.belt_dir[i] = MV_NONE;
3834 game.belt_dir_nr[i] = 3; // not moving, next moving left
3837 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3838 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3840 #if DEBUG_INIT_PLAYER
3841 DebugPrintPlayerStatus("Player status at level initialization");
3844 SCAN_PLAYFIELD(x, y)
3846 Tile[x][y] = Last[x][y] = level.field[x][y];
3847 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3848 ChangeDelay[x][y] = 0;
3849 ChangePage[x][y] = -1;
3850 CustomValue[x][y] = 0; // initialized in InitField()
3851 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3853 WasJustMoving[x][y] = 0;
3854 WasJustFalling[x][y] = 0;
3855 CheckCollision[x][y] = 0;
3856 CheckImpact[x][y] = 0;
3858 Pushed[x][y] = FALSE;
3860 ChangeCount[x][y] = 0;
3861 ChangeEvent[x][y] = -1;
3863 ExplodePhase[x][y] = 0;
3864 ExplodeDelay[x][y] = 0;
3865 ExplodeField[x][y] = EX_TYPE_NONE;
3867 RunnerVisit[x][y] = 0;
3868 PlayerVisit[x][y] = 0;
3871 GfxRandom[x][y] = INIT_GFX_RANDOM();
3872 GfxElement[x][y] = EL_UNDEFINED;
3873 GfxAction[x][y] = ACTION_DEFAULT;
3874 GfxDir[x][y] = MV_NONE;
3875 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3878 SCAN_PLAYFIELD(x, y)
3880 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3882 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3884 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3887 InitField(x, y, TRUE);
3889 ResetGfxAnimation(x, y);
3894 for (i = 0; i < MAX_PLAYERS; i++)
3896 struct PlayerInfo *player = &stored_player[i];
3898 // set number of special actions for bored and sleeping animation
3899 player->num_special_action_bored =
3900 get_num_special_action(player->artwork_element,
3901 ACTION_BORING_1, ACTION_BORING_LAST);
3902 player->num_special_action_sleeping =
3903 get_num_special_action(player->artwork_element,
3904 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3907 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3908 emulate_sb ? EMU_SOKOBAN :
3909 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3911 // initialize type of slippery elements
3912 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3914 if (!IS_CUSTOM_ELEMENT(i))
3916 // default: elements slip down either to the left or right randomly
3917 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3919 // SP style elements prefer to slip down on the left side
3920 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3921 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3923 // BD style elements prefer to slip down on the left side
3924 if (game.emulation == EMU_BOULDERDASH)
3925 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3929 // initialize explosion and ignition delay
3930 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3932 if (!IS_CUSTOM_ELEMENT(i))
3935 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3936 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3937 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3938 int last_phase = (num_phase + 1) * delay;
3939 int half_phase = (num_phase / 2) * delay;
3941 element_info[i].explosion_delay = last_phase - 1;
3942 element_info[i].ignition_delay = half_phase;
3944 if (i == EL_BLACK_ORB)
3945 element_info[i].ignition_delay = 1;
3949 // correct non-moving belts to start moving left
3950 for (i = 0; i < NUM_BELTS; i++)
3951 if (game.belt_dir[i] == MV_NONE)
3952 game.belt_dir_nr[i] = 3; // not moving, next moving left
3954 #if USE_NEW_PLAYER_ASSIGNMENTS
3955 // use preferred player also in local single-player mode
3956 if (!network.enabled && !game.team_mode)
3958 int new_index_nr = setup.network_player_nr;
3960 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3962 for (i = 0; i < MAX_PLAYERS; i++)
3963 stored_player[i].connected_locally = FALSE;
3965 stored_player[new_index_nr].connected_locally = TRUE;
3969 for (i = 0; i < MAX_PLAYERS; i++)
3971 stored_player[i].connected = FALSE;
3973 // in network game mode, the local player might not be the first player
3974 if (stored_player[i].connected_locally)
3975 local_player = &stored_player[i];
3978 if (!network.enabled)
3979 local_player->connected = TRUE;
3983 for (i = 0; i < MAX_PLAYERS; i++)
3984 stored_player[i].connected = tape.player_participates[i];
3986 else if (network.enabled)
3988 // add team mode players connected over the network (needed for correct
3989 // assignment of player figures from level to locally playing players)
3991 for (i = 0; i < MAX_PLAYERS; i++)
3992 if (stored_player[i].connected_network)
3993 stored_player[i].connected = TRUE;
3995 else if (game.team_mode)
3997 // try to guess locally connected team mode players (needed for correct
3998 // assignment of player figures from level to locally playing players)
4000 for (i = 0; i < MAX_PLAYERS; i++)
4001 if (setup.input[i].use_joystick ||
4002 setup.input[i].key.left != KSYM_UNDEFINED)
4003 stored_player[i].connected = TRUE;
4006 #if DEBUG_INIT_PLAYER
4007 DebugPrintPlayerStatus("Player status after level initialization");
4010 #if DEBUG_INIT_PLAYER
4011 Debug("game:init:player", "Reassigning players ...");
4014 // check if any connected player was not found in playfield
4015 for (i = 0; i < MAX_PLAYERS; i++)
4017 struct PlayerInfo *player = &stored_player[i];
4019 if (player->connected && !player->present)
4021 struct PlayerInfo *field_player = NULL;
4023 #if DEBUG_INIT_PLAYER
4024 Debug("game:init:player",
4025 "- looking for field player for player %d ...", i + 1);
4028 // assign first free player found that is present in the playfield
4030 // first try: look for unmapped playfield player that is not connected
4031 for (j = 0; j < MAX_PLAYERS; j++)
4032 if (field_player == NULL &&
4033 stored_player[j].present &&
4034 !stored_player[j].mapped &&
4035 !stored_player[j].connected)
4036 field_player = &stored_player[j];
4038 // second try: look for *any* unmapped playfield player
4039 for (j = 0; j < MAX_PLAYERS; j++)
4040 if (field_player == NULL &&
4041 stored_player[j].present &&
4042 !stored_player[j].mapped)
4043 field_player = &stored_player[j];
4045 if (field_player != NULL)
4047 int jx = field_player->jx, jy = field_player->jy;
4049 #if DEBUG_INIT_PLAYER
4050 Debug("game:init:player", "- found player %d",
4051 field_player->index_nr + 1);
4054 player->present = FALSE;
4055 player->active = FALSE;
4057 field_player->present = TRUE;
4058 field_player->active = TRUE;
4061 player->initial_element = field_player->initial_element;
4062 player->artwork_element = field_player->artwork_element;
4064 player->block_last_field = field_player->block_last_field;
4065 player->block_delay_adjustment = field_player->block_delay_adjustment;
4068 StorePlayer[jx][jy] = field_player->element_nr;
4070 field_player->jx = field_player->last_jx = jx;
4071 field_player->jy = field_player->last_jy = jy;
4073 if (local_player == player)
4074 local_player = field_player;
4076 map_player_action[field_player->index_nr] = i;
4078 field_player->mapped = TRUE;
4080 #if DEBUG_INIT_PLAYER
4081 Debug("game:init:player", "- map_player_action[%d] == %d",
4082 field_player->index_nr + 1, i + 1);
4087 if (player->connected && player->present)
4088 player->mapped = TRUE;
4091 #if DEBUG_INIT_PLAYER
4092 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4097 // check if any connected player was not found in playfield
4098 for (i = 0; i < MAX_PLAYERS; i++)
4100 struct PlayerInfo *player = &stored_player[i];
4102 if (player->connected && !player->present)
4104 for (j = 0; j < MAX_PLAYERS; j++)
4106 struct PlayerInfo *field_player = &stored_player[j];
4107 int jx = field_player->jx, jy = field_player->jy;
4109 // assign first free player found that is present in the playfield
4110 if (field_player->present && !field_player->connected)
4112 player->present = TRUE;
4113 player->active = TRUE;
4115 field_player->present = FALSE;
4116 field_player->active = FALSE;
4118 player->initial_element = field_player->initial_element;
4119 player->artwork_element = field_player->artwork_element;
4121 player->block_last_field = field_player->block_last_field;
4122 player->block_delay_adjustment = field_player->block_delay_adjustment;
4124 StorePlayer[jx][jy] = player->element_nr;
4126 player->jx = player->last_jx = jx;
4127 player->jy = player->last_jy = jy;
4137 Debug("game:init:player", "local_player->present == %d",
4138 local_player->present);
4141 // set focus to local player for network games, else to all players
4142 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4143 game.centered_player_nr_next = game.centered_player_nr;
4144 game.set_centered_player = FALSE;
4145 game.set_centered_player_wrap = FALSE;
4147 if (network_playing && tape.recording)
4149 // store client dependent player focus when recording network games
4150 tape.centered_player_nr_next = game.centered_player_nr_next;
4151 tape.set_centered_player = TRUE;
4156 // when playing a tape, eliminate all players who do not participate
4158 #if USE_NEW_PLAYER_ASSIGNMENTS
4160 if (!game.team_mode)
4162 for (i = 0; i < MAX_PLAYERS; i++)
4164 if (stored_player[i].active &&
4165 !tape.player_participates[map_player_action[i]])
4167 struct PlayerInfo *player = &stored_player[i];
4168 int jx = player->jx, jy = player->jy;
4170 #if DEBUG_INIT_PLAYER
4171 Debug("game:init:player", "Removing player %d at (%d, %d)",
4175 player->active = FALSE;
4176 StorePlayer[jx][jy] = 0;
4177 Tile[jx][jy] = EL_EMPTY;
4184 for (i = 0; i < MAX_PLAYERS; i++)
4186 if (stored_player[i].active &&
4187 !tape.player_participates[i])
4189 struct PlayerInfo *player = &stored_player[i];
4190 int jx = player->jx, jy = player->jy;
4192 player->active = FALSE;
4193 StorePlayer[jx][jy] = 0;
4194 Tile[jx][jy] = EL_EMPTY;
4199 else if (!network.enabled && !game.team_mode) // && !tape.playing
4201 // when in single player mode, eliminate all but the local player
4203 for (i = 0; i < MAX_PLAYERS; i++)
4205 struct PlayerInfo *player = &stored_player[i];
4207 if (player->active && player != local_player)
4209 int jx = player->jx, jy = player->jy;
4211 player->active = FALSE;
4212 player->present = FALSE;
4214 StorePlayer[jx][jy] = 0;
4215 Tile[jx][jy] = EL_EMPTY;
4220 for (i = 0; i < MAX_PLAYERS; i++)
4221 if (stored_player[i].active)
4222 game.players_still_needed++;
4224 if (level.solved_by_one_player)
4225 game.players_still_needed = 1;
4227 // when recording the game, store which players take part in the game
4230 #if USE_NEW_PLAYER_ASSIGNMENTS
4231 for (i = 0; i < MAX_PLAYERS; i++)
4232 if (stored_player[i].connected)
4233 tape.player_participates[i] = TRUE;
4235 for (i = 0; i < MAX_PLAYERS; i++)
4236 if (stored_player[i].active)
4237 tape.player_participates[i] = TRUE;
4241 #if DEBUG_INIT_PLAYER
4242 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4245 if (BorderElement == EL_EMPTY)
4248 SBX_Right = lev_fieldx - SCR_FIELDX;
4250 SBY_Lower = lev_fieldy - SCR_FIELDY;
4255 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4257 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4260 if (full_lev_fieldx <= SCR_FIELDX)
4261 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4262 if (full_lev_fieldy <= SCR_FIELDY)
4263 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4265 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4267 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4270 // if local player not found, look for custom element that might create
4271 // the player (make some assumptions about the right custom element)
4272 if (!local_player->present)
4274 int start_x = 0, start_y = 0;
4275 int found_rating = 0;
4276 int found_element = EL_UNDEFINED;
4277 int player_nr = local_player->index_nr;
4279 SCAN_PLAYFIELD(x, y)
4281 int element = Tile[x][y];
4286 if (level.use_start_element[player_nr] &&
4287 level.start_element[player_nr] == element &&
4294 found_element = element;
4297 if (!IS_CUSTOM_ELEMENT(element))
4300 if (CAN_CHANGE(element))
4302 for (i = 0; i < element_info[element].num_change_pages; i++)
4304 // check for player created from custom element as single target
4305 content = element_info[element].change_page[i].target_element;
4306 is_player = ELEM_IS_PLAYER(content);
4308 if (is_player && (found_rating < 3 ||
4309 (found_rating == 3 && element < found_element)))
4315 found_element = element;
4320 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4322 // check for player created from custom element as explosion content
4323 content = element_info[element].content.e[xx][yy];
4324 is_player = ELEM_IS_PLAYER(content);
4326 if (is_player && (found_rating < 2 ||
4327 (found_rating == 2 && element < found_element)))
4329 start_x = x + xx - 1;
4330 start_y = y + yy - 1;
4333 found_element = element;
4336 if (!CAN_CHANGE(element))
4339 for (i = 0; i < element_info[element].num_change_pages; i++)
4341 // check for player created from custom element as extended target
4343 element_info[element].change_page[i].target_content.e[xx][yy];
4345 is_player = ELEM_IS_PLAYER(content);
4347 if (is_player && (found_rating < 1 ||
4348 (found_rating == 1 && element < found_element)))
4350 start_x = x + xx - 1;
4351 start_y = y + yy - 1;
4354 found_element = element;
4360 scroll_x = SCROLL_POSITION_X(start_x);
4361 scroll_y = SCROLL_POSITION_Y(start_y);
4365 scroll_x = SCROLL_POSITION_X(local_player->jx);
4366 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4369 // !!! FIX THIS (START) !!!
4370 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4372 InitGameEngine_EM();
4374 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4376 InitGameEngine_SP();
4378 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4380 InitGameEngine_MM();
4384 DrawLevel(REDRAW_FIELD);
4387 // after drawing the level, correct some elements
4388 if (game.timegate_time_left == 0)
4389 CloseAllOpenTimegates();
4392 // blit playfield from scroll buffer to normal back buffer for fading in
4393 BlitScreenToBitmap(backbuffer);
4394 // !!! FIX THIS (END) !!!
4396 DrawMaskedBorder(fade_mask);
4401 // full screen redraw is required at this point in the following cases:
4402 // - special editor door undrawn when game was started from level editor
4403 // - drawing area (playfield) was changed and has to be removed completely
4404 redraw_mask = REDRAW_ALL;
4408 if (!game.restart_level)
4410 // copy default game door content to main double buffer
4412 // !!! CHECK AGAIN !!!
4413 SetPanelBackground();
4414 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4415 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4418 SetPanelBackground();
4419 SetDrawBackgroundMask(REDRAW_DOOR_1);
4421 UpdateAndDisplayGameControlValues();
4423 if (!game.restart_level)
4429 CreateGameButtons();
4434 // copy actual game door content to door double buffer for OpenDoor()
4435 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4437 OpenDoor(DOOR_OPEN_ALL);
4439 KeyboardAutoRepeatOffUnlessAutoplay();
4441 #if DEBUG_INIT_PLAYER
4442 DebugPrintPlayerStatus("Player status (final)");
4451 if (!game.restart_level && !tape.playing)
4453 LevelStats_incPlayed(level_nr);
4455 SaveLevelSetup_SeriesInfo();
4458 game.restart_level = FALSE;
4459 game.restart_game_message = NULL;
4461 game.request_active = FALSE;
4462 game.request_active_or_moving = FALSE;
4464 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4465 InitGameActions_MM();
4467 SaveEngineSnapshotToListInitial();
4469 if (!game.restart_level)
4471 PlaySound(SND_GAME_STARTING);
4473 if (setup.sound_music)
4478 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4479 int actual_player_x, int actual_player_y)
4481 // this is used for non-R'n'D game engines to update certain engine values
4483 // needed to determine if sounds are played within the visible screen area
4484 scroll_x = actual_scroll_x;
4485 scroll_y = actual_scroll_y;
4487 // needed to get player position for "follow finger" playing input method
4488 local_player->jx = actual_player_x;
4489 local_player->jy = actual_player_y;
4492 void InitMovDir(int x, int y)
4494 int i, element = Tile[x][y];
4495 static int xy[4][2] =
4502 static int direction[3][4] =
4504 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4505 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4506 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4515 Tile[x][y] = EL_BUG;
4516 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4519 case EL_SPACESHIP_RIGHT:
4520 case EL_SPACESHIP_UP:
4521 case EL_SPACESHIP_LEFT:
4522 case EL_SPACESHIP_DOWN:
4523 Tile[x][y] = EL_SPACESHIP;
4524 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4527 case EL_BD_BUTTERFLY_RIGHT:
4528 case EL_BD_BUTTERFLY_UP:
4529 case EL_BD_BUTTERFLY_LEFT:
4530 case EL_BD_BUTTERFLY_DOWN:
4531 Tile[x][y] = EL_BD_BUTTERFLY;
4532 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4535 case EL_BD_FIREFLY_RIGHT:
4536 case EL_BD_FIREFLY_UP:
4537 case EL_BD_FIREFLY_LEFT:
4538 case EL_BD_FIREFLY_DOWN:
4539 Tile[x][y] = EL_BD_FIREFLY;
4540 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4543 case EL_PACMAN_RIGHT:
4545 case EL_PACMAN_LEFT:
4546 case EL_PACMAN_DOWN:
4547 Tile[x][y] = EL_PACMAN;
4548 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4551 case EL_YAMYAM_LEFT:
4552 case EL_YAMYAM_RIGHT:
4554 case EL_YAMYAM_DOWN:
4555 Tile[x][y] = EL_YAMYAM;
4556 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4559 case EL_SP_SNIKSNAK:
4560 MovDir[x][y] = MV_UP;
4563 case EL_SP_ELECTRON:
4564 MovDir[x][y] = MV_LEFT;
4571 Tile[x][y] = EL_MOLE;
4572 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4575 case EL_SPRING_LEFT:
4576 case EL_SPRING_RIGHT:
4577 Tile[x][y] = EL_SPRING;
4578 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4582 if (IS_CUSTOM_ELEMENT(element))
4584 struct ElementInfo *ei = &element_info[element];
4585 int move_direction_initial = ei->move_direction_initial;
4586 int move_pattern = ei->move_pattern;
4588 if (move_direction_initial == MV_START_PREVIOUS)
4590 if (MovDir[x][y] != MV_NONE)
4593 move_direction_initial = MV_START_AUTOMATIC;
4596 if (move_direction_initial == MV_START_RANDOM)
4597 MovDir[x][y] = 1 << RND(4);
4598 else if (move_direction_initial & MV_ANY_DIRECTION)
4599 MovDir[x][y] = move_direction_initial;
4600 else if (move_pattern == MV_ALL_DIRECTIONS ||
4601 move_pattern == MV_TURNING_LEFT ||
4602 move_pattern == MV_TURNING_RIGHT ||
4603 move_pattern == MV_TURNING_LEFT_RIGHT ||
4604 move_pattern == MV_TURNING_RIGHT_LEFT ||
4605 move_pattern == MV_TURNING_RANDOM)
4606 MovDir[x][y] = 1 << RND(4);
4607 else if (move_pattern == MV_HORIZONTAL)
4608 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4609 else if (move_pattern == MV_VERTICAL)
4610 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4611 else if (move_pattern & MV_ANY_DIRECTION)
4612 MovDir[x][y] = element_info[element].move_pattern;
4613 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4614 move_pattern == MV_ALONG_RIGHT_SIDE)
4616 // use random direction as default start direction
4617 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4618 MovDir[x][y] = 1 << RND(4);
4620 for (i = 0; i < NUM_DIRECTIONS; i++)
4622 int x1 = x + xy[i][0];
4623 int y1 = y + xy[i][1];
4625 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4627 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4628 MovDir[x][y] = direction[0][i];
4630 MovDir[x][y] = direction[1][i];
4639 MovDir[x][y] = 1 << RND(4);
4641 if (element != EL_BUG &&
4642 element != EL_SPACESHIP &&
4643 element != EL_BD_BUTTERFLY &&
4644 element != EL_BD_FIREFLY)
4647 for (i = 0; i < NUM_DIRECTIONS; i++)
4649 int x1 = x + xy[i][0];
4650 int y1 = y + xy[i][1];
4652 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4654 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4656 MovDir[x][y] = direction[0][i];
4659 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4660 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4662 MovDir[x][y] = direction[1][i];
4671 GfxDir[x][y] = MovDir[x][y];
4674 void InitAmoebaNr(int x, int y)
4677 int group_nr = AmoebaNeighbourNr(x, y);
4681 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4683 if (AmoebaCnt[i] == 0)
4691 AmoebaNr[x][y] = group_nr;
4692 AmoebaCnt[group_nr]++;
4693 AmoebaCnt2[group_nr]++;
4696 static void LevelSolved(void)
4698 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4699 game.players_still_needed > 0)
4702 game.LevelSolved = TRUE;
4703 game.GameOver = TRUE;
4705 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4706 game_em.lev->score :
4707 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4710 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4711 MM_HEALTH(game_mm.laser_overload_value) :
4714 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4715 game.LevelSolved_CountingScore = game.score_final;
4716 game.LevelSolved_CountingHealth = game.health_final;
4721 static int time_count_steps;
4722 static int time, time_final;
4723 static float score, score_final; // needed for time score < 10 for 10 seconds
4724 static int health, health_final;
4725 static int game_over_delay_1 = 0;
4726 static int game_over_delay_2 = 0;
4727 static int game_over_delay_3 = 0;
4728 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4729 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4731 if (!game.LevelSolved_GameWon)
4735 // do not start end game actions before the player stops moving (to exit)
4736 if (local_player->active && local_player->MovPos)
4739 game.LevelSolved_GameWon = TRUE;
4740 game.LevelSolved_SaveTape = tape.recording;
4741 game.LevelSolved_SaveScore = !tape.playing;
4745 LevelStats_incSolved(level_nr);
4747 SaveLevelSetup_SeriesInfo();
4750 if (tape.auto_play) // tape might already be stopped here
4751 tape.auto_play_level_solved = TRUE;
4755 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4756 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4757 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4759 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4760 score = score_final = game.score_final;
4761 health = health_final = game.health_final;
4765 int time_frames = 0;
4770 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4772 else if (game.no_time_limit && TimePlayed < 999)
4775 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4778 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4780 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4782 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4785 score_final += health * time_score;
4788 game.score_final = score_final;
4789 game.health_final = health_final;
4792 if (level_editor_test_game || !setup.count_score_after_game)
4795 score = score_final;
4797 game.LevelSolved_CountingTime = time;
4798 game.LevelSolved_CountingScore = score;
4800 game_panel_controls[GAME_PANEL_TIME].value = time;
4801 game_panel_controls[GAME_PANEL_SCORE].value = score;
4803 DisplayGameControlValues();
4806 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4808 // check if last player has left the level
4809 if (game.exit_x >= 0 &&
4812 int x = game.exit_x;
4813 int y = game.exit_y;
4814 int element = Tile[x][y];
4816 // close exit door after last player
4817 if ((game.all_players_gone &&
4818 (element == EL_EXIT_OPEN ||
4819 element == EL_SP_EXIT_OPEN ||
4820 element == EL_STEEL_EXIT_OPEN)) ||
4821 element == EL_EM_EXIT_OPEN ||
4822 element == EL_EM_STEEL_EXIT_OPEN)
4826 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4827 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4828 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4829 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4830 EL_EM_STEEL_EXIT_CLOSING);
4832 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4835 // player disappears
4836 DrawLevelField(x, y);
4839 for (i = 0; i < MAX_PLAYERS; i++)
4841 struct PlayerInfo *player = &stored_player[i];
4843 if (player->present)
4845 RemovePlayer(player);
4847 // player disappears
4848 DrawLevelField(player->jx, player->jy);
4853 PlaySound(SND_GAME_WINNING);
4856 if (setup.count_score_after_game)
4858 if (time != time_final)
4860 if (game_over_delay_1 > 0)
4862 game_over_delay_1--;
4867 int time_to_go = ABS(time_final - time);
4868 int time_count_dir = (time < time_final ? +1 : -1);
4870 if (time_to_go < time_count_steps)
4871 time_count_steps = 1;
4873 time += time_count_steps * time_count_dir;
4874 score += time_count_steps * time_score;
4876 // set final score to correct rounding differences after counting score
4877 if (time == time_final)
4878 score = score_final;
4880 game.LevelSolved_CountingTime = time;
4881 game.LevelSolved_CountingScore = score;
4883 game_panel_controls[GAME_PANEL_TIME].value = time;
4884 game_panel_controls[GAME_PANEL_SCORE].value = score;
4886 DisplayGameControlValues();
4888 if (time == time_final)
4889 StopSound(SND_GAME_LEVELTIME_BONUS);
4890 else if (setup.sound_loops)
4891 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4893 PlaySound(SND_GAME_LEVELTIME_BONUS);
4898 if (health != health_final)
4900 if (game_over_delay_2 > 0)
4902 game_over_delay_2--;
4907 int health_count_dir = (health < health_final ? +1 : -1);
4909 health += health_count_dir;
4910 score += time_score;
4912 game.LevelSolved_CountingHealth = health;
4913 game.LevelSolved_CountingScore = score;
4915 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4916 game_panel_controls[GAME_PANEL_SCORE].value = score;
4918 DisplayGameControlValues();
4920 if (health == health_final)
4921 StopSound(SND_GAME_LEVELTIME_BONUS);
4922 else if (setup.sound_loops)
4923 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4925 PlaySound(SND_GAME_LEVELTIME_BONUS);
4931 game.panel.active = FALSE;
4933 if (game_over_delay_3 > 0)
4935 game_over_delay_3--;
4945 // used instead of "level_nr" (needed for network games)
4946 int last_level_nr = levelset.level_nr;
4949 game.LevelSolved_GameEnd = TRUE;
4951 if (game.LevelSolved_SaveTape)
4953 // make sure that request dialog to save tape does not open door again
4954 if (!global.use_envelope_request)
4955 CloseDoor(DOOR_CLOSE_1);
4957 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4960 // if no tape is to be saved, close both doors simultaneously
4961 CloseDoor(DOOR_CLOSE_ALL);
4963 if (level_editor_test_game)
4965 SetGameStatus(GAME_MODE_MAIN);
4972 if (!game.LevelSolved_SaveScore)
4974 SetGameStatus(GAME_MODE_MAIN);
4981 if (level_nr == leveldir_current->handicap_level)
4983 leveldir_current->handicap_level++;
4985 SaveLevelSetup_SeriesInfo();
4988 if (setup.increment_levels &&
4989 level_nr < leveldir_current->last_level &&
4992 level_nr++; // advance to next level
4993 TapeErase(); // start with empty tape
4995 if (setup.auto_play_next_level)
4997 LoadLevel(level_nr);
4999 SaveLevelSetup_SeriesInfo();
5003 hi_pos = NewHiScore(last_level_nr);
5005 if (hi_pos >= 0 && setup.show_scores_after_game)
5007 SetGameStatus(GAME_MODE_SCORES);
5009 DrawHallOfFame(last_level_nr, hi_pos);
5011 else if (setup.auto_play_next_level && setup.increment_levels &&
5012 last_level_nr < leveldir_current->last_level &&
5015 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5019 SetGameStatus(GAME_MODE_MAIN);
5025 int NewHiScore(int level_nr)
5029 boolean one_score_entry_per_name = !program.many_scores_per_name;
5031 LoadScore(level_nr);
5033 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5034 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5037 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5039 if (game.score_final > highscore[k].Score)
5041 // player has made it to the hall of fame
5043 if (k < MAX_SCORE_ENTRIES - 1)
5045 int m = MAX_SCORE_ENTRIES - 1;
5047 if (one_score_entry_per_name)
5049 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5050 if (strEqual(setup.player_name, highscore[l].Name))
5053 if (m == k) // player's new highscore overwrites his old one
5057 for (l = m; l > k; l--)
5059 strcpy(highscore[l].Name, highscore[l - 1].Name);
5060 highscore[l].Score = highscore[l - 1].Score;
5066 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5067 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5068 highscore[k].Score = game.score_final;
5073 else if (one_score_entry_per_name &&
5074 !strncmp(setup.player_name, highscore[k].Name,
5075 MAX_PLAYER_NAME_LEN))
5076 break; // player already there with a higher score
5080 SaveScore(level_nr);
5085 static int getElementMoveStepsizeExt(int x, int y, int direction)
5087 int element = Tile[x][y];
5088 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5089 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5090 int horiz_move = (dx != 0);
5091 int sign = (horiz_move ? dx : dy);
5092 int step = sign * element_info[element].move_stepsize;
5094 // special values for move stepsize for spring and things on conveyor belt
5097 if (CAN_FALL(element) &&
5098 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5099 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5100 else if (element == EL_SPRING)
5101 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5107 static int getElementMoveStepsize(int x, int y)
5109 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5112 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5114 if (player->GfxAction != action || player->GfxDir != dir)
5116 player->GfxAction = action;
5117 player->GfxDir = dir;
5119 player->StepFrame = 0;
5123 static void ResetGfxFrame(int x, int y)
5125 // profiling showed that "autotest" spends 10~20% of its time in this function
5126 if (DrawingDeactivatedField())
5129 int element = Tile[x][y];
5130 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5132 if (graphic_info[graphic].anim_global_sync)
5133 GfxFrame[x][y] = FrameCounter;
5134 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5135 GfxFrame[x][y] = CustomValue[x][y];
5136 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5137 GfxFrame[x][y] = element_info[element].collect_score;
5138 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5139 GfxFrame[x][y] = ChangeDelay[x][y];
5142 static void ResetGfxAnimation(int x, int y)
5144 GfxAction[x][y] = ACTION_DEFAULT;
5145 GfxDir[x][y] = MovDir[x][y];
5148 ResetGfxFrame(x, y);
5151 static void ResetRandomAnimationValue(int x, int y)
5153 GfxRandom[x][y] = INIT_GFX_RANDOM();
5156 static void InitMovingField(int x, int y, int direction)
5158 int element = Tile[x][y];
5159 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5160 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5163 boolean is_moving_before, is_moving_after;
5165 // check if element was/is moving or being moved before/after mode change
5166 is_moving_before = (WasJustMoving[x][y] != 0);
5167 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5169 // reset animation only for moving elements which change direction of moving
5170 // or which just started or stopped moving
5171 // (else CEs with property "can move" / "not moving" are reset each frame)
5172 if (is_moving_before != is_moving_after ||
5173 direction != MovDir[x][y])
5174 ResetGfxAnimation(x, y);
5176 MovDir[x][y] = direction;
5177 GfxDir[x][y] = direction;
5179 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5180 direction == MV_DOWN && CAN_FALL(element) ?
5181 ACTION_FALLING : ACTION_MOVING);
5183 // this is needed for CEs with property "can move" / "not moving"
5185 if (is_moving_after)
5187 if (Tile[newx][newy] == EL_EMPTY)
5188 Tile[newx][newy] = EL_BLOCKED;
5190 MovDir[newx][newy] = MovDir[x][y];
5192 CustomValue[newx][newy] = CustomValue[x][y];
5194 GfxFrame[newx][newy] = GfxFrame[x][y];
5195 GfxRandom[newx][newy] = GfxRandom[x][y];
5196 GfxAction[newx][newy] = GfxAction[x][y];
5197 GfxDir[newx][newy] = GfxDir[x][y];
5201 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5203 int direction = MovDir[x][y];
5204 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5205 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5211 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5213 int oldx = x, oldy = y;
5214 int direction = MovDir[x][y];
5216 if (direction == MV_LEFT)
5218 else if (direction == MV_RIGHT)
5220 else if (direction == MV_UP)
5222 else if (direction == MV_DOWN)
5225 *comes_from_x = oldx;
5226 *comes_from_y = oldy;
5229 static int MovingOrBlocked2Element(int x, int y)
5231 int element = Tile[x][y];
5233 if (element == EL_BLOCKED)
5237 Blocked2Moving(x, y, &oldx, &oldy);
5238 return Tile[oldx][oldy];
5244 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5246 // like MovingOrBlocked2Element(), but if element is moving
5247 // and (x,y) is the field the moving element is just leaving,
5248 // return EL_BLOCKED instead of the element value
5249 int element = Tile[x][y];
5251 if (IS_MOVING(x, y))
5253 if (element == EL_BLOCKED)
5257 Blocked2Moving(x, y, &oldx, &oldy);
5258 return Tile[oldx][oldy];
5267 static void RemoveField(int x, int y)
5269 Tile[x][y] = EL_EMPTY;
5275 CustomValue[x][y] = 0;
5278 ChangeDelay[x][y] = 0;
5279 ChangePage[x][y] = -1;
5280 Pushed[x][y] = FALSE;
5282 GfxElement[x][y] = EL_UNDEFINED;
5283 GfxAction[x][y] = ACTION_DEFAULT;
5284 GfxDir[x][y] = MV_NONE;
5287 static void RemoveMovingField(int x, int y)
5289 int oldx = x, oldy = y, newx = x, newy = y;
5290 int element = Tile[x][y];
5291 int next_element = EL_UNDEFINED;
5293 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5296 if (IS_MOVING(x, y))
5298 Moving2Blocked(x, y, &newx, &newy);
5300 if (Tile[newx][newy] != EL_BLOCKED)
5302 // element is moving, but target field is not free (blocked), but
5303 // already occupied by something different (example: acid pool);
5304 // in this case, only remove the moving field, but not the target
5306 RemoveField(oldx, oldy);
5308 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5310 TEST_DrawLevelField(oldx, oldy);
5315 else if (element == EL_BLOCKED)
5317 Blocked2Moving(x, y, &oldx, &oldy);
5318 if (!IS_MOVING(oldx, oldy))
5322 if (element == EL_BLOCKED &&
5323 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5324 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5325 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5326 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5327 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5328 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5329 next_element = get_next_element(Tile[oldx][oldy]);
5331 RemoveField(oldx, oldy);
5332 RemoveField(newx, newy);
5334 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5336 if (next_element != EL_UNDEFINED)
5337 Tile[oldx][oldy] = next_element;
5339 TEST_DrawLevelField(oldx, oldy);
5340 TEST_DrawLevelField(newx, newy);
5343 void DrawDynamite(int x, int y)
5345 int sx = SCREENX(x), sy = SCREENY(y);
5346 int graphic = el2img(Tile[x][y]);
5349 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5352 if (IS_WALKABLE_INSIDE(Back[x][y]))
5356 DrawLevelElement(x, y, Back[x][y]);
5357 else if (Store[x][y])
5358 DrawLevelElement(x, y, Store[x][y]);
5359 else if (game.use_masked_elements)
5360 DrawLevelElement(x, y, EL_EMPTY);
5362 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5364 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5365 DrawGraphicThruMask(sx, sy, graphic, frame);
5367 DrawGraphic(sx, sy, graphic, frame);
5370 static void CheckDynamite(int x, int y)
5372 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5376 if (MovDelay[x][y] != 0)
5379 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5385 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5390 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5392 boolean num_checked_players = 0;
5395 for (i = 0; i < MAX_PLAYERS; i++)
5397 if (stored_player[i].active)
5399 int sx = stored_player[i].jx;
5400 int sy = stored_player[i].jy;
5402 if (num_checked_players == 0)
5409 *sx1 = MIN(*sx1, sx);
5410 *sy1 = MIN(*sy1, sy);
5411 *sx2 = MAX(*sx2, sx);
5412 *sy2 = MAX(*sy2, sy);
5415 num_checked_players++;
5420 static boolean checkIfAllPlayersFitToScreen_RND(void)
5422 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5424 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5426 return (sx2 - sx1 < SCR_FIELDX &&
5427 sy2 - sy1 < SCR_FIELDY);
5430 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5432 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5434 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5436 *sx = (sx1 + sx2) / 2;
5437 *sy = (sy1 + sy2) / 2;
5440 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5441 boolean center_screen, boolean quick_relocation)
5443 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5444 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5445 boolean no_delay = (tape.warp_forward);
5446 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5447 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5448 int new_scroll_x, new_scroll_y;
5450 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5452 // case 1: quick relocation inside visible screen (without scrolling)
5459 if (!level.shifted_relocation || center_screen)
5461 // relocation _with_ centering of screen
5463 new_scroll_x = SCROLL_POSITION_X(x);
5464 new_scroll_y = SCROLL_POSITION_Y(y);
5468 // relocation _without_ centering of screen
5470 int center_scroll_x = SCROLL_POSITION_X(old_x);
5471 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5472 int offset_x = x + (scroll_x - center_scroll_x);
5473 int offset_y = y + (scroll_y - center_scroll_y);
5475 // for new screen position, apply previous offset to center position
5476 new_scroll_x = SCROLL_POSITION_X(offset_x);
5477 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5480 if (quick_relocation)
5482 // case 2: quick relocation (redraw without visible scrolling)
5484 scroll_x = new_scroll_x;
5485 scroll_y = new_scroll_y;
5492 // case 3: visible relocation (with scrolling to new position)
5494 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5496 SetVideoFrameDelay(wait_delay_value);
5498 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5500 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5501 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5503 if (dx == 0 && dy == 0) // no scrolling needed at all
5509 // set values for horizontal/vertical screen scrolling (half tile size)
5510 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5511 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5512 int pos_x = dx * TILEX / 2;
5513 int pos_y = dy * TILEY / 2;
5514 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5515 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5517 ScrollLevel(dx, dy);
5520 // scroll in two steps of half tile size to make things smoother
5521 BlitScreenToBitmapExt_RND(window, fx, fy);
5523 // scroll second step to align at full tile size
5524 BlitScreenToBitmap(window);
5530 SetVideoFrameDelay(frame_delay_value_old);
5533 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5535 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5536 int player_nr = GET_PLAYER_NR(el_player);
5537 struct PlayerInfo *player = &stored_player[player_nr];
5538 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5539 boolean no_delay = (tape.warp_forward);
5540 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5541 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5542 int old_jx = player->jx;
5543 int old_jy = player->jy;
5544 int old_element = Tile[old_jx][old_jy];
5545 int element = Tile[jx][jy];
5546 boolean player_relocated = (old_jx != jx || old_jy != jy);
5548 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5549 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5550 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5551 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5552 int leave_side_horiz = move_dir_horiz;
5553 int leave_side_vert = move_dir_vert;
5554 int enter_side = enter_side_horiz | enter_side_vert;
5555 int leave_side = leave_side_horiz | leave_side_vert;
5557 if (player->buried) // do not reanimate dead player
5560 if (!player_relocated) // no need to relocate the player
5563 if (IS_PLAYER(jx, jy)) // player already placed at new position
5565 RemoveField(jx, jy); // temporarily remove newly placed player
5566 DrawLevelField(jx, jy);
5569 if (player->present)
5571 while (player->MovPos)
5573 ScrollPlayer(player, SCROLL_GO_ON);
5574 ScrollScreen(NULL, SCROLL_GO_ON);
5576 AdvanceFrameAndPlayerCounters(player->index_nr);
5580 BackToFront_WithFrameDelay(wait_delay_value);
5583 DrawPlayer(player); // needed here only to cleanup last field
5584 DrawLevelField(player->jx, player->jy); // remove player graphic
5586 player->is_moving = FALSE;
5589 if (IS_CUSTOM_ELEMENT(old_element))
5590 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5592 player->index_bit, leave_side);
5594 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5596 player->index_bit, leave_side);
5598 Tile[jx][jy] = el_player;
5599 InitPlayerField(jx, jy, el_player, TRUE);
5601 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5602 possible that the relocation target field did not contain a player element,
5603 but a walkable element, to which the new player was relocated -- in this
5604 case, restore that (already initialized!) element on the player field */
5605 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5607 Tile[jx][jy] = element; // restore previously existing element
5610 // only visually relocate centered player
5611 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5612 FALSE, level.instant_relocation);
5614 TestIfPlayerTouchesBadThing(jx, jy);
5615 TestIfPlayerTouchesCustomElement(jx, jy);
5617 if (IS_CUSTOM_ELEMENT(element))
5618 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5619 player->index_bit, enter_side);
5621 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5622 player->index_bit, enter_side);
5624 if (player->is_switching)
5626 /* ensure that relocation while still switching an element does not cause
5627 a new element to be treated as also switched directly after relocation
5628 (this is important for teleporter switches that teleport the player to
5629 a place where another teleporter switch is in the same direction, which
5630 would then incorrectly be treated as immediately switched before the
5631 direction key that caused the switch was released) */
5633 player->switch_x += jx - old_jx;
5634 player->switch_y += jy - old_jy;
5638 static void Explode(int ex, int ey, int phase, int mode)
5644 // !!! eliminate this variable !!!
5645 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5647 if (game.explosions_delayed)
5649 ExplodeField[ex][ey] = mode;
5653 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5655 int center_element = Tile[ex][ey];
5656 int artwork_element, explosion_element; // set these values later
5658 // remove things displayed in background while burning dynamite
5659 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5662 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5664 // put moving element to center field (and let it explode there)
5665 center_element = MovingOrBlocked2Element(ex, ey);
5666 RemoveMovingField(ex, ey);
5667 Tile[ex][ey] = center_element;
5670 // now "center_element" is finally determined -- set related values now
5671 artwork_element = center_element; // for custom player artwork
5672 explosion_element = center_element; // for custom player artwork
5674 if (IS_PLAYER(ex, ey))
5676 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5678 artwork_element = stored_player[player_nr].artwork_element;
5680 if (level.use_explosion_element[player_nr])
5682 explosion_element = level.explosion_element[player_nr];
5683 artwork_element = explosion_element;
5687 if (mode == EX_TYPE_NORMAL ||
5688 mode == EX_TYPE_CENTER ||
5689 mode == EX_TYPE_CROSS)
5690 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5692 last_phase = element_info[explosion_element].explosion_delay + 1;
5694 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5696 int xx = x - ex + 1;
5697 int yy = y - ey + 1;
5700 if (!IN_LEV_FIELD(x, y) ||
5701 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5702 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5705 element = Tile[x][y];
5707 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5709 element = MovingOrBlocked2Element(x, y);
5711 if (!IS_EXPLOSION_PROOF(element))
5712 RemoveMovingField(x, y);
5715 // indestructible elements can only explode in center (but not flames)
5716 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5717 mode == EX_TYPE_BORDER)) ||
5718 element == EL_FLAMES)
5721 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5722 behaviour, for example when touching a yamyam that explodes to rocks
5723 with active deadly shield, a rock is created under the player !!! */
5724 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5726 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5727 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5728 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5730 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5733 if (IS_ACTIVE_BOMB(element))
5735 // re-activate things under the bomb like gate or penguin
5736 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5743 // save walkable background elements while explosion on same tile
5744 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5745 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5746 Back[x][y] = element;
5748 // ignite explodable elements reached by other explosion
5749 if (element == EL_EXPLOSION)
5750 element = Store2[x][y];
5752 if (AmoebaNr[x][y] &&
5753 (element == EL_AMOEBA_FULL ||
5754 element == EL_BD_AMOEBA ||
5755 element == EL_AMOEBA_GROWING))
5757 AmoebaCnt[AmoebaNr[x][y]]--;
5758 AmoebaCnt2[AmoebaNr[x][y]]--;
5763 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5765 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5767 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5769 if (PLAYERINFO(ex, ey)->use_murphy)
5770 Store[x][y] = EL_EMPTY;
5773 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5774 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5775 else if (ELEM_IS_PLAYER(center_element))
5776 Store[x][y] = EL_EMPTY;
5777 else if (center_element == EL_YAMYAM)
5778 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5779 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5780 Store[x][y] = element_info[center_element].content.e[xx][yy];
5782 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5783 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5784 // otherwise) -- FIX THIS !!!
5785 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5786 Store[x][y] = element_info[element].content.e[1][1];
5788 else if (!CAN_EXPLODE(element))
5789 Store[x][y] = element_info[element].content.e[1][1];
5792 Store[x][y] = EL_EMPTY;
5794 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5795 center_element == EL_AMOEBA_TO_DIAMOND)
5796 Store2[x][y] = element;
5798 Tile[x][y] = EL_EXPLOSION;
5799 GfxElement[x][y] = artwork_element;
5801 ExplodePhase[x][y] = 1;
5802 ExplodeDelay[x][y] = last_phase;
5807 if (center_element == EL_YAMYAM)
5808 game.yamyam_content_nr =
5809 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5821 GfxFrame[x][y] = 0; // restart explosion animation
5823 last_phase = ExplodeDelay[x][y];
5825 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5827 // this can happen if the player leaves an explosion just in time
5828 if (GfxElement[x][y] == EL_UNDEFINED)
5829 GfxElement[x][y] = EL_EMPTY;
5831 border_element = Store2[x][y];
5832 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5833 border_element = StorePlayer[x][y];
5835 if (phase == element_info[border_element].ignition_delay ||
5836 phase == last_phase)
5838 boolean border_explosion = FALSE;
5840 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5841 !PLAYER_EXPLOSION_PROTECTED(x, y))
5843 KillPlayerUnlessExplosionProtected(x, y);
5844 border_explosion = TRUE;
5846 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5848 Tile[x][y] = Store2[x][y];
5851 border_explosion = TRUE;
5853 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5855 AmoebaToDiamond(x, y);
5857 border_explosion = TRUE;
5860 // if an element just explodes due to another explosion (chain-reaction),
5861 // do not immediately end the new explosion when it was the last frame of
5862 // the explosion (as it would be done in the following "if"-statement!)
5863 if (border_explosion && phase == last_phase)
5867 if (phase == last_phase)
5871 element = Tile[x][y] = Store[x][y];
5872 Store[x][y] = Store2[x][y] = 0;
5873 GfxElement[x][y] = EL_UNDEFINED;
5875 // player can escape from explosions and might therefore be still alive
5876 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5877 element <= EL_PLAYER_IS_EXPLODING_4)
5879 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5880 int explosion_element = EL_PLAYER_1 + player_nr;
5881 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5882 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5884 if (level.use_explosion_element[player_nr])
5885 explosion_element = level.explosion_element[player_nr];
5887 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5888 element_info[explosion_element].content.e[xx][yy]);
5891 // restore probably existing indestructible background element
5892 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5893 element = Tile[x][y] = Back[x][y];
5896 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5897 GfxDir[x][y] = MV_NONE;
5898 ChangeDelay[x][y] = 0;
5899 ChangePage[x][y] = -1;
5901 CustomValue[x][y] = 0;
5903 InitField_WithBug2(x, y, FALSE);
5905 TEST_DrawLevelField(x, y);
5907 TestIfElementTouchesCustomElement(x, y);
5909 if (GFX_CRUMBLED(element))
5910 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5912 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5913 StorePlayer[x][y] = 0;
5915 if (ELEM_IS_PLAYER(element))
5916 RelocatePlayer(x, y, element);
5918 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5920 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5921 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5924 TEST_DrawLevelFieldCrumbled(x, y);
5926 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5928 DrawLevelElement(x, y, Back[x][y]);
5929 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5931 else if (IS_WALKABLE_UNDER(Back[x][y]))
5933 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5934 DrawLevelElementThruMask(x, y, Back[x][y]);
5936 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5937 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5941 static void DynaExplode(int ex, int ey)
5944 int dynabomb_element = Tile[ex][ey];
5945 int dynabomb_size = 1;
5946 boolean dynabomb_xl = FALSE;
5947 struct PlayerInfo *player;
5948 static int xy[4][2] =
5956 if (IS_ACTIVE_BOMB(dynabomb_element))
5958 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5959 dynabomb_size = player->dynabomb_size;
5960 dynabomb_xl = player->dynabomb_xl;
5961 player->dynabombs_left++;
5964 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5966 for (i = 0; i < NUM_DIRECTIONS; i++)
5968 for (j = 1; j <= dynabomb_size; j++)
5970 int x = ex + j * xy[i][0];
5971 int y = ey + j * xy[i][1];
5974 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5977 element = Tile[x][y];
5979 // do not restart explosions of fields with active bombs
5980 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5983 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5985 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5986 !IS_DIGGABLE(element) && !dynabomb_xl)
5992 void Bang(int x, int y)
5994 int element = MovingOrBlocked2Element(x, y);
5995 int explosion_type = EX_TYPE_NORMAL;
5997 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5999 struct PlayerInfo *player = PLAYERINFO(x, y);
6001 element = Tile[x][y] = player->initial_element;
6003 if (level.use_explosion_element[player->index_nr])
6005 int explosion_element = level.explosion_element[player->index_nr];
6007 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6008 explosion_type = EX_TYPE_CROSS;
6009 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6010 explosion_type = EX_TYPE_CENTER;
6018 case EL_BD_BUTTERFLY:
6021 case EL_DARK_YAMYAM:
6025 RaiseScoreElement(element);
6028 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6029 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6030 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6031 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6032 case EL_DYNABOMB_INCREASE_NUMBER:
6033 case EL_DYNABOMB_INCREASE_SIZE:
6034 case EL_DYNABOMB_INCREASE_POWER:
6035 explosion_type = EX_TYPE_DYNA;
6038 case EL_DC_LANDMINE:
6039 explosion_type = EX_TYPE_CENTER;
6044 case EL_LAMP_ACTIVE:
6045 case EL_AMOEBA_TO_DIAMOND:
6046 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6047 explosion_type = EX_TYPE_CENTER;
6051 if (element_info[element].explosion_type == EXPLODES_CROSS)
6052 explosion_type = EX_TYPE_CROSS;
6053 else if (element_info[element].explosion_type == EXPLODES_1X1)
6054 explosion_type = EX_TYPE_CENTER;
6058 if (explosion_type == EX_TYPE_DYNA)
6061 Explode(x, y, EX_PHASE_START, explosion_type);
6063 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6066 static void SplashAcid(int x, int y)
6068 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6069 (!IN_LEV_FIELD(x - 1, y - 2) ||
6070 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6071 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6073 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6074 (!IN_LEV_FIELD(x + 1, y - 2) ||
6075 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6076 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6078 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6081 static void InitBeltMovement(void)
6083 static int belt_base_element[4] =
6085 EL_CONVEYOR_BELT_1_LEFT,
6086 EL_CONVEYOR_BELT_2_LEFT,
6087 EL_CONVEYOR_BELT_3_LEFT,
6088 EL_CONVEYOR_BELT_4_LEFT
6090 static int belt_base_active_element[4] =
6092 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6093 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6094 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6095 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6100 // set frame order for belt animation graphic according to belt direction
6101 for (i = 0; i < NUM_BELTS; i++)
6105 for (j = 0; j < NUM_BELT_PARTS; j++)
6107 int element = belt_base_active_element[belt_nr] + j;
6108 int graphic_1 = el2img(element);
6109 int graphic_2 = el2panelimg(element);
6111 if (game.belt_dir[i] == MV_LEFT)
6113 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6114 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6118 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6119 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6124 SCAN_PLAYFIELD(x, y)
6126 int element = Tile[x][y];
6128 for (i = 0; i < NUM_BELTS; i++)
6130 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6132 int e_belt_nr = getBeltNrFromBeltElement(element);
6135 if (e_belt_nr == belt_nr)
6137 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6139 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6146 static void ToggleBeltSwitch(int x, int y)
6148 static int belt_base_element[4] =
6150 EL_CONVEYOR_BELT_1_LEFT,
6151 EL_CONVEYOR_BELT_2_LEFT,
6152 EL_CONVEYOR_BELT_3_LEFT,
6153 EL_CONVEYOR_BELT_4_LEFT
6155 static int belt_base_active_element[4] =
6157 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6158 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6159 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6160 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6162 static int belt_base_switch_element[4] =
6164 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6165 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6166 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6167 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6169 static int belt_move_dir[4] =
6177 int element = Tile[x][y];
6178 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6179 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6180 int belt_dir = belt_move_dir[belt_dir_nr];
6183 if (!IS_BELT_SWITCH(element))
6186 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6187 game.belt_dir[belt_nr] = belt_dir;
6189 if (belt_dir_nr == 3)
6192 // set frame order for belt animation graphic according to belt direction
6193 for (i = 0; i < NUM_BELT_PARTS; i++)
6195 int element = belt_base_active_element[belt_nr] + i;
6196 int graphic_1 = el2img(element);
6197 int graphic_2 = el2panelimg(element);
6199 if (belt_dir == MV_LEFT)
6201 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6202 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6206 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6207 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6211 SCAN_PLAYFIELD(xx, yy)
6213 int element = Tile[xx][yy];
6215 if (IS_BELT_SWITCH(element))
6217 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6219 if (e_belt_nr == belt_nr)
6221 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6222 TEST_DrawLevelField(xx, yy);
6225 else if (IS_BELT(element) && belt_dir != MV_NONE)
6227 int e_belt_nr = getBeltNrFromBeltElement(element);
6229 if (e_belt_nr == belt_nr)
6231 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6233 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6234 TEST_DrawLevelField(xx, yy);
6237 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6239 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6241 if (e_belt_nr == belt_nr)
6243 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6245 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6246 TEST_DrawLevelField(xx, yy);
6252 static void ToggleSwitchgateSwitch(int x, int y)
6256 game.switchgate_pos = !game.switchgate_pos;
6258 SCAN_PLAYFIELD(xx, yy)
6260 int element = Tile[xx][yy];
6262 if (element == EL_SWITCHGATE_SWITCH_UP)
6264 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6265 TEST_DrawLevelField(xx, yy);
6267 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6269 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6270 TEST_DrawLevelField(xx, yy);
6272 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6274 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6275 TEST_DrawLevelField(xx, yy);
6277 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6279 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6280 TEST_DrawLevelField(xx, yy);
6282 else if (element == EL_SWITCHGATE_OPEN ||
6283 element == EL_SWITCHGATE_OPENING)
6285 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6287 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6289 else if (element == EL_SWITCHGATE_CLOSED ||
6290 element == EL_SWITCHGATE_CLOSING)
6292 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6294 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6299 static int getInvisibleActiveFromInvisibleElement(int element)
6301 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6302 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6303 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6307 static int getInvisibleFromInvisibleActiveElement(int element)
6309 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6310 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6311 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6315 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6319 SCAN_PLAYFIELD(x, y)
6321 int element = Tile[x][y];
6323 if (element == EL_LIGHT_SWITCH &&
6324 game.light_time_left > 0)
6326 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6327 TEST_DrawLevelField(x, y);
6329 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6330 game.light_time_left == 0)
6332 Tile[x][y] = EL_LIGHT_SWITCH;
6333 TEST_DrawLevelField(x, y);
6335 else if (element == EL_EMC_DRIPPER &&
6336 game.light_time_left > 0)
6338 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6339 TEST_DrawLevelField(x, y);
6341 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6342 game.light_time_left == 0)
6344 Tile[x][y] = EL_EMC_DRIPPER;
6345 TEST_DrawLevelField(x, y);
6347 else if (element == EL_INVISIBLE_STEELWALL ||
6348 element == EL_INVISIBLE_WALL ||
6349 element == EL_INVISIBLE_SAND)
6351 if (game.light_time_left > 0)
6352 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6354 TEST_DrawLevelField(x, y);
6356 // uncrumble neighbour fields, if needed
6357 if (element == EL_INVISIBLE_SAND)
6358 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6360 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6361 element == EL_INVISIBLE_WALL_ACTIVE ||
6362 element == EL_INVISIBLE_SAND_ACTIVE)
6364 if (game.light_time_left == 0)
6365 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6367 TEST_DrawLevelField(x, y);
6369 // re-crumble neighbour fields, if needed
6370 if (element == EL_INVISIBLE_SAND)
6371 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6376 static void RedrawAllInvisibleElementsForLenses(void)
6380 SCAN_PLAYFIELD(x, y)
6382 int element = Tile[x][y];
6384 if (element == EL_EMC_DRIPPER &&
6385 game.lenses_time_left > 0)
6387 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6388 TEST_DrawLevelField(x, y);
6390 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6391 game.lenses_time_left == 0)
6393 Tile[x][y] = EL_EMC_DRIPPER;
6394 TEST_DrawLevelField(x, y);
6396 else if (element == EL_INVISIBLE_STEELWALL ||
6397 element == EL_INVISIBLE_WALL ||
6398 element == EL_INVISIBLE_SAND)
6400 if (game.lenses_time_left > 0)
6401 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6403 TEST_DrawLevelField(x, y);
6405 // uncrumble neighbour fields, if needed
6406 if (element == EL_INVISIBLE_SAND)
6407 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6409 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6410 element == EL_INVISIBLE_WALL_ACTIVE ||
6411 element == EL_INVISIBLE_SAND_ACTIVE)
6413 if (game.lenses_time_left == 0)
6414 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6416 TEST_DrawLevelField(x, y);
6418 // re-crumble neighbour fields, if needed
6419 if (element == EL_INVISIBLE_SAND)
6420 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6425 static void RedrawAllInvisibleElementsForMagnifier(void)
6429 SCAN_PLAYFIELD(x, y)
6431 int element = Tile[x][y];
6433 if (element == EL_EMC_FAKE_GRASS &&
6434 game.magnify_time_left > 0)
6436 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6437 TEST_DrawLevelField(x, y);
6439 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6440 game.magnify_time_left == 0)
6442 Tile[x][y] = EL_EMC_FAKE_GRASS;
6443 TEST_DrawLevelField(x, y);
6445 else if (IS_GATE_GRAY(element) &&
6446 game.magnify_time_left > 0)
6448 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6449 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6450 IS_EM_GATE_GRAY(element) ?
6451 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6452 IS_EMC_GATE_GRAY(element) ?
6453 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6454 IS_DC_GATE_GRAY(element) ?
6455 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6457 TEST_DrawLevelField(x, y);
6459 else if (IS_GATE_GRAY_ACTIVE(element) &&
6460 game.magnify_time_left == 0)
6462 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6463 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6464 IS_EM_GATE_GRAY_ACTIVE(element) ?
6465 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6466 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6467 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6468 IS_DC_GATE_GRAY_ACTIVE(element) ?
6469 EL_DC_GATE_WHITE_GRAY :
6471 TEST_DrawLevelField(x, y);
6476 static void ToggleLightSwitch(int x, int y)
6478 int element = Tile[x][y];
6480 game.light_time_left =
6481 (element == EL_LIGHT_SWITCH ?
6482 level.time_light * FRAMES_PER_SECOND : 0);
6484 RedrawAllLightSwitchesAndInvisibleElements();
6487 static void ActivateTimegateSwitch(int x, int y)
6491 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6493 SCAN_PLAYFIELD(xx, yy)
6495 int element = Tile[xx][yy];
6497 if (element == EL_TIMEGATE_CLOSED ||
6498 element == EL_TIMEGATE_CLOSING)
6500 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6501 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6505 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6507 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6508 TEST_DrawLevelField(xx, yy);
6514 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6515 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6518 static void Impact(int x, int y)
6520 boolean last_line = (y == lev_fieldy - 1);
6521 boolean object_hit = FALSE;
6522 boolean impact = (last_line || object_hit);
6523 int element = Tile[x][y];
6524 int smashed = EL_STEELWALL;
6526 if (!last_line) // check if element below was hit
6528 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6531 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6532 MovDir[x][y + 1] != MV_DOWN ||
6533 MovPos[x][y + 1] <= TILEY / 2));
6535 // do not smash moving elements that left the smashed field in time
6536 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6537 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6540 #if USE_QUICKSAND_IMPACT_BUGFIX
6541 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6543 RemoveMovingField(x, y + 1);
6544 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6545 Tile[x][y + 2] = EL_ROCK;
6546 TEST_DrawLevelField(x, y + 2);
6551 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6553 RemoveMovingField(x, y + 1);
6554 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6555 Tile[x][y + 2] = EL_ROCK;
6556 TEST_DrawLevelField(x, y + 2);
6563 smashed = MovingOrBlocked2Element(x, y + 1);
6565 impact = (last_line || object_hit);
6568 if (!last_line && smashed == EL_ACID) // element falls into acid
6570 SplashAcid(x, y + 1);
6574 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6575 // only reset graphic animation if graphic really changes after impact
6577 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6579 ResetGfxAnimation(x, y);
6580 TEST_DrawLevelField(x, y);
6583 if (impact && CAN_EXPLODE_IMPACT(element))
6588 else if (impact && element == EL_PEARL &&
6589 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6591 ResetGfxAnimation(x, y);
6593 Tile[x][y] = EL_PEARL_BREAKING;
6594 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6597 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6599 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6604 if (impact && element == EL_AMOEBA_DROP)
6606 if (object_hit && IS_PLAYER(x, y + 1))
6607 KillPlayerUnlessEnemyProtected(x, y + 1);
6608 else if (object_hit && smashed == EL_PENGUIN)
6612 Tile[x][y] = EL_AMOEBA_GROWING;
6613 Store[x][y] = EL_AMOEBA_WET;
6615 ResetRandomAnimationValue(x, y);
6620 if (object_hit) // check which object was hit
6622 if ((CAN_PASS_MAGIC_WALL(element) &&
6623 (smashed == EL_MAGIC_WALL ||
6624 smashed == EL_BD_MAGIC_WALL)) ||
6625 (CAN_PASS_DC_MAGIC_WALL(element) &&
6626 smashed == EL_DC_MAGIC_WALL))
6629 int activated_magic_wall =
6630 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6631 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6632 EL_DC_MAGIC_WALL_ACTIVE);
6634 // activate magic wall / mill
6635 SCAN_PLAYFIELD(xx, yy)
6637 if (Tile[xx][yy] == smashed)
6638 Tile[xx][yy] = activated_magic_wall;
6641 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6642 game.magic_wall_active = TRUE;
6644 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6645 SND_MAGIC_WALL_ACTIVATING :
6646 smashed == EL_BD_MAGIC_WALL ?
6647 SND_BD_MAGIC_WALL_ACTIVATING :
6648 SND_DC_MAGIC_WALL_ACTIVATING));
6651 if (IS_PLAYER(x, y + 1))
6653 if (CAN_SMASH_PLAYER(element))
6655 KillPlayerUnlessEnemyProtected(x, y + 1);
6659 else if (smashed == EL_PENGUIN)
6661 if (CAN_SMASH_PLAYER(element))
6667 else if (element == EL_BD_DIAMOND)
6669 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6675 else if (((element == EL_SP_INFOTRON ||
6676 element == EL_SP_ZONK) &&
6677 (smashed == EL_SP_SNIKSNAK ||
6678 smashed == EL_SP_ELECTRON ||
6679 smashed == EL_SP_DISK_ORANGE)) ||
6680 (element == EL_SP_INFOTRON &&
6681 smashed == EL_SP_DISK_YELLOW))
6686 else if (CAN_SMASH_EVERYTHING(element))
6688 if (IS_CLASSIC_ENEMY(smashed) ||
6689 CAN_EXPLODE_SMASHED(smashed))
6694 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6696 if (smashed == EL_LAMP ||
6697 smashed == EL_LAMP_ACTIVE)
6702 else if (smashed == EL_NUT)
6704 Tile[x][y + 1] = EL_NUT_BREAKING;
6705 PlayLevelSound(x, y, SND_NUT_BREAKING);
6706 RaiseScoreElement(EL_NUT);
6709 else if (smashed == EL_PEARL)
6711 ResetGfxAnimation(x, y);
6713 Tile[x][y + 1] = EL_PEARL_BREAKING;
6714 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6717 else if (smashed == EL_DIAMOND)
6719 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6720 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6723 else if (IS_BELT_SWITCH(smashed))
6725 ToggleBeltSwitch(x, y + 1);
6727 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6728 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6729 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6730 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6732 ToggleSwitchgateSwitch(x, y + 1);
6734 else if (smashed == EL_LIGHT_SWITCH ||
6735 smashed == EL_LIGHT_SWITCH_ACTIVE)
6737 ToggleLightSwitch(x, y + 1);
6741 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6743 CheckElementChangeBySide(x, y + 1, smashed, element,
6744 CE_SWITCHED, CH_SIDE_TOP);
6745 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6751 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6756 // play sound of magic wall / mill
6758 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6759 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6760 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6762 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6763 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6764 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6765 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6766 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6767 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6772 // play sound of object that hits the ground
6773 if (last_line || object_hit)
6774 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6777 static void TurnRoundExt(int x, int y)
6789 { 0, 0 }, { 0, 0 }, { 0, 0 },
6794 int left, right, back;
6798 { MV_DOWN, MV_UP, MV_RIGHT },
6799 { MV_UP, MV_DOWN, MV_LEFT },
6801 { MV_LEFT, MV_RIGHT, MV_DOWN },
6805 { MV_RIGHT, MV_LEFT, MV_UP }
6808 int element = Tile[x][y];
6809 int move_pattern = element_info[element].move_pattern;
6811 int old_move_dir = MovDir[x][y];
6812 int left_dir = turn[old_move_dir].left;
6813 int right_dir = turn[old_move_dir].right;
6814 int back_dir = turn[old_move_dir].back;
6816 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6817 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6818 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6819 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6821 int left_x = x + left_dx, left_y = y + left_dy;
6822 int right_x = x + right_dx, right_y = y + right_dy;
6823 int move_x = x + move_dx, move_y = y + move_dy;
6827 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6829 TestIfBadThingTouchesOtherBadThing(x, y);
6831 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6832 MovDir[x][y] = right_dir;
6833 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6834 MovDir[x][y] = left_dir;
6836 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6838 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6841 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6843 TestIfBadThingTouchesOtherBadThing(x, y);
6845 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6846 MovDir[x][y] = left_dir;
6847 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6848 MovDir[x][y] = right_dir;
6850 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6852 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6855 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6857 TestIfBadThingTouchesOtherBadThing(x, y);
6859 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6860 MovDir[x][y] = left_dir;
6861 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6862 MovDir[x][y] = right_dir;
6864 if (MovDir[x][y] != old_move_dir)
6867 else if (element == EL_YAMYAM)
6869 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6870 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6872 if (can_turn_left && can_turn_right)
6873 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6874 else if (can_turn_left)
6875 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6876 else if (can_turn_right)
6877 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6879 MovDir[x][y] = back_dir;
6881 MovDelay[x][y] = 16 + 16 * RND(3);
6883 else if (element == EL_DARK_YAMYAM)
6885 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6887 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6890 if (can_turn_left && can_turn_right)
6891 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6892 else if (can_turn_left)
6893 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6894 else if (can_turn_right)
6895 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6897 MovDir[x][y] = back_dir;
6899 MovDelay[x][y] = 16 + 16 * RND(3);
6901 else if (element == EL_PACMAN)
6903 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6904 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6906 if (can_turn_left && can_turn_right)
6907 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6908 else if (can_turn_left)
6909 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6910 else if (can_turn_right)
6911 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6913 MovDir[x][y] = back_dir;
6915 MovDelay[x][y] = 6 + RND(40);
6917 else if (element == EL_PIG)
6919 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6920 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6921 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6922 boolean should_turn_left, should_turn_right, should_move_on;
6924 int rnd = RND(rnd_value);
6926 should_turn_left = (can_turn_left &&
6928 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6929 y + back_dy + left_dy)));
6930 should_turn_right = (can_turn_right &&
6932 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6933 y + back_dy + right_dy)));
6934 should_move_on = (can_move_on &&
6937 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6938 y + move_dy + left_dy) ||
6939 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6940 y + move_dy + right_dy)));
6942 if (should_turn_left || should_turn_right || should_move_on)
6944 if (should_turn_left && should_turn_right && should_move_on)
6945 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6946 rnd < 2 * rnd_value / 3 ? right_dir :
6948 else if (should_turn_left && should_turn_right)
6949 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6950 else if (should_turn_left && should_move_on)
6951 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6952 else if (should_turn_right && should_move_on)
6953 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6954 else if (should_turn_left)
6955 MovDir[x][y] = left_dir;
6956 else if (should_turn_right)
6957 MovDir[x][y] = right_dir;
6958 else if (should_move_on)
6959 MovDir[x][y] = old_move_dir;
6961 else if (can_move_on && rnd > rnd_value / 8)
6962 MovDir[x][y] = old_move_dir;
6963 else if (can_turn_left && can_turn_right)
6964 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6965 else if (can_turn_left && rnd > rnd_value / 8)
6966 MovDir[x][y] = left_dir;
6967 else if (can_turn_right && rnd > rnd_value/8)
6968 MovDir[x][y] = right_dir;
6970 MovDir[x][y] = back_dir;
6972 xx = x + move_xy[MovDir[x][y]].dx;
6973 yy = y + move_xy[MovDir[x][y]].dy;
6975 if (!IN_LEV_FIELD(xx, yy) ||
6976 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6977 MovDir[x][y] = old_move_dir;
6981 else if (element == EL_DRAGON)
6983 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6984 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6985 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6987 int rnd = RND(rnd_value);
6989 if (can_move_on && rnd > rnd_value / 8)
6990 MovDir[x][y] = old_move_dir;
6991 else if (can_turn_left && can_turn_right)
6992 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6993 else if (can_turn_left && rnd > rnd_value / 8)
6994 MovDir[x][y] = left_dir;
6995 else if (can_turn_right && rnd > rnd_value / 8)
6996 MovDir[x][y] = right_dir;
6998 MovDir[x][y] = back_dir;
7000 xx = x + move_xy[MovDir[x][y]].dx;
7001 yy = y + move_xy[MovDir[x][y]].dy;
7003 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7004 MovDir[x][y] = old_move_dir;
7008 else if (element == EL_MOLE)
7010 boolean can_move_on =
7011 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7012 IS_AMOEBOID(Tile[move_x][move_y]) ||
7013 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7016 boolean can_turn_left =
7017 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7018 IS_AMOEBOID(Tile[left_x][left_y])));
7020 boolean can_turn_right =
7021 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7022 IS_AMOEBOID(Tile[right_x][right_y])));
7024 if (can_turn_left && can_turn_right)
7025 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7026 else if (can_turn_left)
7027 MovDir[x][y] = left_dir;
7029 MovDir[x][y] = right_dir;
7032 if (MovDir[x][y] != old_move_dir)
7035 else if (element == EL_BALLOON)
7037 MovDir[x][y] = game.wind_direction;
7040 else if (element == EL_SPRING)
7042 if (MovDir[x][y] & MV_HORIZONTAL)
7044 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7045 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7047 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7048 ResetGfxAnimation(move_x, move_y);
7049 TEST_DrawLevelField(move_x, move_y);
7051 MovDir[x][y] = back_dir;
7053 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7054 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7055 MovDir[x][y] = MV_NONE;
7060 else if (element == EL_ROBOT ||
7061 element == EL_SATELLITE ||
7062 element == EL_PENGUIN ||
7063 element == EL_EMC_ANDROID)
7065 int attr_x = -1, attr_y = -1;
7067 if (game.all_players_gone)
7069 attr_x = game.exit_x;
7070 attr_y = game.exit_y;
7076 for (i = 0; i < MAX_PLAYERS; i++)
7078 struct PlayerInfo *player = &stored_player[i];
7079 int jx = player->jx, jy = player->jy;
7081 if (!player->active)
7085 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7093 if (element == EL_ROBOT &&
7094 game.robot_wheel_x >= 0 &&
7095 game.robot_wheel_y >= 0 &&
7096 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7097 game.engine_version < VERSION_IDENT(3,1,0,0)))
7099 attr_x = game.robot_wheel_x;
7100 attr_y = game.robot_wheel_y;
7103 if (element == EL_PENGUIN)
7106 static int xy[4][2] =
7114 for (i = 0; i < NUM_DIRECTIONS; i++)
7116 int ex = x + xy[i][0];
7117 int ey = y + xy[i][1];
7119 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7120 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7121 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7122 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7131 MovDir[x][y] = MV_NONE;
7133 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7134 else if (attr_x > x)
7135 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7137 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7138 else if (attr_y > y)
7139 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7141 if (element == EL_ROBOT)
7145 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7146 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7147 Moving2Blocked(x, y, &newx, &newy);
7149 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7150 MovDelay[x][y] = 8 + 8 * !RND(3);
7152 MovDelay[x][y] = 16;
7154 else if (element == EL_PENGUIN)
7160 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7162 boolean first_horiz = RND(2);
7163 int new_move_dir = MovDir[x][y];
7166 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7167 Moving2Blocked(x, y, &newx, &newy);
7169 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7173 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7174 Moving2Blocked(x, y, &newx, &newy);
7176 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7179 MovDir[x][y] = old_move_dir;
7183 else if (element == EL_SATELLITE)
7189 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7191 boolean first_horiz = RND(2);
7192 int new_move_dir = MovDir[x][y];
7195 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7196 Moving2Blocked(x, y, &newx, &newy);
7198 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7202 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7203 Moving2Blocked(x, y, &newx, &newy);
7205 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7208 MovDir[x][y] = old_move_dir;
7212 else if (element == EL_EMC_ANDROID)
7214 static int check_pos[16] =
7216 -1, // 0 => (invalid)
7219 -1, // 3 => (invalid)
7221 0, // 5 => MV_LEFT | MV_UP
7222 2, // 6 => MV_RIGHT | MV_UP
7223 -1, // 7 => (invalid)
7225 6, // 9 => MV_LEFT | MV_DOWN
7226 4, // 10 => MV_RIGHT | MV_DOWN
7227 -1, // 11 => (invalid)
7228 -1, // 12 => (invalid)
7229 -1, // 13 => (invalid)
7230 -1, // 14 => (invalid)
7231 -1, // 15 => (invalid)
7239 { -1, -1, MV_LEFT | MV_UP },
7241 { +1, -1, MV_RIGHT | MV_UP },
7242 { +1, 0, MV_RIGHT },
7243 { +1, +1, MV_RIGHT | MV_DOWN },
7245 { -1, +1, MV_LEFT | MV_DOWN },
7248 int start_pos, check_order;
7249 boolean can_clone = FALSE;
7252 // check if there is any free field around current position
7253 for (i = 0; i < 8; i++)
7255 int newx = x + check_xy[i].dx;
7256 int newy = y + check_xy[i].dy;
7258 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7266 if (can_clone) // randomly find an element to clone
7270 start_pos = check_pos[RND(8)];
7271 check_order = (RND(2) ? -1 : +1);
7273 for (i = 0; i < 8; i++)
7275 int pos_raw = start_pos + i * check_order;
7276 int pos = (pos_raw + 8) % 8;
7277 int newx = x + check_xy[pos].dx;
7278 int newy = y + check_xy[pos].dy;
7280 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7282 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7283 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7285 Store[x][y] = Tile[newx][newy];
7294 if (can_clone) // randomly find a direction to move
7298 start_pos = check_pos[RND(8)];
7299 check_order = (RND(2) ? -1 : +1);
7301 for (i = 0; i < 8; i++)
7303 int pos_raw = start_pos + i * check_order;
7304 int pos = (pos_raw + 8) % 8;
7305 int newx = x + check_xy[pos].dx;
7306 int newy = y + check_xy[pos].dy;
7307 int new_move_dir = check_xy[pos].dir;
7309 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7311 MovDir[x][y] = new_move_dir;
7312 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7321 if (can_clone) // cloning and moving successful
7324 // cannot clone -- try to move towards player
7326 start_pos = check_pos[MovDir[x][y] & 0x0f];
7327 check_order = (RND(2) ? -1 : +1);
7329 for (i = 0; i < 3; i++)
7331 // first check start_pos, then previous/next or (next/previous) pos
7332 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7333 int pos = (pos_raw + 8) % 8;
7334 int newx = x + check_xy[pos].dx;
7335 int newy = y + check_xy[pos].dy;
7336 int new_move_dir = check_xy[pos].dir;
7338 if (IS_PLAYER(newx, newy))
7341 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7343 MovDir[x][y] = new_move_dir;
7344 MovDelay[x][y] = level.android_move_time * 8 + 1;
7351 else if (move_pattern == MV_TURNING_LEFT ||
7352 move_pattern == MV_TURNING_RIGHT ||
7353 move_pattern == MV_TURNING_LEFT_RIGHT ||
7354 move_pattern == MV_TURNING_RIGHT_LEFT ||
7355 move_pattern == MV_TURNING_RANDOM ||
7356 move_pattern == MV_ALL_DIRECTIONS)
7358 boolean can_turn_left =
7359 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7360 boolean can_turn_right =
7361 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7363 if (element_info[element].move_stepsize == 0) // "not moving"
7366 if (move_pattern == MV_TURNING_LEFT)
7367 MovDir[x][y] = left_dir;
7368 else if (move_pattern == MV_TURNING_RIGHT)
7369 MovDir[x][y] = right_dir;
7370 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7371 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7372 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7373 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7374 else if (move_pattern == MV_TURNING_RANDOM)
7375 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7376 can_turn_right && !can_turn_left ? right_dir :
7377 RND(2) ? left_dir : right_dir);
7378 else if (can_turn_left && can_turn_right)
7379 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7380 else if (can_turn_left)
7381 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7382 else if (can_turn_right)
7383 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7385 MovDir[x][y] = back_dir;
7387 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7389 else if (move_pattern == MV_HORIZONTAL ||
7390 move_pattern == MV_VERTICAL)
7392 if (move_pattern & old_move_dir)
7393 MovDir[x][y] = back_dir;
7394 else if (move_pattern == MV_HORIZONTAL)
7395 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7396 else if (move_pattern == MV_VERTICAL)
7397 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7399 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7401 else if (move_pattern & MV_ANY_DIRECTION)
7403 MovDir[x][y] = move_pattern;
7404 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7406 else if (move_pattern & MV_WIND_DIRECTION)
7408 MovDir[x][y] = game.wind_direction;
7409 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7411 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7413 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7414 MovDir[x][y] = left_dir;
7415 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7416 MovDir[x][y] = right_dir;
7418 if (MovDir[x][y] != old_move_dir)
7419 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7421 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7423 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7424 MovDir[x][y] = right_dir;
7425 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7426 MovDir[x][y] = left_dir;
7428 if (MovDir[x][y] != old_move_dir)
7429 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7431 else if (move_pattern == MV_TOWARDS_PLAYER ||
7432 move_pattern == MV_AWAY_FROM_PLAYER)
7434 int attr_x = -1, attr_y = -1;
7436 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7438 if (game.all_players_gone)
7440 attr_x = game.exit_x;
7441 attr_y = game.exit_y;
7447 for (i = 0; i < MAX_PLAYERS; i++)
7449 struct PlayerInfo *player = &stored_player[i];
7450 int jx = player->jx, jy = player->jy;
7452 if (!player->active)
7456 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7464 MovDir[x][y] = MV_NONE;
7466 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7467 else if (attr_x > x)
7468 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7470 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7471 else if (attr_y > y)
7472 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7474 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7476 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7478 boolean first_horiz = RND(2);
7479 int new_move_dir = MovDir[x][y];
7481 if (element_info[element].move_stepsize == 0) // "not moving"
7483 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7484 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7490 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7491 Moving2Blocked(x, y, &newx, &newy);
7493 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7497 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7498 Moving2Blocked(x, y, &newx, &newy);
7500 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7503 MovDir[x][y] = old_move_dir;
7506 else if (move_pattern == MV_WHEN_PUSHED ||
7507 move_pattern == MV_WHEN_DROPPED)
7509 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7510 MovDir[x][y] = MV_NONE;
7514 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7516 static int test_xy[7][2] =
7526 static int test_dir[7] =
7536 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7537 int move_preference = -1000000; // start with very low preference
7538 int new_move_dir = MV_NONE;
7539 int start_test = RND(4);
7542 for (i = 0; i < NUM_DIRECTIONS; i++)
7544 int move_dir = test_dir[start_test + i];
7545 int move_dir_preference;
7547 xx = x + test_xy[start_test + i][0];
7548 yy = y + test_xy[start_test + i][1];
7550 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7551 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7553 new_move_dir = move_dir;
7558 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7561 move_dir_preference = -1 * RunnerVisit[xx][yy];
7562 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7563 move_dir_preference = PlayerVisit[xx][yy];
7565 if (move_dir_preference > move_preference)
7567 // prefer field that has not been visited for the longest time
7568 move_preference = move_dir_preference;
7569 new_move_dir = move_dir;
7571 else if (move_dir_preference == move_preference &&
7572 move_dir == old_move_dir)
7574 // prefer last direction when all directions are preferred equally
7575 move_preference = move_dir_preference;
7576 new_move_dir = move_dir;
7580 MovDir[x][y] = new_move_dir;
7581 if (old_move_dir != new_move_dir)
7582 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7586 static void TurnRound(int x, int y)
7588 int direction = MovDir[x][y];
7592 GfxDir[x][y] = MovDir[x][y];
7594 if (direction != MovDir[x][y])
7598 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7600 ResetGfxFrame(x, y);
7603 static boolean JustBeingPushed(int x, int y)
7607 for (i = 0; i < MAX_PLAYERS; i++)
7609 struct PlayerInfo *player = &stored_player[i];
7611 if (player->active && player->is_pushing && player->MovPos)
7613 int next_jx = player->jx + (player->jx - player->last_jx);
7614 int next_jy = player->jy + (player->jy - player->last_jy);
7616 if (x == next_jx && y == next_jy)
7624 static void StartMoving(int x, int y)
7626 boolean started_moving = FALSE; // some elements can fall _and_ move
7627 int element = Tile[x][y];
7632 if (MovDelay[x][y] == 0)
7633 GfxAction[x][y] = ACTION_DEFAULT;
7635 if (CAN_FALL(element) && y < lev_fieldy - 1)
7637 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7638 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7639 if (JustBeingPushed(x, y))
7642 if (element == EL_QUICKSAND_FULL)
7644 if (IS_FREE(x, y + 1))
7646 InitMovingField(x, y, MV_DOWN);
7647 started_moving = TRUE;
7649 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7650 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7651 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7652 Store[x][y] = EL_ROCK;
7654 Store[x][y] = EL_ROCK;
7657 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7659 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7661 if (!MovDelay[x][y])
7663 MovDelay[x][y] = TILEY + 1;
7665 ResetGfxAnimation(x, y);
7666 ResetGfxAnimation(x, y + 1);
7671 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7672 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7679 Tile[x][y] = EL_QUICKSAND_EMPTY;
7680 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7681 Store[x][y + 1] = Store[x][y];
7684 PlayLevelSoundAction(x, y, ACTION_FILLING);
7686 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7688 if (!MovDelay[x][y])
7690 MovDelay[x][y] = TILEY + 1;
7692 ResetGfxAnimation(x, y);
7693 ResetGfxAnimation(x, y + 1);
7698 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7699 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7706 Tile[x][y] = EL_QUICKSAND_EMPTY;
7707 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7708 Store[x][y + 1] = Store[x][y];
7711 PlayLevelSoundAction(x, y, ACTION_FILLING);
7714 else if (element == EL_QUICKSAND_FAST_FULL)
7716 if (IS_FREE(x, y + 1))
7718 InitMovingField(x, y, MV_DOWN);
7719 started_moving = TRUE;
7721 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7722 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7723 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7724 Store[x][y] = EL_ROCK;
7726 Store[x][y] = EL_ROCK;
7729 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7731 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7733 if (!MovDelay[x][y])
7735 MovDelay[x][y] = TILEY + 1;
7737 ResetGfxAnimation(x, y);
7738 ResetGfxAnimation(x, y + 1);
7743 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7744 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7751 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7752 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7753 Store[x][y + 1] = Store[x][y];
7756 PlayLevelSoundAction(x, y, ACTION_FILLING);
7758 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7760 if (!MovDelay[x][y])
7762 MovDelay[x][y] = TILEY + 1;
7764 ResetGfxAnimation(x, y);
7765 ResetGfxAnimation(x, y + 1);
7770 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7771 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7778 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7779 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7780 Store[x][y + 1] = Store[x][y];
7783 PlayLevelSoundAction(x, y, ACTION_FILLING);
7786 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7787 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7789 InitMovingField(x, y, MV_DOWN);
7790 started_moving = TRUE;
7792 Tile[x][y] = EL_QUICKSAND_FILLING;
7793 Store[x][y] = element;
7795 PlayLevelSoundAction(x, y, ACTION_FILLING);
7797 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7798 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7800 InitMovingField(x, y, MV_DOWN);
7801 started_moving = TRUE;
7803 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7804 Store[x][y] = element;
7806 PlayLevelSoundAction(x, y, ACTION_FILLING);
7808 else if (element == EL_MAGIC_WALL_FULL)
7810 if (IS_FREE(x, y + 1))
7812 InitMovingField(x, y, MV_DOWN);
7813 started_moving = TRUE;
7815 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7816 Store[x][y] = EL_CHANGED(Store[x][y]);
7818 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7820 if (!MovDelay[x][y])
7821 MovDelay[x][y] = TILEY / 4 + 1;
7830 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7831 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7832 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7836 else if (element == EL_BD_MAGIC_WALL_FULL)
7838 if (IS_FREE(x, y + 1))
7840 InitMovingField(x, y, MV_DOWN);
7841 started_moving = TRUE;
7843 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7844 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7846 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7848 if (!MovDelay[x][y])
7849 MovDelay[x][y] = TILEY / 4 + 1;
7858 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7859 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7860 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7864 else if (element == EL_DC_MAGIC_WALL_FULL)
7866 if (IS_FREE(x, y + 1))
7868 InitMovingField(x, y, MV_DOWN);
7869 started_moving = TRUE;
7871 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7872 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7874 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7876 if (!MovDelay[x][y])
7877 MovDelay[x][y] = TILEY / 4 + 1;
7886 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7887 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7888 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7892 else if ((CAN_PASS_MAGIC_WALL(element) &&
7893 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7894 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7895 (CAN_PASS_DC_MAGIC_WALL(element) &&
7896 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7899 InitMovingField(x, y, MV_DOWN);
7900 started_moving = TRUE;
7903 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7904 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7905 EL_DC_MAGIC_WALL_FILLING);
7906 Store[x][y] = element;
7908 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7910 SplashAcid(x, y + 1);
7912 InitMovingField(x, y, MV_DOWN);
7913 started_moving = TRUE;
7915 Store[x][y] = EL_ACID;
7918 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7919 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7920 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7921 CAN_FALL(element) && WasJustFalling[x][y] &&
7922 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7924 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7925 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7926 (Tile[x][y + 1] == EL_BLOCKED)))
7928 /* this is needed for a special case not covered by calling "Impact()"
7929 from "ContinueMoving()": if an element moves to a tile directly below
7930 another element which was just falling on that tile (which was empty
7931 in the previous frame), the falling element above would just stop
7932 instead of smashing the element below (in previous version, the above
7933 element was just checked for "moving" instead of "falling", resulting
7934 in incorrect smashes caused by horizontal movement of the above
7935 element; also, the case of the player being the element to smash was
7936 simply not covered here... :-/ ) */
7938 CheckCollision[x][y] = 0;
7939 CheckImpact[x][y] = 0;
7943 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7945 if (MovDir[x][y] == MV_NONE)
7947 InitMovingField(x, y, MV_DOWN);
7948 started_moving = TRUE;
7951 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7953 if (WasJustFalling[x][y]) // prevent animation from being restarted
7954 MovDir[x][y] = MV_DOWN;
7956 InitMovingField(x, y, MV_DOWN);
7957 started_moving = TRUE;
7959 else if (element == EL_AMOEBA_DROP)
7961 Tile[x][y] = EL_AMOEBA_GROWING;
7962 Store[x][y] = EL_AMOEBA_WET;
7964 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7965 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7966 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7967 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7969 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7970 (IS_FREE(x - 1, y + 1) ||
7971 Tile[x - 1][y + 1] == EL_ACID));
7972 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7973 (IS_FREE(x + 1, y + 1) ||
7974 Tile[x + 1][y + 1] == EL_ACID));
7975 boolean can_fall_any = (can_fall_left || can_fall_right);
7976 boolean can_fall_both = (can_fall_left && can_fall_right);
7977 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7979 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7981 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7982 can_fall_right = FALSE;
7983 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7984 can_fall_left = FALSE;
7985 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7986 can_fall_right = FALSE;
7987 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7988 can_fall_left = FALSE;
7990 can_fall_any = (can_fall_left || can_fall_right);
7991 can_fall_both = FALSE;
7996 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7997 can_fall_right = FALSE; // slip down on left side
7999 can_fall_left = !(can_fall_right = RND(2));
8001 can_fall_both = FALSE;
8006 // if not determined otherwise, prefer left side for slipping down
8007 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8008 started_moving = TRUE;
8011 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8013 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8014 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8015 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8016 int belt_dir = game.belt_dir[belt_nr];
8018 if ((belt_dir == MV_LEFT && left_is_free) ||
8019 (belt_dir == MV_RIGHT && right_is_free))
8021 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8023 InitMovingField(x, y, belt_dir);
8024 started_moving = TRUE;
8026 Pushed[x][y] = TRUE;
8027 Pushed[nextx][y] = TRUE;
8029 GfxAction[x][y] = ACTION_DEFAULT;
8033 MovDir[x][y] = 0; // if element was moving, stop it
8038 // not "else if" because of elements that can fall and move (EL_SPRING)
8039 if (CAN_MOVE(element) && !started_moving)
8041 int move_pattern = element_info[element].move_pattern;
8044 Moving2Blocked(x, y, &newx, &newy);
8046 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8049 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8050 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8052 WasJustMoving[x][y] = 0;
8053 CheckCollision[x][y] = 0;
8055 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8057 if (Tile[x][y] != element) // element has changed
8061 if (!MovDelay[x][y]) // start new movement phase
8063 // all objects that can change their move direction after each step
8064 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8066 if (element != EL_YAMYAM &&
8067 element != EL_DARK_YAMYAM &&
8068 element != EL_PACMAN &&
8069 !(move_pattern & MV_ANY_DIRECTION) &&
8070 move_pattern != MV_TURNING_LEFT &&
8071 move_pattern != MV_TURNING_RIGHT &&
8072 move_pattern != MV_TURNING_LEFT_RIGHT &&
8073 move_pattern != MV_TURNING_RIGHT_LEFT &&
8074 move_pattern != MV_TURNING_RANDOM)
8078 if (MovDelay[x][y] && (element == EL_BUG ||
8079 element == EL_SPACESHIP ||
8080 element == EL_SP_SNIKSNAK ||
8081 element == EL_SP_ELECTRON ||
8082 element == EL_MOLE))
8083 TEST_DrawLevelField(x, y);
8087 if (MovDelay[x][y]) // wait some time before next movement
8091 if (element == EL_ROBOT ||
8092 element == EL_YAMYAM ||
8093 element == EL_DARK_YAMYAM)
8095 DrawLevelElementAnimationIfNeeded(x, y, element);
8096 PlayLevelSoundAction(x, y, ACTION_WAITING);
8098 else if (element == EL_SP_ELECTRON)
8099 DrawLevelElementAnimationIfNeeded(x, y, element);
8100 else if (element == EL_DRAGON)
8103 int dir = MovDir[x][y];
8104 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8105 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8106 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8107 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8108 dir == MV_UP ? IMG_FLAMES_1_UP :
8109 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8110 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8112 GfxAction[x][y] = ACTION_ATTACKING;
8114 if (IS_PLAYER(x, y))
8115 DrawPlayerField(x, y);
8117 TEST_DrawLevelField(x, y);
8119 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8121 for (i = 1; i <= 3; i++)
8123 int xx = x + i * dx;
8124 int yy = y + i * dy;
8125 int sx = SCREENX(xx);
8126 int sy = SCREENY(yy);
8127 int flame_graphic = graphic + (i - 1);
8129 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8134 int flamed = MovingOrBlocked2Element(xx, yy);
8136 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8139 RemoveMovingField(xx, yy);
8141 ChangeDelay[xx][yy] = 0;
8143 Tile[xx][yy] = EL_FLAMES;
8145 if (IN_SCR_FIELD(sx, sy))
8147 TEST_DrawLevelFieldCrumbled(xx, yy);
8148 DrawGraphic(sx, sy, flame_graphic, frame);
8153 if (Tile[xx][yy] == EL_FLAMES)
8154 Tile[xx][yy] = EL_EMPTY;
8155 TEST_DrawLevelField(xx, yy);
8160 if (MovDelay[x][y]) // element still has to wait some time
8162 PlayLevelSoundAction(x, y, ACTION_WAITING);
8168 // now make next step
8170 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8172 if (DONT_COLLIDE_WITH(element) &&
8173 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8174 !PLAYER_ENEMY_PROTECTED(newx, newy))
8176 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8181 else if (CAN_MOVE_INTO_ACID(element) &&
8182 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8183 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8184 (MovDir[x][y] == MV_DOWN ||
8185 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8187 SplashAcid(newx, newy);
8188 Store[x][y] = EL_ACID;
8190 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8192 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8193 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8194 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8195 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8198 TEST_DrawLevelField(x, y);
8200 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8201 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8202 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8204 game.friends_still_needed--;
8205 if (!game.friends_still_needed &&
8207 game.all_players_gone)
8212 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8214 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8215 TEST_DrawLevelField(newx, newy);
8217 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8219 else if (!IS_FREE(newx, newy))
8221 GfxAction[x][y] = ACTION_WAITING;
8223 if (IS_PLAYER(x, y))
8224 DrawPlayerField(x, y);
8226 TEST_DrawLevelField(x, y);
8231 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8233 if (IS_FOOD_PIG(Tile[newx][newy]))
8235 if (IS_MOVING(newx, newy))
8236 RemoveMovingField(newx, newy);
8239 Tile[newx][newy] = EL_EMPTY;
8240 TEST_DrawLevelField(newx, newy);
8243 PlayLevelSound(x, y, SND_PIG_DIGGING);
8245 else if (!IS_FREE(newx, newy))
8247 if (IS_PLAYER(x, y))
8248 DrawPlayerField(x, y);
8250 TEST_DrawLevelField(x, y);
8255 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8257 if (Store[x][y] != EL_EMPTY)
8259 boolean can_clone = FALSE;
8262 // check if element to clone is still there
8263 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8265 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8273 // cannot clone or target field not free anymore -- do not clone
8274 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8275 Store[x][y] = EL_EMPTY;
8278 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8280 if (IS_MV_DIAGONAL(MovDir[x][y]))
8282 int diagonal_move_dir = MovDir[x][y];
8283 int stored = Store[x][y];
8284 int change_delay = 8;
8287 // android is moving diagonally
8289 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8291 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8292 GfxElement[x][y] = EL_EMC_ANDROID;
8293 GfxAction[x][y] = ACTION_SHRINKING;
8294 GfxDir[x][y] = diagonal_move_dir;
8295 ChangeDelay[x][y] = change_delay;
8297 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8300 DrawLevelGraphicAnimation(x, y, graphic);
8301 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8303 if (Tile[newx][newy] == EL_ACID)
8305 SplashAcid(newx, newy);
8310 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8312 Store[newx][newy] = EL_EMC_ANDROID;
8313 GfxElement[newx][newy] = EL_EMC_ANDROID;
8314 GfxAction[newx][newy] = ACTION_GROWING;
8315 GfxDir[newx][newy] = diagonal_move_dir;
8316 ChangeDelay[newx][newy] = change_delay;
8318 graphic = el_act_dir2img(GfxElement[newx][newy],
8319 GfxAction[newx][newy], GfxDir[newx][newy]);
8321 DrawLevelGraphicAnimation(newx, newy, graphic);
8322 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8328 Tile[newx][newy] = EL_EMPTY;
8329 TEST_DrawLevelField(newx, newy);
8331 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8334 else if (!IS_FREE(newx, newy))
8339 else if (IS_CUSTOM_ELEMENT(element) &&
8340 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8342 if (!DigFieldByCE(newx, newy, element))
8345 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8347 RunnerVisit[x][y] = FrameCounter;
8348 PlayerVisit[x][y] /= 8; // expire player visit path
8351 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8353 if (!IS_FREE(newx, newy))
8355 if (IS_PLAYER(x, y))
8356 DrawPlayerField(x, y);
8358 TEST_DrawLevelField(x, y);
8364 boolean wanna_flame = !RND(10);
8365 int dx = newx - x, dy = newy - y;
8366 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8367 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8368 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8369 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8370 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8371 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8374 IS_CLASSIC_ENEMY(element1) ||
8375 IS_CLASSIC_ENEMY(element2)) &&
8376 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8377 element1 != EL_FLAMES && element2 != EL_FLAMES)
8379 ResetGfxAnimation(x, y);
8380 GfxAction[x][y] = ACTION_ATTACKING;
8382 if (IS_PLAYER(x, y))
8383 DrawPlayerField(x, y);
8385 TEST_DrawLevelField(x, y);
8387 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8389 MovDelay[x][y] = 50;
8391 Tile[newx][newy] = EL_FLAMES;
8392 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8393 Tile[newx1][newy1] = EL_FLAMES;
8394 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8395 Tile[newx2][newy2] = EL_FLAMES;
8401 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8402 Tile[newx][newy] == EL_DIAMOND)
8404 if (IS_MOVING(newx, newy))
8405 RemoveMovingField(newx, newy);
8408 Tile[newx][newy] = EL_EMPTY;
8409 TEST_DrawLevelField(newx, newy);
8412 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8414 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8415 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8417 if (AmoebaNr[newx][newy])
8419 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8420 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8421 Tile[newx][newy] == EL_BD_AMOEBA)
8422 AmoebaCnt[AmoebaNr[newx][newy]]--;
8425 if (IS_MOVING(newx, newy))
8427 RemoveMovingField(newx, newy);
8431 Tile[newx][newy] = EL_EMPTY;
8432 TEST_DrawLevelField(newx, newy);
8435 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8437 else if ((element == EL_PACMAN || element == EL_MOLE)
8438 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8440 if (AmoebaNr[newx][newy])
8442 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8443 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8444 Tile[newx][newy] == EL_BD_AMOEBA)
8445 AmoebaCnt[AmoebaNr[newx][newy]]--;
8448 if (element == EL_MOLE)
8450 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8451 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8453 ResetGfxAnimation(x, y);
8454 GfxAction[x][y] = ACTION_DIGGING;
8455 TEST_DrawLevelField(x, y);
8457 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8459 return; // wait for shrinking amoeba
8461 else // element == EL_PACMAN
8463 Tile[newx][newy] = EL_EMPTY;
8464 TEST_DrawLevelField(newx, newy);
8465 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8468 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8469 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8470 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8472 // wait for shrinking amoeba to completely disappear
8475 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8477 // object was running against a wall
8481 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8482 DrawLevelElementAnimation(x, y, element);
8484 if (DONT_TOUCH(element))
8485 TestIfBadThingTouchesPlayer(x, y);
8490 InitMovingField(x, y, MovDir[x][y]);
8492 PlayLevelSoundAction(x, y, ACTION_MOVING);
8496 ContinueMoving(x, y);
8499 void ContinueMoving(int x, int y)
8501 int element = Tile[x][y];
8502 struct ElementInfo *ei = &element_info[element];
8503 int direction = MovDir[x][y];
8504 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8505 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8506 int newx = x + dx, newy = y + dy;
8507 int stored = Store[x][y];
8508 int stored_new = Store[newx][newy];
8509 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8510 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8511 boolean last_line = (newy == lev_fieldy - 1);
8512 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8514 if (pushed_by_player) // special case: moving object pushed by player
8516 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8518 else if (use_step_delay) // special case: moving object has step delay
8520 if (!MovDelay[x][y])
8521 MovPos[x][y] += getElementMoveStepsize(x, y);
8526 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8530 TEST_DrawLevelField(x, y);
8532 return; // element is still waiting
8535 else // normal case: generically moving object
8537 MovPos[x][y] += getElementMoveStepsize(x, y);
8540 if (ABS(MovPos[x][y]) < TILEX)
8542 TEST_DrawLevelField(x, y);
8544 return; // element is still moving
8547 // element reached destination field
8549 Tile[x][y] = EL_EMPTY;
8550 Tile[newx][newy] = element;
8551 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8553 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8555 element = Tile[newx][newy] = EL_ACID;
8557 else if (element == EL_MOLE)
8559 Tile[x][y] = EL_SAND;
8561 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8563 else if (element == EL_QUICKSAND_FILLING)
8565 element = Tile[newx][newy] = get_next_element(element);
8566 Store[newx][newy] = Store[x][y];
8568 else if (element == EL_QUICKSAND_EMPTYING)
8570 Tile[x][y] = get_next_element(element);
8571 element = Tile[newx][newy] = Store[x][y];
8573 else if (element == EL_QUICKSAND_FAST_FILLING)
8575 element = Tile[newx][newy] = get_next_element(element);
8576 Store[newx][newy] = Store[x][y];
8578 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8580 Tile[x][y] = get_next_element(element);
8581 element = Tile[newx][newy] = Store[x][y];
8583 else if (element == EL_MAGIC_WALL_FILLING)
8585 element = Tile[newx][newy] = get_next_element(element);
8586 if (!game.magic_wall_active)
8587 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8588 Store[newx][newy] = Store[x][y];
8590 else if (element == EL_MAGIC_WALL_EMPTYING)
8592 Tile[x][y] = get_next_element(element);
8593 if (!game.magic_wall_active)
8594 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8595 element = Tile[newx][newy] = Store[x][y];
8597 InitField(newx, newy, FALSE);
8599 else if (element == EL_BD_MAGIC_WALL_FILLING)
8601 element = Tile[newx][newy] = get_next_element(element);
8602 if (!game.magic_wall_active)
8603 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8604 Store[newx][newy] = Store[x][y];
8606 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8608 Tile[x][y] = get_next_element(element);
8609 if (!game.magic_wall_active)
8610 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8611 element = Tile[newx][newy] = Store[x][y];
8613 InitField(newx, newy, FALSE);
8615 else if (element == EL_DC_MAGIC_WALL_FILLING)
8617 element = Tile[newx][newy] = get_next_element(element);
8618 if (!game.magic_wall_active)
8619 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8620 Store[newx][newy] = Store[x][y];
8622 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8624 Tile[x][y] = get_next_element(element);
8625 if (!game.magic_wall_active)
8626 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8627 element = Tile[newx][newy] = Store[x][y];
8629 InitField(newx, newy, FALSE);
8631 else if (element == EL_AMOEBA_DROPPING)
8633 Tile[x][y] = get_next_element(element);
8634 element = Tile[newx][newy] = Store[x][y];
8636 else if (element == EL_SOKOBAN_OBJECT)
8639 Tile[x][y] = Back[x][y];
8641 if (Back[newx][newy])
8642 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8644 Back[x][y] = Back[newx][newy] = 0;
8647 Store[x][y] = EL_EMPTY;
8652 MovDelay[newx][newy] = 0;
8654 if (CAN_CHANGE_OR_HAS_ACTION(element))
8656 // copy element change control values to new field
8657 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8658 ChangePage[newx][newy] = ChangePage[x][y];
8659 ChangeCount[newx][newy] = ChangeCount[x][y];
8660 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8663 CustomValue[newx][newy] = CustomValue[x][y];
8665 ChangeDelay[x][y] = 0;
8666 ChangePage[x][y] = -1;
8667 ChangeCount[x][y] = 0;
8668 ChangeEvent[x][y] = -1;
8670 CustomValue[x][y] = 0;
8672 // copy animation control values to new field
8673 GfxFrame[newx][newy] = GfxFrame[x][y];
8674 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8675 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8676 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8678 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8680 // some elements can leave other elements behind after moving
8681 if (ei->move_leave_element != EL_EMPTY &&
8682 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8683 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8685 int move_leave_element = ei->move_leave_element;
8687 // this makes it possible to leave the removed element again
8688 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8689 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8691 Tile[x][y] = move_leave_element;
8693 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8694 MovDir[x][y] = direction;
8696 InitField(x, y, FALSE);
8698 if (GFX_CRUMBLED(Tile[x][y]))
8699 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8701 if (ELEM_IS_PLAYER(move_leave_element))
8702 RelocatePlayer(x, y, move_leave_element);
8705 // do this after checking for left-behind element
8706 ResetGfxAnimation(x, y); // reset animation values for old field
8708 if (!CAN_MOVE(element) ||
8709 (CAN_FALL(element) && direction == MV_DOWN &&
8710 (element == EL_SPRING ||
8711 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8712 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8713 GfxDir[x][y] = MovDir[newx][newy] = 0;
8715 TEST_DrawLevelField(x, y);
8716 TEST_DrawLevelField(newx, newy);
8718 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8720 // prevent pushed element from moving on in pushed direction
8721 if (pushed_by_player && CAN_MOVE(element) &&
8722 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8723 !(element_info[element].move_pattern & direction))
8724 TurnRound(newx, newy);
8726 // prevent elements on conveyor belt from moving on in last direction
8727 if (pushed_by_conveyor && CAN_FALL(element) &&
8728 direction & MV_HORIZONTAL)
8729 MovDir[newx][newy] = 0;
8731 if (!pushed_by_player)
8733 int nextx = newx + dx, nexty = newy + dy;
8734 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8736 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8738 if (CAN_FALL(element) && direction == MV_DOWN)
8739 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8741 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8742 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8744 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8745 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8748 if (DONT_TOUCH(element)) // object may be nasty to player or others
8750 TestIfBadThingTouchesPlayer(newx, newy);
8751 TestIfBadThingTouchesFriend(newx, newy);
8753 if (!IS_CUSTOM_ELEMENT(element))
8754 TestIfBadThingTouchesOtherBadThing(newx, newy);
8756 else if (element == EL_PENGUIN)
8757 TestIfFriendTouchesBadThing(newx, newy);
8759 if (DONT_GET_HIT_BY(element))
8761 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8764 // give the player one last chance (one more frame) to move away
8765 if (CAN_FALL(element) && direction == MV_DOWN &&
8766 (last_line || (!IS_FREE(x, newy + 1) &&
8767 (!IS_PLAYER(x, newy + 1) ||
8768 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8771 if (pushed_by_player && !game.use_change_when_pushing_bug)
8773 int push_side = MV_DIR_OPPOSITE(direction);
8774 struct PlayerInfo *player = PLAYERINFO(x, y);
8776 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8777 player->index_bit, push_side);
8778 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8779 player->index_bit, push_side);
8782 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8783 MovDelay[newx][newy] = 1;
8785 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8787 TestIfElementTouchesCustomElement(x, y); // empty or new element
8788 TestIfElementHitsCustomElement(newx, newy, direction);
8789 TestIfPlayerTouchesCustomElement(newx, newy);
8790 TestIfElementTouchesCustomElement(newx, newy);
8792 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8793 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8794 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8795 MV_DIR_OPPOSITE(direction));
8798 int AmoebaNeighbourNr(int ax, int ay)
8801 int element = Tile[ax][ay];
8803 static int xy[4][2] =
8811 for (i = 0; i < NUM_DIRECTIONS; i++)
8813 int x = ax + xy[i][0];
8814 int y = ay + xy[i][1];
8816 if (!IN_LEV_FIELD(x, y))
8819 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8820 group_nr = AmoebaNr[x][y];
8826 static void AmoebaMerge(int ax, int ay)
8828 int i, x, y, xx, yy;
8829 int new_group_nr = AmoebaNr[ax][ay];
8830 static int xy[4][2] =
8838 if (new_group_nr == 0)
8841 for (i = 0; i < NUM_DIRECTIONS; i++)
8846 if (!IN_LEV_FIELD(x, y))
8849 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8850 Tile[x][y] == EL_BD_AMOEBA ||
8851 Tile[x][y] == EL_AMOEBA_DEAD) &&
8852 AmoebaNr[x][y] != new_group_nr)
8854 int old_group_nr = AmoebaNr[x][y];
8856 if (old_group_nr == 0)
8859 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8860 AmoebaCnt[old_group_nr] = 0;
8861 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8862 AmoebaCnt2[old_group_nr] = 0;
8864 SCAN_PLAYFIELD(xx, yy)
8866 if (AmoebaNr[xx][yy] == old_group_nr)
8867 AmoebaNr[xx][yy] = new_group_nr;
8873 void AmoebaToDiamond(int ax, int ay)
8877 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8879 int group_nr = AmoebaNr[ax][ay];
8884 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8885 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8891 SCAN_PLAYFIELD(x, y)
8893 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8896 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8900 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8901 SND_AMOEBA_TURNING_TO_GEM :
8902 SND_AMOEBA_TURNING_TO_ROCK));
8907 static int xy[4][2] =
8915 for (i = 0; i < NUM_DIRECTIONS; i++)
8920 if (!IN_LEV_FIELD(x, y))
8923 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8925 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8926 SND_AMOEBA_TURNING_TO_GEM :
8927 SND_AMOEBA_TURNING_TO_ROCK));
8934 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8937 int group_nr = AmoebaNr[ax][ay];
8938 boolean done = FALSE;
8943 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8944 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8950 SCAN_PLAYFIELD(x, y)
8952 if (AmoebaNr[x][y] == group_nr &&
8953 (Tile[x][y] == EL_AMOEBA_DEAD ||
8954 Tile[x][y] == EL_BD_AMOEBA ||
8955 Tile[x][y] == EL_AMOEBA_GROWING))
8958 Tile[x][y] = new_element;
8959 InitField(x, y, FALSE);
8960 TEST_DrawLevelField(x, y);
8966 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8967 SND_BD_AMOEBA_TURNING_TO_ROCK :
8968 SND_BD_AMOEBA_TURNING_TO_GEM));
8971 static void AmoebaGrowing(int x, int y)
8973 static unsigned int sound_delay = 0;
8974 static unsigned int sound_delay_value = 0;
8976 if (!MovDelay[x][y]) // start new growing cycle
8980 if (DelayReached(&sound_delay, sound_delay_value))
8982 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8983 sound_delay_value = 30;
8987 if (MovDelay[x][y]) // wait some time before growing bigger
8990 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8992 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8993 6 - MovDelay[x][y]);
8995 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8998 if (!MovDelay[x][y])
9000 Tile[x][y] = Store[x][y];
9002 TEST_DrawLevelField(x, y);
9007 static void AmoebaShrinking(int x, int y)
9009 static unsigned int sound_delay = 0;
9010 static unsigned int sound_delay_value = 0;
9012 if (!MovDelay[x][y]) // start new shrinking cycle
9016 if (DelayReached(&sound_delay, sound_delay_value))
9017 sound_delay_value = 30;
9020 if (MovDelay[x][y]) // wait some time before shrinking
9023 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9025 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9026 6 - MovDelay[x][y]);
9028 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9031 if (!MovDelay[x][y])
9033 Tile[x][y] = EL_EMPTY;
9034 TEST_DrawLevelField(x, y);
9036 // don't let mole enter this field in this cycle;
9037 // (give priority to objects falling to this field from above)
9043 static void AmoebaReproduce(int ax, int ay)
9046 int element = Tile[ax][ay];
9047 int graphic = el2img(element);
9048 int newax = ax, neway = ay;
9049 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9050 static int xy[4][2] =
9058 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9060 Tile[ax][ay] = EL_AMOEBA_DEAD;
9061 TEST_DrawLevelField(ax, ay);
9065 if (IS_ANIMATED(graphic))
9066 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9068 if (!MovDelay[ax][ay]) // start making new amoeba field
9069 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9071 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9074 if (MovDelay[ax][ay])
9078 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9081 int x = ax + xy[start][0];
9082 int y = ay + xy[start][1];
9084 if (!IN_LEV_FIELD(x, y))
9087 if (IS_FREE(x, y) ||
9088 CAN_GROW_INTO(Tile[x][y]) ||
9089 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9090 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9096 if (newax == ax && neway == ay)
9099 else // normal or "filled" (BD style) amoeba
9102 boolean waiting_for_player = FALSE;
9104 for (i = 0; i < NUM_DIRECTIONS; i++)
9106 int j = (start + i) % 4;
9107 int x = ax + xy[j][0];
9108 int y = ay + xy[j][1];
9110 if (!IN_LEV_FIELD(x, y))
9113 if (IS_FREE(x, y) ||
9114 CAN_GROW_INTO(Tile[x][y]) ||
9115 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9116 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9122 else if (IS_PLAYER(x, y))
9123 waiting_for_player = TRUE;
9126 if (newax == ax && neway == ay) // amoeba cannot grow
9128 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9130 Tile[ax][ay] = EL_AMOEBA_DEAD;
9131 TEST_DrawLevelField(ax, ay);
9132 AmoebaCnt[AmoebaNr[ax][ay]]--;
9134 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9136 if (element == EL_AMOEBA_FULL)
9137 AmoebaToDiamond(ax, ay);
9138 else if (element == EL_BD_AMOEBA)
9139 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9144 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9146 // amoeba gets larger by growing in some direction
9148 int new_group_nr = AmoebaNr[ax][ay];
9151 if (new_group_nr == 0)
9153 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9155 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9161 AmoebaNr[newax][neway] = new_group_nr;
9162 AmoebaCnt[new_group_nr]++;
9163 AmoebaCnt2[new_group_nr]++;
9165 // if amoeba touches other amoeba(s) after growing, unify them
9166 AmoebaMerge(newax, neway);
9168 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9170 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9176 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9177 (neway == lev_fieldy - 1 && newax != ax))
9179 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9180 Store[newax][neway] = element;
9182 else if (neway == ay || element == EL_EMC_DRIPPER)
9184 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9186 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9190 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9191 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9192 Store[ax][ay] = EL_AMOEBA_DROP;
9193 ContinueMoving(ax, ay);
9197 TEST_DrawLevelField(newax, neway);
9200 static void Life(int ax, int ay)
9204 int element = Tile[ax][ay];
9205 int graphic = el2img(element);
9206 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9208 boolean changed = FALSE;
9210 if (IS_ANIMATED(graphic))
9211 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9216 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9217 MovDelay[ax][ay] = life_time;
9219 if (MovDelay[ax][ay]) // wait some time before next cycle
9222 if (MovDelay[ax][ay])
9226 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9228 int xx = ax+x1, yy = ay+y1;
9229 int old_element = Tile[xx][yy];
9230 int num_neighbours = 0;
9232 if (!IN_LEV_FIELD(xx, yy))
9235 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9237 int x = xx+x2, y = yy+y2;
9239 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9242 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9243 boolean is_neighbour = FALSE;
9245 if (level.use_life_bugs)
9247 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9248 (IS_FREE(x, y) && Stop[x][y]));
9251 (Last[x][y] == element || is_player_cell);
9257 boolean is_free = FALSE;
9259 if (level.use_life_bugs)
9260 is_free = (IS_FREE(xx, yy));
9262 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9264 if (xx == ax && yy == ay) // field in the middle
9266 if (num_neighbours < life_parameter[0] ||
9267 num_neighbours > life_parameter[1])
9269 Tile[xx][yy] = EL_EMPTY;
9270 if (Tile[xx][yy] != old_element)
9271 TEST_DrawLevelField(xx, yy);
9272 Stop[xx][yy] = TRUE;
9276 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9277 { // free border field
9278 if (num_neighbours >= life_parameter[2] &&
9279 num_neighbours <= life_parameter[3])
9281 Tile[xx][yy] = element;
9282 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9283 if (Tile[xx][yy] != old_element)
9284 TEST_DrawLevelField(xx, yy);
9285 Stop[xx][yy] = TRUE;
9292 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9293 SND_GAME_OF_LIFE_GROWING);
9296 static void InitRobotWheel(int x, int y)
9298 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9301 static void RunRobotWheel(int x, int y)
9303 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9306 static void StopRobotWheel(int x, int y)
9308 if (game.robot_wheel_x == x &&
9309 game.robot_wheel_y == y)
9311 game.robot_wheel_x = -1;
9312 game.robot_wheel_y = -1;
9313 game.robot_wheel_active = FALSE;
9317 static void InitTimegateWheel(int x, int y)
9319 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9322 static void RunTimegateWheel(int x, int y)
9324 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9327 static void InitMagicBallDelay(int x, int y)
9329 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9332 static void ActivateMagicBall(int bx, int by)
9336 if (level.ball_random)
9338 int pos_border = RND(8); // select one of the eight border elements
9339 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9340 int xx = pos_content % 3;
9341 int yy = pos_content / 3;
9346 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9347 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9351 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9353 int xx = x - bx + 1;
9354 int yy = y - by + 1;
9356 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9357 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9361 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9364 static void CheckExit(int x, int y)
9366 if (game.gems_still_needed > 0 ||
9367 game.sokoban_fields_still_needed > 0 ||
9368 game.sokoban_objects_still_needed > 0 ||
9369 game.lights_still_needed > 0)
9371 int element = Tile[x][y];
9372 int graphic = el2img(element);
9374 if (IS_ANIMATED(graphic))
9375 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9380 // do not re-open exit door closed after last player
9381 if (game.all_players_gone)
9384 Tile[x][y] = EL_EXIT_OPENING;
9386 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9389 static void CheckExitEM(int x, int y)
9391 if (game.gems_still_needed > 0 ||
9392 game.sokoban_fields_still_needed > 0 ||
9393 game.sokoban_objects_still_needed > 0 ||
9394 game.lights_still_needed > 0)
9396 int element = Tile[x][y];
9397 int graphic = el2img(element);
9399 if (IS_ANIMATED(graphic))
9400 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9405 // do not re-open exit door closed after last player
9406 if (game.all_players_gone)
9409 Tile[x][y] = EL_EM_EXIT_OPENING;
9411 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9414 static void CheckExitSteel(int x, int y)
9416 if (game.gems_still_needed > 0 ||
9417 game.sokoban_fields_still_needed > 0 ||
9418 game.sokoban_objects_still_needed > 0 ||
9419 game.lights_still_needed > 0)
9421 int element = Tile[x][y];
9422 int graphic = el2img(element);
9424 if (IS_ANIMATED(graphic))
9425 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9430 // do not re-open exit door closed after last player
9431 if (game.all_players_gone)
9434 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9436 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9439 static void CheckExitSteelEM(int x, int y)
9441 if (game.gems_still_needed > 0 ||
9442 game.sokoban_fields_still_needed > 0 ||
9443 game.sokoban_objects_still_needed > 0 ||
9444 game.lights_still_needed > 0)
9446 int element = Tile[x][y];
9447 int graphic = el2img(element);
9449 if (IS_ANIMATED(graphic))
9450 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9455 // do not re-open exit door closed after last player
9456 if (game.all_players_gone)
9459 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9461 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9464 static void CheckExitSP(int x, int y)
9466 if (game.gems_still_needed > 0)
9468 int element = Tile[x][y];
9469 int graphic = el2img(element);
9471 if (IS_ANIMATED(graphic))
9472 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9477 // do not re-open exit door closed after last player
9478 if (game.all_players_gone)
9481 Tile[x][y] = EL_SP_EXIT_OPENING;
9483 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9486 static void CloseAllOpenTimegates(void)
9490 SCAN_PLAYFIELD(x, y)
9492 int element = Tile[x][y];
9494 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9496 Tile[x][y] = EL_TIMEGATE_CLOSING;
9498 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9503 static void DrawTwinkleOnField(int x, int y)
9505 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9508 if (Tile[x][y] == EL_BD_DIAMOND)
9511 if (MovDelay[x][y] == 0) // next animation frame
9512 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9514 if (MovDelay[x][y] != 0) // wait some time before next frame
9518 DrawLevelElementAnimation(x, y, Tile[x][y]);
9520 if (MovDelay[x][y] != 0)
9522 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9523 10 - MovDelay[x][y]);
9525 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9530 static void MauerWaechst(int x, int y)
9534 if (!MovDelay[x][y]) // next animation frame
9535 MovDelay[x][y] = 3 * delay;
9537 if (MovDelay[x][y]) // wait some time before next frame
9541 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9543 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9544 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9546 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9549 if (!MovDelay[x][y])
9551 if (MovDir[x][y] == MV_LEFT)
9553 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9554 TEST_DrawLevelField(x - 1, y);
9556 else if (MovDir[x][y] == MV_RIGHT)
9558 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9559 TEST_DrawLevelField(x + 1, y);
9561 else if (MovDir[x][y] == MV_UP)
9563 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9564 TEST_DrawLevelField(x, y - 1);
9568 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9569 TEST_DrawLevelField(x, y + 1);
9572 Tile[x][y] = Store[x][y];
9574 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9575 TEST_DrawLevelField(x, y);
9580 static void MauerAbleger(int ax, int ay)
9582 int element = Tile[ax][ay];
9583 int graphic = el2img(element);
9584 boolean oben_frei = FALSE, unten_frei = FALSE;
9585 boolean links_frei = FALSE, rechts_frei = FALSE;
9586 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9587 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9588 boolean new_wall = FALSE;
9590 if (IS_ANIMATED(graphic))
9591 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9593 if (!MovDelay[ax][ay]) // start building new wall
9594 MovDelay[ax][ay] = 6;
9596 if (MovDelay[ax][ay]) // wait some time before building new wall
9599 if (MovDelay[ax][ay])
9603 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9605 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9607 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9609 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9612 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9613 element == EL_EXPANDABLE_WALL_ANY)
9617 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9618 Store[ax][ay-1] = element;
9619 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9620 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9621 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9622 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9627 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9628 Store[ax][ay+1] = element;
9629 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9630 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9631 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9632 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9637 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9638 element == EL_EXPANDABLE_WALL_ANY ||
9639 element == EL_EXPANDABLE_WALL ||
9640 element == EL_BD_EXPANDABLE_WALL)
9644 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9645 Store[ax-1][ay] = element;
9646 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9647 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9648 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9649 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9655 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9656 Store[ax+1][ay] = element;
9657 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9658 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9659 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9660 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9665 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9666 TEST_DrawLevelField(ax, ay);
9668 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9670 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9671 unten_massiv = TRUE;
9672 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9673 links_massiv = TRUE;
9674 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9675 rechts_massiv = TRUE;
9677 if (((oben_massiv && unten_massiv) ||
9678 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9679 element == EL_EXPANDABLE_WALL) &&
9680 ((links_massiv && rechts_massiv) ||
9681 element == EL_EXPANDABLE_WALL_VERTICAL))
9682 Tile[ax][ay] = EL_WALL;
9685 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9688 static void MauerAblegerStahl(int ax, int ay)
9690 int element = Tile[ax][ay];
9691 int graphic = el2img(element);
9692 boolean oben_frei = FALSE, unten_frei = FALSE;
9693 boolean links_frei = FALSE, rechts_frei = FALSE;
9694 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9695 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9696 boolean new_wall = FALSE;
9698 if (IS_ANIMATED(graphic))
9699 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9701 if (!MovDelay[ax][ay]) // start building new wall
9702 MovDelay[ax][ay] = 6;
9704 if (MovDelay[ax][ay]) // wait some time before building new wall
9707 if (MovDelay[ax][ay])
9711 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9713 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9715 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9717 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9720 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9721 element == EL_EXPANDABLE_STEELWALL_ANY)
9725 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9726 Store[ax][ay-1] = element;
9727 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9728 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9729 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9730 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9735 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9736 Store[ax][ay+1] = element;
9737 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9738 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9739 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9740 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9745 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9746 element == EL_EXPANDABLE_STEELWALL_ANY)
9750 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9751 Store[ax-1][ay] = element;
9752 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9753 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9754 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9755 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9761 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9762 Store[ax+1][ay] = element;
9763 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9764 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9765 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9766 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9771 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9773 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9774 unten_massiv = TRUE;
9775 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9776 links_massiv = TRUE;
9777 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9778 rechts_massiv = TRUE;
9780 if (((oben_massiv && unten_massiv) ||
9781 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9782 ((links_massiv && rechts_massiv) ||
9783 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9784 Tile[ax][ay] = EL_STEELWALL;
9787 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9790 static void CheckForDragon(int x, int y)
9793 boolean dragon_found = FALSE;
9794 static int xy[4][2] =
9802 for (i = 0; i < NUM_DIRECTIONS; i++)
9804 for (j = 0; j < 4; j++)
9806 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9808 if (IN_LEV_FIELD(xx, yy) &&
9809 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9811 if (Tile[xx][yy] == EL_DRAGON)
9812 dragon_found = TRUE;
9821 for (i = 0; i < NUM_DIRECTIONS; i++)
9823 for (j = 0; j < 3; j++)
9825 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9827 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9829 Tile[xx][yy] = EL_EMPTY;
9830 TEST_DrawLevelField(xx, yy);
9839 static void InitBuggyBase(int x, int y)
9841 int element = Tile[x][y];
9842 int activating_delay = FRAMES_PER_SECOND / 4;
9845 (element == EL_SP_BUGGY_BASE ?
9846 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9847 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9849 element == EL_SP_BUGGY_BASE_ACTIVE ?
9850 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9853 static void WarnBuggyBase(int x, int y)
9856 static int xy[4][2] =
9864 for (i = 0; i < NUM_DIRECTIONS; i++)
9866 int xx = x + xy[i][0];
9867 int yy = y + xy[i][1];
9869 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9871 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9878 static void InitTrap(int x, int y)
9880 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9883 static void ActivateTrap(int x, int y)
9885 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9888 static void ChangeActiveTrap(int x, int y)
9890 int graphic = IMG_TRAP_ACTIVE;
9892 // if new animation frame was drawn, correct crumbled sand border
9893 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9894 TEST_DrawLevelFieldCrumbled(x, y);
9897 static int getSpecialActionElement(int element, int number, int base_element)
9899 return (element != EL_EMPTY ? element :
9900 number != -1 ? base_element + number - 1 :
9904 static int getModifiedActionNumber(int value_old, int operator, int operand,
9905 int value_min, int value_max)
9907 int value_new = (operator == CA_MODE_SET ? operand :
9908 operator == CA_MODE_ADD ? value_old + operand :
9909 operator == CA_MODE_SUBTRACT ? value_old - operand :
9910 operator == CA_MODE_MULTIPLY ? value_old * operand :
9911 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9912 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9915 return (value_new < value_min ? value_min :
9916 value_new > value_max ? value_max :
9920 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9922 struct ElementInfo *ei = &element_info[element];
9923 struct ElementChangeInfo *change = &ei->change_page[page];
9924 int target_element = change->target_element;
9925 int action_type = change->action_type;
9926 int action_mode = change->action_mode;
9927 int action_arg = change->action_arg;
9928 int action_element = change->action_element;
9931 if (!change->has_action)
9934 // ---------- determine action paramater values -----------------------------
9936 int level_time_value =
9937 (level.time > 0 ? TimeLeft :
9940 int action_arg_element_raw =
9941 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9942 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9943 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9944 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9945 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9946 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9947 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9949 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9951 int action_arg_direction =
9952 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9953 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9954 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9955 change->actual_trigger_side :
9956 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9957 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9960 int action_arg_number_min =
9961 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9964 int action_arg_number_max =
9965 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9966 action_type == CA_SET_LEVEL_GEMS ? 999 :
9967 action_type == CA_SET_LEVEL_TIME ? 9999 :
9968 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9969 action_type == CA_SET_CE_VALUE ? 9999 :
9970 action_type == CA_SET_CE_SCORE ? 9999 :
9973 int action_arg_number_reset =
9974 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9975 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9976 action_type == CA_SET_LEVEL_TIME ? level.time :
9977 action_type == CA_SET_LEVEL_SCORE ? 0 :
9978 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9979 action_type == CA_SET_CE_SCORE ? 0 :
9982 int action_arg_number =
9983 (action_arg <= CA_ARG_MAX ? action_arg :
9984 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9985 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9986 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9987 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9988 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9989 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9990 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9991 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9992 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9993 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9994 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9995 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9996 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9997 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9998 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9999 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10000 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10001 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10002 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10003 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10004 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10007 int action_arg_number_old =
10008 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10009 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10010 action_type == CA_SET_LEVEL_SCORE ? game.score :
10011 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10012 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10015 int action_arg_number_new =
10016 getModifiedActionNumber(action_arg_number_old,
10017 action_mode, action_arg_number,
10018 action_arg_number_min, action_arg_number_max);
10020 int trigger_player_bits =
10021 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10022 change->actual_trigger_player_bits : change->trigger_player);
10024 int action_arg_player_bits =
10025 (action_arg >= CA_ARG_PLAYER_1 &&
10026 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10027 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10028 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10031 // ---------- execute action -----------------------------------------------
10033 switch (action_type)
10040 // ---------- level actions ----------------------------------------------
10042 case CA_RESTART_LEVEL:
10044 game.restart_level = TRUE;
10049 case CA_SHOW_ENVELOPE:
10051 int element = getSpecialActionElement(action_arg_element,
10052 action_arg_number, EL_ENVELOPE_1);
10054 if (IS_ENVELOPE(element))
10055 local_player->show_envelope = element;
10060 case CA_SET_LEVEL_TIME:
10062 if (level.time > 0) // only modify limited time value
10064 TimeLeft = action_arg_number_new;
10066 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10068 DisplayGameControlValues();
10070 if (!TimeLeft && setup.time_limit)
10071 for (i = 0; i < MAX_PLAYERS; i++)
10072 KillPlayer(&stored_player[i]);
10078 case CA_SET_LEVEL_SCORE:
10080 game.score = action_arg_number_new;
10082 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10084 DisplayGameControlValues();
10089 case CA_SET_LEVEL_GEMS:
10091 game.gems_still_needed = action_arg_number_new;
10093 game.snapshot.collected_item = TRUE;
10095 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10097 DisplayGameControlValues();
10102 case CA_SET_LEVEL_WIND:
10104 game.wind_direction = action_arg_direction;
10109 case CA_SET_LEVEL_RANDOM_SEED:
10111 // ensure that setting a new random seed while playing is predictable
10112 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10117 // ---------- player actions ---------------------------------------------
10119 case CA_MOVE_PLAYER:
10120 case CA_MOVE_PLAYER_NEW:
10122 // automatically move to the next field in specified direction
10123 for (i = 0; i < MAX_PLAYERS; i++)
10124 if (trigger_player_bits & (1 << i))
10125 if (action_type == CA_MOVE_PLAYER ||
10126 stored_player[i].MovPos == 0)
10127 stored_player[i].programmed_action = action_arg_direction;
10132 case CA_EXIT_PLAYER:
10134 for (i = 0; i < MAX_PLAYERS; i++)
10135 if (action_arg_player_bits & (1 << i))
10136 ExitPlayer(&stored_player[i]);
10138 if (game.players_still_needed == 0)
10144 case CA_KILL_PLAYER:
10146 for (i = 0; i < MAX_PLAYERS; i++)
10147 if (action_arg_player_bits & (1 << i))
10148 KillPlayer(&stored_player[i]);
10153 case CA_SET_PLAYER_KEYS:
10155 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10156 int element = getSpecialActionElement(action_arg_element,
10157 action_arg_number, EL_KEY_1);
10159 if (IS_KEY(element))
10161 for (i = 0; i < MAX_PLAYERS; i++)
10163 if (trigger_player_bits & (1 << i))
10165 stored_player[i].key[KEY_NR(element)] = key_state;
10167 DrawGameDoorValues();
10175 case CA_SET_PLAYER_SPEED:
10177 for (i = 0; i < MAX_PLAYERS; i++)
10179 if (trigger_player_bits & (1 << i))
10181 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10183 if (action_arg == CA_ARG_SPEED_FASTER &&
10184 stored_player[i].cannot_move)
10186 action_arg_number = STEPSIZE_VERY_SLOW;
10188 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10189 action_arg == CA_ARG_SPEED_FASTER)
10191 action_arg_number = 2;
10192 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10195 else if (action_arg == CA_ARG_NUMBER_RESET)
10197 action_arg_number = level.initial_player_stepsize[i];
10201 getModifiedActionNumber(move_stepsize,
10204 action_arg_number_min,
10205 action_arg_number_max);
10207 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10214 case CA_SET_PLAYER_SHIELD:
10216 for (i = 0; i < MAX_PLAYERS; i++)
10218 if (trigger_player_bits & (1 << i))
10220 if (action_arg == CA_ARG_SHIELD_OFF)
10222 stored_player[i].shield_normal_time_left = 0;
10223 stored_player[i].shield_deadly_time_left = 0;
10225 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10227 stored_player[i].shield_normal_time_left = 999999;
10229 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10231 stored_player[i].shield_normal_time_left = 999999;
10232 stored_player[i].shield_deadly_time_left = 999999;
10240 case CA_SET_PLAYER_GRAVITY:
10242 for (i = 0; i < MAX_PLAYERS; i++)
10244 if (trigger_player_bits & (1 << i))
10246 stored_player[i].gravity =
10247 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10248 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10249 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10250 stored_player[i].gravity);
10257 case CA_SET_PLAYER_ARTWORK:
10259 for (i = 0; i < MAX_PLAYERS; i++)
10261 if (trigger_player_bits & (1 << i))
10263 int artwork_element = action_arg_element;
10265 if (action_arg == CA_ARG_ELEMENT_RESET)
10267 (level.use_artwork_element[i] ? level.artwork_element[i] :
10268 stored_player[i].element_nr);
10270 if (stored_player[i].artwork_element != artwork_element)
10271 stored_player[i].Frame = 0;
10273 stored_player[i].artwork_element = artwork_element;
10275 SetPlayerWaiting(&stored_player[i], FALSE);
10277 // set number of special actions for bored and sleeping animation
10278 stored_player[i].num_special_action_bored =
10279 get_num_special_action(artwork_element,
10280 ACTION_BORING_1, ACTION_BORING_LAST);
10281 stored_player[i].num_special_action_sleeping =
10282 get_num_special_action(artwork_element,
10283 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10290 case CA_SET_PLAYER_INVENTORY:
10292 for (i = 0; i < MAX_PLAYERS; i++)
10294 struct PlayerInfo *player = &stored_player[i];
10297 if (trigger_player_bits & (1 << i))
10299 int inventory_element = action_arg_element;
10301 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10302 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10303 action_arg == CA_ARG_ELEMENT_ACTION)
10305 int element = inventory_element;
10306 int collect_count = element_info[element].collect_count_initial;
10308 if (!IS_CUSTOM_ELEMENT(element))
10311 if (collect_count == 0)
10312 player->inventory_infinite_element = element;
10314 for (k = 0; k < collect_count; k++)
10315 if (player->inventory_size < MAX_INVENTORY_SIZE)
10316 player->inventory_element[player->inventory_size++] =
10319 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10320 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10321 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10323 if (player->inventory_infinite_element != EL_UNDEFINED &&
10324 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10325 action_arg_element_raw))
10326 player->inventory_infinite_element = EL_UNDEFINED;
10328 for (k = 0, j = 0; j < player->inventory_size; j++)
10330 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10331 action_arg_element_raw))
10332 player->inventory_element[k++] = player->inventory_element[j];
10335 player->inventory_size = k;
10337 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10339 if (player->inventory_size > 0)
10341 for (j = 0; j < player->inventory_size - 1; j++)
10342 player->inventory_element[j] = player->inventory_element[j + 1];
10344 player->inventory_size--;
10347 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10349 if (player->inventory_size > 0)
10350 player->inventory_size--;
10352 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10354 player->inventory_infinite_element = EL_UNDEFINED;
10355 player->inventory_size = 0;
10357 else if (action_arg == CA_ARG_INVENTORY_RESET)
10359 player->inventory_infinite_element = EL_UNDEFINED;
10360 player->inventory_size = 0;
10362 if (level.use_initial_inventory[i])
10364 for (j = 0; j < level.initial_inventory_size[i]; j++)
10366 int element = level.initial_inventory_content[i][j];
10367 int collect_count = element_info[element].collect_count_initial;
10369 if (!IS_CUSTOM_ELEMENT(element))
10372 if (collect_count == 0)
10373 player->inventory_infinite_element = element;
10375 for (k = 0; k < collect_count; k++)
10376 if (player->inventory_size < MAX_INVENTORY_SIZE)
10377 player->inventory_element[player->inventory_size++] =
10388 // ---------- CE actions -------------------------------------------------
10390 case CA_SET_CE_VALUE:
10392 int last_ce_value = CustomValue[x][y];
10394 CustomValue[x][y] = action_arg_number_new;
10396 if (CustomValue[x][y] != last_ce_value)
10398 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10399 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10401 if (CustomValue[x][y] == 0)
10403 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10404 ChangeCount[x][y] = 0; // allow at least one more change
10406 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10407 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10414 case CA_SET_CE_SCORE:
10416 int last_ce_score = ei->collect_score;
10418 ei->collect_score = action_arg_number_new;
10420 if (ei->collect_score != last_ce_score)
10422 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10423 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10425 if (ei->collect_score == 0)
10429 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10430 ChangeCount[x][y] = 0; // allow at least one more change
10432 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10433 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10436 This is a very special case that seems to be a mixture between
10437 CheckElementChange() and CheckTriggeredElementChange(): while
10438 the first one only affects single elements that are triggered
10439 directly, the second one affects multiple elements in the playfield
10440 that are triggered indirectly by another element. This is a third
10441 case: Changing the CE score always affects multiple identical CEs,
10442 so every affected CE must be checked, not only the single CE for
10443 which the CE score was changed in the first place (as every instance
10444 of that CE shares the same CE score, and therefore also can change)!
10446 SCAN_PLAYFIELD(xx, yy)
10448 if (Tile[xx][yy] == element)
10449 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10450 CE_SCORE_GETS_ZERO);
10458 case CA_SET_CE_ARTWORK:
10460 int artwork_element = action_arg_element;
10461 boolean reset_frame = FALSE;
10464 if (action_arg == CA_ARG_ELEMENT_RESET)
10465 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10468 if (ei->gfx_element != artwork_element)
10469 reset_frame = TRUE;
10471 ei->gfx_element = artwork_element;
10473 SCAN_PLAYFIELD(xx, yy)
10475 if (Tile[xx][yy] == element)
10479 ResetGfxAnimation(xx, yy);
10480 ResetRandomAnimationValue(xx, yy);
10483 TEST_DrawLevelField(xx, yy);
10490 // ---------- engine actions ---------------------------------------------
10492 case CA_SET_ENGINE_SCAN_MODE:
10494 InitPlayfieldScanMode(action_arg);
10504 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10506 int old_element = Tile[x][y];
10507 int new_element = GetElementFromGroupElement(element);
10508 int previous_move_direction = MovDir[x][y];
10509 int last_ce_value = CustomValue[x][y];
10510 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10511 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10512 boolean add_player_onto_element = (new_element_is_player &&
10513 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10514 IS_WALKABLE(old_element));
10516 if (!add_player_onto_element)
10518 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10519 RemoveMovingField(x, y);
10523 Tile[x][y] = new_element;
10525 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10526 MovDir[x][y] = previous_move_direction;
10528 if (element_info[new_element].use_last_ce_value)
10529 CustomValue[x][y] = last_ce_value;
10531 InitField_WithBug1(x, y, FALSE);
10533 new_element = Tile[x][y]; // element may have changed
10535 ResetGfxAnimation(x, y);
10536 ResetRandomAnimationValue(x, y);
10538 TEST_DrawLevelField(x, y);
10540 if (GFX_CRUMBLED(new_element))
10541 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10544 // check if element under the player changes from accessible to unaccessible
10545 // (needed for special case of dropping element which then changes)
10546 // (must be checked after creating new element for walkable group elements)
10547 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10548 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10555 // "ChangeCount" not set yet to allow "entered by player" change one time
10556 if (new_element_is_player)
10557 RelocatePlayer(x, y, new_element);
10560 ChangeCount[x][y]++; // count number of changes in the same frame
10562 TestIfBadThingTouchesPlayer(x, y);
10563 TestIfPlayerTouchesCustomElement(x, y);
10564 TestIfElementTouchesCustomElement(x, y);
10567 static void CreateField(int x, int y, int element)
10569 CreateFieldExt(x, y, element, FALSE);
10572 static void CreateElementFromChange(int x, int y, int element)
10574 element = GET_VALID_RUNTIME_ELEMENT(element);
10576 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10578 int old_element = Tile[x][y];
10580 // prevent changed element from moving in same engine frame
10581 // unless both old and new element can either fall or move
10582 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10583 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10587 CreateFieldExt(x, y, element, TRUE);
10590 static boolean ChangeElement(int x, int y, int element, int page)
10592 struct ElementInfo *ei = &element_info[element];
10593 struct ElementChangeInfo *change = &ei->change_page[page];
10594 int ce_value = CustomValue[x][y];
10595 int ce_score = ei->collect_score;
10596 int target_element;
10597 int old_element = Tile[x][y];
10599 // always use default change event to prevent running into a loop
10600 if (ChangeEvent[x][y] == -1)
10601 ChangeEvent[x][y] = CE_DELAY;
10603 if (ChangeEvent[x][y] == CE_DELAY)
10605 // reset actual trigger element, trigger player and action element
10606 change->actual_trigger_element = EL_EMPTY;
10607 change->actual_trigger_player = EL_EMPTY;
10608 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10609 change->actual_trigger_side = CH_SIDE_NONE;
10610 change->actual_trigger_ce_value = 0;
10611 change->actual_trigger_ce_score = 0;
10614 // do not change elements more than a specified maximum number of changes
10615 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10618 ChangeCount[x][y]++; // count number of changes in the same frame
10620 if (change->explode)
10627 if (change->use_target_content)
10629 boolean complete_replace = TRUE;
10630 boolean can_replace[3][3];
10633 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10636 boolean is_walkable;
10637 boolean is_diggable;
10638 boolean is_collectible;
10639 boolean is_removable;
10640 boolean is_destructible;
10641 int ex = x + xx - 1;
10642 int ey = y + yy - 1;
10643 int content_element = change->target_content.e[xx][yy];
10646 can_replace[xx][yy] = TRUE;
10648 if (ex == x && ey == y) // do not check changing element itself
10651 if (content_element == EL_EMPTY_SPACE)
10653 can_replace[xx][yy] = FALSE; // do not replace border with space
10658 if (!IN_LEV_FIELD(ex, ey))
10660 can_replace[xx][yy] = FALSE;
10661 complete_replace = FALSE;
10668 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10669 e = MovingOrBlocked2Element(ex, ey);
10671 is_empty = (IS_FREE(ex, ey) ||
10672 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10674 is_walkable = (is_empty || IS_WALKABLE(e));
10675 is_diggable = (is_empty || IS_DIGGABLE(e));
10676 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10677 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10678 is_removable = (is_diggable || is_collectible);
10680 can_replace[xx][yy] =
10681 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10682 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10683 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10684 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10685 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10686 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10687 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10689 if (!can_replace[xx][yy])
10690 complete_replace = FALSE;
10693 if (!change->only_if_complete || complete_replace)
10695 boolean something_has_changed = FALSE;
10697 if (change->only_if_complete && change->use_random_replace &&
10698 RND(100) < change->random_percentage)
10701 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10703 int ex = x + xx - 1;
10704 int ey = y + yy - 1;
10705 int content_element;
10707 if (can_replace[xx][yy] && (!change->use_random_replace ||
10708 RND(100) < change->random_percentage))
10710 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10711 RemoveMovingField(ex, ey);
10713 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10715 content_element = change->target_content.e[xx][yy];
10716 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10717 ce_value, ce_score);
10719 CreateElementFromChange(ex, ey, target_element);
10721 something_has_changed = TRUE;
10723 // for symmetry reasons, freeze newly created border elements
10724 if (ex != x || ey != y)
10725 Stop[ex][ey] = TRUE; // no more moving in this frame
10729 if (something_has_changed)
10731 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10732 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10738 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10739 ce_value, ce_score);
10741 if (element == EL_DIAGONAL_GROWING ||
10742 element == EL_DIAGONAL_SHRINKING)
10744 target_element = Store[x][y];
10746 Store[x][y] = EL_EMPTY;
10749 CreateElementFromChange(x, y, target_element);
10751 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10752 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10755 // this uses direct change before indirect change
10756 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10761 static void HandleElementChange(int x, int y, int page)
10763 int element = MovingOrBlocked2Element(x, y);
10764 struct ElementInfo *ei = &element_info[element];
10765 struct ElementChangeInfo *change = &ei->change_page[page];
10766 boolean handle_action_before_change = FALSE;
10769 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10770 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10772 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10773 x, y, element, element_info[element].token_name);
10774 Debug("game:playing:HandleElementChange", "This should never happen!");
10778 // this can happen with classic bombs on walkable, changing elements
10779 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10784 if (ChangeDelay[x][y] == 0) // initialize element change
10786 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10788 if (change->can_change)
10790 // !!! not clear why graphic animation should be reset at all here !!!
10791 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10792 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10795 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10797 When using an animation frame delay of 1 (this only happens with
10798 "sp_zonk.moving.left/right" in the classic graphics), the default
10799 (non-moving) animation shows wrong animation frames (while the
10800 moving animation, like "sp_zonk.moving.left/right", is correct,
10801 so this graphical bug never shows up with the classic graphics).
10802 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10803 be drawn instead of the correct frames 0,1,2,3. This is caused by
10804 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10805 an element change: First when the change delay ("ChangeDelay[][]")
10806 counter has reached zero after decrementing, then a second time in
10807 the next frame (after "GfxFrame[][]" was already incremented) when
10808 "ChangeDelay[][]" is reset to the initial delay value again.
10810 This causes frame 0 to be drawn twice, while the last frame won't
10811 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10813 As some animations may already be cleverly designed around this bug
10814 (at least the "Snake Bite" snake tail animation does this), it cannot
10815 simply be fixed here without breaking such existing animations.
10816 Unfortunately, it cannot easily be detected if a graphics set was
10817 designed "before" or "after" the bug was fixed. As a workaround,
10818 a new graphics set option "game.graphics_engine_version" was added
10819 to be able to specify the game's major release version for which the
10820 graphics set was designed, which can then be used to decide if the
10821 bugfix should be used (version 4 and above) or not (version 3 or
10822 below, or if no version was specified at all, as with old sets).
10824 (The wrong/fixed animation frames can be tested with the test level set
10825 "test_gfxframe" and level "000", which contains a specially prepared
10826 custom element at level position (x/y) == (11/9) which uses the zonk
10827 animation mentioned above. Using "game.graphics_engine_version: 4"
10828 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10829 This can also be seen from the debug output for this test element.)
10832 // when a custom element is about to change (for example by change delay),
10833 // do not reset graphic animation when the custom element is moving
10834 if (game.graphics_engine_version < 4 &&
10837 ResetGfxAnimation(x, y);
10838 ResetRandomAnimationValue(x, y);
10841 if (change->pre_change_function)
10842 change->pre_change_function(x, y);
10846 ChangeDelay[x][y]--;
10848 if (ChangeDelay[x][y] != 0) // continue element change
10850 if (change->can_change)
10852 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10854 if (IS_ANIMATED(graphic))
10855 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10857 if (change->change_function)
10858 change->change_function(x, y);
10861 else // finish element change
10863 if (ChangePage[x][y] != -1) // remember page from delayed change
10865 page = ChangePage[x][y];
10866 ChangePage[x][y] = -1;
10868 change = &ei->change_page[page];
10871 if (IS_MOVING(x, y)) // never change a running system ;-)
10873 ChangeDelay[x][y] = 1; // try change after next move step
10874 ChangePage[x][y] = page; // remember page to use for change
10879 // special case: set new level random seed before changing element
10880 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10881 handle_action_before_change = TRUE;
10883 if (change->has_action && handle_action_before_change)
10884 ExecuteCustomElementAction(x, y, element, page);
10886 if (change->can_change)
10888 if (ChangeElement(x, y, element, page))
10890 if (change->post_change_function)
10891 change->post_change_function(x, y);
10895 if (change->has_action && !handle_action_before_change)
10896 ExecuteCustomElementAction(x, y, element, page);
10900 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10901 int trigger_element,
10903 int trigger_player,
10907 boolean change_done_any = FALSE;
10908 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10911 if (!(trigger_events[trigger_element][trigger_event]))
10914 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10916 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10918 int element = EL_CUSTOM_START + i;
10919 boolean change_done = FALSE;
10922 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10923 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10926 for (p = 0; p < element_info[element].num_change_pages; p++)
10928 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10930 if (change->can_change_or_has_action &&
10931 change->has_event[trigger_event] &&
10932 change->trigger_side & trigger_side &&
10933 change->trigger_player & trigger_player &&
10934 change->trigger_page & trigger_page_bits &&
10935 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10937 change->actual_trigger_element = trigger_element;
10938 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10939 change->actual_trigger_player_bits = trigger_player;
10940 change->actual_trigger_side = trigger_side;
10941 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10942 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10944 if ((change->can_change && !change_done) || change->has_action)
10948 SCAN_PLAYFIELD(x, y)
10950 if (Tile[x][y] == element)
10952 if (change->can_change && !change_done)
10954 // if element already changed in this frame, not only prevent
10955 // another element change (checked in ChangeElement()), but
10956 // also prevent additional element actions for this element
10958 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10959 !level.use_action_after_change_bug)
10962 ChangeDelay[x][y] = 1;
10963 ChangeEvent[x][y] = trigger_event;
10965 HandleElementChange(x, y, p);
10967 else if (change->has_action)
10969 // if element already changed in this frame, not only prevent
10970 // another element change (checked in ChangeElement()), but
10971 // also prevent additional element actions for this element
10973 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10974 !level.use_action_after_change_bug)
10977 ExecuteCustomElementAction(x, y, element, p);
10978 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10983 if (change->can_change)
10985 change_done = TRUE;
10986 change_done_any = TRUE;
10993 RECURSION_LOOP_DETECTION_END();
10995 return change_done_any;
10998 static boolean CheckElementChangeExt(int x, int y,
11000 int trigger_element,
11002 int trigger_player,
11005 boolean change_done = FALSE;
11008 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11009 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11012 if (Tile[x][y] == EL_BLOCKED)
11014 Blocked2Moving(x, y, &x, &y);
11015 element = Tile[x][y];
11018 // check if element has already changed or is about to change after moving
11019 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11020 Tile[x][y] != element) ||
11022 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11023 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11024 ChangePage[x][y] != -1)))
11027 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11029 for (p = 0; p < element_info[element].num_change_pages; p++)
11031 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11033 /* check trigger element for all events where the element that is checked
11034 for changing interacts with a directly adjacent element -- this is
11035 different to element changes that affect other elements to change on the
11036 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11037 boolean check_trigger_element =
11038 (trigger_event == CE_TOUCHING_X ||
11039 trigger_event == CE_HITTING_X ||
11040 trigger_event == CE_HIT_BY_X ||
11041 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11043 if (change->can_change_or_has_action &&
11044 change->has_event[trigger_event] &&
11045 change->trigger_side & trigger_side &&
11046 change->trigger_player & trigger_player &&
11047 (!check_trigger_element ||
11048 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11050 change->actual_trigger_element = trigger_element;
11051 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11052 change->actual_trigger_player_bits = trigger_player;
11053 change->actual_trigger_side = trigger_side;
11054 change->actual_trigger_ce_value = CustomValue[x][y];
11055 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11057 // special case: trigger element not at (x,y) position for some events
11058 if (check_trigger_element)
11070 { 0, 0 }, { 0, 0 }, { 0, 0 },
11074 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11075 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11077 change->actual_trigger_ce_value = CustomValue[xx][yy];
11078 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11081 if (change->can_change && !change_done)
11083 ChangeDelay[x][y] = 1;
11084 ChangeEvent[x][y] = trigger_event;
11086 HandleElementChange(x, y, p);
11088 change_done = TRUE;
11090 else if (change->has_action)
11092 ExecuteCustomElementAction(x, y, element, p);
11093 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11098 RECURSION_LOOP_DETECTION_END();
11100 return change_done;
11103 static void PlayPlayerSound(struct PlayerInfo *player)
11105 int jx = player->jx, jy = player->jy;
11106 int sound_element = player->artwork_element;
11107 int last_action = player->last_action_waiting;
11108 int action = player->action_waiting;
11110 if (player->is_waiting)
11112 if (action != last_action)
11113 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11115 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11119 if (action != last_action)
11120 StopSound(element_info[sound_element].sound[last_action]);
11122 if (last_action == ACTION_SLEEPING)
11123 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11127 static void PlayAllPlayersSound(void)
11131 for (i = 0; i < MAX_PLAYERS; i++)
11132 if (stored_player[i].active)
11133 PlayPlayerSound(&stored_player[i]);
11136 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11138 boolean last_waiting = player->is_waiting;
11139 int move_dir = player->MovDir;
11141 player->dir_waiting = move_dir;
11142 player->last_action_waiting = player->action_waiting;
11146 if (!last_waiting) // not waiting -> waiting
11148 player->is_waiting = TRUE;
11150 player->frame_counter_bored =
11152 game.player_boring_delay_fixed +
11153 GetSimpleRandom(game.player_boring_delay_random);
11154 player->frame_counter_sleeping =
11156 game.player_sleeping_delay_fixed +
11157 GetSimpleRandom(game.player_sleeping_delay_random);
11159 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11162 if (game.player_sleeping_delay_fixed +
11163 game.player_sleeping_delay_random > 0 &&
11164 player->anim_delay_counter == 0 &&
11165 player->post_delay_counter == 0 &&
11166 FrameCounter >= player->frame_counter_sleeping)
11167 player->is_sleeping = TRUE;
11168 else if (game.player_boring_delay_fixed +
11169 game.player_boring_delay_random > 0 &&
11170 FrameCounter >= player->frame_counter_bored)
11171 player->is_bored = TRUE;
11173 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11174 player->is_bored ? ACTION_BORING :
11177 if (player->is_sleeping && player->use_murphy)
11179 // special case for sleeping Murphy when leaning against non-free tile
11181 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11182 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11183 !IS_MOVING(player->jx - 1, player->jy)))
11184 move_dir = MV_LEFT;
11185 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11186 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11187 !IS_MOVING(player->jx + 1, player->jy)))
11188 move_dir = MV_RIGHT;
11190 player->is_sleeping = FALSE;
11192 player->dir_waiting = move_dir;
11195 if (player->is_sleeping)
11197 if (player->num_special_action_sleeping > 0)
11199 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11201 int last_special_action = player->special_action_sleeping;
11202 int num_special_action = player->num_special_action_sleeping;
11203 int special_action =
11204 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11205 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11206 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11207 last_special_action + 1 : ACTION_SLEEPING);
11208 int special_graphic =
11209 el_act_dir2img(player->artwork_element, special_action, move_dir);
11211 player->anim_delay_counter =
11212 graphic_info[special_graphic].anim_delay_fixed +
11213 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11214 player->post_delay_counter =
11215 graphic_info[special_graphic].post_delay_fixed +
11216 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11218 player->special_action_sleeping = special_action;
11221 if (player->anim_delay_counter > 0)
11223 player->action_waiting = player->special_action_sleeping;
11224 player->anim_delay_counter--;
11226 else if (player->post_delay_counter > 0)
11228 player->post_delay_counter--;
11232 else if (player->is_bored)
11234 if (player->num_special_action_bored > 0)
11236 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11238 int special_action =
11239 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11240 int special_graphic =
11241 el_act_dir2img(player->artwork_element, special_action, move_dir);
11243 player->anim_delay_counter =
11244 graphic_info[special_graphic].anim_delay_fixed +
11245 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11246 player->post_delay_counter =
11247 graphic_info[special_graphic].post_delay_fixed +
11248 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11250 player->special_action_bored = special_action;
11253 if (player->anim_delay_counter > 0)
11255 player->action_waiting = player->special_action_bored;
11256 player->anim_delay_counter--;
11258 else if (player->post_delay_counter > 0)
11260 player->post_delay_counter--;
11265 else if (last_waiting) // waiting -> not waiting
11267 player->is_waiting = FALSE;
11268 player->is_bored = FALSE;
11269 player->is_sleeping = FALSE;
11271 player->frame_counter_bored = -1;
11272 player->frame_counter_sleeping = -1;
11274 player->anim_delay_counter = 0;
11275 player->post_delay_counter = 0;
11277 player->dir_waiting = player->MovDir;
11278 player->action_waiting = ACTION_DEFAULT;
11280 player->special_action_bored = ACTION_DEFAULT;
11281 player->special_action_sleeping = ACTION_DEFAULT;
11285 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11287 if ((!player->is_moving && player->was_moving) ||
11288 (player->MovPos == 0 && player->was_moving) ||
11289 (player->is_snapping && !player->was_snapping) ||
11290 (player->is_dropping && !player->was_dropping))
11292 if (!CheckSaveEngineSnapshotToList())
11295 player->was_moving = FALSE;
11296 player->was_snapping = TRUE;
11297 player->was_dropping = TRUE;
11301 if (player->is_moving)
11302 player->was_moving = TRUE;
11304 if (!player->is_snapping)
11305 player->was_snapping = FALSE;
11307 if (!player->is_dropping)
11308 player->was_dropping = FALSE;
11311 static struct MouseActionInfo mouse_action_last = { 0 };
11312 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11313 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11316 CheckSaveEngineSnapshotToList();
11318 mouse_action_last = mouse_action;
11321 static void CheckSingleStepMode(struct PlayerInfo *player)
11323 if (tape.single_step && tape.recording && !tape.pausing)
11325 // as it is called "single step mode", just return to pause mode when the
11326 // player stopped moving after one tile (or never starts moving at all)
11327 // (reverse logic needed here in case single step mode used in team mode)
11328 if (player->is_moving ||
11329 player->is_pushing ||
11330 player->is_dropping_pressed ||
11331 player->effective_mouse_action.button)
11332 game.enter_single_step_mode = FALSE;
11335 CheckSaveEngineSnapshot(player);
11338 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11340 int left = player_action & JOY_LEFT;
11341 int right = player_action & JOY_RIGHT;
11342 int up = player_action & JOY_UP;
11343 int down = player_action & JOY_DOWN;
11344 int button1 = player_action & JOY_BUTTON_1;
11345 int button2 = player_action & JOY_BUTTON_2;
11346 int dx = (left ? -1 : right ? 1 : 0);
11347 int dy = (up ? -1 : down ? 1 : 0);
11349 if (!player->active || tape.pausing)
11355 SnapField(player, dx, dy);
11359 DropElement(player);
11361 MovePlayer(player, dx, dy);
11364 CheckSingleStepMode(player);
11366 SetPlayerWaiting(player, FALSE);
11368 return player_action;
11372 // no actions for this player (no input at player's configured device)
11374 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11375 SnapField(player, 0, 0);
11376 CheckGravityMovementWhenNotMoving(player);
11378 if (player->MovPos == 0)
11379 SetPlayerWaiting(player, TRUE);
11381 if (player->MovPos == 0) // needed for tape.playing
11382 player->is_moving = FALSE;
11384 player->is_dropping = FALSE;
11385 player->is_dropping_pressed = FALSE;
11386 player->drop_pressed_delay = 0;
11388 CheckSingleStepMode(player);
11394 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11397 if (!tape.use_mouse_actions)
11400 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11401 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11402 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11405 static void SetTapeActionFromMouseAction(byte *tape_action,
11406 struct MouseActionInfo *mouse_action)
11408 if (!tape.use_mouse_actions)
11411 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11412 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11413 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11416 static void CheckLevelSolved(void)
11418 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11420 if (game_em.level_solved &&
11421 !game_em.game_over) // game won
11425 game_em.game_over = TRUE;
11427 game.all_players_gone = TRUE;
11430 if (game_em.game_over) // game lost
11431 game.all_players_gone = TRUE;
11433 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11435 if (game_sp.level_solved &&
11436 !game_sp.game_over) // game won
11440 game_sp.game_over = TRUE;
11442 game.all_players_gone = TRUE;
11445 if (game_sp.game_over) // game lost
11446 game.all_players_gone = TRUE;
11448 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11450 if (game_mm.level_solved &&
11451 !game_mm.game_over) // game won
11455 game_mm.game_over = TRUE;
11457 game.all_players_gone = TRUE;
11460 if (game_mm.game_over) // game lost
11461 game.all_players_gone = TRUE;
11465 static void CheckLevelTime(void)
11469 if (TimeFrames >= FRAMES_PER_SECOND)
11474 for (i = 0; i < MAX_PLAYERS; i++)
11476 struct PlayerInfo *player = &stored_player[i];
11478 if (SHIELD_ON(player))
11480 player->shield_normal_time_left--;
11482 if (player->shield_deadly_time_left > 0)
11483 player->shield_deadly_time_left--;
11487 if (!game.LevelSolved && !level.use_step_counter)
11495 if (TimeLeft <= 10 && setup.time_limit)
11496 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11498 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11499 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11501 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11503 if (!TimeLeft && setup.time_limit)
11505 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11506 game_em.lev->killed_out_of_time = TRUE;
11508 for (i = 0; i < MAX_PLAYERS; i++)
11509 KillPlayer(&stored_player[i]);
11512 else if (game.no_time_limit && !game.all_players_gone)
11514 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11517 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11520 if (tape.recording || tape.playing)
11521 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11524 if (tape.recording || tape.playing)
11525 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11527 UpdateAndDisplayGameControlValues();
11530 void AdvanceFrameAndPlayerCounters(int player_nr)
11534 // advance frame counters (global frame counter and time frame counter)
11538 // advance player counters (counters for move delay, move animation etc.)
11539 for (i = 0; i < MAX_PLAYERS; i++)
11541 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11542 int move_delay_value = stored_player[i].move_delay_value;
11543 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11545 if (!advance_player_counters) // not all players may be affected
11548 if (move_frames == 0) // less than one move per game frame
11550 int stepsize = TILEX / move_delay_value;
11551 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11552 int count = (stored_player[i].is_moving ?
11553 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11555 if (count % delay == 0)
11559 stored_player[i].Frame += move_frames;
11561 if (stored_player[i].MovPos != 0)
11562 stored_player[i].StepFrame += move_frames;
11564 if (stored_player[i].move_delay > 0)
11565 stored_player[i].move_delay--;
11567 // due to bugs in previous versions, counter must count up, not down
11568 if (stored_player[i].push_delay != -1)
11569 stored_player[i].push_delay++;
11571 if (stored_player[i].drop_delay > 0)
11572 stored_player[i].drop_delay--;
11574 if (stored_player[i].is_dropping_pressed)
11575 stored_player[i].drop_pressed_delay++;
11579 void StartGameActions(boolean init_network_game, boolean record_tape,
11582 unsigned int new_random_seed = InitRND(random_seed);
11585 TapeStartRecording(new_random_seed);
11587 if (init_network_game)
11589 SendToServer_LevelFile();
11590 SendToServer_StartPlaying();
11598 static void GameActionsExt(void)
11601 static unsigned int game_frame_delay = 0;
11603 unsigned int game_frame_delay_value;
11604 byte *recorded_player_action;
11605 byte summarized_player_action = 0;
11606 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11609 // detect endless loops, caused by custom element programming
11610 if (recursion_loop_detected && recursion_loop_depth == 0)
11612 char *message = getStringCat3("Internal Error! Element ",
11613 EL_NAME(recursion_loop_element),
11614 " caused endless loop! Quit the game?");
11616 Warn("element '%s' caused endless loop in game engine",
11617 EL_NAME(recursion_loop_element));
11619 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11621 recursion_loop_detected = FALSE; // if game should be continued
11628 if (game.restart_level)
11629 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11631 CheckLevelSolved();
11633 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11636 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11639 if (game_status != GAME_MODE_PLAYING) // status might have changed
11642 game_frame_delay_value =
11643 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11645 if (tape.playing && tape.warp_forward && !tape.pausing)
11646 game_frame_delay_value = 0;
11648 SetVideoFrameDelay(game_frame_delay_value);
11650 // (de)activate virtual buttons depending on current game status
11651 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11653 if (game.all_players_gone) // if no players there to be controlled anymore
11654 SetOverlayActive(FALSE);
11655 else if (!tape.playing) // if game continues after tape stopped playing
11656 SetOverlayActive(TRUE);
11661 // ---------- main game synchronization point ----------
11663 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11665 Debug("game:playing:skip", "skip == %d", skip);
11668 // ---------- main game synchronization point ----------
11670 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11674 if (network_playing && !network_player_action_received)
11676 // try to get network player actions in time
11678 // last chance to get network player actions without main loop delay
11679 HandleNetworking();
11681 // game was quit by network peer
11682 if (game_status != GAME_MODE_PLAYING)
11685 // check if network player actions still missing and game still running
11686 if (!network_player_action_received && !checkGameEnded())
11687 return; // failed to get network player actions in time
11689 // do not yet reset "network_player_action_received" (for tape.pausing)
11695 // at this point we know that we really continue executing the game
11697 network_player_action_received = FALSE;
11699 // when playing tape, read previously recorded player input from tape data
11700 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11702 local_player->effective_mouse_action = local_player->mouse_action;
11704 if (recorded_player_action != NULL)
11705 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11706 recorded_player_action);
11708 // TapePlayAction() may return NULL when toggling to "pause before death"
11712 if (tape.set_centered_player)
11714 game.centered_player_nr_next = tape.centered_player_nr_next;
11715 game.set_centered_player = TRUE;
11718 for (i = 0; i < MAX_PLAYERS; i++)
11720 summarized_player_action |= stored_player[i].action;
11722 if (!network_playing && (game.team_mode || tape.playing))
11723 stored_player[i].effective_action = stored_player[i].action;
11726 if (network_playing && !checkGameEnded())
11727 SendToServer_MovePlayer(summarized_player_action);
11729 // summarize all actions at local players mapped input device position
11730 // (this allows using different input devices in single player mode)
11731 if (!network.enabled && !game.team_mode)
11732 stored_player[map_player_action[local_player->index_nr]].effective_action =
11733 summarized_player_action;
11735 // summarize all actions at centered player in local team mode
11736 if (tape.recording &&
11737 setup.team_mode && !network.enabled &&
11738 setup.input_on_focus &&
11739 game.centered_player_nr != -1)
11741 for (i = 0; i < MAX_PLAYERS; i++)
11742 stored_player[map_player_action[i]].effective_action =
11743 (i == game.centered_player_nr ? summarized_player_action : 0);
11746 if (recorded_player_action != NULL)
11747 for (i = 0; i < MAX_PLAYERS; i++)
11748 stored_player[i].effective_action = recorded_player_action[i];
11750 for (i = 0; i < MAX_PLAYERS; i++)
11752 tape_action[i] = stored_player[i].effective_action;
11754 /* (this may happen in the RND game engine if a player was not present on
11755 the playfield on level start, but appeared later from a custom element */
11756 if (setup.team_mode &&
11759 !tape.player_participates[i])
11760 tape.player_participates[i] = TRUE;
11763 SetTapeActionFromMouseAction(tape_action,
11764 &local_player->effective_mouse_action);
11766 // only record actions from input devices, but not programmed actions
11767 if (tape.recording)
11768 TapeRecordAction(tape_action);
11770 // remember if game was played (especially after tape stopped playing)
11771 if (!tape.playing && summarized_player_action)
11772 game.GamePlayed = TRUE;
11774 #if USE_NEW_PLAYER_ASSIGNMENTS
11775 // !!! also map player actions in single player mode !!!
11776 // if (game.team_mode)
11779 byte mapped_action[MAX_PLAYERS];
11781 #if DEBUG_PLAYER_ACTIONS
11782 for (i = 0; i < MAX_PLAYERS; i++)
11783 DebugContinued("", "%d, ", stored_player[i].effective_action);
11786 for (i = 0; i < MAX_PLAYERS; i++)
11787 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11789 for (i = 0; i < MAX_PLAYERS; i++)
11790 stored_player[i].effective_action = mapped_action[i];
11792 #if DEBUG_PLAYER_ACTIONS
11793 DebugContinued("", "=> ");
11794 for (i = 0; i < MAX_PLAYERS; i++)
11795 DebugContinued("", "%d, ", stored_player[i].effective_action);
11796 DebugContinued("game:playing:player", "\n");
11799 #if DEBUG_PLAYER_ACTIONS
11802 for (i = 0; i < MAX_PLAYERS; i++)
11803 DebugContinued("", "%d, ", stored_player[i].effective_action);
11804 DebugContinued("game:playing:player", "\n");
11809 for (i = 0; i < MAX_PLAYERS; i++)
11811 // allow engine snapshot in case of changed movement attempt
11812 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11813 (stored_player[i].effective_action & KEY_MOTION))
11814 game.snapshot.changed_action = TRUE;
11816 // allow engine snapshot in case of snapping/dropping attempt
11817 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11818 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11819 game.snapshot.changed_action = TRUE;
11821 game.snapshot.last_action[i] = stored_player[i].effective_action;
11824 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11826 GameActions_EM_Main();
11828 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11830 GameActions_SP_Main();
11832 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11834 GameActions_MM_Main();
11838 GameActions_RND_Main();
11841 BlitScreenToBitmap(backbuffer);
11843 CheckLevelSolved();
11846 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11848 if (global.show_frames_per_second)
11850 static unsigned int fps_counter = 0;
11851 static int fps_frames = 0;
11852 unsigned int fps_delay_ms = Counter() - fps_counter;
11856 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11858 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11861 fps_counter = Counter();
11863 // always draw FPS to screen after FPS value was updated
11864 redraw_mask |= REDRAW_FPS;
11867 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11868 if (GetDrawDeactivationMask() == REDRAW_NONE)
11869 redraw_mask |= REDRAW_FPS;
11873 static void GameActions_CheckSaveEngineSnapshot(void)
11875 if (!game.snapshot.save_snapshot)
11878 // clear flag for saving snapshot _before_ saving snapshot
11879 game.snapshot.save_snapshot = FALSE;
11881 SaveEngineSnapshotToList();
11884 void GameActions(void)
11888 GameActions_CheckSaveEngineSnapshot();
11891 void GameActions_EM_Main(void)
11893 byte effective_action[MAX_PLAYERS];
11894 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11897 for (i = 0; i < MAX_PLAYERS; i++)
11898 effective_action[i] = stored_player[i].effective_action;
11900 GameActions_EM(effective_action, warp_mode);
11903 void GameActions_SP_Main(void)
11905 byte effective_action[MAX_PLAYERS];
11906 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11909 for (i = 0; i < MAX_PLAYERS; i++)
11910 effective_action[i] = stored_player[i].effective_action;
11912 GameActions_SP(effective_action, warp_mode);
11914 for (i = 0; i < MAX_PLAYERS; i++)
11916 if (stored_player[i].force_dropping)
11917 stored_player[i].action |= KEY_BUTTON_DROP;
11919 stored_player[i].force_dropping = FALSE;
11923 void GameActions_MM_Main(void)
11925 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11927 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11930 void GameActions_RND_Main(void)
11935 void GameActions_RND(void)
11937 static struct MouseActionInfo mouse_action_last = { 0 };
11938 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11939 int magic_wall_x = 0, magic_wall_y = 0;
11940 int i, x, y, element, graphic, last_gfx_frame;
11942 InitPlayfieldScanModeVars();
11944 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11946 SCAN_PLAYFIELD(x, y)
11948 ChangeCount[x][y] = 0;
11949 ChangeEvent[x][y] = -1;
11953 if (game.set_centered_player)
11955 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11957 // switching to "all players" only possible if all players fit to screen
11958 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11960 game.centered_player_nr_next = game.centered_player_nr;
11961 game.set_centered_player = FALSE;
11964 // do not switch focus to non-existing (or non-active) player
11965 if (game.centered_player_nr_next >= 0 &&
11966 !stored_player[game.centered_player_nr_next].active)
11968 game.centered_player_nr_next = game.centered_player_nr;
11969 game.set_centered_player = FALSE;
11973 if (game.set_centered_player &&
11974 ScreenMovPos == 0) // screen currently aligned at tile position
11978 if (game.centered_player_nr_next == -1)
11980 setScreenCenteredToAllPlayers(&sx, &sy);
11984 sx = stored_player[game.centered_player_nr_next].jx;
11985 sy = stored_player[game.centered_player_nr_next].jy;
11988 game.centered_player_nr = game.centered_player_nr_next;
11989 game.set_centered_player = FALSE;
11991 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11992 DrawGameDoorValues();
11995 // check single step mode (set flag and clear again if any player is active)
11996 game.enter_single_step_mode =
11997 (tape.single_step && tape.recording && !tape.pausing);
11999 for (i = 0; i < MAX_PLAYERS; i++)
12001 int actual_player_action = stored_player[i].effective_action;
12004 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12005 - rnd_equinox_tetrachloride 048
12006 - rnd_equinox_tetrachloride_ii 096
12007 - rnd_emanuel_schmieg 002
12008 - doctor_sloan_ww 001, 020
12010 if (stored_player[i].MovPos == 0)
12011 CheckGravityMovement(&stored_player[i]);
12014 // overwrite programmed action with tape action
12015 if (stored_player[i].programmed_action)
12016 actual_player_action = stored_player[i].programmed_action;
12018 PlayerActions(&stored_player[i], actual_player_action);
12020 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12023 // single step pause mode may already have been toggled by "ScrollPlayer()"
12024 if (game.enter_single_step_mode && !tape.pausing)
12025 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12027 ScrollScreen(NULL, SCROLL_GO_ON);
12029 /* for backwards compatibility, the following code emulates a fixed bug that
12030 occured when pushing elements (causing elements that just made their last
12031 pushing step to already (if possible) make their first falling step in the
12032 same game frame, which is bad); this code is also needed to use the famous
12033 "spring push bug" which is used in older levels and might be wanted to be
12034 used also in newer levels, but in this case the buggy pushing code is only
12035 affecting the "spring" element and no other elements */
12037 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12039 for (i = 0; i < MAX_PLAYERS; i++)
12041 struct PlayerInfo *player = &stored_player[i];
12042 int x = player->jx;
12043 int y = player->jy;
12045 if (player->active && player->is_pushing && player->is_moving &&
12047 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12048 Tile[x][y] == EL_SPRING))
12050 ContinueMoving(x, y);
12052 // continue moving after pushing (this is actually a bug)
12053 if (!IS_MOVING(x, y))
12054 Stop[x][y] = FALSE;
12059 SCAN_PLAYFIELD(x, y)
12061 Last[x][y] = Tile[x][y];
12063 ChangeCount[x][y] = 0;
12064 ChangeEvent[x][y] = -1;
12066 // this must be handled before main playfield loop
12067 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12070 if (MovDelay[x][y] <= 0)
12074 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12077 if (MovDelay[x][y] <= 0)
12079 int element = Store[x][y];
12080 int move_direction = MovDir[x][y];
12081 int player_index_bit = Store2[x][y];
12087 TEST_DrawLevelField(x, y);
12089 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12094 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12096 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12098 Debug("game:playing:GameActions_RND", "This should never happen!");
12100 ChangePage[x][y] = -1;
12104 Stop[x][y] = FALSE;
12105 if (WasJustMoving[x][y] > 0)
12106 WasJustMoving[x][y]--;
12107 if (WasJustFalling[x][y] > 0)
12108 WasJustFalling[x][y]--;
12109 if (CheckCollision[x][y] > 0)
12110 CheckCollision[x][y]--;
12111 if (CheckImpact[x][y] > 0)
12112 CheckImpact[x][y]--;
12116 /* reset finished pushing action (not done in ContinueMoving() to allow
12117 continuous pushing animation for elements with zero push delay) */
12118 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12120 ResetGfxAnimation(x, y);
12121 TEST_DrawLevelField(x, y);
12125 if (IS_BLOCKED(x, y))
12129 Blocked2Moving(x, y, &oldx, &oldy);
12130 if (!IS_MOVING(oldx, oldy))
12132 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12133 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12134 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12135 Debug("game:playing:GameActions_RND", "This should never happen!");
12141 if (mouse_action.button)
12143 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12145 x = mouse_action.lx;
12146 y = mouse_action.ly;
12147 element = Tile[x][y];
12151 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12152 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12155 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12156 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12159 SCAN_PLAYFIELD(x, y)
12161 element = Tile[x][y];
12162 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12163 last_gfx_frame = GfxFrame[x][y];
12165 ResetGfxFrame(x, y);
12167 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12168 DrawLevelGraphicAnimation(x, y, graphic);
12170 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12171 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12172 ResetRandomAnimationValue(x, y);
12174 SetRandomAnimationValue(x, y);
12176 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12178 if (IS_INACTIVE(element))
12180 if (IS_ANIMATED(graphic))
12181 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12186 // this may take place after moving, so 'element' may have changed
12187 if (IS_CHANGING(x, y) &&
12188 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12190 int page = element_info[element].event_page_nr[CE_DELAY];
12192 HandleElementChange(x, y, page);
12194 element = Tile[x][y];
12195 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12198 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12202 element = Tile[x][y];
12203 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12205 if (IS_ANIMATED(graphic) &&
12206 !IS_MOVING(x, y) &&
12208 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12210 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12211 TEST_DrawTwinkleOnField(x, y);
12213 else if (element == EL_ACID)
12216 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12218 else if ((element == EL_EXIT_OPEN ||
12219 element == EL_EM_EXIT_OPEN ||
12220 element == EL_SP_EXIT_OPEN ||
12221 element == EL_STEEL_EXIT_OPEN ||
12222 element == EL_EM_STEEL_EXIT_OPEN ||
12223 element == EL_SP_TERMINAL ||
12224 element == EL_SP_TERMINAL_ACTIVE ||
12225 element == EL_EXTRA_TIME ||
12226 element == EL_SHIELD_NORMAL ||
12227 element == EL_SHIELD_DEADLY) &&
12228 IS_ANIMATED(graphic))
12229 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12230 else if (IS_MOVING(x, y))
12231 ContinueMoving(x, y);
12232 else if (IS_ACTIVE_BOMB(element))
12233 CheckDynamite(x, y);
12234 else if (element == EL_AMOEBA_GROWING)
12235 AmoebaGrowing(x, y);
12236 else if (element == EL_AMOEBA_SHRINKING)
12237 AmoebaShrinking(x, y);
12239 #if !USE_NEW_AMOEBA_CODE
12240 else if (IS_AMOEBALIVE(element))
12241 AmoebaReproduce(x, y);
12244 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12246 else if (element == EL_EXIT_CLOSED)
12248 else if (element == EL_EM_EXIT_CLOSED)
12250 else if (element == EL_STEEL_EXIT_CLOSED)
12251 CheckExitSteel(x, y);
12252 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12253 CheckExitSteelEM(x, y);
12254 else if (element == EL_SP_EXIT_CLOSED)
12256 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12257 element == EL_EXPANDABLE_STEELWALL_GROWING)
12258 MauerWaechst(x, y);
12259 else if (element == EL_EXPANDABLE_WALL ||
12260 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12261 element == EL_EXPANDABLE_WALL_VERTICAL ||
12262 element == EL_EXPANDABLE_WALL_ANY ||
12263 element == EL_BD_EXPANDABLE_WALL)
12264 MauerAbleger(x, y);
12265 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12266 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12267 element == EL_EXPANDABLE_STEELWALL_ANY)
12268 MauerAblegerStahl(x, y);
12269 else if (element == EL_FLAMES)
12270 CheckForDragon(x, y);
12271 else if (element == EL_EXPLOSION)
12272 ; // drawing of correct explosion animation is handled separately
12273 else if (element == EL_ELEMENT_SNAPPING ||
12274 element == EL_DIAGONAL_SHRINKING ||
12275 element == EL_DIAGONAL_GROWING)
12277 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12279 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12281 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12282 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12284 if (IS_BELT_ACTIVE(element))
12285 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12287 if (game.magic_wall_active)
12289 int jx = local_player->jx, jy = local_player->jy;
12291 // play the element sound at the position nearest to the player
12292 if ((element == EL_MAGIC_WALL_FULL ||
12293 element == EL_MAGIC_WALL_ACTIVE ||
12294 element == EL_MAGIC_WALL_EMPTYING ||
12295 element == EL_BD_MAGIC_WALL_FULL ||
12296 element == EL_BD_MAGIC_WALL_ACTIVE ||
12297 element == EL_BD_MAGIC_WALL_EMPTYING ||
12298 element == EL_DC_MAGIC_WALL_FULL ||
12299 element == EL_DC_MAGIC_WALL_ACTIVE ||
12300 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12301 ABS(x - jx) + ABS(y - jy) <
12302 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12310 #if USE_NEW_AMOEBA_CODE
12311 // new experimental amoeba growth stuff
12312 if (!(FrameCounter % 8))
12314 static unsigned int random = 1684108901;
12316 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12318 x = RND(lev_fieldx);
12319 y = RND(lev_fieldy);
12320 element = Tile[x][y];
12322 if (!IS_PLAYER(x,y) &&
12323 (element == EL_EMPTY ||
12324 CAN_GROW_INTO(element) ||
12325 element == EL_QUICKSAND_EMPTY ||
12326 element == EL_QUICKSAND_FAST_EMPTY ||
12327 element == EL_ACID_SPLASH_LEFT ||
12328 element == EL_ACID_SPLASH_RIGHT))
12330 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12331 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12332 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12333 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12334 Tile[x][y] = EL_AMOEBA_DROP;
12337 random = random * 129 + 1;
12342 game.explosions_delayed = FALSE;
12344 SCAN_PLAYFIELD(x, y)
12346 element = Tile[x][y];
12348 if (ExplodeField[x][y])
12349 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12350 else if (element == EL_EXPLOSION)
12351 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12353 ExplodeField[x][y] = EX_TYPE_NONE;
12356 game.explosions_delayed = TRUE;
12358 if (game.magic_wall_active)
12360 if (!(game.magic_wall_time_left % 4))
12362 int element = Tile[magic_wall_x][magic_wall_y];
12364 if (element == EL_BD_MAGIC_WALL_FULL ||
12365 element == EL_BD_MAGIC_WALL_ACTIVE ||
12366 element == EL_BD_MAGIC_WALL_EMPTYING)
12367 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12368 else if (element == EL_DC_MAGIC_WALL_FULL ||
12369 element == EL_DC_MAGIC_WALL_ACTIVE ||
12370 element == EL_DC_MAGIC_WALL_EMPTYING)
12371 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12373 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12376 if (game.magic_wall_time_left > 0)
12378 game.magic_wall_time_left--;
12380 if (!game.magic_wall_time_left)
12382 SCAN_PLAYFIELD(x, y)
12384 element = Tile[x][y];
12386 if (element == EL_MAGIC_WALL_ACTIVE ||
12387 element == EL_MAGIC_WALL_FULL)
12389 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12390 TEST_DrawLevelField(x, y);
12392 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12393 element == EL_BD_MAGIC_WALL_FULL)
12395 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12396 TEST_DrawLevelField(x, y);
12398 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12399 element == EL_DC_MAGIC_WALL_FULL)
12401 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12402 TEST_DrawLevelField(x, y);
12406 game.magic_wall_active = FALSE;
12411 if (game.light_time_left > 0)
12413 game.light_time_left--;
12415 if (game.light_time_left == 0)
12416 RedrawAllLightSwitchesAndInvisibleElements();
12419 if (game.timegate_time_left > 0)
12421 game.timegate_time_left--;
12423 if (game.timegate_time_left == 0)
12424 CloseAllOpenTimegates();
12427 if (game.lenses_time_left > 0)
12429 game.lenses_time_left--;
12431 if (game.lenses_time_left == 0)
12432 RedrawAllInvisibleElementsForLenses();
12435 if (game.magnify_time_left > 0)
12437 game.magnify_time_left--;
12439 if (game.magnify_time_left == 0)
12440 RedrawAllInvisibleElementsForMagnifier();
12443 for (i = 0; i < MAX_PLAYERS; i++)
12445 struct PlayerInfo *player = &stored_player[i];
12447 if (SHIELD_ON(player))
12449 if (player->shield_deadly_time_left)
12450 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12451 else if (player->shield_normal_time_left)
12452 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12456 #if USE_DELAYED_GFX_REDRAW
12457 SCAN_PLAYFIELD(x, y)
12459 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12461 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12462 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12464 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12465 DrawLevelField(x, y);
12467 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12468 DrawLevelFieldCrumbled(x, y);
12470 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12471 DrawLevelFieldCrumbledNeighbours(x, y);
12473 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12474 DrawTwinkleOnField(x, y);
12477 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12482 PlayAllPlayersSound();
12484 for (i = 0; i < MAX_PLAYERS; i++)
12486 struct PlayerInfo *player = &stored_player[i];
12488 if (player->show_envelope != 0 && (!player->active ||
12489 player->MovPos == 0))
12491 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12493 player->show_envelope = 0;
12497 // use random number generator in every frame to make it less predictable
12498 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12501 mouse_action_last = mouse_action;
12504 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12506 int min_x = x, min_y = y, max_x = x, max_y = y;
12507 int scr_fieldx = getScreenFieldSizeX();
12508 int scr_fieldy = getScreenFieldSizeY();
12511 for (i = 0; i < MAX_PLAYERS; i++)
12513 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12515 if (!stored_player[i].active || &stored_player[i] == player)
12518 min_x = MIN(min_x, jx);
12519 min_y = MIN(min_y, jy);
12520 max_x = MAX(max_x, jx);
12521 max_y = MAX(max_y, jy);
12524 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12527 static boolean AllPlayersInVisibleScreen(void)
12531 for (i = 0; i < MAX_PLAYERS; i++)
12533 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12535 if (!stored_player[i].active)
12538 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12545 void ScrollLevel(int dx, int dy)
12547 int scroll_offset = 2 * TILEX_VAR;
12550 BlitBitmap(drawto_field, drawto_field,
12551 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12552 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12553 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12554 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12555 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12556 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12560 x = (dx == 1 ? BX1 : BX2);
12561 for (y = BY1; y <= BY2; y++)
12562 DrawScreenField(x, y);
12567 y = (dy == 1 ? BY1 : BY2);
12568 for (x = BX1; x <= BX2; x++)
12569 DrawScreenField(x, y);
12572 redraw_mask |= REDRAW_FIELD;
12575 static boolean canFallDown(struct PlayerInfo *player)
12577 int jx = player->jx, jy = player->jy;
12579 return (IN_LEV_FIELD(jx, jy + 1) &&
12580 (IS_FREE(jx, jy + 1) ||
12581 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12582 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12583 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12586 static boolean canPassField(int x, int y, int move_dir)
12588 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12589 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12590 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12591 int nextx = x + dx;
12592 int nexty = y + dy;
12593 int element = Tile[x][y];
12595 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12596 !CAN_MOVE(element) &&
12597 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12598 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12599 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12602 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12604 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12605 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12606 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12610 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12611 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12612 (IS_DIGGABLE(Tile[newx][newy]) ||
12613 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12614 canPassField(newx, newy, move_dir)));
12617 static void CheckGravityMovement(struct PlayerInfo *player)
12619 if (player->gravity && !player->programmed_action)
12621 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12622 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12623 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12624 int jx = player->jx, jy = player->jy;
12625 boolean player_is_moving_to_valid_field =
12626 (!player_is_snapping &&
12627 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12628 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12629 boolean player_can_fall_down = canFallDown(player);
12631 if (player_can_fall_down &&
12632 !player_is_moving_to_valid_field)
12633 player->programmed_action = MV_DOWN;
12637 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12639 return CheckGravityMovement(player);
12641 if (player->gravity && !player->programmed_action)
12643 int jx = player->jx, jy = player->jy;
12644 boolean field_under_player_is_free =
12645 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12646 boolean player_is_standing_on_valid_field =
12647 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12648 (IS_WALKABLE(Tile[jx][jy]) &&
12649 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12651 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12652 player->programmed_action = MV_DOWN;
12657 MovePlayerOneStep()
12658 -----------------------------------------------------------------------------
12659 dx, dy: direction (non-diagonal) to try to move the player to
12660 real_dx, real_dy: direction as read from input device (can be diagonal)
12663 boolean MovePlayerOneStep(struct PlayerInfo *player,
12664 int dx, int dy, int real_dx, int real_dy)
12666 int jx = player->jx, jy = player->jy;
12667 int new_jx = jx + dx, new_jy = jy + dy;
12669 boolean player_can_move = !player->cannot_move;
12671 if (!player->active || (!dx && !dy))
12672 return MP_NO_ACTION;
12674 player->MovDir = (dx < 0 ? MV_LEFT :
12675 dx > 0 ? MV_RIGHT :
12677 dy > 0 ? MV_DOWN : MV_NONE);
12679 if (!IN_LEV_FIELD(new_jx, new_jy))
12680 return MP_NO_ACTION;
12682 if (!player_can_move)
12684 if (player->MovPos == 0)
12686 player->is_moving = FALSE;
12687 player->is_digging = FALSE;
12688 player->is_collecting = FALSE;
12689 player->is_snapping = FALSE;
12690 player->is_pushing = FALSE;
12694 if (!network.enabled && game.centered_player_nr == -1 &&
12695 !AllPlayersInSight(player, new_jx, new_jy))
12696 return MP_NO_ACTION;
12698 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12699 if (can_move != MP_MOVING)
12702 // check if DigField() has caused relocation of the player
12703 if (player->jx != jx || player->jy != jy)
12704 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12706 StorePlayer[jx][jy] = 0;
12707 player->last_jx = jx;
12708 player->last_jy = jy;
12709 player->jx = new_jx;
12710 player->jy = new_jy;
12711 StorePlayer[new_jx][new_jy] = player->element_nr;
12713 if (player->move_delay_value_next != -1)
12715 player->move_delay_value = player->move_delay_value_next;
12716 player->move_delay_value_next = -1;
12720 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12722 player->step_counter++;
12724 PlayerVisit[jx][jy] = FrameCounter;
12726 player->is_moving = TRUE;
12729 // should better be called in MovePlayer(), but this breaks some tapes
12730 ScrollPlayer(player, SCROLL_INIT);
12736 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12738 int jx = player->jx, jy = player->jy;
12739 int old_jx = jx, old_jy = jy;
12740 int moved = MP_NO_ACTION;
12742 if (!player->active)
12747 if (player->MovPos == 0)
12749 player->is_moving = FALSE;
12750 player->is_digging = FALSE;
12751 player->is_collecting = FALSE;
12752 player->is_snapping = FALSE;
12753 player->is_pushing = FALSE;
12759 if (player->move_delay > 0)
12762 player->move_delay = -1; // set to "uninitialized" value
12764 // store if player is automatically moved to next field
12765 player->is_auto_moving = (player->programmed_action != MV_NONE);
12767 // remove the last programmed player action
12768 player->programmed_action = 0;
12770 if (player->MovPos)
12772 // should only happen if pre-1.2 tape recordings are played
12773 // this is only for backward compatibility
12775 int original_move_delay_value = player->move_delay_value;
12778 Debug("game:playing:MovePlayer",
12779 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12783 // scroll remaining steps with finest movement resolution
12784 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12786 while (player->MovPos)
12788 ScrollPlayer(player, SCROLL_GO_ON);
12789 ScrollScreen(NULL, SCROLL_GO_ON);
12791 AdvanceFrameAndPlayerCounters(player->index_nr);
12794 BackToFront_WithFrameDelay(0);
12797 player->move_delay_value = original_move_delay_value;
12800 player->is_active = FALSE;
12802 if (player->last_move_dir & MV_HORIZONTAL)
12804 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12805 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12809 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12810 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12813 if (!moved && !player->is_active)
12815 player->is_moving = FALSE;
12816 player->is_digging = FALSE;
12817 player->is_collecting = FALSE;
12818 player->is_snapping = FALSE;
12819 player->is_pushing = FALSE;
12825 if (moved & MP_MOVING && !ScreenMovPos &&
12826 (player->index_nr == game.centered_player_nr ||
12827 game.centered_player_nr == -1))
12829 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12831 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12833 // actual player has left the screen -- scroll in that direction
12834 if (jx != old_jx) // player has moved horizontally
12835 scroll_x += (jx - old_jx);
12836 else // player has moved vertically
12837 scroll_y += (jy - old_jy);
12841 int offset_raw = game.scroll_delay_value;
12843 if (jx != old_jx) // player has moved horizontally
12845 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12846 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12847 int new_scroll_x = jx - MIDPOSX + offset_x;
12849 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12850 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12851 scroll_x = new_scroll_x;
12853 // don't scroll over playfield boundaries
12854 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12856 // don't scroll more than one field at a time
12857 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12859 // don't scroll against the player's moving direction
12860 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12861 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12862 scroll_x = old_scroll_x;
12864 else // player has moved vertically
12866 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12867 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12868 int new_scroll_y = jy - MIDPOSY + offset_y;
12870 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12871 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12872 scroll_y = new_scroll_y;
12874 // don't scroll over playfield boundaries
12875 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12877 // don't scroll more than one field at a time
12878 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12880 // don't scroll against the player's moving direction
12881 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12882 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12883 scroll_y = old_scroll_y;
12887 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12889 if (!network.enabled && game.centered_player_nr == -1 &&
12890 !AllPlayersInVisibleScreen())
12892 scroll_x = old_scroll_x;
12893 scroll_y = old_scroll_y;
12897 ScrollScreen(player, SCROLL_INIT);
12898 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12903 player->StepFrame = 0;
12905 if (moved & MP_MOVING)
12907 if (old_jx != jx && old_jy == jy)
12908 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12909 else if (old_jx == jx && old_jy != jy)
12910 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12912 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12914 player->last_move_dir = player->MovDir;
12915 player->is_moving = TRUE;
12916 player->is_snapping = FALSE;
12917 player->is_switching = FALSE;
12918 player->is_dropping = FALSE;
12919 player->is_dropping_pressed = FALSE;
12920 player->drop_pressed_delay = 0;
12923 // should better be called here than above, but this breaks some tapes
12924 ScrollPlayer(player, SCROLL_INIT);
12929 CheckGravityMovementWhenNotMoving(player);
12931 player->is_moving = FALSE;
12933 /* at this point, the player is allowed to move, but cannot move right now
12934 (e.g. because of something blocking the way) -- ensure that the player
12935 is also allowed to move in the next frame (in old versions before 3.1.1,
12936 the player was forced to wait again for eight frames before next try) */
12938 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12939 player->move_delay = 0; // allow direct movement in the next frame
12942 if (player->move_delay == -1) // not yet initialized by DigField()
12943 player->move_delay = player->move_delay_value;
12945 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12947 TestIfPlayerTouchesBadThing(jx, jy);
12948 TestIfPlayerTouchesCustomElement(jx, jy);
12951 if (!player->active)
12952 RemovePlayer(player);
12957 void ScrollPlayer(struct PlayerInfo *player, int mode)
12959 int jx = player->jx, jy = player->jy;
12960 int last_jx = player->last_jx, last_jy = player->last_jy;
12961 int move_stepsize = TILEX / player->move_delay_value;
12963 if (!player->active)
12966 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12969 if (mode == SCROLL_INIT)
12971 player->actual_frame_counter = FrameCounter;
12972 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12974 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12975 Tile[last_jx][last_jy] == EL_EMPTY)
12977 int last_field_block_delay = 0; // start with no blocking at all
12978 int block_delay_adjustment = player->block_delay_adjustment;
12980 // if player blocks last field, add delay for exactly one move
12981 if (player->block_last_field)
12983 last_field_block_delay += player->move_delay_value;
12985 // when blocking enabled, prevent moving up despite gravity
12986 if (player->gravity && player->MovDir == MV_UP)
12987 block_delay_adjustment = -1;
12990 // add block delay adjustment (also possible when not blocking)
12991 last_field_block_delay += block_delay_adjustment;
12993 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12994 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12997 if (player->MovPos != 0) // player has not yet reached destination
13000 else if (!FrameReached(&player->actual_frame_counter, 1))
13003 if (player->MovPos != 0)
13005 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13006 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13008 // before DrawPlayer() to draw correct player graphic for this case
13009 if (player->MovPos == 0)
13010 CheckGravityMovement(player);
13013 if (player->MovPos == 0) // player reached destination field
13015 if (player->move_delay_reset_counter > 0)
13017 player->move_delay_reset_counter--;
13019 if (player->move_delay_reset_counter == 0)
13021 // continue with normal speed after quickly moving through gate
13022 HALVE_PLAYER_SPEED(player);
13024 // be able to make the next move without delay
13025 player->move_delay = 0;
13029 player->last_jx = jx;
13030 player->last_jy = jy;
13032 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13033 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13034 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13035 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13036 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13037 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13038 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13039 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13041 ExitPlayer(player);
13043 if (game.players_still_needed == 0 &&
13044 (game.friends_still_needed == 0 ||
13045 IS_SP_ELEMENT(Tile[jx][jy])))
13049 // this breaks one level: "machine", level 000
13051 int move_direction = player->MovDir;
13052 int enter_side = MV_DIR_OPPOSITE(move_direction);
13053 int leave_side = move_direction;
13054 int old_jx = last_jx;
13055 int old_jy = last_jy;
13056 int old_element = Tile[old_jx][old_jy];
13057 int new_element = Tile[jx][jy];
13059 if (IS_CUSTOM_ELEMENT(old_element))
13060 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13062 player->index_bit, leave_side);
13064 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13065 CE_PLAYER_LEAVES_X,
13066 player->index_bit, leave_side);
13068 if (IS_CUSTOM_ELEMENT(new_element))
13069 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13070 player->index_bit, enter_side);
13072 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13073 CE_PLAYER_ENTERS_X,
13074 player->index_bit, enter_side);
13076 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13077 CE_MOVE_OF_X, move_direction);
13080 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13082 TestIfPlayerTouchesBadThing(jx, jy);
13083 TestIfPlayerTouchesCustomElement(jx, jy);
13085 /* needed because pushed element has not yet reached its destination,
13086 so it would trigger a change event at its previous field location */
13087 if (!player->is_pushing)
13088 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13090 if (level.finish_dig_collect &&
13091 (player->is_digging || player->is_collecting))
13093 int last_element = player->last_removed_element;
13094 int move_direction = player->MovDir;
13095 int enter_side = MV_DIR_OPPOSITE(move_direction);
13096 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13097 CE_PLAYER_COLLECTS_X);
13099 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13100 player->index_bit, enter_side);
13102 player->last_removed_element = EL_UNDEFINED;
13105 if (!player->active)
13106 RemovePlayer(player);
13109 if (!game.LevelSolved && level.use_step_counter)
13119 if (TimeLeft <= 10 && setup.time_limit)
13120 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13122 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13124 DisplayGameControlValues();
13126 if (!TimeLeft && setup.time_limit)
13127 for (i = 0; i < MAX_PLAYERS; i++)
13128 KillPlayer(&stored_player[i]);
13130 else if (game.no_time_limit && !game.all_players_gone)
13132 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13134 DisplayGameControlValues();
13138 if (tape.single_step && tape.recording && !tape.pausing &&
13139 !player->programmed_action)
13140 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13142 if (!player->programmed_action)
13143 CheckSaveEngineSnapshot(player);
13147 void ScrollScreen(struct PlayerInfo *player, int mode)
13149 static unsigned int screen_frame_counter = 0;
13151 if (mode == SCROLL_INIT)
13153 // set scrolling step size according to actual player's moving speed
13154 ScrollStepSize = TILEX / player->move_delay_value;
13156 screen_frame_counter = FrameCounter;
13157 ScreenMovDir = player->MovDir;
13158 ScreenMovPos = player->MovPos;
13159 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13162 else if (!FrameReached(&screen_frame_counter, 1))
13167 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13168 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13169 redraw_mask |= REDRAW_FIELD;
13172 ScreenMovDir = MV_NONE;
13175 void TestIfPlayerTouchesCustomElement(int x, int y)
13177 static int xy[4][2] =
13184 static int trigger_sides[4][2] =
13186 // center side border side
13187 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13188 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13189 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13190 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13192 static int touch_dir[4] =
13194 MV_LEFT | MV_RIGHT,
13199 int center_element = Tile[x][y]; // should always be non-moving!
13202 for (i = 0; i < NUM_DIRECTIONS; i++)
13204 int xx = x + xy[i][0];
13205 int yy = y + xy[i][1];
13206 int center_side = trigger_sides[i][0];
13207 int border_side = trigger_sides[i][1];
13208 int border_element;
13210 if (!IN_LEV_FIELD(xx, yy))
13213 if (IS_PLAYER(x, y)) // player found at center element
13215 struct PlayerInfo *player = PLAYERINFO(x, y);
13217 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13218 border_element = Tile[xx][yy]; // may be moving!
13219 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13220 border_element = Tile[xx][yy];
13221 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13222 border_element = MovingOrBlocked2Element(xx, yy);
13224 continue; // center and border element do not touch
13226 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13227 player->index_bit, border_side);
13228 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13229 CE_PLAYER_TOUCHES_X,
13230 player->index_bit, border_side);
13233 /* use player element that is initially defined in the level playfield,
13234 not the player element that corresponds to the runtime player number
13235 (example: a level that contains EL_PLAYER_3 as the only player would
13236 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13237 int player_element = PLAYERINFO(x, y)->initial_element;
13239 CheckElementChangeBySide(xx, yy, border_element, player_element,
13240 CE_TOUCHING_X, border_side);
13243 else if (IS_PLAYER(xx, yy)) // player found at border element
13245 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13247 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13249 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13250 continue; // center and border element do not touch
13253 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13254 player->index_bit, center_side);
13255 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13256 CE_PLAYER_TOUCHES_X,
13257 player->index_bit, center_side);
13260 /* use player element that is initially defined in the level playfield,
13261 not the player element that corresponds to the runtime player number
13262 (example: a level that contains EL_PLAYER_3 as the only player would
13263 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13264 int player_element = PLAYERINFO(xx, yy)->initial_element;
13266 CheckElementChangeBySide(x, y, center_element, player_element,
13267 CE_TOUCHING_X, center_side);
13275 void TestIfElementTouchesCustomElement(int x, int y)
13277 static int xy[4][2] =
13284 static int trigger_sides[4][2] =
13286 // center side border side
13287 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13288 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13289 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13290 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13292 static int touch_dir[4] =
13294 MV_LEFT | MV_RIGHT,
13299 boolean change_center_element = FALSE;
13300 int center_element = Tile[x][y]; // should always be non-moving!
13301 int border_element_old[NUM_DIRECTIONS];
13304 for (i = 0; i < NUM_DIRECTIONS; i++)
13306 int xx = x + xy[i][0];
13307 int yy = y + xy[i][1];
13308 int border_element;
13310 border_element_old[i] = -1;
13312 if (!IN_LEV_FIELD(xx, yy))
13315 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13316 border_element = Tile[xx][yy]; // may be moving!
13317 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13318 border_element = Tile[xx][yy];
13319 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13320 border_element = MovingOrBlocked2Element(xx, yy);
13322 continue; // center and border element do not touch
13324 border_element_old[i] = border_element;
13327 for (i = 0; i < NUM_DIRECTIONS; i++)
13329 int xx = x + xy[i][0];
13330 int yy = y + xy[i][1];
13331 int center_side = trigger_sides[i][0];
13332 int border_element = border_element_old[i];
13334 if (border_element == -1)
13337 // check for change of border element
13338 CheckElementChangeBySide(xx, yy, border_element, center_element,
13339 CE_TOUCHING_X, center_side);
13341 // (center element cannot be player, so we dont have to check this here)
13344 for (i = 0; i < NUM_DIRECTIONS; i++)
13346 int xx = x + xy[i][0];
13347 int yy = y + xy[i][1];
13348 int border_side = trigger_sides[i][1];
13349 int border_element = border_element_old[i];
13351 if (border_element == -1)
13354 // check for change of center element (but change it only once)
13355 if (!change_center_element)
13356 change_center_element =
13357 CheckElementChangeBySide(x, y, center_element, border_element,
13358 CE_TOUCHING_X, border_side);
13360 if (IS_PLAYER(xx, yy))
13362 /* use player element that is initially defined in the level playfield,
13363 not the player element that corresponds to the runtime player number
13364 (example: a level that contains EL_PLAYER_3 as the only player would
13365 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13366 int player_element = PLAYERINFO(xx, yy)->initial_element;
13368 CheckElementChangeBySide(x, y, center_element, player_element,
13369 CE_TOUCHING_X, border_side);
13374 void TestIfElementHitsCustomElement(int x, int y, int direction)
13376 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13377 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13378 int hitx = x + dx, hity = y + dy;
13379 int hitting_element = Tile[x][y];
13380 int touched_element;
13382 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13385 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13386 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13388 if (IN_LEV_FIELD(hitx, hity))
13390 int opposite_direction = MV_DIR_OPPOSITE(direction);
13391 int hitting_side = direction;
13392 int touched_side = opposite_direction;
13393 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13394 MovDir[hitx][hity] != direction ||
13395 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13401 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13402 CE_HITTING_X, touched_side);
13404 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13405 CE_HIT_BY_X, hitting_side);
13407 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13408 CE_HIT_BY_SOMETHING, opposite_direction);
13410 if (IS_PLAYER(hitx, hity))
13412 /* use player element that is initially defined in the level playfield,
13413 not the player element that corresponds to the runtime player number
13414 (example: a level that contains EL_PLAYER_3 as the only player would
13415 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13416 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13418 CheckElementChangeBySide(x, y, hitting_element, player_element,
13419 CE_HITTING_X, touched_side);
13424 // "hitting something" is also true when hitting the playfield border
13425 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13426 CE_HITTING_SOMETHING, direction);
13429 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13431 int i, kill_x = -1, kill_y = -1;
13433 int bad_element = -1;
13434 static int test_xy[4][2] =
13441 static int test_dir[4] =
13449 for (i = 0; i < NUM_DIRECTIONS; i++)
13451 int test_x, test_y, test_move_dir, test_element;
13453 test_x = good_x + test_xy[i][0];
13454 test_y = good_y + test_xy[i][1];
13456 if (!IN_LEV_FIELD(test_x, test_y))
13460 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13462 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13464 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13465 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13467 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13468 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13472 bad_element = test_element;
13478 if (kill_x != -1 || kill_y != -1)
13480 if (IS_PLAYER(good_x, good_y))
13482 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13484 if (player->shield_deadly_time_left > 0 &&
13485 !IS_INDESTRUCTIBLE(bad_element))
13486 Bang(kill_x, kill_y);
13487 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13488 KillPlayer(player);
13491 Bang(good_x, good_y);
13495 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13497 int i, kill_x = -1, kill_y = -1;
13498 int bad_element = Tile[bad_x][bad_y];
13499 static int test_xy[4][2] =
13506 static int touch_dir[4] =
13508 MV_LEFT | MV_RIGHT,
13513 static int test_dir[4] =
13521 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13524 for (i = 0; i < NUM_DIRECTIONS; i++)
13526 int test_x, test_y, test_move_dir, test_element;
13528 test_x = bad_x + test_xy[i][0];
13529 test_y = bad_y + test_xy[i][1];
13531 if (!IN_LEV_FIELD(test_x, test_y))
13535 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13537 test_element = Tile[test_x][test_y];
13539 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13540 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13542 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13543 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13545 // good thing is player or penguin that does not move away
13546 if (IS_PLAYER(test_x, test_y))
13548 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13550 if (bad_element == EL_ROBOT && player->is_moving)
13551 continue; // robot does not kill player if he is moving
13553 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13555 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13556 continue; // center and border element do not touch
13564 else if (test_element == EL_PENGUIN)
13574 if (kill_x != -1 || kill_y != -1)
13576 if (IS_PLAYER(kill_x, kill_y))
13578 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13580 if (player->shield_deadly_time_left > 0 &&
13581 !IS_INDESTRUCTIBLE(bad_element))
13582 Bang(bad_x, bad_y);
13583 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13584 KillPlayer(player);
13587 Bang(kill_x, kill_y);
13591 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13593 int bad_element = Tile[bad_x][bad_y];
13594 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13595 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13596 int test_x = bad_x + dx, test_y = bad_y + dy;
13597 int test_move_dir, test_element;
13598 int kill_x = -1, kill_y = -1;
13600 if (!IN_LEV_FIELD(test_x, test_y))
13604 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13606 test_element = Tile[test_x][test_y];
13608 if (test_move_dir != bad_move_dir)
13610 // good thing can be player or penguin that does not move away
13611 if (IS_PLAYER(test_x, test_y))
13613 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13615 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13616 player as being hit when he is moving towards the bad thing, because
13617 the "get hit by" condition would be lost after the player stops) */
13618 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13619 return; // player moves away from bad thing
13624 else if (test_element == EL_PENGUIN)
13631 if (kill_x != -1 || kill_y != -1)
13633 if (IS_PLAYER(kill_x, kill_y))
13635 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13637 if (player->shield_deadly_time_left > 0 &&
13638 !IS_INDESTRUCTIBLE(bad_element))
13639 Bang(bad_x, bad_y);
13640 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13641 KillPlayer(player);
13644 Bang(kill_x, kill_y);
13648 void TestIfPlayerTouchesBadThing(int x, int y)
13650 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13653 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13655 TestIfGoodThingHitsBadThing(x, y, move_dir);
13658 void TestIfBadThingTouchesPlayer(int x, int y)
13660 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13663 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13665 TestIfBadThingHitsGoodThing(x, y, move_dir);
13668 void TestIfFriendTouchesBadThing(int x, int y)
13670 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13673 void TestIfBadThingTouchesFriend(int x, int y)
13675 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13678 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13680 int i, kill_x = bad_x, kill_y = bad_y;
13681 static int xy[4][2] =
13689 for (i = 0; i < NUM_DIRECTIONS; i++)
13693 x = bad_x + xy[i][0];
13694 y = bad_y + xy[i][1];
13695 if (!IN_LEV_FIELD(x, y))
13698 element = Tile[x][y];
13699 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13700 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13708 if (kill_x != bad_x || kill_y != bad_y)
13709 Bang(bad_x, bad_y);
13712 void KillPlayer(struct PlayerInfo *player)
13714 int jx = player->jx, jy = player->jy;
13716 if (!player->active)
13720 Debug("game:playing:KillPlayer",
13721 "0: killed == %d, active == %d, reanimated == %d",
13722 player->killed, player->active, player->reanimated);
13725 /* the following code was introduced to prevent an infinite loop when calling
13727 -> CheckTriggeredElementChangeExt()
13728 -> ExecuteCustomElementAction()
13730 -> (infinitely repeating the above sequence of function calls)
13731 which occurs when killing the player while having a CE with the setting
13732 "kill player X when explosion of <player X>"; the solution using a new
13733 field "player->killed" was chosen for backwards compatibility, although
13734 clever use of the fields "player->active" etc. would probably also work */
13736 if (player->killed)
13740 player->killed = TRUE;
13742 // remove accessible field at the player's position
13743 Tile[jx][jy] = EL_EMPTY;
13745 // deactivate shield (else Bang()/Explode() would not work right)
13746 player->shield_normal_time_left = 0;
13747 player->shield_deadly_time_left = 0;
13750 Debug("game:playing:KillPlayer",
13751 "1: killed == %d, active == %d, reanimated == %d",
13752 player->killed, player->active, player->reanimated);
13758 Debug("game:playing:KillPlayer",
13759 "2: killed == %d, active == %d, reanimated == %d",
13760 player->killed, player->active, player->reanimated);
13763 if (player->reanimated) // killed player may have been reanimated
13764 player->killed = player->reanimated = FALSE;
13766 BuryPlayer(player);
13769 static void KillPlayerUnlessEnemyProtected(int x, int y)
13771 if (!PLAYER_ENEMY_PROTECTED(x, y))
13772 KillPlayer(PLAYERINFO(x, y));
13775 static void KillPlayerUnlessExplosionProtected(int x, int y)
13777 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13778 KillPlayer(PLAYERINFO(x, y));
13781 void BuryPlayer(struct PlayerInfo *player)
13783 int jx = player->jx, jy = player->jy;
13785 if (!player->active)
13788 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13789 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13791 RemovePlayer(player);
13793 player->buried = TRUE;
13795 if (game.all_players_gone)
13796 game.GameOver = TRUE;
13799 void RemovePlayer(struct PlayerInfo *player)
13801 int jx = player->jx, jy = player->jy;
13802 int i, found = FALSE;
13804 player->present = FALSE;
13805 player->active = FALSE;
13807 // required for some CE actions (even if the player is not active anymore)
13808 player->MovPos = 0;
13810 if (!ExplodeField[jx][jy])
13811 StorePlayer[jx][jy] = 0;
13813 if (player->is_moving)
13814 TEST_DrawLevelField(player->last_jx, player->last_jy);
13816 for (i = 0; i < MAX_PLAYERS; i++)
13817 if (stored_player[i].active)
13822 game.all_players_gone = TRUE;
13823 game.GameOver = TRUE;
13826 game.exit_x = game.robot_wheel_x = jx;
13827 game.exit_y = game.robot_wheel_y = jy;
13830 void ExitPlayer(struct PlayerInfo *player)
13832 DrawPlayer(player); // needed here only to cleanup last field
13833 RemovePlayer(player);
13835 if (game.players_still_needed > 0)
13836 game.players_still_needed--;
13839 static void SetFieldForSnapping(int x, int y, int element, int direction,
13840 int player_index_bit)
13842 struct ElementInfo *ei = &element_info[element];
13843 int direction_bit = MV_DIR_TO_BIT(direction);
13844 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13845 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13846 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13848 Tile[x][y] = EL_ELEMENT_SNAPPING;
13849 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13850 MovDir[x][y] = direction;
13851 Store[x][y] = element;
13852 Store2[x][y] = player_index_bit;
13854 ResetGfxAnimation(x, y);
13856 GfxElement[x][y] = element;
13857 GfxAction[x][y] = action;
13858 GfxDir[x][y] = direction;
13859 GfxFrame[x][y] = -1;
13862 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13863 int player_index_bit)
13865 TestIfElementTouchesCustomElement(x, y); // for empty space
13867 if (level.finish_dig_collect)
13869 int dig_side = MV_DIR_OPPOSITE(direction);
13871 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13872 player_index_bit, dig_side);
13877 =============================================================================
13878 checkDiagonalPushing()
13879 -----------------------------------------------------------------------------
13880 check if diagonal input device direction results in pushing of object
13881 (by checking if the alternative direction is walkable, diggable, ...)
13882 =============================================================================
13885 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13886 int x, int y, int real_dx, int real_dy)
13888 int jx, jy, dx, dy, xx, yy;
13890 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13893 // diagonal direction: check alternative direction
13898 xx = jx + (dx == 0 ? real_dx : 0);
13899 yy = jy + (dy == 0 ? real_dy : 0);
13901 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13905 =============================================================================
13907 -----------------------------------------------------------------------------
13908 x, y: field next to player (non-diagonal) to try to dig to
13909 real_dx, real_dy: direction as read from input device (can be diagonal)
13910 =============================================================================
13913 static int DigField(struct PlayerInfo *player,
13914 int oldx, int oldy, int x, int y,
13915 int real_dx, int real_dy, int mode)
13917 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13918 boolean player_was_pushing = player->is_pushing;
13919 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13920 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13921 int jx = oldx, jy = oldy;
13922 int dx = x - jx, dy = y - jy;
13923 int nextx = x + dx, nexty = y + dy;
13924 int move_direction = (dx == -1 ? MV_LEFT :
13925 dx == +1 ? MV_RIGHT :
13927 dy == +1 ? MV_DOWN : MV_NONE);
13928 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13929 int dig_side = MV_DIR_OPPOSITE(move_direction);
13930 int old_element = Tile[jx][jy];
13931 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13934 if (is_player) // function can also be called by EL_PENGUIN
13936 if (player->MovPos == 0)
13938 player->is_digging = FALSE;
13939 player->is_collecting = FALSE;
13942 if (player->MovPos == 0) // last pushing move finished
13943 player->is_pushing = FALSE;
13945 if (mode == DF_NO_PUSH) // player just stopped pushing
13947 player->is_switching = FALSE;
13948 player->push_delay = -1;
13950 return MP_NO_ACTION;
13954 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13955 old_element = Back[jx][jy];
13957 // in case of element dropped at player position, check background
13958 else if (Back[jx][jy] != EL_EMPTY &&
13959 game.engine_version >= VERSION_IDENT(2,2,0,0))
13960 old_element = Back[jx][jy];
13962 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13963 return MP_NO_ACTION; // field has no opening in this direction
13965 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13966 return MP_NO_ACTION; // field has no opening in this direction
13968 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13972 Tile[jx][jy] = player->artwork_element;
13973 InitMovingField(jx, jy, MV_DOWN);
13974 Store[jx][jy] = EL_ACID;
13975 ContinueMoving(jx, jy);
13976 BuryPlayer(player);
13978 return MP_DONT_RUN_INTO;
13981 if (player_can_move && DONT_RUN_INTO(element))
13983 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13985 return MP_DONT_RUN_INTO;
13988 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13989 return MP_NO_ACTION;
13991 collect_count = element_info[element].collect_count_initial;
13993 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13994 return MP_NO_ACTION;
13996 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13997 player_can_move = player_can_move_or_snap;
13999 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14000 game.engine_version >= VERSION_IDENT(2,2,0,0))
14002 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14003 player->index_bit, dig_side);
14004 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14005 player->index_bit, dig_side);
14007 if (element == EL_DC_LANDMINE)
14010 if (Tile[x][y] != element) // field changed by snapping
14013 return MP_NO_ACTION;
14016 if (player->gravity && is_player && !player->is_auto_moving &&
14017 canFallDown(player) && move_direction != MV_DOWN &&
14018 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14019 return MP_NO_ACTION; // player cannot walk here due to gravity
14021 if (player_can_move &&
14022 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14024 int sound_element = SND_ELEMENT(element);
14025 int sound_action = ACTION_WALKING;
14027 if (IS_RND_GATE(element))
14029 if (!player->key[RND_GATE_NR(element)])
14030 return MP_NO_ACTION;
14032 else if (IS_RND_GATE_GRAY(element))
14034 if (!player->key[RND_GATE_GRAY_NR(element)])
14035 return MP_NO_ACTION;
14037 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14039 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14040 return MP_NO_ACTION;
14042 else if (element == EL_EXIT_OPEN ||
14043 element == EL_EM_EXIT_OPEN ||
14044 element == EL_EM_EXIT_OPENING ||
14045 element == EL_STEEL_EXIT_OPEN ||
14046 element == EL_EM_STEEL_EXIT_OPEN ||
14047 element == EL_EM_STEEL_EXIT_OPENING ||
14048 element == EL_SP_EXIT_OPEN ||
14049 element == EL_SP_EXIT_OPENING)
14051 sound_action = ACTION_PASSING; // player is passing exit
14053 else if (element == EL_EMPTY)
14055 sound_action = ACTION_MOVING; // nothing to walk on
14058 // play sound from background or player, whatever is available
14059 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14060 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14062 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14064 else if (player_can_move &&
14065 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14067 if (!ACCESS_FROM(element, opposite_direction))
14068 return MP_NO_ACTION; // field not accessible from this direction
14070 if (CAN_MOVE(element)) // only fixed elements can be passed!
14071 return MP_NO_ACTION;
14073 if (IS_EM_GATE(element))
14075 if (!player->key[EM_GATE_NR(element)])
14076 return MP_NO_ACTION;
14078 else if (IS_EM_GATE_GRAY(element))
14080 if (!player->key[EM_GATE_GRAY_NR(element)])
14081 return MP_NO_ACTION;
14083 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14085 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14086 return MP_NO_ACTION;
14088 else if (IS_EMC_GATE(element))
14090 if (!player->key[EMC_GATE_NR(element)])
14091 return MP_NO_ACTION;
14093 else if (IS_EMC_GATE_GRAY(element))
14095 if (!player->key[EMC_GATE_GRAY_NR(element)])
14096 return MP_NO_ACTION;
14098 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14100 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14101 return MP_NO_ACTION;
14103 else if (element == EL_DC_GATE_WHITE ||
14104 element == EL_DC_GATE_WHITE_GRAY ||
14105 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14107 if (player->num_white_keys == 0)
14108 return MP_NO_ACTION;
14110 player->num_white_keys--;
14112 else if (IS_SP_PORT(element))
14114 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14115 element == EL_SP_GRAVITY_PORT_RIGHT ||
14116 element == EL_SP_GRAVITY_PORT_UP ||
14117 element == EL_SP_GRAVITY_PORT_DOWN)
14118 player->gravity = !player->gravity;
14119 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14120 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14121 element == EL_SP_GRAVITY_ON_PORT_UP ||
14122 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14123 player->gravity = TRUE;
14124 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14125 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14126 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14127 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14128 player->gravity = FALSE;
14131 // automatically move to the next field with double speed
14132 player->programmed_action = move_direction;
14134 if (player->move_delay_reset_counter == 0)
14136 player->move_delay_reset_counter = 2; // two double speed steps
14138 DOUBLE_PLAYER_SPEED(player);
14141 PlayLevelSoundAction(x, y, ACTION_PASSING);
14143 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14147 if (mode != DF_SNAP)
14149 GfxElement[x][y] = GFX_ELEMENT(element);
14150 player->is_digging = TRUE;
14153 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14155 // use old behaviour for old levels (digging)
14156 if (!level.finish_dig_collect)
14158 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14159 player->index_bit, dig_side);
14161 // if digging triggered player relocation, finish digging tile
14162 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14163 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14166 if (mode == DF_SNAP)
14168 if (level.block_snap_field)
14169 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14171 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14173 // use old behaviour for old levels (snapping)
14174 if (!level.finish_dig_collect)
14175 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14176 player->index_bit, dig_side);
14179 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14183 if (is_player && mode != DF_SNAP)
14185 GfxElement[x][y] = element;
14186 player->is_collecting = TRUE;
14189 if (element == EL_SPEED_PILL)
14191 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14193 else if (element == EL_EXTRA_TIME && level.time > 0)
14195 TimeLeft += level.extra_time;
14197 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14199 DisplayGameControlValues();
14201 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14203 player->shield_normal_time_left += level.shield_normal_time;
14204 if (element == EL_SHIELD_DEADLY)
14205 player->shield_deadly_time_left += level.shield_deadly_time;
14207 else if (element == EL_DYNAMITE ||
14208 element == EL_EM_DYNAMITE ||
14209 element == EL_SP_DISK_RED)
14211 if (player->inventory_size < MAX_INVENTORY_SIZE)
14212 player->inventory_element[player->inventory_size++] = element;
14214 DrawGameDoorValues();
14216 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14218 player->dynabomb_count++;
14219 player->dynabombs_left++;
14221 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14223 player->dynabomb_size++;
14225 else if (element == EL_DYNABOMB_INCREASE_POWER)
14227 player->dynabomb_xl = TRUE;
14229 else if (IS_KEY(element))
14231 player->key[KEY_NR(element)] = TRUE;
14233 DrawGameDoorValues();
14235 else if (element == EL_DC_KEY_WHITE)
14237 player->num_white_keys++;
14239 // display white keys?
14240 // DrawGameDoorValues();
14242 else if (IS_ENVELOPE(element))
14244 player->show_envelope = element;
14246 else if (element == EL_EMC_LENSES)
14248 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14250 RedrawAllInvisibleElementsForLenses();
14252 else if (element == EL_EMC_MAGNIFIER)
14254 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14256 RedrawAllInvisibleElementsForMagnifier();
14258 else if (IS_DROPPABLE(element) ||
14259 IS_THROWABLE(element)) // can be collected and dropped
14263 if (collect_count == 0)
14264 player->inventory_infinite_element = element;
14266 for (i = 0; i < collect_count; i++)
14267 if (player->inventory_size < MAX_INVENTORY_SIZE)
14268 player->inventory_element[player->inventory_size++] = element;
14270 DrawGameDoorValues();
14272 else if (collect_count > 0)
14274 game.gems_still_needed -= collect_count;
14275 if (game.gems_still_needed < 0)
14276 game.gems_still_needed = 0;
14278 game.snapshot.collected_item = TRUE;
14280 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14282 DisplayGameControlValues();
14285 RaiseScoreElement(element);
14286 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14288 // use old behaviour for old levels (collecting)
14289 if (!level.finish_dig_collect && is_player)
14291 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14292 player->index_bit, dig_side);
14294 // if collecting triggered player relocation, finish collecting tile
14295 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14296 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14299 if (mode == DF_SNAP)
14301 if (level.block_snap_field)
14302 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14304 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14306 // use old behaviour for old levels (snapping)
14307 if (!level.finish_dig_collect)
14308 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14309 player->index_bit, dig_side);
14312 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14314 if (mode == DF_SNAP && element != EL_BD_ROCK)
14315 return MP_NO_ACTION;
14317 if (CAN_FALL(element) && dy)
14318 return MP_NO_ACTION;
14320 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14321 !(element == EL_SPRING && level.use_spring_bug))
14322 return MP_NO_ACTION;
14324 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14325 ((move_direction & MV_VERTICAL &&
14326 ((element_info[element].move_pattern & MV_LEFT &&
14327 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14328 (element_info[element].move_pattern & MV_RIGHT &&
14329 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14330 (move_direction & MV_HORIZONTAL &&
14331 ((element_info[element].move_pattern & MV_UP &&
14332 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14333 (element_info[element].move_pattern & MV_DOWN &&
14334 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14335 return MP_NO_ACTION;
14337 // do not push elements already moving away faster than player
14338 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14339 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14340 return MP_NO_ACTION;
14342 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14344 if (player->push_delay_value == -1 || !player_was_pushing)
14345 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14347 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14349 if (player->push_delay_value == -1)
14350 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14352 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14354 if (!player->is_pushing)
14355 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14358 player->is_pushing = TRUE;
14359 player->is_active = TRUE;
14361 if (!(IN_LEV_FIELD(nextx, nexty) &&
14362 (IS_FREE(nextx, nexty) ||
14363 (IS_SB_ELEMENT(element) &&
14364 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14365 (IS_CUSTOM_ELEMENT(element) &&
14366 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14367 return MP_NO_ACTION;
14369 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14370 return MP_NO_ACTION;
14372 if (player->push_delay == -1) // new pushing; restart delay
14373 player->push_delay = 0;
14375 if (player->push_delay < player->push_delay_value &&
14376 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14377 element != EL_SPRING && element != EL_BALLOON)
14379 // make sure that there is no move delay before next try to push
14380 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14381 player->move_delay = 0;
14383 return MP_NO_ACTION;
14386 if (IS_CUSTOM_ELEMENT(element) &&
14387 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14389 if (!DigFieldByCE(nextx, nexty, element))
14390 return MP_NO_ACTION;
14393 if (IS_SB_ELEMENT(element))
14395 boolean sokoban_task_solved = FALSE;
14397 if (element == EL_SOKOBAN_FIELD_FULL)
14399 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14401 IncrementSokobanFieldsNeeded();
14402 IncrementSokobanObjectsNeeded();
14405 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14407 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14409 DecrementSokobanFieldsNeeded();
14410 DecrementSokobanObjectsNeeded();
14412 // sokoban object was pushed from empty field to sokoban field
14413 if (Back[x][y] == EL_EMPTY)
14414 sokoban_task_solved = TRUE;
14417 Tile[x][y] = EL_SOKOBAN_OBJECT;
14419 if (Back[x][y] == Back[nextx][nexty])
14420 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14421 else if (Back[x][y] != 0)
14422 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14425 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14428 if (sokoban_task_solved &&
14429 game.sokoban_fields_still_needed == 0 &&
14430 game.sokoban_objects_still_needed == 0 &&
14431 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14433 game.players_still_needed = 0;
14437 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14441 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14443 InitMovingField(x, y, move_direction);
14444 GfxAction[x][y] = ACTION_PUSHING;
14446 if (mode == DF_SNAP)
14447 ContinueMoving(x, y);
14449 MovPos[x][y] = (dx != 0 ? dx : dy);
14451 Pushed[x][y] = TRUE;
14452 Pushed[nextx][nexty] = TRUE;
14454 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14455 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14457 player->push_delay_value = -1; // get new value later
14459 // check for element change _after_ element has been pushed
14460 if (game.use_change_when_pushing_bug)
14462 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14463 player->index_bit, dig_side);
14464 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14465 player->index_bit, dig_side);
14468 else if (IS_SWITCHABLE(element))
14470 if (PLAYER_SWITCHING(player, x, y))
14472 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14473 player->index_bit, dig_side);
14478 player->is_switching = TRUE;
14479 player->switch_x = x;
14480 player->switch_y = y;
14482 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14484 if (element == EL_ROBOT_WHEEL)
14486 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14488 game.robot_wheel_x = x;
14489 game.robot_wheel_y = y;
14490 game.robot_wheel_active = TRUE;
14492 TEST_DrawLevelField(x, y);
14494 else if (element == EL_SP_TERMINAL)
14498 SCAN_PLAYFIELD(xx, yy)
14500 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14504 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14506 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14508 ResetGfxAnimation(xx, yy);
14509 TEST_DrawLevelField(xx, yy);
14513 else if (IS_BELT_SWITCH(element))
14515 ToggleBeltSwitch(x, y);
14517 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14518 element == EL_SWITCHGATE_SWITCH_DOWN ||
14519 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14520 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14522 ToggleSwitchgateSwitch(x, y);
14524 else if (element == EL_LIGHT_SWITCH ||
14525 element == EL_LIGHT_SWITCH_ACTIVE)
14527 ToggleLightSwitch(x, y);
14529 else if (element == EL_TIMEGATE_SWITCH ||
14530 element == EL_DC_TIMEGATE_SWITCH)
14532 ActivateTimegateSwitch(x, y);
14534 else if (element == EL_BALLOON_SWITCH_LEFT ||
14535 element == EL_BALLOON_SWITCH_RIGHT ||
14536 element == EL_BALLOON_SWITCH_UP ||
14537 element == EL_BALLOON_SWITCH_DOWN ||
14538 element == EL_BALLOON_SWITCH_NONE ||
14539 element == EL_BALLOON_SWITCH_ANY)
14541 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14542 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14543 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14544 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14545 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14548 else if (element == EL_LAMP)
14550 Tile[x][y] = EL_LAMP_ACTIVE;
14551 game.lights_still_needed--;
14553 ResetGfxAnimation(x, y);
14554 TEST_DrawLevelField(x, y);
14556 else if (element == EL_TIME_ORB_FULL)
14558 Tile[x][y] = EL_TIME_ORB_EMPTY;
14560 if (level.time > 0 || level.use_time_orb_bug)
14562 TimeLeft += level.time_orb_time;
14563 game.no_time_limit = FALSE;
14565 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14567 DisplayGameControlValues();
14570 ResetGfxAnimation(x, y);
14571 TEST_DrawLevelField(x, y);
14573 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14574 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14578 game.ball_active = !game.ball_active;
14580 SCAN_PLAYFIELD(xx, yy)
14582 int e = Tile[xx][yy];
14584 if (game.ball_active)
14586 if (e == EL_EMC_MAGIC_BALL)
14587 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14588 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14589 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14593 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14594 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14595 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14596 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14601 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14602 player->index_bit, dig_side);
14604 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14605 player->index_bit, dig_side);
14607 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14608 player->index_bit, dig_side);
14614 if (!PLAYER_SWITCHING(player, x, y))
14616 player->is_switching = TRUE;
14617 player->switch_x = x;
14618 player->switch_y = y;
14620 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14621 player->index_bit, dig_side);
14622 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14623 player->index_bit, dig_side);
14625 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14626 player->index_bit, dig_side);
14627 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14628 player->index_bit, dig_side);
14631 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14632 player->index_bit, dig_side);
14633 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14634 player->index_bit, dig_side);
14636 return MP_NO_ACTION;
14639 player->push_delay = -1;
14641 if (is_player) // function can also be called by EL_PENGUIN
14643 if (Tile[x][y] != element) // really digged/collected something
14645 player->is_collecting = !player->is_digging;
14646 player->is_active = TRUE;
14648 player->last_removed_element = element;
14655 static boolean DigFieldByCE(int x, int y, int digging_element)
14657 int element = Tile[x][y];
14659 if (!IS_FREE(x, y))
14661 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14662 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14665 // no element can dig solid indestructible elements
14666 if (IS_INDESTRUCTIBLE(element) &&
14667 !IS_DIGGABLE(element) &&
14668 !IS_COLLECTIBLE(element))
14671 if (AmoebaNr[x][y] &&
14672 (element == EL_AMOEBA_FULL ||
14673 element == EL_BD_AMOEBA ||
14674 element == EL_AMOEBA_GROWING))
14676 AmoebaCnt[AmoebaNr[x][y]]--;
14677 AmoebaCnt2[AmoebaNr[x][y]]--;
14680 if (IS_MOVING(x, y))
14681 RemoveMovingField(x, y);
14685 TEST_DrawLevelField(x, y);
14688 // if digged element was about to explode, prevent the explosion
14689 ExplodeField[x][y] = EX_TYPE_NONE;
14691 PlayLevelSoundAction(x, y, action);
14694 Store[x][y] = EL_EMPTY;
14696 // this makes it possible to leave the removed element again
14697 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14698 Store[x][y] = element;
14703 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14705 int jx = player->jx, jy = player->jy;
14706 int x = jx + dx, y = jy + dy;
14707 int snap_direction = (dx == -1 ? MV_LEFT :
14708 dx == +1 ? MV_RIGHT :
14710 dy == +1 ? MV_DOWN : MV_NONE);
14711 boolean can_continue_snapping = (level.continuous_snapping &&
14712 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14714 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14717 if (!player->active || !IN_LEV_FIELD(x, y))
14725 if (player->MovPos == 0)
14726 player->is_pushing = FALSE;
14728 player->is_snapping = FALSE;
14730 if (player->MovPos == 0)
14732 player->is_moving = FALSE;
14733 player->is_digging = FALSE;
14734 player->is_collecting = FALSE;
14740 // prevent snapping with already pressed snap key when not allowed
14741 if (player->is_snapping && !can_continue_snapping)
14744 player->MovDir = snap_direction;
14746 if (player->MovPos == 0)
14748 player->is_moving = FALSE;
14749 player->is_digging = FALSE;
14750 player->is_collecting = FALSE;
14753 player->is_dropping = FALSE;
14754 player->is_dropping_pressed = FALSE;
14755 player->drop_pressed_delay = 0;
14757 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14760 player->is_snapping = TRUE;
14761 player->is_active = TRUE;
14763 if (player->MovPos == 0)
14765 player->is_moving = FALSE;
14766 player->is_digging = FALSE;
14767 player->is_collecting = FALSE;
14770 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14771 TEST_DrawLevelField(player->last_jx, player->last_jy);
14773 TEST_DrawLevelField(x, y);
14778 static boolean DropElement(struct PlayerInfo *player)
14780 int old_element, new_element;
14781 int dropx = player->jx, dropy = player->jy;
14782 int drop_direction = player->MovDir;
14783 int drop_side = drop_direction;
14784 int drop_element = get_next_dropped_element(player);
14786 /* do not drop an element on top of another element; when holding drop key
14787 pressed without moving, dropped element must move away before the next
14788 element can be dropped (this is especially important if the next element
14789 is dynamite, which can be placed on background for historical reasons) */
14790 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14793 if (IS_THROWABLE(drop_element))
14795 dropx += GET_DX_FROM_DIR(drop_direction);
14796 dropy += GET_DY_FROM_DIR(drop_direction);
14798 if (!IN_LEV_FIELD(dropx, dropy))
14802 old_element = Tile[dropx][dropy]; // old element at dropping position
14803 new_element = drop_element; // default: no change when dropping
14805 // check if player is active, not moving and ready to drop
14806 if (!player->active || player->MovPos || player->drop_delay > 0)
14809 // check if player has anything that can be dropped
14810 if (new_element == EL_UNDEFINED)
14813 // only set if player has anything that can be dropped
14814 player->is_dropping_pressed = TRUE;
14816 // check if drop key was pressed long enough for EM style dynamite
14817 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14820 // check if anything can be dropped at the current position
14821 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14824 // collected custom elements can only be dropped on empty fields
14825 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14828 if (old_element != EL_EMPTY)
14829 Back[dropx][dropy] = old_element; // store old element on this field
14831 ResetGfxAnimation(dropx, dropy);
14832 ResetRandomAnimationValue(dropx, dropy);
14834 if (player->inventory_size > 0 ||
14835 player->inventory_infinite_element != EL_UNDEFINED)
14837 if (player->inventory_size > 0)
14839 player->inventory_size--;
14841 DrawGameDoorValues();
14843 if (new_element == EL_DYNAMITE)
14844 new_element = EL_DYNAMITE_ACTIVE;
14845 else if (new_element == EL_EM_DYNAMITE)
14846 new_element = EL_EM_DYNAMITE_ACTIVE;
14847 else if (new_element == EL_SP_DISK_RED)
14848 new_element = EL_SP_DISK_RED_ACTIVE;
14851 Tile[dropx][dropy] = new_element;
14853 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14854 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14855 el2img(Tile[dropx][dropy]), 0);
14857 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14859 // needed if previous element just changed to "empty" in the last frame
14860 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14862 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14863 player->index_bit, drop_side);
14864 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14866 player->index_bit, drop_side);
14868 TestIfElementTouchesCustomElement(dropx, dropy);
14870 else // player is dropping a dyna bomb
14872 player->dynabombs_left--;
14874 Tile[dropx][dropy] = new_element;
14876 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14877 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14878 el2img(Tile[dropx][dropy]), 0);
14880 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14883 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14884 InitField_WithBug1(dropx, dropy, FALSE);
14886 new_element = Tile[dropx][dropy]; // element might have changed
14888 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14889 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14891 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14892 MovDir[dropx][dropy] = drop_direction;
14894 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14896 // do not cause impact style collision by dropping elements that can fall
14897 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14900 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14901 player->is_dropping = TRUE;
14903 player->drop_pressed_delay = 0;
14904 player->is_dropping_pressed = FALSE;
14906 player->drop_x = dropx;
14907 player->drop_y = dropy;
14912 // ----------------------------------------------------------------------------
14913 // game sound playing functions
14914 // ----------------------------------------------------------------------------
14916 static int *loop_sound_frame = NULL;
14917 static int *loop_sound_volume = NULL;
14919 void InitPlayLevelSound(void)
14921 int num_sounds = getSoundListSize();
14923 checked_free(loop_sound_frame);
14924 checked_free(loop_sound_volume);
14926 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14927 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14930 static void PlayLevelSound(int x, int y, int nr)
14932 int sx = SCREENX(x), sy = SCREENY(y);
14933 int volume, stereo_position;
14934 int max_distance = 8;
14935 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14937 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14938 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14941 if (!IN_LEV_FIELD(x, y) ||
14942 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14943 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14946 volume = SOUND_MAX_VOLUME;
14948 if (!IN_SCR_FIELD(sx, sy))
14950 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14951 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14953 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14956 stereo_position = (SOUND_MAX_LEFT +
14957 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14958 (SCR_FIELDX + 2 * max_distance));
14960 if (IS_LOOP_SOUND(nr))
14962 /* This assures that quieter loop sounds do not overwrite louder ones,
14963 while restarting sound volume comparison with each new game frame. */
14965 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14968 loop_sound_volume[nr] = volume;
14969 loop_sound_frame[nr] = FrameCounter;
14972 PlaySoundExt(nr, volume, stereo_position, type);
14975 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14977 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14978 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14979 y < LEVELY(BY1) ? LEVELY(BY1) :
14980 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14984 static void PlayLevelSoundAction(int x, int y, int action)
14986 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14989 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14991 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14993 if (sound_effect != SND_UNDEFINED)
14994 PlayLevelSound(x, y, sound_effect);
14997 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15000 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15002 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15003 PlayLevelSound(x, y, sound_effect);
15006 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15008 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15010 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15011 PlayLevelSound(x, y, sound_effect);
15014 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15016 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15018 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15019 StopSound(sound_effect);
15022 static int getLevelMusicNr(void)
15024 if (levelset.music[level_nr] != MUS_UNDEFINED)
15025 return levelset.music[level_nr]; // from config file
15027 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15030 static void FadeLevelSounds(void)
15035 static void FadeLevelMusic(void)
15037 int music_nr = getLevelMusicNr();
15038 char *curr_music = getCurrentlyPlayingMusicFilename();
15039 char *next_music = getMusicInfoEntryFilename(music_nr);
15041 if (!strEqual(curr_music, next_music))
15045 void FadeLevelSoundsAndMusic(void)
15051 static void PlayLevelMusic(void)
15053 int music_nr = getLevelMusicNr();
15054 char *curr_music = getCurrentlyPlayingMusicFilename();
15055 char *next_music = getMusicInfoEntryFilename(music_nr);
15057 if (!strEqual(curr_music, next_music))
15058 PlayMusicLoop(music_nr);
15061 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15063 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15065 int x = xx - offset;
15066 int y = yy - offset;
15071 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15075 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15079 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15083 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15087 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15091 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15095 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15098 case SOUND_android_clone:
15099 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15102 case SOUND_android_move:
15103 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15107 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15111 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15115 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15118 case SOUND_eater_eat:
15119 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15123 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15126 case SOUND_collect:
15127 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15130 case SOUND_diamond:
15131 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15135 // !!! CHECK THIS !!!
15137 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15139 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15143 case SOUND_wonderfall:
15144 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15148 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15152 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15156 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15160 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15164 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15168 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15172 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15179 case SOUND_exit_open:
15180 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15183 case SOUND_exit_leave:
15184 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15187 case SOUND_dynamite:
15188 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15192 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15196 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15200 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15204 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15208 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15212 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15216 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15221 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15223 int element = map_element_SP_to_RND(element_sp);
15224 int action = map_action_SP_to_RND(action_sp);
15225 int offset = (setup.sp_show_border_elements ? 0 : 1);
15226 int x = xx - offset;
15227 int y = yy - offset;
15229 PlayLevelSoundElementAction(x, y, element, action);
15232 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15234 int element = map_element_MM_to_RND(element_mm);
15235 int action = map_action_MM_to_RND(action_mm);
15237 int x = xx - offset;
15238 int y = yy - offset;
15240 if (!IS_MM_ELEMENT(element))
15241 element = EL_MM_DEFAULT;
15243 PlayLevelSoundElementAction(x, y, element, action);
15246 void PlaySound_MM(int sound_mm)
15248 int sound = map_sound_MM_to_RND(sound_mm);
15250 if (sound == SND_UNDEFINED)
15256 void PlaySoundLoop_MM(int sound_mm)
15258 int sound = map_sound_MM_to_RND(sound_mm);
15260 if (sound == SND_UNDEFINED)
15263 PlaySoundLoop(sound);
15266 void StopSound_MM(int sound_mm)
15268 int sound = map_sound_MM_to_RND(sound_mm);
15270 if (sound == SND_UNDEFINED)
15276 void RaiseScore(int value)
15278 game.score += value;
15280 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15282 DisplayGameControlValues();
15285 void RaiseScoreElement(int element)
15290 case EL_BD_DIAMOND:
15291 case EL_EMERALD_YELLOW:
15292 case EL_EMERALD_RED:
15293 case EL_EMERALD_PURPLE:
15294 case EL_SP_INFOTRON:
15295 RaiseScore(level.score[SC_EMERALD]);
15298 RaiseScore(level.score[SC_DIAMOND]);
15301 RaiseScore(level.score[SC_CRYSTAL]);
15304 RaiseScore(level.score[SC_PEARL]);
15307 case EL_BD_BUTTERFLY:
15308 case EL_SP_ELECTRON:
15309 RaiseScore(level.score[SC_BUG]);
15312 case EL_BD_FIREFLY:
15313 case EL_SP_SNIKSNAK:
15314 RaiseScore(level.score[SC_SPACESHIP]);
15317 case EL_DARK_YAMYAM:
15318 RaiseScore(level.score[SC_YAMYAM]);
15321 RaiseScore(level.score[SC_ROBOT]);
15324 RaiseScore(level.score[SC_PACMAN]);
15327 RaiseScore(level.score[SC_NUT]);
15330 case EL_EM_DYNAMITE:
15331 case EL_SP_DISK_RED:
15332 case EL_DYNABOMB_INCREASE_NUMBER:
15333 case EL_DYNABOMB_INCREASE_SIZE:
15334 case EL_DYNABOMB_INCREASE_POWER:
15335 RaiseScore(level.score[SC_DYNAMITE]);
15337 case EL_SHIELD_NORMAL:
15338 case EL_SHIELD_DEADLY:
15339 RaiseScore(level.score[SC_SHIELD]);
15341 case EL_EXTRA_TIME:
15342 RaiseScore(level.extra_time_score);
15356 case EL_DC_KEY_WHITE:
15357 RaiseScore(level.score[SC_KEY]);
15360 RaiseScore(element_info[element].collect_score);
15365 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15367 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15369 // closing door required in case of envelope style request dialogs
15372 // prevent short reactivation of overlay buttons while closing door
15373 SetOverlayActive(FALSE);
15375 CloseDoor(DOOR_CLOSE_1);
15378 if (network.enabled)
15379 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15383 FadeSkipNextFadeIn();
15385 SetGameStatus(GAME_MODE_MAIN);
15390 else // continue playing the game
15392 if (tape.playing && tape.deactivate_display)
15393 TapeDeactivateDisplayOff(TRUE);
15395 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15397 if (tape.playing && tape.deactivate_display)
15398 TapeDeactivateDisplayOn();
15402 void RequestQuitGame(boolean ask_if_really_quit)
15404 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15405 boolean skip_request = game.all_players_gone || quick_quit;
15407 RequestQuitGameExt(skip_request, quick_quit,
15408 "Do you really want to quit the game?");
15411 void RequestRestartGame(char *message)
15413 game.restart_game_message = NULL;
15415 boolean has_started_game = hasStartedNetworkGame();
15416 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15418 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15420 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15424 // needed in case of envelope request to close game panel
15425 CloseDoor(DOOR_CLOSE_1);
15427 SetGameStatus(GAME_MODE_MAIN);
15433 void CheckGameOver(void)
15435 static boolean last_game_over = FALSE;
15436 static int game_over_delay = 0;
15437 int game_over_delay_value = 50;
15438 boolean game_over = checkGameFailed();
15440 // do not handle game over if request dialog is already active
15441 if (game.request_active)
15444 // do not ask to play again if game was never actually played
15445 if (!game.GamePlayed)
15450 last_game_over = FALSE;
15451 game_over_delay = game_over_delay_value;
15456 if (game_over_delay > 0)
15463 if (last_game_over != game_over)
15464 game.restart_game_message = (hasStartedNetworkGame() ?
15465 "Game over! Play it again?" :
15468 last_game_over = game_over;
15471 boolean checkGameSolved(void)
15473 // set for all game engines if level was solved
15474 return game.LevelSolved_GameEnd;
15477 boolean checkGameFailed(void)
15479 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15480 return (game_em.game_over && !game_em.level_solved);
15481 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15482 return (game_sp.game_over && !game_sp.level_solved);
15483 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15484 return (game_mm.game_over && !game_mm.level_solved);
15485 else // GAME_ENGINE_TYPE_RND
15486 return (game.GameOver && !game.LevelSolved);
15489 boolean checkGameEnded(void)
15491 return (checkGameSolved() || checkGameFailed());
15495 // ----------------------------------------------------------------------------
15496 // random generator functions
15497 // ----------------------------------------------------------------------------
15499 unsigned int InitEngineRandom_RND(int seed)
15501 game.num_random_calls = 0;
15503 return InitEngineRandom(seed);
15506 unsigned int RND(int max)
15510 game.num_random_calls++;
15512 return GetEngineRandom(max);
15519 // ----------------------------------------------------------------------------
15520 // game engine snapshot handling functions
15521 // ----------------------------------------------------------------------------
15523 struct EngineSnapshotInfo
15525 // runtime values for custom element collect score
15526 int collect_score[NUM_CUSTOM_ELEMENTS];
15528 // runtime values for group element choice position
15529 int choice_pos[NUM_GROUP_ELEMENTS];
15531 // runtime values for belt position animations
15532 int belt_graphic[4][NUM_BELT_PARTS];
15533 int belt_anim_mode[4][NUM_BELT_PARTS];
15536 static struct EngineSnapshotInfo engine_snapshot_rnd;
15537 static char *snapshot_level_identifier = NULL;
15538 static int snapshot_level_nr = -1;
15540 static void SaveEngineSnapshotValues_RND(void)
15542 static int belt_base_active_element[4] =
15544 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15545 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15546 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15547 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15551 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15553 int element = EL_CUSTOM_START + i;
15555 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15558 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15560 int element = EL_GROUP_START + i;
15562 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15565 for (i = 0; i < 4; i++)
15567 for (j = 0; j < NUM_BELT_PARTS; j++)
15569 int element = belt_base_active_element[i] + j;
15570 int graphic = el2img(element);
15571 int anim_mode = graphic_info[graphic].anim_mode;
15573 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15574 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15579 static void LoadEngineSnapshotValues_RND(void)
15581 unsigned int num_random_calls = game.num_random_calls;
15584 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15586 int element = EL_CUSTOM_START + i;
15588 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15591 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15593 int element = EL_GROUP_START + i;
15595 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15598 for (i = 0; i < 4; i++)
15600 for (j = 0; j < NUM_BELT_PARTS; j++)
15602 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15603 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15605 graphic_info[graphic].anim_mode = anim_mode;
15609 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15611 InitRND(tape.random_seed);
15612 for (i = 0; i < num_random_calls; i++)
15616 if (game.num_random_calls != num_random_calls)
15618 Error("number of random calls out of sync");
15619 Error("number of random calls should be %d", num_random_calls);
15620 Error("number of random calls is %d", game.num_random_calls);
15622 Fail("this should not happen -- please debug");
15626 void FreeEngineSnapshotSingle(void)
15628 FreeSnapshotSingle();
15630 setString(&snapshot_level_identifier, NULL);
15631 snapshot_level_nr = -1;
15634 void FreeEngineSnapshotList(void)
15636 FreeSnapshotList();
15639 static ListNode *SaveEngineSnapshotBuffers(void)
15641 ListNode *buffers = NULL;
15643 // copy some special values to a structure better suited for the snapshot
15645 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15646 SaveEngineSnapshotValues_RND();
15647 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15648 SaveEngineSnapshotValues_EM();
15649 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15650 SaveEngineSnapshotValues_SP(&buffers);
15651 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15652 SaveEngineSnapshotValues_MM(&buffers);
15654 // save values stored in special snapshot structure
15656 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15657 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15658 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15660 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15662 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15665 // save further RND engine values
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15683 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15692 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15698 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15699 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15700 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15701 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15702 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15703 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15705 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15706 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15708 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15709 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15710 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15712 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15713 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15715 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15716 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15717 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15718 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15719 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15721 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15722 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15725 ListNode *node = engine_snapshot_list_rnd;
15728 while (node != NULL)
15730 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15735 Debug("game:playing:SaveEngineSnapshotBuffers",
15736 "size of engine snapshot: %d bytes", num_bytes);
15742 void SaveEngineSnapshotSingle(void)
15744 ListNode *buffers = SaveEngineSnapshotBuffers();
15746 // finally save all snapshot buffers to single snapshot
15747 SaveSnapshotSingle(buffers);
15749 // save level identification information
15750 setString(&snapshot_level_identifier, leveldir_current->identifier);
15751 snapshot_level_nr = level_nr;
15754 boolean CheckSaveEngineSnapshotToList(void)
15756 boolean save_snapshot =
15757 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15758 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15759 game.snapshot.changed_action) ||
15760 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15761 game.snapshot.collected_item));
15763 game.snapshot.changed_action = FALSE;
15764 game.snapshot.collected_item = FALSE;
15765 game.snapshot.save_snapshot = save_snapshot;
15767 return save_snapshot;
15770 void SaveEngineSnapshotToList(void)
15772 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15776 ListNode *buffers = SaveEngineSnapshotBuffers();
15778 // finally save all snapshot buffers to snapshot list
15779 SaveSnapshotToList(buffers);
15782 void SaveEngineSnapshotToListInitial(void)
15784 FreeEngineSnapshotList();
15786 SaveEngineSnapshotToList();
15789 static void LoadEngineSnapshotValues(void)
15791 // restore special values from snapshot structure
15793 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15794 LoadEngineSnapshotValues_RND();
15795 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15796 LoadEngineSnapshotValues_EM();
15797 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15798 LoadEngineSnapshotValues_SP();
15799 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15800 LoadEngineSnapshotValues_MM();
15803 void LoadEngineSnapshotSingle(void)
15805 LoadSnapshotSingle();
15807 LoadEngineSnapshotValues();
15810 static void LoadEngineSnapshot_Undo(int steps)
15812 LoadSnapshotFromList_Older(steps);
15814 LoadEngineSnapshotValues();
15817 static void LoadEngineSnapshot_Redo(int steps)
15819 LoadSnapshotFromList_Newer(steps);
15821 LoadEngineSnapshotValues();
15824 boolean CheckEngineSnapshotSingle(void)
15826 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15827 snapshot_level_nr == level_nr);
15830 boolean CheckEngineSnapshotList(void)
15832 return CheckSnapshotList();
15836 // ---------- new game button stuff -------------------------------------------
15843 boolean *setup_value;
15844 boolean allowed_on_tape;
15845 boolean is_touch_button;
15847 } gamebutton_info[NUM_GAME_BUTTONS] =
15850 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15851 GAME_CTRL_ID_STOP, NULL,
15852 TRUE, FALSE, "stop game"
15855 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15856 GAME_CTRL_ID_PAUSE, NULL,
15857 TRUE, FALSE, "pause game"
15860 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15861 GAME_CTRL_ID_PLAY, NULL,
15862 TRUE, FALSE, "play game"
15865 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15866 GAME_CTRL_ID_UNDO, NULL,
15867 TRUE, FALSE, "undo step"
15870 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15871 GAME_CTRL_ID_REDO, NULL,
15872 TRUE, FALSE, "redo step"
15875 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15876 GAME_CTRL_ID_SAVE, NULL,
15877 TRUE, FALSE, "save game"
15880 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15881 GAME_CTRL_ID_PAUSE2, NULL,
15882 TRUE, FALSE, "pause game"
15885 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15886 GAME_CTRL_ID_LOAD, NULL,
15887 TRUE, FALSE, "load game"
15890 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15891 GAME_CTRL_ID_PANEL_STOP, NULL,
15892 FALSE, FALSE, "stop game"
15895 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15896 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15897 FALSE, FALSE, "pause game"
15900 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15901 GAME_CTRL_ID_PANEL_PLAY, NULL,
15902 FALSE, FALSE, "play game"
15905 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15906 GAME_CTRL_ID_TOUCH_STOP, NULL,
15907 FALSE, TRUE, "stop game"
15910 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15911 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15912 FALSE, TRUE, "pause game"
15915 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15916 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15917 TRUE, FALSE, "background music on/off"
15920 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15921 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15922 TRUE, FALSE, "sound loops on/off"
15925 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15926 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15927 TRUE, FALSE, "normal sounds on/off"
15930 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15931 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15932 FALSE, FALSE, "background music on/off"
15935 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15936 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15937 FALSE, FALSE, "sound loops on/off"
15940 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15941 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15942 FALSE, FALSE, "normal sounds on/off"
15946 void CreateGameButtons(void)
15950 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15952 int graphic = gamebutton_info[i].graphic;
15953 struct GraphicInfo *gfx = &graphic_info[graphic];
15954 struct XY *pos = gamebutton_info[i].pos;
15955 struct GadgetInfo *gi;
15958 unsigned int event_mask;
15959 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15960 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15961 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15962 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15963 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15964 int gd_x = gfx->src_x;
15965 int gd_y = gfx->src_y;
15966 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15967 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15968 int gd_xa = gfx->src_x + gfx->active_xoffset;
15969 int gd_ya = gfx->src_y + gfx->active_yoffset;
15970 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15971 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15972 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15973 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15976 if (gfx->bitmap == NULL)
15978 game_gadget[id] = NULL;
15983 if (id == GAME_CTRL_ID_STOP ||
15984 id == GAME_CTRL_ID_PANEL_STOP ||
15985 id == GAME_CTRL_ID_TOUCH_STOP ||
15986 id == GAME_CTRL_ID_PLAY ||
15987 id == GAME_CTRL_ID_PANEL_PLAY ||
15988 id == GAME_CTRL_ID_SAVE ||
15989 id == GAME_CTRL_ID_LOAD)
15991 button_type = GD_TYPE_NORMAL_BUTTON;
15993 event_mask = GD_EVENT_RELEASED;
15995 else if (id == GAME_CTRL_ID_UNDO ||
15996 id == GAME_CTRL_ID_REDO)
15998 button_type = GD_TYPE_NORMAL_BUTTON;
16000 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16004 button_type = GD_TYPE_CHECK_BUTTON;
16005 checked = (gamebutton_info[i].setup_value != NULL ?
16006 *gamebutton_info[i].setup_value : FALSE);
16007 event_mask = GD_EVENT_PRESSED;
16010 gi = CreateGadget(GDI_CUSTOM_ID, id,
16011 GDI_IMAGE_ID, graphic,
16012 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16015 GDI_WIDTH, gfx->width,
16016 GDI_HEIGHT, gfx->height,
16017 GDI_TYPE, button_type,
16018 GDI_STATE, GD_BUTTON_UNPRESSED,
16019 GDI_CHECKED, checked,
16020 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16021 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16022 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16023 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16024 GDI_DIRECT_DRAW, FALSE,
16025 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16026 GDI_EVENT_MASK, event_mask,
16027 GDI_CALLBACK_ACTION, HandleGameButtons,
16031 Fail("cannot create gadget");
16033 game_gadget[id] = gi;
16037 void FreeGameButtons(void)
16041 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16042 FreeGadget(game_gadget[i]);
16045 static void UnmapGameButtonsAtSamePosition(int id)
16049 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16051 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16052 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16053 UnmapGadget(game_gadget[i]);
16056 static void UnmapGameButtonsAtSamePosition_All(void)
16058 if (setup.show_snapshot_buttons)
16060 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16061 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16062 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16066 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16067 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16068 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16070 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16071 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16072 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16076 static void MapGameButtonsAtSamePosition(int id)
16080 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16082 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16083 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16084 MapGadget(game_gadget[i]);
16086 UnmapGameButtonsAtSamePosition_All();
16089 void MapUndoRedoButtons(void)
16091 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16092 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16094 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16095 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16098 void UnmapUndoRedoButtons(void)
16100 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16101 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16103 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16104 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16107 void ModifyPauseButtons(void)
16111 GAME_CTRL_ID_PAUSE,
16112 GAME_CTRL_ID_PAUSE2,
16113 GAME_CTRL_ID_PANEL_PAUSE,
16114 GAME_CTRL_ID_TOUCH_PAUSE,
16119 for (i = 0; ids[i] > -1; i++)
16120 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16123 static void MapGameButtonsExt(boolean on_tape)
16127 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16128 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16129 i != GAME_CTRL_ID_UNDO &&
16130 i != GAME_CTRL_ID_REDO)
16131 MapGadget(game_gadget[i]);
16133 UnmapGameButtonsAtSamePosition_All();
16135 RedrawGameButtons();
16138 static void UnmapGameButtonsExt(boolean on_tape)
16142 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16143 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16144 UnmapGadget(game_gadget[i]);
16147 static void RedrawGameButtonsExt(boolean on_tape)
16151 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16152 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16153 RedrawGadget(game_gadget[i]);
16156 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16161 gi->checked = state;
16164 static void RedrawSoundButtonGadget(int id)
16166 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16167 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16168 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16169 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16170 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16171 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16174 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16175 RedrawGadget(game_gadget[id2]);
16178 void MapGameButtons(void)
16180 MapGameButtonsExt(FALSE);
16183 void UnmapGameButtons(void)
16185 UnmapGameButtonsExt(FALSE);
16188 void RedrawGameButtons(void)
16190 RedrawGameButtonsExt(FALSE);
16193 void MapGameButtonsOnTape(void)
16195 MapGameButtonsExt(TRUE);
16198 void UnmapGameButtonsOnTape(void)
16200 UnmapGameButtonsExt(TRUE);
16203 void RedrawGameButtonsOnTape(void)
16205 RedrawGameButtonsExt(TRUE);
16208 static void GameUndoRedoExt(void)
16210 ClearPlayerAction();
16212 tape.pausing = TRUE;
16215 UpdateAndDisplayGameControlValues();
16217 DrawCompleteVideoDisplay();
16218 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16219 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16220 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16225 static void GameUndo(int steps)
16227 if (!CheckEngineSnapshotList())
16230 LoadEngineSnapshot_Undo(steps);
16235 static void GameRedo(int steps)
16237 if (!CheckEngineSnapshotList())
16240 LoadEngineSnapshot_Redo(steps);
16245 static void HandleGameButtonsExt(int id, int button)
16247 static boolean game_undo_executed = FALSE;
16248 int steps = BUTTON_STEPSIZE(button);
16249 boolean handle_game_buttons =
16250 (game_status == GAME_MODE_PLAYING ||
16251 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16253 if (!handle_game_buttons)
16258 case GAME_CTRL_ID_STOP:
16259 case GAME_CTRL_ID_PANEL_STOP:
16260 case GAME_CTRL_ID_TOUCH_STOP:
16261 if (game_status == GAME_MODE_MAIN)
16267 RequestQuitGame(TRUE);
16271 case GAME_CTRL_ID_PAUSE:
16272 case GAME_CTRL_ID_PAUSE2:
16273 case GAME_CTRL_ID_PANEL_PAUSE:
16274 case GAME_CTRL_ID_TOUCH_PAUSE:
16275 if (network.enabled && game_status == GAME_MODE_PLAYING)
16278 SendToServer_ContinuePlaying();
16280 SendToServer_PausePlaying();
16283 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16285 game_undo_executed = FALSE;
16289 case GAME_CTRL_ID_PLAY:
16290 case GAME_CTRL_ID_PANEL_PLAY:
16291 if (game_status == GAME_MODE_MAIN)
16293 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16295 else if (tape.pausing)
16297 if (network.enabled)
16298 SendToServer_ContinuePlaying();
16300 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16304 case GAME_CTRL_ID_UNDO:
16305 // Important: When using "save snapshot when collecting an item" mode,
16306 // load last (current) snapshot for first "undo" after pressing "pause"
16307 // (else the last-but-one snapshot would be loaded, because the snapshot
16308 // pointer already points to the last snapshot when pressing "pause",
16309 // which is fine for "every step/move" mode, but not for "every collect")
16310 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16311 !game_undo_executed)
16314 game_undo_executed = TRUE;
16319 case GAME_CTRL_ID_REDO:
16323 case GAME_CTRL_ID_SAVE:
16327 case GAME_CTRL_ID_LOAD:
16331 case SOUND_CTRL_ID_MUSIC:
16332 case SOUND_CTRL_ID_PANEL_MUSIC:
16333 if (setup.sound_music)
16335 setup.sound_music = FALSE;
16339 else if (audio.music_available)
16341 setup.sound = setup.sound_music = TRUE;
16343 SetAudioMode(setup.sound);
16345 if (game_status == GAME_MODE_PLAYING)
16349 RedrawSoundButtonGadget(id);
16353 case SOUND_CTRL_ID_LOOPS:
16354 case SOUND_CTRL_ID_PANEL_LOOPS:
16355 if (setup.sound_loops)
16356 setup.sound_loops = FALSE;
16357 else if (audio.loops_available)
16359 setup.sound = setup.sound_loops = TRUE;
16361 SetAudioMode(setup.sound);
16364 RedrawSoundButtonGadget(id);
16368 case SOUND_CTRL_ID_SIMPLE:
16369 case SOUND_CTRL_ID_PANEL_SIMPLE:
16370 if (setup.sound_simple)
16371 setup.sound_simple = FALSE;
16372 else if (audio.sound_available)
16374 setup.sound = setup.sound_simple = TRUE;
16376 SetAudioMode(setup.sound);
16379 RedrawSoundButtonGadget(id);
16388 static void HandleGameButtons(struct GadgetInfo *gi)
16390 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16393 void HandleSoundButtonKeys(Key key)
16395 if (key == setup.shortcut.sound_simple)
16396 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16397 else if (key == setup.shortcut.sound_loops)
16398 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16399 else if (key == setup.shortcut.sound_music)
16400 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);