1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void TestIfPlayerTouchesCustomElement(int, int);
1062 static void TestIfElementTouchesCustomElement(int, int);
1063 static void TestIfElementHitsCustomElement(int, int, int);
1065 static void HandleElementChange(int, int, int);
1066 static void ExecuteCustomElementAction(int, int, int, int);
1067 static boolean ChangeElement(int, int, int, int);
1069 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1070 #define CheckTriggeredElementChange(x, y, e, ev) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1072 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1073 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1074 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1075 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1076 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1078 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1081 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1082 #define CheckElementChange(x, y, e, te, ev) \
1083 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1084 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1085 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1086 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1087 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1088 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1089 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1091 static void PlayLevelSound(int, int, int);
1092 static void PlayLevelSoundNearest(int, int, int);
1093 static void PlayLevelSoundAction(int, int, int);
1094 static void PlayLevelSoundElementAction(int, int, int, int);
1095 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1096 static void PlayLevelSoundActionIfLoop(int, int, int);
1097 static void StopLevelSoundActionIfLoop(int, int, int);
1098 static void PlayLevelMusic(void);
1099 static void FadeLevelSoundsAndMusic(void);
1101 static void HandleGameButtons(struct GadgetInfo *);
1103 int AmoebaNeighbourNr(int, int);
1104 void AmoebaToDiamond(int, int);
1105 void ContinueMoving(int, int);
1106 void Bang(int, int);
1107 void InitMovDir(int, int);
1108 void InitAmoebaNr(int, int);
1109 int NewHiScore(int);
1111 void TestIfGoodThingHitsBadThing(int, int, int);
1112 void TestIfBadThingHitsGoodThing(int, int, int);
1113 void TestIfPlayerTouchesBadThing(int, int);
1114 void TestIfPlayerRunsIntoBadThing(int, int, int);
1115 void TestIfBadThingTouchesPlayer(int, int);
1116 void TestIfBadThingRunsIntoPlayer(int, int, int);
1117 void TestIfFriendTouchesBadThing(int, int);
1118 void TestIfBadThingTouchesFriend(int, int);
1119 void TestIfBadThingTouchesOtherBadThing(int, int);
1120 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1122 void KillPlayer(struct PlayerInfo *);
1123 void BuryPlayer(struct PlayerInfo *);
1124 void RemovePlayer(struct PlayerInfo *);
1125 void ExitPlayer(struct PlayerInfo *);
1127 static int getInvisibleActiveFromInvisibleElement(int);
1128 static int getInvisibleFromInvisibleActiveElement(int);
1130 static void TestFieldAfterSnapping(int, int, int, int, int);
1132 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1134 // for detection of endless loops, caused by custom element programming
1135 // (using maximal playfield width x 10 is just a rough approximation)
1136 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1138 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1140 if (recursion_loop_detected) \
1143 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1145 recursion_loop_detected = TRUE; \
1146 recursion_loop_element = (e); \
1149 recursion_loop_depth++; \
1152 #define RECURSION_LOOP_DETECTION_END() \
1154 recursion_loop_depth--; \
1157 static int recursion_loop_depth;
1158 static boolean recursion_loop_detected;
1159 static boolean recursion_loop_element;
1161 static int map_player_action[MAX_PLAYERS];
1164 // ----------------------------------------------------------------------------
1165 // definition of elements that automatically change to other elements after
1166 // a specified time, eventually calling a function when changing
1167 // ----------------------------------------------------------------------------
1169 // forward declaration for changer functions
1170 static void InitBuggyBase(int, int);
1171 static void WarnBuggyBase(int, int);
1173 static void InitTrap(int, int);
1174 static void ActivateTrap(int, int);
1175 static void ChangeActiveTrap(int, int);
1177 static void InitRobotWheel(int, int);
1178 static void RunRobotWheel(int, int);
1179 static void StopRobotWheel(int, int);
1181 static void InitTimegateWheel(int, int);
1182 static void RunTimegateWheel(int, int);
1184 static void InitMagicBallDelay(int, int);
1185 static void ActivateMagicBall(int, int);
1187 struct ChangingElementInfo
1192 void (*pre_change_function)(int x, int y);
1193 void (*change_function)(int x, int y);
1194 void (*post_change_function)(int x, int y);
1197 static struct ChangingElementInfo change_delay_list[] =
1232 EL_STEEL_EXIT_OPENING,
1240 EL_STEEL_EXIT_CLOSING,
1241 EL_STEEL_EXIT_CLOSED,
1264 EL_EM_STEEL_EXIT_OPENING,
1265 EL_EM_STEEL_EXIT_OPEN,
1272 EL_EM_STEEL_EXIT_CLOSING,
1296 EL_SWITCHGATE_OPENING,
1304 EL_SWITCHGATE_CLOSING,
1305 EL_SWITCHGATE_CLOSED,
1312 EL_TIMEGATE_OPENING,
1320 EL_TIMEGATE_CLOSING,
1329 EL_ACID_SPLASH_LEFT,
1337 EL_ACID_SPLASH_RIGHT,
1346 EL_SP_BUGGY_BASE_ACTIVATING,
1353 EL_SP_BUGGY_BASE_ACTIVATING,
1354 EL_SP_BUGGY_BASE_ACTIVE,
1361 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_ROBOT_WHEEL_ACTIVE,
1393 EL_TIMEGATE_SWITCH_ACTIVE,
1401 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1402 EL_DC_TIMEGATE_SWITCH,
1409 EL_EMC_MAGIC_BALL_ACTIVE,
1410 EL_EMC_MAGIC_BALL_ACTIVE,
1417 EL_EMC_SPRING_BUMPER_ACTIVE,
1418 EL_EMC_SPRING_BUMPER,
1425 EL_DIAGONAL_SHRINKING,
1433 EL_DIAGONAL_GROWING,
1454 int push_delay_fixed, push_delay_random;
1458 { EL_SPRING, 0, 0 },
1459 { EL_BALLOON, 0, 0 },
1461 { EL_SOKOBAN_OBJECT, 2, 0 },
1462 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1463 { EL_SATELLITE, 2, 0 },
1464 { EL_SP_DISK_YELLOW, 2, 0 },
1466 { EL_UNDEFINED, 0, 0 },
1474 move_stepsize_list[] =
1476 { EL_AMOEBA_DROP, 2 },
1477 { EL_AMOEBA_DROPPING, 2 },
1478 { EL_QUICKSAND_FILLING, 1 },
1479 { EL_QUICKSAND_EMPTYING, 1 },
1480 { EL_QUICKSAND_FAST_FILLING, 2 },
1481 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1482 { EL_MAGIC_WALL_FILLING, 2 },
1483 { EL_MAGIC_WALL_EMPTYING, 2 },
1484 { EL_BD_MAGIC_WALL_FILLING, 2 },
1485 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1486 { EL_DC_MAGIC_WALL_FILLING, 2 },
1487 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_UNDEFINED, 0 },
1497 collect_count_list[] =
1500 { EL_BD_DIAMOND, 1 },
1501 { EL_EMERALD_YELLOW, 1 },
1502 { EL_EMERALD_RED, 1 },
1503 { EL_EMERALD_PURPLE, 1 },
1505 { EL_SP_INFOTRON, 1 },
1509 { EL_UNDEFINED, 0 },
1517 access_direction_list[] =
1519 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1521 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1522 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1525 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1526 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1527 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1528 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1529 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1531 { EL_SP_PORT_LEFT, MV_RIGHT },
1532 { EL_SP_PORT_RIGHT, MV_LEFT },
1533 { EL_SP_PORT_UP, MV_DOWN },
1534 { EL_SP_PORT_DOWN, MV_UP },
1535 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1536 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1537 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1538 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1546 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1547 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1548 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1549 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1551 { EL_UNDEFINED, MV_NONE }
1554 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1556 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1557 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1558 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1559 IS_JUST_CHANGING(x, y))
1561 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1563 // static variables for playfield scan mode (scanning forward or backward)
1564 static int playfield_scan_start_x = 0;
1565 static int playfield_scan_start_y = 0;
1566 static int playfield_scan_delta_x = 1;
1567 static int playfield_scan_delta_y = 1;
1569 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1570 (y) >= 0 && (y) <= lev_fieldy - 1; \
1571 (y) += playfield_scan_delta_y) \
1572 for ((x) = playfield_scan_start_x; \
1573 (x) >= 0 && (x) <= lev_fieldx - 1; \
1574 (x) += playfield_scan_delta_x)
1577 void DEBUG_SetMaximumDynamite(void)
1581 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1582 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1583 local_player->inventory_element[local_player->inventory_size++] =
1588 static void InitPlayfieldScanModeVars(void)
1590 if (game.use_reverse_scan_direction)
1592 playfield_scan_start_x = lev_fieldx - 1;
1593 playfield_scan_start_y = lev_fieldy - 1;
1595 playfield_scan_delta_x = -1;
1596 playfield_scan_delta_y = -1;
1600 playfield_scan_start_x = 0;
1601 playfield_scan_start_y = 0;
1603 playfield_scan_delta_x = 1;
1604 playfield_scan_delta_y = 1;
1608 static void InitPlayfieldScanMode(int mode)
1610 game.use_reverse_scan_direction =
1611 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1613 InitPlayfieldScanModeVars();
1616 static int get_move_delay_from_stepsize(int move_stepsize)
1619 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1621 // make sure that stepsize value is always a power of 2
1622 move_stepsize = (1 << log_2(move_stepsize));
1624 return TILEX / move_stepsize;
1627 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1630 int player_nr = player->index_nr;
1631 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1632 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1634 // do no immediately change move delay -- the player might just be moving
1635 player->move_delay_value_next = move_delay;
1637 // information if player can move must be set separately
1638 player->cannot_move = cannot_move;
1642 player->move_delay = game.initial_move_delay[player_nr];
1643 player->move_delay_value = game.initial_move_delay_value[player_nr];
1645 player->move_delay_value_next = -1;
1647 player->move_delay_reset_counter = 0;
1651 void GetPlayerConfig(void)
1653 GameFrameDelay = setup.game_frame_delay;
1655 if (!audio.sound_available)
1656 setup.sound_simple = FALSE;
1658 if (!audio.loops_available)
1659 setup.sound_loops = FALSE;
1661 if (!audio.music_available)
1662 setup.sound_music = FALSE;
1664 if (!video.fullscreen_available)
1665 setup.fullscreen = FALSE;
1667 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1669 SetAudioMode(setup.sound);
1672 int GetElementFromGroupElement(int element)
1674 if (IS_GROUP_ELEMENT(element))
1676 struct ElementGroupInfo *group = element_info[element].group;
1677 int last_anim_random_frame = gfx.anim_random_frame;
1680 if (group->choice_mode == ANIM_RANDOM)
1681 gfx.anim_random_frame = RND(group->num_elements_resolved);
1683 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1684 group->choice_mode, 0,
1687 if (group->choice_mode == ANIM_RANDOM)
1688 gfx.anim_random_frame = last_anim_random_frame;
1690 group->choice_pos++;
1692 element = group->element_resolved[element_pos];
1698 static void IncrementSokobanFieldsNeeded(void)
1700 if (level.sb_fields_needed)
1701 game.sokoban_fields_still_needed++;
1704 static void IncrementSokobanObjectsNeeded(void)
1706 if (level.sb_objects_needed)
1707 game.sokoban_objects_still_needed++;
1710 static void DecrementSokobanFieldsNeeded(void)
1712 if (game.sokoban_fields_still_needed > 0)
1713 game.sokoban_fields_still_needed--;
1716 static void DecrementSokobanObjectsNeeded(void)
1718 if (game.sokoban_objects_still_needed > 0)
1719 game.sokoban_objects_still_needed--;
1722 static void InitPlayerField(int x, int y, int element, boolean init_game)
1724 if (element == EL_SP_MURPHY)
1728 if (stored_player[0].present)
1730 Tile[x][y] = EL_SP_MURPHY_CLONE;
1736 stored_player[0].initial_element = element;
1737 stored_player[0].use_murphy = TRUE;
1739 if (!level.use_artwork_element[0])
1740 stored_player[0].artwork_element = EL_SP_MURPHY;
1743 Tile[x][y] = EL_PLAYER_1;
1749 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1750 int jx = player->jx, jy = player->jy;
1752 player->present = TRUE;
1754 player->block_last_field = (element == EL_SP_MURPHY ?
1755 level.sp_block_last_field :
1756 level.block_last_field);
1758 // ---------- initialize player's last field block delay ------------------
1760 // always start with reliable default value (no adjustment needed)
1761 player->block_delay_adjustment = 0;
1763 // special case 1: in Supaplex, Murphy blocks last field one more frame
1764 if (player->block_last_field && element == EL_SP_MURPHY)
1765 player->block_delay_adjustment = 1;
1767 // special case 2: in game engines before 3.1.1, blocking was different
1768 if (game.use_block_last_field_bug)
1769 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1771 if (!network.enabled || player->connected_network)
1773 player->active = TRUE;
1775 // remove potentially duplicate players
1776 if (StorePlayer[jx][jy] == Tile[x][y])
1777 StorePlayer[jx][jy] = 0;
1779 StorePlayer[x][y] = Tile[x][y];
1781 #if DEBUG_INIT_PLAYER
1782 Debug("game:init:player", "- player element %d activated",
1783 player->element_nr);
1784 Debug("game:init:player", " (local player is %d and currently %s)",
1785 local_player->element_nr,
1786 local_player->active ? "active" : "not active");
1790 Tile[x][y] = EL_EMPTY;
1792 player->jx = player->last_jx = x;
1793 player->jy = player->last_jy = y;
1796 // always check if player was just killed and should be reanimated
1798 int player_nr = GET_PLAYER_NR(element);
1799 struct PlayerInfo *player = &stored_player[player_nr];
1801 if (player->active && player->killed)
1802 player->reanimated = TRUE; // if player was just killed, reanimate him
1806 static void InitField(int x, int y, boolean init_game)
1808 int element = Tile[x][y];
1817 InitPlayerField(x, y, element, init_game);
1820 case EL_SOKOBAN_FIELD_PLAYER:
1821 element = Tile[x][y] = EL_PLAYER_1;
1822 InitField(x, y, init_game);
1824 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1825 InitField(x, y, init_game);
1828 case EL_SOKOBAN_FIELD_EMPTY:
1829 IncrementSokobanFieldsNeeded();
1832 case EL_SOKOBAN_OBJECT:
1833 IncrementSokobanObjectsNeeded();
1837 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1838 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1839 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1843 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1844 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1845 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1846 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1855 case EL_SPACESHIP_RIGHT:
1856 case EL_SPACESHIP_UP:
1857 case EL_SPACESHIP_LEFT:
1858 case EL_SPACESHIP_DOWN:
1859 case EL_BD_BUTTERFLY:
1860 case EL_BD_BUTTERFLY_RIGHT:
1861 case EL_BD_BUTTERFLY_UP:
1862 case EL_BD_BUTTERFLY_LEFT:
1863 case EL_BD_BUTTERFLY_DOWN:
1865 case EL_BD_FIREFLY_RIGHT:
1866 case EL_BD_FIREFLY_UP:
1867 case EL_BD_FIREFLY_LEFT:
1868 case EL_BD_FIREFLY_DOWN:
1869 case EL_PACMAN_RIGHT:
1871 case EL_PACMAN_LEFT:
1872 case EL_PACMAN_DOWN:
1874 case EL_YAMYAM_LEFT:
1875 case EL_YAMYAM_RIGHT:
1877 case EL_YAMYAM_DOWN:
1878 case EL_DARK_YAMYAM:
1881 case EL_SP_SNIKSNAK:
1882 case EL_SP_ELECTRON:
1888 case EL_SPRING_LEFT:
1889 case EL_SPRING_RIGHT:
1893 case EL_AMOEBA_FULL:
1898 case EL_AMOEBA_DROP:
1899 if (y == lev_fieldy - 1)
1901 Tile[x][y] = EL_AMOEBA_GROWING;
1902 Store[x][y] = EL_AMOEBA_WET;
1906 case EL_DYNAMITE_ACTIVE:
1907 case EL_SP_DISK_RED_ACTIVE:
1908 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1909 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1910 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1912 MovDelay[x][y] = 96;
1915 case EL_EM_DYNAMITE_ACTIVE:
1916 MovDelay[x][y] = 32;
1920 game.lights_still_needed++;
1924 game.friends_still_needed++;
1929 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1932 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1933 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1934 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1935 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1946 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1947 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1948 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1950 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1952 game.belt_dir[belt_nr] = belt_dir;
1953 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1955 else // more than one switch -- set it like the first switch
1957 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1962 case EL_LIGHT_SWITCH_ACTIVE:
1964 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1967 case EL_INVISIBLE_STEELWALL:
1968 case EL_INVISIBLE_WALL:
1969 case EL_INVISIBLE_SAND:
1970 if (game.light_time_left > 0 ||
1971 game.lenses_time_left > 0)
1972 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1975 case EL_EMC_MAGIC_BALL:
1976 if (game.ball_active)
1977 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1980 case EL_EMC_MAGIC_BALL_SWITCH:
1981 if (game.ball_active)
1982 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1985 case EL_TRIGGER_PLAYER:
1986 case EL_TRIGGER_ELEMENT:
1987 case EL_TRIGGER_CE_VALUE:
1988 case EL_TRIGGER_CE_SCORE:
1990 case EL_ANY_ELEMENT:
1991 case EL_CURRENT_CE_VALUE:
1992 case EL_CURRENT_CE_SCORE:
2009 // reference elements should not be used on the playfield
2010 Tile[x][y] = EL_EMPTY;
2014 if (IS_CUSTOM_ELEMENT(element))
2016 if (CAN_MOVE(element))
2019 if (!element_info[element].use_last_ce_value || init_game)
2020 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2022 else if (IS_GROUP_ELEMENT(element))
2024 Tile[x][y] = GetElementFromGroupElement(element);
2026 InitField(x, y, init_game);
2033 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2036 static void InitField_WithBug1(int x, int y, boolean init_game)
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(Tile[x][y]))
2046 static void InitField_WithBug2(int x, int y, boolean init_game)
2048 int old_element = Tile[x][y];
2050 InitField(x, y, init_game);
2052 // not needed to call InitMovDir() -- already done by InitField()!
2053 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2054 CAN_MOVE(old_element) &&
2055 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2058 /* this case is in fact a combination of not less than three bugs:
2059 first, it calls InitMovDir() for elements that can move, although this is
2060 already done by InitField(); then, it checks the element that was at this
2061 field _before_ the call to InitField() (which can change it); lastly, it
2062 was not called for "mole with direction" elements, which were treated as
2063 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2067 static int get_key_element_from_nr(int key_nr)
2069 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2070 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2071 EL_EM_KEY_1 : EL_KEY_1);
2073 return key_base_element + key_nr;
2076 static int get_next_dropped_element(struct PlayerInfo *player)
2078 return (player->inventory_size > 0 ?
2079 player->inventory_element[player->inventory_size - 1] :
2080 player->inventory_infinite_element != EL_UNDEFINED ?
2081 player->inventory_infinite_element :
2082 player->dynabombs_left > 0 ?
2083 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2087 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2089 // pos >= 0: get element from bottom of the stack;
2090 // pos < 0: get element from top of the stack
2094 int min_inventory_size = -pos;
2095 int inventory_pos = player->inventory_size - min_inventory_size;
2096 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2098 return (player->inventory_size >= min_inventory_size ?
2099 player->inventory_element[inventory_pos] :
2100 player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 int min_dynabombs_left = pos + 1;
2109 int min_inventory_size = pos + 1 - player->dynabombs_left;
2110 int inventory_pos = pos - player->dynabombs_left;
2112 return (player->inventory_infinite_element != EL_UNDEFINED ?
2113 player->inventory_infinite_element :
2114 player->dynabombs_left >= min_dynabombs_left ?
2115 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 player->inventory_size >= min_inventory_size ?
2117 player->inventory_element[inventory_pos] :
2122 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2124 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2125 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2128 if (gpo1->sort_priority != gpo2->sort_priority)
2129 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2131 compare_result = gpo1->nr - gpo2->nr;
2133 return compare_result;
2136 int getPlayerInventorySize(int player_nr)
2138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2139 return game_em.ply[player_nr]->dynamite;
2140 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2141 return game_sp.red_disk_count;
2143 return stored_player[player_nr].inventory_size;
2146 static void InitGameControlValues(void)
2150 for (i = 0; game_panel_controls[i].nr != -1; i++)
2152 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2153 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2154 struct TextPosInfo *pos = gpc->pos;
2156 int type = gpc->type;
2160 Error("'game_panel_controls' structure corrupted at %d", i);
2162 Fail("this should not happen -- please debug");
2165 // force update of game controls after initialization
2166 gpc->value = gpc->last_value = -1;
2167 gpc->frame = gpc->last_frame = -1;
2168 gpc->gfx_frame = -1;
2170 // determine panel value width for later calculation of alignment
2171 if (type == TYPE_INTEGER || type == TYPE_STRING)
2173 pos->width = pos->size * getFontWidth(pos->font);
2174 pos->height = getFontHeight(pos->font);
2176 else if (type == TYPE_ELEMENT)
2178 pos->width = pos->size;
2179 pos->height = pos->size;
2182 // fill structure for game panel draw order
2184 gpo->sort_priority = pos->sort_priority;
2187 // sort game panel controls according to sort_priority and control number
2188 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2189 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2192 static void UpdatePlayfieldElementCount(void)
2194 boolean use_element_count = FALSE;
2197 // first check if it is needed at all to calculate playfield element count
2198 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2199 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2200 use_element_count = TRUE;
2202 if (!use_element_count)
2205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2206 element_info[i].element_count = 0;
2208 SCAN_PLAYFIELD(x, y)
2210 element_info[Tile[x][y]].element_count++;
2213 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2214 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2215 if (IS_IN_GROUP(j, i))
2216 element_info[EL_GROUP_START + i].element_count +=
2217 element_info[j].element_count;
2220 static void UpdateGameControlValues(void)
2223 int time = (game.LevelSolved ?
2224 game.LevelSolved_CountingTime :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 game_sp.time_played :
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 game_mm.energy_left :
2231 game.no_time_limit ? TimePlayed : TimeLeft);
2232 int score = (game.LevelSolved ?
2233 game.LevelSolved_CountingScore :
2234 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 game_em.lev->score :
2236 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2242 game_em.lev->gems_needed :
2243 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2244 game_sp.infotrons_still_needed :
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2246 game_mm.kettles_still_needed :
2247 game.gems_still_needed);
2248 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 game_em.lev->gems_needed > 0 :
2250 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2251 game_sp.infotrons_still_needed > 0 :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 game_mm.kettles_still_needed > 0 ||
2254 game_mm.lights_still_needed > 0 :
2255 game.gems_still_needed > 0 ||
2256 game.sokoban_fields_still_needed > 0 ||
2257 game.sokoban_objects_still_needed > 0 ||
2258 game.lights_still_needed > 0);
2259 int health = (game.LevelSolved ?
2260 game.LevelSolved_CountingHealth :
2261 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2262 MM_HEALTH(game_mm.laser_overload_value) :
2264 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2266 UpdatePlayfieldElementCount();
2268 // update game panel control values
2270 // used instead of "level_nr" (for network games)
2271 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2272 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2274 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2275 for (i = 0; i < MAX_NUM_KEYS; i++)
2276 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2277 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2278 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2280 if (game.centered_player_nr == -1)
2282 for (i = 0; i < MAX_PLAYERS; i++)
2284 // only one player in Supaplex game engine
2285 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2288 for (k = 0; k < MAX_NUM_KEYS; k++)
2290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2292 if (game_em.ply[i]->keys & (1 << k))
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2296 else if (stored_player[i].key[k])
2297 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2298 get_key_element_from_nr(k);
2301 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2302 getPlayerInventorySize(i);
2304 if (stored_player[i].num_white_keys > 0)
2305 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2308 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2309 stored_player[i].num_white_keys;
2314 int player_nr = game.centered_player_nr;
2316 for (k = 0; k < MAX_NUM_KEYS; k++)
2318 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2320 if (game_em.ply[player_nr]->keys & (1 << k))
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2324 else if (stored_player[player_nr].key[k])
2325 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2326 get_key_element_from_nr(k);
2329 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2330 getPlayerInventorySize(player_nr);
2332 if (stored_player[player_nr].num_white_keys > 0)
2333 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2336 stored_player[player_nr].num_white_keys;
2339 // re-arrange keys on game panel, if needed or if defined by style settings
2340 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2342 int nr = GAME_PANEL_KEY_1 + i;
2343 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2344 struct TextPosInfo *pos = gpc->pos;
2346 // skip check if key is not in the player's inventory
2347 if (gpc->value == EL_EMPTY)
2350 // check if keys should be arranged on panel from left to right
2351 if (pos->style == STYLE_LEFTMOST_POSITION)
2353 // check previous key positions (left from current key)
2354 for (k = 0; k < i; k++)
2356 int nr_new = GAME_PANEL_KEY_1 + k;
2358 if (game_panel_controls[nr_new].value == EL_EMPTY)
2360 game_panel_controls[nr_new].value = gpc->value;
2361 gpc->value = EL_EMPTY;
2368 // check if "undefined" keys can be placed at some other position
2369 if (pos->x == -1 && pos->y == -1)
2371 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2373 // 1st try: display key at the same position as normal or EM keys
2374 if (game_panel_controls[nr_new].value == EL_EMPTY)
2376 game_panel_controls[nr_new].value = gpc->value;
2380 // 2nd try: display key at the next free position in the key panel
2381 for (k = 0; k < STD_NUM_KEYS; k++)
2383 nr_new = GAME_PANEL_KEY_1 + k;
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2396 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2398 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, i);
2400 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2401 get_inventory_element_from_pos(local_player, -i - 1);
2404 game_panel_controls[GAME_PANEL_SCORE].value = score;
2405 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2407 game_panel_controls[GAME_PANEL_TIME].value = time;
2409 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2410 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2411 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2413 if (level.time == 0)
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2416 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2418 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2419 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2421 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2423 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2424 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2427 local_player->shield_normal_time_left;
2428 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2429 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2432 local_player->shield_deadly_time_left;
2434 game_panel_controls[GAME_PANEL_EXIT].value =
2435 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2439 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2440 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2441 EL_EMC_MAGIC_BALL_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2444 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2445 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2446 game.light_time_left;
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2449 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2450 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2451 game.timegate_time_left;
2453 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2454 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2456 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2457 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2458 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2459 game.lenses_time_left;
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2462 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2463 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2464 game.magnify_time_left;
2466 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2467 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2468 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2469 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2470 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2471 EL_BALLOON_SWITCH_NONE);
2473 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2474 local_player->dynabomb_count;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2476 local_player->dynabomb_size;
2477 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2478 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_PENGUINS].value =
2481 game.friends_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2484 game.sokoban_objects_still_needed;
2485 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2486 game.sokoban_fields_still_needed;
2488 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2489 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2491 for (i = 0; i < NUM_BELTS; i++)
2493 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2494 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2495 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2497 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2501 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2502 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2503 game.magic_wall_time_left;
2505 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2506 local_player->gravity;
2508 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2509 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2511 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2512 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2513 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2514 game.panel.element[i].id : EL_UNDEFINED);
2516 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2517 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2518 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2519 element_info[game.panel.element_count[i].id].element_count : 0);
2521 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2522 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2523 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2524 element_info[game.panel.ce_score[i].id].collect_score : 0);
2526 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2527 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2528 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2529 element_info[game.panel.ce_score_element[i].id].collect_score :
2532 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2533 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2534 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2536 // update game panel control frames
2538 for (i = 0; game_panel_controls[i].nr != -1; i++)
2540 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2542 if (gpc->type == TYPE_ELEMENT)
2544 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2546 int last_anim_random_frame = gfx.anim_random_frame;
2547 int element = gpc->value;
2548 int graphic = el2panelimg(element);
2549 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2550 sync_random_frame : INIT_GFX_RANDOM());
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = init_gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2570 gpc->gfx_frame = element_info[element].collect_score;
2572 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = last_anim_random_frame;
2578 else if (gpc->type == TYPE_GRAPHIC)
2580 if (gpc->graphic != IMG_UNDEFINED)
2582 int last_anim_random_frame = gfx.anim_random_frame;
2583 int graphic = gpc->graphic;
2584 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2585 sync_random_frame : INIT_GFX_RANDOM());
2587 if (gpc->value != gpc->last_value)
2590 gpc->gfx_random = init_gfx_random;
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2597 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2598 gpc->gfx_random = init_gfx_random;
2601 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2602 gfx.anim_random_frame = gpc->gfx_random;
2604 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2606 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2607 gfx.anim_random_frame = last_anim_random_frame;
2613 static void DisplayGameControlValues(void)
2615 boolean redraw_panel = FALSE;
2618 for (i = 0; game_panel_controls[i].nr != -1; i++)
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2622 if (PANEL_DEACTIVATED(gpc->pos))
2625 if (gpc->value == gpc->last_value &&
2626 gpc->frame == gpc->last_frame)
2629 redraw_panel = TRUE;
2635 // copy default game door content to main double buffer
2637 // !!! CHECK AGAIN !!!
2638 SetPanelBackground();
2639 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2640 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2642 // redraw game control buttons
2643 RedrawGameButtons();
2645 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2647 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2649 int nr = game_panel_order[i].nr;
2650 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2651 struct TextPosInfo *pos = gpc->pos;
2652 int type = gpc->type;
2653 int value = gpc->value;
2654 int frame = gpc->frame;
2655 int size = pos->size;
2656 int font = pos->font;
2657 boolean draw_masked = pos->draw_masked;
2658 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2660 if (PANEL_DEACTIVATED(pos))
2663 if (pos->class == get_hash_from_key("extra_panel_items") &&
2664 !setup.prefer_extra_panel_items)
2667 gpc->last_value = value;
2668 gpc->last_frame = frame;
2670 if (type == TYPE_INTEGER)
2672 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2673 nr == GAME_PANEL_TIME)
2675 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2677 if (use_dynamic_size) // use dynamic number of digits
2679 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2680 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2681 int size2 = size1 + 1;
2682 int font1 = pos->font;
2683 int font2 = pos->font_alt;
2685 size = (value < value_change ? size1 : size2);
2686 font = (value < value_change ? font1 : font2);
2690 // correct text size if "digits" is zero or less
2692 size = strlen(int2str(value, size));
2694 // dynamically correct text alignment
2695 pos->width = size * getFontWidth(font);
2697 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2698 int2str(value, size), font, mask_mode);
2700 else if (type == TYPE_ELEMENT)
2702 int element, graphic;
2706 int dst_x = PANEL_XPOS(pos);
2707 int dst_y = PANEL_YPOS(pos);
2709 if (value != EL_UNDEFINED && value != EL_EMPTY)
2712 graphic = el2panelimg(value);
2715 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2716 element, EL_NAME(element), size);
2719 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2722 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2725 width = graphic_info[graphic].width * size / TILESIZE;
2726 height = graphic_info[graphic].height * size / TILESIZE;
2729 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2732 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2736 else if (type == TYPE_GRAPHIC)
2738 int graphic = gpc->graphic;
2739 int graphic_active = gpc->graphic_active;
2743 int dst_x = PANEL_XPOS(pos);
2744 int dst_y = PANEL_YPOS(pos);
2745 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2746 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2748 if (graphic != IMG_UNDEFINED && !skip)
2750 if (pos->style == STYLE_REVERSE)
2751 value = 100 - value;
2753 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2755 if (pos->direction & MV_HORIZONTAL)
2757 width = graphic_info[graphic_active].width * value / 100;
2758 height = graphic_info[graphic_active].height;
2760 if (pos->direction == MV_LEFT)
2762 src_x += graphic_info[graphic_active].width - width;
2763 dst_x += graphic_info[graphic_active].width - width;
2768 width = graphic_info[graphic_active].width;
2769 height = graphic_info[graphic_active].height * value / 100;
2771 if (pos->direction == MV_UP)
2773 src_y += graphic_info[graphic_active].height - height;
2774 dst_y += graphic_info[graphic_active].height - height;
2779 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2782 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2785 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2787 if (pos->direction & MV_HORIZONTAL)
2789 if (pos->direction == MV_RIGHT)
2796 dst_x = PANEL_XPOS(pos);
2799 width = graphic_info[graphic].width - width;
2803 if (pos->direction == MV_DOWN)
2810 dst_y = PANEL_YPOS(pos);
2813 height = graphic_info[graphic].height - height;
2817 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2820 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2824 else if (type == TYPE_STRING)
2826 boolean active = (value != 0);
2827 char *state_normal = "off";
2828 char *state_active = "on";
2829 char *state = (active ? state_active : state_normal);
2830 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2831 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2832 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2833 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2835 if (nr == GAME_PANEL_GRAVITY_STATE)
2837 int font1 = pos->font; // (used for normal state)
2838 int font2 = pos->font_alt; // (used for active state)
2840 font = (active ? font2 : font1);
2849 // don't truncate output if "chars" is zero or less
2852 // dynamically correct text alignment
2853 pos->width = size * getFontWidth(font);
2856 s_cut = getStringCopyN(s, size);
2858 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2859 s_cut, font, mask_mode);
2865 redraw_mask |= REDRAW_DOOR_1;
2868 SetGameStatus(GAME_MODE_PLAYING);
2871 void UpdateAndDisplayGameControlValues(void)
2873 if (tape.deactivate_display)
2876 UpdateGameControlValues();
2877 DisplayGameControlValues();
2880 void UpdateGameDoorValues(void)
2882 UpdateGameControlValues();
2885 void DrawGameDoorValues(void)
2887 DisplayGameControlValues();
2891 // ============================================================================
2893 // ----------------------------------------------------------------------------
2894 // initialize game engine due to level / tape version number
2895 // ============================================================================
2897 static void InitGameEngine(void)
2899 int i, j, k, l, x, y;
2901 // set game engine from tape file when re-playing, else from level file
2902 game.engine_version = (tape.playing ? tape.engine_version :
2903 level.game_version);
2905 // set single or multi-player game mode (needed for re-playing tapes)
2906 game.team_mode = setup.team_mode;
2910 int num_players = 0;
2912 for (i = 0; i < MAX_PLAYERS; i++)
2913 if (tape.player_participates[i])
2916 // multi-player tapes contain input data for more than one player
2917 game.team_mode = (num_players > 1);
2921 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2922 level.game_version);
2923 Debug("game:init:level", " tape.file_version == %06d",
2925 Debug("game:init:level", " tape.game_version == %06d",
2927 Debug("game:init:level", " tape.engine_version == %06d",
2928 tape.engine_version);
2929 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2930 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2933 // --------------------------------------------------------------------------
2934 // set flags for bugs and changes according to active game engine version
2935 // --------------------------------------------------------------------------
2939 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2941 Bug was introduced in version:
2944 Bug was fixed in version:
2948 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2949 but the property "can fall" was missing, which caused some levels to be
2950 unsolvable. This was fixed in version 4.2.0.0.
2952 Affected levels/tapes:
2953 An example for a tape that was fixed by this bugfix is tape 029 from the
2954 level set "rnd_sam_bateman".
2955 The wrong behaviour will still be used for all levels or tapes that were
2956 created/recorded with it. An example for this is tape 023 from the level
2957 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2960 boolean use_amoeba_dropping_cannot_fall_bug =
2961 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2962 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2964 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2965 tape.game_version < VERSION_IDENT(4,2,0,0)));
2968 Summary of bugfix/change:
2969 Fixed move speed of elements entering or leaving magic wall.
2971 Fixed/changed in version:
2975 Before 2.0.1, move speed of elements entering or leaving magic wall was
2976 twice as fast as it is now.
2977 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 2.0.1, which might use the old behaviour before it was changed; known tapes
2982 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2985 above, but before it was known that this change would break tapes like the
2986 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2987 although the engine version while recording maybe was before 2.0.1. There
2988 are a lot of tapes that are affected by this exception, like tape 006 from
2989 the level set "rnd_conor_mancone".
2992 boolean use_old_move_stepsize_for_magic_wall =
2993 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2995 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2996 tape.game_version < VERSION_IDENT(4,2,0,0)));
2999 Summary of bugfix/change:
3000 Fixed handling for custom elements that change when pushed by the player.
3002 Fixed/changed in version:
3006 Before 3.1.0, custom elements that "change when pushing" changed directly
3007 after the player started pushing them (until then handled in "DigField()").
3008 Since 3.1.0, these custom elements are not changed until the "pushing"
3009 move of the element is finished (now handled in "ContinueMoving()").
3011 Affected levels/tapes:
3012 The first condition is generally needed for all levels/tapes before version
3013 3.1.0, which might use the old behaviour before it was changed; known tapes
3014 that are affected are some tapes from the level set "Walpurgis Gardens" by
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3018 above (including some development versions of 3.1.0), but before it was
3019 known that this change would break tapes like the above and was fixed in
3020 3.1.1, so that the changed behaviour was active although the engine version
3021 while recording maybe was before 3.1.0. There is at least one tape that is
3022 affected by this exception, which is the tape for the one-level set "Bug
3023 Machine" by Juergen Bonhagen.
3026 game.use_change_when_pushing_bug =
3027 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3029 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3030 tape.game_version < VERSION_IDENT(3,1,1,0)));
3033 Summary of bugfix/change:
3034 Fixed handling for blocking the field the player leaves when moving.
3036 Fixed/changed in version:
3040 Before 3.1.1, when "block last field when moving" was enabled, the field
3041 the player is leaving when moving was blocked for the time of the move,
3042 and was directly unblocked afterwards. This resulted in the last field
3043 being blocked for exactly one less than the number of frames of one player
3044 move. Additionally, even when blocking was disabled, the last field was
3045 blocked for exactly one frame.
3046 Since 3.1.1, due to changes in player movement handling, the last field
3047 is not blocked at all when blocking is disabled. When blocking is enabled,
3048 the last field is blocked for exactly the number of frames of one player
3049 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3050 last field is blocked for exactly one more than the number of frames of
3053 Affected levels/tapes:
3054 (!!! yet to be determined -- probably many !!!)
3057 game.use_block_last_field_bug =
3058 (game.engine_version < VERSION_IDENT(3,1,1,0));
3060 /* various special flags and settings for native Emerald Mine game engine */
3062 game_em.use_single_button =
3063 (game.engine_version > VERSION_IDENT(4,0,0,2));
3065 game_em.use_snap_key_bug =
3066 (game.engine_version < VERSION_IDENT(4,0,1,0));
3068 game_em.use_random_bug =
3069 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3071 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3073 game_em.use_old_explosions = use_old_em_engine;
3074 game_em.use_old_android = use_old_em_engine;
3075 game_em.use_old_push_elements = use_old_em_engine;
3076 game_em.use_old_push_into_acid = use_old_em_engine;
3078 game_em.use_wrap_around = !use_old_em_engine;
3080 // --------------------------------------------------------------------------
3082 // set maximal allowed number of custom element changes per game frame
3083 game.max_num_changes_per_frame = 1;
3085 // default scan direction: scan playfield from top/left to bottom/right
3086 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3088 // dynamically adjust element properties according to game engine version
3089 InitElementPropertiesEngine(game.engine_version);
3091 // ---------- initialize special element properties -------------------------
3093 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3094 if (use_amoeba_dropping_cannot_fall_bug)
3095 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3097 // ---------- initialize player's initial move delay ------------------------
3099 // dynamically adjust player properties according to level information
3100 for (i = 0; i < MAX_PLAYERS; i++)
3101 game.initial_move_delay_value[i] =
3102 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3104 // dynamically adjust player properties according to game engine version
3105 for (i = 0; i < MAX_PLAYERS; i++)
3106 game.initial_move_delay[i] =
3107 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3108 game.initial_move_delay_value[i] : 0);
3110 // ---------- initialize player's initial push delay ------------------------
3112 // dynamically adjust player properties according to game engine version
3113 game.initial_push_delay_value =
3114 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3116 // ---------- initialize changing elements ----------------------------------
3118 // initialize changing elements information
3119 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3121 struct ElementInfo *ei = &element_info[i];
3123 // this pointer might have been changed in the level editor
3124 ei->change = &ei->change_page[0];
3126 if (!IS_CUSTOM_ELEMENT(i))
3128 ei->change->target_element = EL_EMPTY_SPACE;
3129 ei->change->delay_fixed = 0;
3130 ei->change->delay_random = 0;
3131 ei->change->delay_frames = 1;
3134 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3136 ei->has_change_event[j] = FALSE;
3138 ei->event_page_nr[j] = 0;
3139 ei->event_page[j] = &ei->change_page[0];
3143 // add changing elements from pre-defined list
3144 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3146 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3147 struct ElementInfo *ei = &element_info[ch_delay->element];
3149 ei->change->target_element = ch_delay->target_element;
3150 ei->change->delay_fixed = ch_delay->change_delay;
3152 ei->change->pre_change_function = ch_delay->pre_change_function;
3153 ei->change->change_function = ch_delay->change_function;
3154 ei->change->post_change_function = ch_delay->post_change_function;
3156 ei->change->can_change = TRUE;
3157 ei->change->can_change_or_has_action = TRUE;
3159 ei->has_change_event[CE_DELAY] = TRUE;
3161 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3162 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3165 // ---------- initialize internal run-time variables ------------------------
3167 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3169 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3171 for (j = 0; j < ei->num_change_pages; j++)
3173 ei->change_page[j].can_change_or_has_action =
3174 (ei->change_page[j].can_change |
3175 ei->change_page[j].has_action);
3179 // add change events from custom element configuration
3180 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3182 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3184 for (j = 0; j < ei->num_change_pages; j++)
3186 if (!ei->change_page[j].can_change_or_has_action)
3189 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3191 // only add event page for the first page found with this event
3192 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3194 ei->has_change_event[k] = TRUE;
3196 ei->event_page_nr[k] = j;
3197 ei->event_page[k] = &ei->change_page[j];
3203 // ---------- initialize reference elements in change conditions ------------
3205 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3207 int element = EL_CUSTOM_START + i;
3208 struct ElementInfo *ei = &element_info[element];
3210 for (j = 0; j < ei->num_change_pages; j++)
3212 int trigger_element = ei->change_page[j].initial_trigger_element;
3214 if (trigger_element >= EL_PREV_CE_8 &&
3215 trigger_element <= EL_NEXT_CE_8)
3216 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3218 ei->change_page[j].trigger_element = trigger_element;
3222 // ---------- initialize run-time trigger player and element ----------------
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3231 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3232 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3233 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3234 ei->change_page[j].actual_trigger_ce_value = 0;
3235 ei->change_page[j].actual_trigger_ce_score = 0;
3239 // ---------- initialize trigger events -------------------------------------
3241 // initialize trigger events information
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3244 trigger_events[i][j] = FALSE;
3246 // add trigger events from element change event properties
3247 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3249 struct ElementInfo *ei = &element_info[i];
3251 for (j = 0; j < ei->num_change_pages; j++)
3253 if (!ei->change_page[j].can_change_or_has_action)
3256 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3258 int trigger_element = ei->change_page[j].trigger_element;
3260 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3262 if (ei->change_page[j].has_event[k])
3264 if (IS_GROUP_ELEMENT(trigger_element))
3266 struct ElementGroupInfo *group =
3267 element_info[trigger_element].group;
3269 for (l = 0; l < group->num_elements_resolved; l++)
3270 trigger_events[group->element_resolved[l]][k] = TRUE;
3272 else if (trigger_element == EL_ANY_ELEMENT)
3273 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3274 trigger_events[l][k] = TRUE;
3276 trigger_events[trigger_element][k] = TRUE;
3283 // ---------- initialize push delay -----------------------------------------
3285 // initialize push delay values to default
3286 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3290 // set default push delay values (corrected since version 3.0.7-1)
3291 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3293 element_info[i].push_delay_fixed = 2;
3294 element_info[i].push_delay_random = 8;
3298 element_info[i].push_delay_fixed = 8;
3299 element_info[i].push_delay_random = 8;
3304 // set push delay value for certain elements from pre-defined list
3305 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3307 int e = push_delay_list[i].element;
3309 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3310 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3313 // set push delay value for Supaplex elements for newer engine versions
3314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3316 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3318 if (IS_SP_ELEMENT(i))
3320 // set SP push delay to just enough to push under a falling zonk
3321 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3323 element_info[i].push_delay_fixed = delay;
3324 element_info[i].push_delay_random = 0;
3329 // ---------- initialize move stepsize --------------------------------------
3331 // initialize move stepsize values to default
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3336 // set move stepsize value for certain elements from pre-defined list
3337 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3339 int e = move_stepsize_list[i].element;
3341 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3343 // set move stepsize value for certain elements for older engine versions
3344 if (use_old_move_stepsize_for_magic_wall)
3346 if (e == EL_MAGIC_WALL_FILLING ||
3347 e == EL_MAGIC_WALL_EMPTYING ||
3348 e == EL_BD_MAGIC_WALL_FILLING ||
3349 e == EL_BD_MAGIC_WALL_EMPTYING)
3350 element_info[e].move_stepsize *= 2;
3354 // ---------- initialize collect score --------------------------------------
3356 // initialize collect score values for custom elements from initial value
3357 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3358 if (IS_CUSTOM_ELEMENT(i))
3359 element_info[i].collect_score = element_info[i].collect_score_initial;
3361 // ---------- initialize collect count --------------------------------------
3363 // initialize collect count values for non-custom elements
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3365 if (!IS_CUSTOM_ELEMENT(i))
3366 element_info[i].collect_count_initial = 0;
3368 // add collect count values for all elements from pre-defined list
3369 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3370 element_info[collect_count_list[i].element].collect_count_initial =
3371 collect_count_list[i].count;
3373 // ---------- initialize access direction -----------------------------------
3375 // initialize access direction values to default (access from every side)
3376 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3377 if (!IS_CUSTOM_ELEMENT(i))
3378 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3380 // set access direction value for certain elements from pre-defined list
3381 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3382 element_info[access_direction_list[i].element].access_direction =
3383 access_direction_list[i].direction;
3385 // ---------- initialize explosion content ----------------------------------
3386 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (IS_CUSTOM_ELEMENT(i))
3391 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3393 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3395 element_info[i].content.e[x][y] =
3396 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3397 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3398 i == EL_PLAYER_3 ? EL_EMERALD :
3399 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3400 i == EL_MOLE ? EL_EMERALD_RED :
3401 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3402 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3403 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3404 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3405 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3406 i == EL_WALL_EMERALD ? EL_EMERALD :
3407 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3408 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3409 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3410 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3411 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3412 i == EL_WALL_PEARL ? EL_PEARL :
3413 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3418 // ---------- initialize recursion detection --------------------------------
3419 recursion_loop_depth = 0;
3420 recursion_loop_detected = FALSE;
3421 recursion_loop_element = EL_UNDEFINED;
3423 // ---------- initialize graphics engine ------------------------------------
3424 game.scroll_delay_value =
3425 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3426 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3427 !setup.forced_scroll_delay ? 0 :
3428 setup.scroll_delay ? setup.scroll_delay_value : 0);
3429 game.scroll_delay_value =
3430 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3432 // ---------- initialize game engine snapshots ------------------------------
3433 for (i = 0; i < MAX_PLAYERS; i++)
3434 game.snapshot.last_action[i] = 0;
3435 game.snapshot.changed_action = FALSE;
3436 game.snapshot.collected_item = FALSE;
3437 game.snapshot.mode =
3438 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3439 SNAPSHOT_MODE_EVERY_STEP :
3440 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3441 SNAPSHOT_MODE_EVERY_MOVE :
3442 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3443 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3444 game.snapshot.save_snapshot = FALSE;
3446 // ---------- initialize level time for Supaplex engine ---------------------
3447 // Supaplex levels with time limit currently unsupported -- should be added
3448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3451 // ---------- initialize flags for handling game actions --------------------
3453 // set flags for game actions to default values
3454 game.use_key_actions = TRUE;
3455 game.use_mouse_actions = FALSE;
3457 // when using Mirror Magic game engine, handle mouse events only
3458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3460 game.use_key_actions = FALSE;
3461 game.use_mouse_actions = TRUE;
3464 // check for custom elements with mouse click events
3465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3469 int element = EL_CUSTOM_START + i;
3471 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3472 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3473 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3474 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3475 game.use_mouse_actions = TRUE;
3480 static int get_num_special_action(int element, int action_first,
3483 int num_special_action = 0;
3486 for (i = action_first; i <= action_last; i++)
3488 boolean found = FALSE;
3490 for (j = 0; j < NUM_DIRECTIONS; j++)
3491 if (el_act_dir2img(element, i, j) !=
3492 el_act_dir2img(element, ACTION_DEFAULT, j))
3496 num_special_action++;
3501 return num_special_action;
3505 // ============================================================================
3507 // ----------------------------------------------------------------------------
3508 // initialize and start new game
3509 // ============================================================================
3511 #if DEBUG_INIT_PLAYER
3512 static void DebugPrintPlayerStatus(char *message)
3519 Debug("game:init:player", "%s:", message);
3521 for (i = 0; i < MAX_PLAYERS; i++)
3523 struct PlayerInfo *player = &stored_player[i];
3525 Debug("game:init:player",
3526 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3530 player->connected_locally,
3531 player->connected_network,
3533 (local_player == player ? " (local player)" : ""));
3540 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3541 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3542 int fade_mask = REDRAW_FIELD;
3544 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3545 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3546 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3547 int initial_move_dir = MV_DOWN;
3550 // required here to update video display before fading (FIX THIS)
3551 DrawMaskedBorder(REDRAW_DOOR_2);
3553 if (!game.restart_level)
3554 CloseDoor(DOOR_CLOSE_1);
3556 SetGameStatus(GAME_MODE_PLAYING);
3558 if (level_editor_test_game)
3559 FadeSkipNextFadeOut();
3561 FadeSetEnterScreen();
3564 fade_mask = REDRAW_ALL;
3566 FadeLevelSoundsAndMusic();
3568 ExpireSoundLoops(TRUE);
3572 if (level_editor_test_game)
3573 FadeSkipNextFadeIn();
3575 // needed if different viewport properties defined for playing
3576 ChangeViewportPropertiesIfNeeded();
3580 DrawCompleteVideoDisplay();
3582 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3585 InitGameControlValues();
3589 // initialize tape actions from game when recording tape
3590 tape.use_key_actions = game.use_key_actions;
3591 tape.use_mouse_actions = game.use_mouse_actions;
3593 // initialize visible playfield size when recording tape (for team mode)
3594 tape.scr_fieldx = SCR_FIELDX;
3595 tape.scr_fieldy = SCR_FIELDY;
3598 // don't play tapes over network
3599 network_playing = (network.enabled && !tape.playing);
3601 for (i = 0; i < MAX_PLAYERS; i++)
3603 struct PlayerInfo *player = &stored_player[i];
3605 player->index_nr = i;
3606 player->index_bit = (1 << i);
3607 player->element_nr = EL_PLAYER_1 + i;
3609 player->present = FALSE;
3610 player->active = FALSE;
3611 player->mapped = FALSE;
3613 player->killed = FALSE;
3614 player->reanimated = FALSE;
3615 player->buried = FALSE;
3618 player->effective_action = 0;
3619 player->programmed_action = 0;
3620 player->snap_action = 0;
3622 player->mouse_action.lx = 0;
3623 player->mouse_action.ly = 0;
3624 player->mouse_action.button = 0;
3625 player->mouse_action.button_hint = 0;
3627 player->effective_mouse_action.lx = 0;
3628 player->effective_mouse_action.ly = 0;
3629 player->effective_mouse_action.button = 0;
3630 player->effective_mouse_action.button_hint = 0;
3632 for (j = 0; j < MAX_NUM_KEYS; j++)
3633 player->key[j] = FALSE;
3635 player->num_white_keys = 0;
3637 player->dynabomb_count = 0;
3638 player->dynabomb_size = 1;
3639 player->dynabombs_left = 0;
3640 player->dynabomb_xl = FALSE;
3642 player->MovDir = initial_move_dir;
3645 player->GfxDir = initial_move_dir;
3646 player->GfxAction = ACTION_DEFAULT;
3648 player->StepFrame = 0;
3650 player->initial_element = player->element_nr;
3651 player->artwork_element =
3652 (level.use_artwork_element[i] ? level.artwork_element[i] :
3653 player->element_nr);
3654 player->use_murphy = FALSE;
3656 player->block_last_field = FALSE; // initialized in InitPlayerField()
3657 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3659 player->gravity = level.initial_player_gravity[i];
3661 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3663 player->actual_frame_counter = 0;
3665 player->step_counter = 0;
3667 player->last_move_dir = initial_move_dir;
3669 player->is_active = FALSE;
3671 player->is_waiting = FALSE;
3672 player->is_moving = FALSE;
3673 player->is_auto_moving = FALSE;
3674 player->is_digging = FALSE;
3675 player->is_snapping = FALSE;
3676 player->is_collecting = FALSE;
3677 player->is_pushing = FALSE;
3678 player->is_switching = FALSE;
3679 player->is_dropping = FALSE;
3680 player->is_dropping_pressed = FALSE;
3682 player->is_bored = FALSE;
3683 player->is_sleeping = FALSE;
3685 player->was_waiting = TRUE;
3686 player->was_moving = FALSE;
3687 player->was_snapping = FALSE;
3688 player->was_dropping = FALSE;
3690 player->force_dropping = FALSE;
3692 player->frame_counter_bored = -1;
3693 player->frame_counter_sleeping = -1;
3695 player->anim_delay_counter = 0;
3696 player->post_delay_counter = 0;
3698 player->dir_waiting = initial_move_dir;
3699 player->action_waiting = ACTION_DEFAULT;
3700 player->last_action_waiting = ACTION_DEFAULT;
3701 player->special_action_bored = ACTION_DEFAULT;
3702 player->special_action_sleeping = ACTION_DEFAULT;
3704 player->switch_x = -1;
3705 player->switch_y = -1;
3707 player->drop_x = -1;
3708 player->drop_y = -1;
3710 player->show_envelope = 0;
3712 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3714 player->push_delay = -1; // initialized when pushing starts
3715 player->push_delay_value = game.initial_push_delay_value;
3717 player->drop_delay = 0;
3718 player->drop_pressed_delay = 0;
3720 player->last_jx = -1;
3721 player->last_jy = -1;
3725 player->shield_normal_time_left = 0;
3726 player->shield_deadly_time_left = 0;
3728 player->last_removed_element = EL_UNDEFINED;
3730 player->inventory_infinite_element = EL_UNDEFINED;
3731 player->inventory_size = 0;
3733 if (level.use_initial_inventory[i])
3735 for (j = 0; j < level.initial_inventory_size[i]; j++)
3737 int element = level.initial_inventory_content[i][j];
3738 int collect_count = element_info[element].collect_count_initial;
3741 if (!IS_CUSTOM_ELEMENT(element))
3744 if (collect_count == 0)
3745 player->inventory_infinite_element = element;
3747 for (k = 0; k < collect_count; k++)
3748 if (player->inventory_size < MAX_INVENTORY_SIZE)
3749 player->inventory_element[player->inventory_size++] = element;
3753 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3754 SnapField(player, 0, 0);
3756 map_player_action[i] = i;
3759 network_player_action_received = FALSE;
3761 // initial null action
3762 if (network_playing)
3763 SendToServer_MovePlayer(MV_NONE);
3768 TimeLeft = level.time;
3771 ScreenMovDir = MV_NONE;
3775 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3777 game.robot_wheel_x = -1;
3778 game.robot_wheel_y = -1;
3783 game.all_players_gone = FALSE;
3785 game.LevelSolved = FALSE;
3786 game.GameOver = FALSE;
3788 game.GamePlayed = !tape.playing;
3790 game.LevelSolved_GameWon = FALSE;
3791 game.LevelSolved_GameEnd = FALSE;
3792 game.LevelSolved_SaveTape = FALSE;
3793 game.LevelSolved_SaveScore = FALSE;
3795 game.LevelSolved_CountingTime = 0;
3796 game.LevelSolved_CountingScore = 0;
3797 game.LevelSolved_CountingHealth = 0;
3799 game.panel.active = TRUE;
3801 game.no_time_limit = (level.time == 0);
3803 game.yamyam_content_nr = 0;
3804 game.robot_wheel_active = FALSE;
3805 game.magic_wall_active = FALSE;
3806 game.magic_wall_time_left = 0;
3807 game.light_time_left = 0;
3808 game.timegate_time_left = 0;
3809 game.switchgate_pos = 0;
3810 game.wind_direction = level.wind_direction_initial;
3813 game.score_final = 0;
3815 game.health = MAX_HEALTH;
3816 game.health_final = MAX_HEALTH;
3818 game.gems_still_needed = level.gems_needed;
3819 game.sokoban_fields_still_needed = 0;
3820 game.sokoban_objects_still_needed = 0;
3821 game.lights_still_needed = 0;
3822 game.players_still_needed = 0;
3823 game.friends_still_needed = 0;
3825 game.lenses_time_left = 0;
3826 game.magnify_time_left = 0;
3828 game.ball_active = level.ball_active_initial;
3829 game.ball_content_nr = 0;
3831 game.explosions_delayed = TRUE;
3833 game.envelope_active = FALSE;
3835 for (i = 0; i < NUM_BELTS; i++)
3837 game.belt_dir[i] = MV_NONE;
3838 game.belt_dir_nr[i] = 3; // not moving, next moving left
3841 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3842 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3844 #if DEBUG_INIT_PLAYER
3845 DebugPrintPlayerStatus("Player status at level initialization");
3848 SCAN_PLAYFIELD(x, y)
3850 Tile[x][y] = Last[x][y] = level.field[x][y];
3851 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3852 ChangeDelay[x][y] = 0;
3853 ChangePage[x][y] = -1;
3854 CustomValue[x][y] = 0; // initialized in InitField()
3855 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3857 WasJustMoving[x][y] = 0;
3858 WasJustFalling[x][y] = 0;
3859 CheckCollision[x][y] = 0;
3860 CheckImpact[x][y] = 0;
3862 Pushed[x][y] = FALSE;
3864 ChangeCount[x][y] = 0;
3865 ChangeEvent[x][y] = -1;
3867 ExplodePhase[x][y] = 0;
3868 ExplodeDelay[x][y] = 0;
3869 ExplodeField[x][y] = EX_TYPE_NONE;
3871 RunnerVisit[x][y] = 0;
3872 PlayerVisit[x][y] = 0;
3875 GfxRandom[x][y] = INIT_GFX_RANDOM();
3876 GfxElement[x][y] = EL_UNDEFINED;
3877 GfxAction[x][y] = ACTION_DEFAULT;
3878 GfxDir[x][y] = MV_NONE;
3879 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3882 SCAN_PLAYFIELD(x, y)
3884 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3886 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3888 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3891 InitField(x, y, TRUE);
3893 ResetGfxAnimation(x, y);
3898 for (i = 0; i < MAX_PLAYERS; i++)
3900 struct PlayerInfo *player = &stored_player[i];
3902 // set number of special actions for bored and sleeping animation
3903 player->num_special_action_bored =
3904 get_num_special_action(player->artwork_element,
3905 ACTION_BORING_1, ACTION_BORING_LAST);
3906 player->num_special_action_sleeping =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3911 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3912 emulate_sb ? EMU_SOKOBAN :
3913 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3915 // initialize type of slippery elements
3916 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3918 if (!IS_CUSTOM_ELEMENT(i))
3920 // default: elements slip down either to the left or right randomly
3921 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3923 // SP style elements prefer to slip down on the left side
3924 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3925 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3927 // BD style elements prefer to slip down on the left side
3928 if (game.emulation == EMU_BOULDERDASH)
3929 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3933 // initialize explosion and ignition delay
3934 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3936 if (!IS_CUSTOM_ELEMENT(i))
3939 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3940 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3941 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3942 int last_phase = (num_phase + 1) * delay;
3943 int half_phase = (num_phase / 2) * delay;
3945 element_info[i].explosion_delay = last_phase - 1;
3946 element_info[i].ignition_delay = half_phase;
3948 if (i == EL_BLACK_ORB)
3949 element_info[i].ignition_delay = 1;
3953 // correct non-moving belts to start moving left
3954 for (i = 0; i < NUM_BELTS; i++)
3955 if (game.belt_dir[i] == MV_NONE)
3956 game.belt_dir_nr[i] = 3; // not moving, next moving left
3958 #if USE_NEW_PLAYER_ASSIGNMENTS
3959 // use preferred player also in local single-player mode
3960 if (!network.enabled && !game.team_mode)
3962 int new_index_nr = setup.network_player_nr;
3964 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3966 for (i = 0; i < MAX_PLAYERS; i++)
3967 stored_player[i].connected_locally = FALSE;
3969 stored_player[new_index_nr].connected_locally = TRUE;
3973 for (i = 0; i < MAX_PLAYERS; i++)
3975 stored_player[i].connected = FALSE;
3977 // in network game mode, the local player might not be the first player
3978 if (stored_player[i].connected_locally)
3979 local_player = &stored_player[i];
3982 if (!network.enabled)
3983 local_player->connected = TRUE;
3987 for (i = 0; i < MAX_PLAYERS; i++)
3988 stored_player[i].connected = tape.player_participates[i];
3990 else if (network.enabled)
3992 // add team mode players connected over the network (needed for correct
3993 // assignment of player figures from level to locally playing players)
3995 for (i = 0; i < MAX_PLAYERS; i++)
3996 if (stored_player[i].connected_network)
3997 stored_player[i].connected = TRUE;
3999 else if (game.team_mode)
4001 // try to guess locally connected team mode players (needed for correct
4002 // assignment of player figures from level to locally playing players)
4004 for (i = 0; i < MAX_PLAYERS; i++)
4005 if (setup.input[i].use_joystick ||
4006 setup.input[i].key.left != KSYM_UNDEFINED)
4007 stored_player[i].connected = TRUE;
4010 #if DEBUG_INIT_PLAYER
4011 DebugPrintPlayerStatus("Player status after level initialization");
4014 #if DEBUG_INIT_PLAYER
4015 Debug("game:init:player", "Reassigning players ...");
4018 // check if any connected player was not found in playfield
4019 for (i = 0; i < MAX_PLAYERS; i++)
4021 struct PlayerInfo *player = &stored_player[i];
4023 if (player->connected && !player->present)
4025 struct PlayerInfo *field_player = NULL;
4027 #if DEBUG_INIT_PLAYER
4028 Debug("game:init:player",
4029 "- looking for field player for player %d ...", i + 1);
4032 // assign first free player found that is present in the playfield
4034 // first try: look for unmapped playfield player that is not connected
4035 for (j = 0; j < MAX_PLAYERS; j++)
4036 if (field_player == NULL &&
4037 stored_player[j].present &&
4038 !stored_player[j].mapped &&
4039 !stored_player[j].connected)
4040 field_player = &stored_player[j];
4042 // second try: look for *any* unmapped playfield player
4043 for (j = 0; j < MAX_PLAYERS; j++)
4044 if (field_player == NULL &&
4045 stored_player[j].present &&
4046 !stored_player[j].mapped)
4047 field_player = &stored_player[j];
4049 if (field_player != NULL)
4051 int jx = field_player->jx, jy = field_player->jy;
4053 #if DEBUG_INIT_PLAYER
4054 Debug("game:init:player", "- found player %d",
4055 field_player->index_nr + 1);
4058 player->present = FALSE;
4059 player->active = FALSE;
4061 field_player->present = TRUE;
4062 field_player->active = TRUE;
4065 player->initial_element = field_player->initial_element;
4066 player->artwork_element = field_player->artwork_element;
4068 player->block_last_field = field_player->block_last_field;
4069 player->block_delay_adjustment = field_player->block_delay_adjustment;
4072 StorePlayer[jx][jy] = field_player->element_nr;
4074 field_player->jx = field_player->last_jx = jx;
4075 field_player->jy = field_player->last_jy = jy;
4077 if (local_player == player)
4078 local_player = field_player;
4080 map_player_action[field_player->index_nr] = i;
4082 field_player->mapped = TRUE;
4084 #if DEBUG_INIT_PLAYER
4085 Debug("game:init:player", "- map_player_action[%d] == %d",
4086 field_player->index_nr + 1, i + 1);
4091 if (player->connected && player->present)
4092 player->mapped = TRUE;
4095 #if DEBUG_INIT_PLAYER
4096 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4101 // check if any connected player was not found in playfield
4102 for (i = 0; i < MAX_PLAYERS; i++)
4104 struct PlayerInfo *player = &stored_player[i];
4106 if (player->connected && !player->present)
4108 for (j = 0; j < MAX_PLAYERS; j++)
4110 struct PlayerInfo *field_player = &stored_player[j];
4111 int jx = field_player->jx, jy = field_player->jy;
4113 // assign first free player found that is present in the playfield
4114 if (field_player->present && !field_player->connected)
4116 player->present = TRUE;
4117 player->active = TRUE;
4119 field_player->present = FALSE;
4120 field_player->active = FALSE;
4122 player->initial_element = field_player->initial_element;
4123 player->artwork_element = field_player->artwork_element;
4125 player->block_last_field = field_player->block_last_field;
4126 player->block_delay_adjustment = field_player->block_delay_adjustment;
4128 StorePlayer[jx][jy] = player->element_nr;
4130 player->jx = player->last_jx = jx;
4131 player->jy = player->last_jy = jy;
4141 Debug("game:init:player", "local_player->present == %d",
4142 local_player->present);
4145 // set focus to local player for network games, else to all players
4146 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4147 game.centered_player_nr_next = game.centered_player_nr;
4148 game.set_centered_player = FALSE;
4149 game.set_centered_player_wrap = FALSE;
4151 if (network_playing && tape.recording)
4153 // store client dependent player focus when recording network games
4154 tape.centered_player_nr_next = game.centered_player_nr_next;
4155 tape.set_centered_player = TRUE;
4160 // when playing a tape, eliminate all players who do not participate
4162 #if USE_NEW_PLAYER_ASSIGNMENTS
4164 if (!game.team_mode)
4166 for (i = 0; i < MAX_PLAYERS; i++)
4168 if (stored_player[i].active &&
4169 !tape.player_participates[map_player_action[i]])
4171 struct PlayerInfo *player = &stored_player[i];
4172 int jx = player->jx, jy = player->jy;
4174 #if DEBUG_INIT_PLAYER
4175 Debug("game:init:player", "Removing player %d at (%d, %d)",
4179 player->active = FALSE;
4180 StorePlayer[jx][jy] = 0;
4181 Tile[jx][jy] = EL_EMPTY;
4188 for (i = 0; i < MAX_PLAYERS; i++)
4190 if (stored_player[i].active &&
4191 !tape.player_participates[i])
4193 struct PlayerInfo *player = &stored_player[i];
4194 int jx = player->jx, jy = player->jy;
4196 player->active = FALSE;
4197 StorePlayer[jx][jy] = 0;
4198 Tile[jx][jy] = EL_EMPTY;
4203 else if (!network.enabled && !game.team_mode) // && !tape.playing
4205 // when in single player mode, eliminate all but the local player
4207 for (i = 0; i < MAX_PLAYERS; i++)
4209 struct PlayerInfo *player = &stored_player[i];
4211 if (player->active && player != local_player)
4213 int jx = player->jx, jy = player->jy;
4215 player->active = FALSE;
4216 player->present = FALSE;
4218 StorePlayer[jx][jy] = 0;
4219 Tile[jx][jy] = EL_EMPTY;
4224 for (i = 0; i < MAX_PLAYERS; i++)
4225 if (stored_player[i].active)
4226 game.players_still_needed++;
4228 if (level.solved_by_one_player)
4229 game.players_still_needed = 1;
4231 // when recording the game, store which players take part in the game
4234 #if USE_NEW_PLAYER_ASSIGNMENTS
4235 for (i = 0; i < MAX_PLAYERS; i++)
4236 if (stored_player[i].connected)
4237 tape.player_participates[i] = TRUE;
4239 for (i = 0; i < MAX_PLAYERS; i++)
4240 if (stored_player[i].active)
4241 tape.player_participates[i] = TRUE;
4245 #if DEBUG_INIT_PLAYER
4246 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4249 if (BorderElement == EL_EMPTY)
4252 SBX_Right = lev_fieldx - SCR_FIELDX;
4254 SBY_Lower = lev_fieldy - SCR_FIELDY;
4259 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4261 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4264 if (full_lev_fieldx <= SCR_FIELDX)
4265 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4266 if (full_lev_fieldy <= SCR_FIELDY)
4267 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4269 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4271 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4274 // if local player not found, look for custom element that might create
4275 // the player (make some assumptions about the right custom element)
4276 if (!local_player->present)
4278 int start_x = 0, start_y = 0;
4279 int found_rating = 0;
4280 int found_element = EL_UNDEFINED;
4281 int player_nr = local_player->index_nr;
4283 SCAN_PLAYFIELD(x, y)
4285 int element = Tile[x][y];
4290 if (level.use_start_element[player_nr] &&
4291 level.start_element[player_nr] == element &&
4298 found_element = element;
4301 if (!IS_CUSTOM_ELEMENT(element))
4304 if (CAN_CHANGE(element))
4306 for (i = 0; i < element_info[element].num_change_pages; i++)
4308 // check for player created from custom element as single target
4309 content = element_info[element].change_page[i].target_element;
4310 is_player = ELEM_IS_PLAYER(content);
4312 if (is_player && (found_rating < 3 ||
4313 (found_rating == 3 && element < found_element)))
4319 found_element = element;
4324 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4326 // check for player created from custom element as explosion content
4327 content = element_info[element].content.e[xx][yy];
4328 is_player = ELEM_IS_PLAYER(content);
4330 if (is_player && (found_rating < 2 ||
4331 (found_rating == 2 && element < found_element)))
4333 start_x = x + xx - 1;
4334 start_y = y + yy - 1;
4337 found_element = element;
4340 if (!CAN_CHANGE(element))
4343 for (i = 0; i < element_info[element].num_change_pages; i++)
4345 // check for player created from custom element as extended target
4347 element_info[element].change_page[i].target_content.e[xx][yy];
4349 is_player = ELEM_IS_PLAYER(content);
4351 if (is_player && (found_rating < 1 ||
4352 (found_rating == 1 && element < found_element)))
4354 start_x = x + xx - 1;
4355 start_y = y + yy - 1;
4358 found_element = element;
4364 scroll_x = SCROLL_POSITION_X(start_x);
4365 scroll_y = SCROLL_POSITION_Y(start_y);
4369 scroll_x = SCROLL_POSITION_X(local_player->jx);
4370 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4373 // !!! FIX THIS (START) !!!
4374 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4376 InitGameEngine_EM();
4378 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4380 InitGameEngine_SP();
4382 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4384 InitGameEngine_MM();
4388 DrawLevel(REDRAW_FIELD);
4391 // after drawing the level, correct some elements
4392 if (game.timegate_time_left == 0)
4393 CloseAllOpenTimegates();
4396 // blit playfield from scroll buffer to normal back buffer for fading in
4397 BlitScreenToBitmap(backbuffer);
4398 // !!! FIX THIS (END) !!!
4400 DrawMaskedBorder(fade_mask);
4405 // full screen redraw is required at this point in the following cases:
4406 // - special editor door undrawn when game was started from level editor
4407 // - drawing area (playfield) was changed and has to be removed completely
4408 redraw_mask = REDRAW_ALL;
4412 if (!game.restart_level)
4414 // copy default game door content to main double buffer
4416 // !!! CHECK AGAIN !!!
4417 SetPanelBackground();
4418 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4419 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4422 SetPanelBackground();
4423 SetDrawBackgroundMask(REDRAW_DOOR_1);
4425 UpdateAndDisplayGameControlValues();
4427 if (!game.restart_level)
4433 CreateGameButtons();
4438 // copy actual game door content to door double buffer for OpenDoor()
4439 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4441 OpenDoor(DOOR_OPEN_ALL);
4443 KeyboardAutoRepeatOffUnlessAutoplay();
4445 #if DEBUG_INIT_PLAYER
4446 DebugPrintPlayerStatus("Player status (final)");
4455 if (!game.restart_level && !tape.playing)
4457 LevelStats_incPlayed(level_nr);
4459 SaveLevelSetup_SeriesInfo();
4462 game.restart_level = FALSE;
4463 game.restart_game_message = NULL;
4465 game.request_active = FALSE;
4466 game.request_active_or_moving = FALSE;
4468 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4469 InitGameActions_MM();
4471 SaveEngineSnapshotToListInitial();
4473 if (!game.restart_level)
4475 PlaySound(SND_GAME_STARTING);
4477 if (setup.sound_music)
4482 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4483 int actual_player_x, int actual_player_y)
4485 // this is used for non-R'n'D game engines to update certain engine values
4487 // needed to determine if sounds are played within the visible screen area
4488 scroll_x = actual_scroll_x;
4489 scroll_y = actual_scroll_y;
4491 // needed to get player position for "follow finger" playing input method
4492 local_player->jx = actual_player_x;
4493 local_player->jy = actual_player_y;
4496 void InitMovDir(int x, int y)
4498 int i, element = Tile[x][y];
4499 static int xy[4][2] =
4506 static int direction[3][4] =
4508 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4509 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4510 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4519 Tile[x][y] = EL_BUG;
4520 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4523 case EL_SPACESHIP_RIGHT:
4524 case EL_SPACESHIP_UP:
4525 case EL_SPACESHIP_LEFT:
4526 case EL_SPACESHIP_DOWN:
4527 Tile[x][y] = EL_SPACESHIP;
4528 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4531 case EL_BD_BUTTERFLY_RIGHT:
4532 case EL_BD_BUTTERFLY_UP:
4533 case EL_BD_BUTTERFLY_LEFT:
4534 case EL_BD_BUTTERFLY_DOWN:
4535 Tile[x][y] = EL_BD_BUTTERFLY;
4536 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4539 case EL_BD_FIREFLY_RIGHT:
4540 case EL_BD_FIREFLY_UP:
4541 case EL_BD_FIREFLY_LEFT:
4542 case EL_BD_FIREFLY_DOWN:
4543 Tile[x][y] = EL_BD_FIREFLY;
4544 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4547 case EL_PACMAN_RIGHT:
4549 case EL_PACMAN_LEFT:
4550 case EL_PACMAN_DOWN:
4551 Tile[x][y] = EL_PACMAN;
4552 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4555 case EL_YAMYAM_LEFT:
4556 case EL_YAMYAM_RIGHT:
4558 case EL_YAMYAM_DOWN:
4559 Tile[x][y] = EL_YAMYAM;
4560 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4563 case EL_SP_SNIKSNAK:
4564 MovDir[x][y] = MV_UP;
4567 case EL_SP_ELECTRON:
4568 MovDir[x][y] = MV_LEFT;
4575 Tile[x][y] = EL_MOLE;
4576 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4579 case EL_SPRING_LEFT:
4580 case EL_SPRING_RIGHT:
4581 Tile[x][y] = EL_SPRING;
4582 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4586 if (IS_CUSTOM_ELEMENT(element))
4588 struct ElementInfo *ei = &element_info[element];
4589 int move_direction_initial = ei->move_direction_initial;
4590 int move_pattern = ei->move_pattern;
4592 if (move_direction_initial == MV_START_PREVIOUS)
4594 if (MovDir[x][y] != MV_NONE)
4597 move_direction_initial = MV_START_AUTOMATIC;
4600 if (move_direction_initial == MV_START_RANDOM)
4601 MovDir[x][y] = 1 << RND(4);
4602 else if (move_direction_initial & MV_ANY_DIRECTION)
4603 MovDir[x][y] = move_direction_initial;
4604 else if (move_pattern == MV_ALL_DIRECTIONS ||
4605 move_pattern == MV_TURNING_LEFT ||
4606 move_pattern == MV_TURNING_RIGHT ||
4607 move_pattern == MV_TURNING_LEFT_RIGHT ||
4608 move_pattern == MV_TURNING_RIGHT_LEFT ||
4609 move_pattern == MV_TURNING_RANDOM)
4610 MovDir[x][y] = 1 << RND(4);
4611 else if (move_pattern == MV_HORIZONTAL)
4612 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4613 else if (move_pattern == MV_VERTICAL)
4614 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4615 else if (move_pattern & MV_ANY_DIRECTION)
4616 MovDir[x][y] = element_info[element].move_pattern;
4617 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4618 move_pattern == MV_ALONG_RIGHT_SIDE)
4620 // use random direction as default start direction
4621 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4622 MovDir[x][y] = 1 << RND(4);
4624 for (i = 0; i < NUM_DIRECTIONS; i++)
4626 int x1 = x + xy[i][0];
4627 int y1 = y + xy[i][1];
4629 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4631 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4632 MovDir[x][y] = direction[0][i];
4634 MovDir[x][y] = direction[1][i];
4643 MovDir[x][y] = 1 << RND(4);
4645 if (element != EL_BUG &&
4646 element != EL_SPACESHIP &&
4647 element != EL_BD_BUTTERFLY &&
4648 element != EL_BD_FIREFLY)
4651 for (i = 0; i < NUM_DIRECTIONS; i++)
4653 int x1 = x + xy[i][0];
4654 int y1 = y + xy[i][1];
4656 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4658 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4660 MovDir[x][y] = direction[0][i];
4663 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4664 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4666 MovDir[x][y] = direction[1][i];
4675 GfxDir[x][y] = MovDir[x][y];
4678 void InitAmoebaNr(int x, int y)
4681 int group_nr = AmoebaNeighbourNr(x, y);
4685 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4687 if (AmoebaCnt[i] == 0)
4695 AmoebaNr[x][y] = group_nr;
4696 AmoebaCnt[group_nr]++;
4697 AmoebaCnt2[group_nr]++;
4700 static void LevelSolved(void)
4702 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4703 game.players_still_needed > 0)
4706 game.LevelSolved = TRUE;
4707 game.GameOver = TRUE;
4709 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4710 game_em.lev->score :
4711 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4714 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4715 MM_HEALTH(game_mm.laser_overload_value) :
4718 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4719 game.LevelSolved_CountingScore = game.score_final;
4720 game.LevelSolved_CountingHealth = game.health_final;
4725 static int time_count_steps;
4726 static int time, time_final;
4727 static float score, score_final; // needed for time score < 10 for 10 seconds
4728 static int health, health_final;
4729 static int game_over_delay_1 = 0;
4730 static int game_over_delay_2 = 0;
4731 static int game_over_delay_3 = 0;
4732 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4733 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4735 if (!game.LevelSolved_GameWon)
4739 // do not start end game actions before the player stops moving (to exit)
4740 if (local_player->active && local_player->MovPos)
4743 game.LevelSolved_GameWon = TRUE;
4744 game.LevelSolved_SaveTape = tape.recording;
4745 game.LevelSolved_SaveScore = !tape.playing;
4749 LevelStats_incSolved(level_nr);
4751 SaveLevelSetup_SeriesInfo();
4754 if (tape.auto_play) // tape might already be stopped here
4755 tape.auto_play_level_solved = TRUE;
4759 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4760 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4761 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4763 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4764 score = score_final = game.score_final;
4765 health = health_final = game.health_final;
4769 int time_frames = 0;
4774 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4776 else if (game.no_time_limit && TimePlayed < 999)
4779 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4782 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4784 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4786 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4789 score_final += health * time_score;
4792 game.score_final = score_final;
4793 game.health_final = health_final;
4796 if (level_editor_test_game || !setup.count_score_after_game)
4799 score = score_final;
4801 game.LevelSolved_CountingTime = time;
4802 game.LevelSolved_CountingScore = score;
4804 game_panel_controls[GAME_PANEL_TIME].value = time;
4805 game_panel_controls[GAME_PANEL_SCORE].value = score;
4807 DisplayGameControlValues();
4810 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4812 // check if last player has left the level
4813 if (game.exit_x >= 0 &&
4816 int x = game.exit_x;
4817 int y = game.exit_y;
4818 int element = Tile[x][y];
4820 // close exit door after last player
4821 if ((game.all_players_gone &&
4822 (element == EL_EXIT_OPEN ||
4823 element == EL_SP_EXIT_OPEN ||
4824 element == EL_STEEL_EXIT_OPEN)) ||
4825 element == EL_EM_EXIT_OPEN ||
4826 element == EL_EM_STEEL_EXIT_OPEN)
4830 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4831 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4832 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4833 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4834 EL_EM_STEEL_EXIT_CLOSING);
4836 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4839 // player disappears
4840 DrawLevelField(x, y);
4843 for (i = 0; i < MAX_PLAYERS; i++)
4845 struct PlayerInfo *player = &stored_player[i];
4847 if (player->present)
4849 RemovePlayer(player);
4851 // player disappears
4852 DrawLevelField(player->jx, player->jy);
4857 PlaySound(SND_GAME_WINNING);
4860 if (setup.count_score_after_game)
4862 if (time != time_final)
4864 if (game_over_delay_1 > 0)
4866 game_over_delay_1--;
4871 int time_to_go = ABS(time_final - time);
4872 int time_count_dir = (time < time_final ? +1 : -1);
4874 if (time_to_go < time_count_steps)
4875 time_count_steps = 1;
4877 time += time_count_steps * time_count_dir;
4878 score += time_count_steps * time_score;
4880 // set final score to correct rounding differences after counting score
4881 if (time == time_final)
4882 score = score_final;
4884 game.LevelSolved_CountingTime = time;
4885 game.LevelSolved_CountingScore = score;
4887 game_panel_controls[GAME_PANEL_TIME].value = time;
4888 game_panel_controls[GAME_PANEL_SCORE].value = score;
4890 DisplayGameControlValues();
4892 if (time == time_final)
4893 StopSound(SND_GAME_LEVELTIME_BONUS);
4894 else if (setup.sound_loops)
4895 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4897 PlaySound(SND_GAME_LEVELTIME_BONUS);
4902 if (health != health_final)
4904 if (game_over_delay_2 > 0)
4906 game_over_delay_2--;
4911 int health_count_dir = (health < health_final ? +1 : -1);
4913 health += health_count_dir;
4914 score += time_score;
4916 game.LevelSolved_CountingHealth = health;
4917 game.LevelSolved_CountingScore = score;
4919 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4920 game_panel_controls[GAME_PANEL_SCORE].value = score;
4922 DisplayGameControlValues();
4924 if (health == health_final)
4925 StopSound(SND_GAME_LEVELTIME_BONUS);
4926 else if (setup.sound_loops)
4927 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4929 PlaySound(SND_GAME_LEVELTIME_BONUS);
4935 game.panel.active = FALSE;
4937 if (game_over_delay_3 > 0)
4939 game_over_delay_3--;
4949 // used instead of "level_nr" (needed for network games)
4950 int last_level_nr = levelset.level_nr;
4953 game.LevelSolved_GameEnd = TRUE;
4955 if (game.LevelSolved_SaveTape)
4957 // make sure that request dialog to save tape does not open door again
4958 if (!global.use_envelope_request)
4959 CloseDoor(DOOR_CLOSE_1);
4961 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4964 // if no tape is to be saved, close both doors simultaneously
4965 CloseDoor(DOOR_CLOSE_ALL);
4967 if (level_editor_test_game)
4969 SetGameStatus(GAME_MODE_MAIN);
4976 if (!game.LevelSolved_SaveScore)
4978 SetGameStatus(GAME_MODE_MAIN);
4985 if (level_nr == leveldir_current->handicap_level)
4987 leveldir_current->handicap_level++;
4989 SaveLevelSetup_SeriesInfo();
4992 if (setup.increment_levels &&
4993 level_nr < leveldir_current->last_level &&
4996 level_nr++; // advance to next level
4997 TapeErase(); // start with empty tape
4999 if (setup.auto_play_next_level)
5001 LoadLevel(level_nr);
5003 SaveLevelSetup_SeriesInfo();
5007 hi_pos = NewHiScore(last_level_nr);
5009 if (hi_pos >= 0 && setup.show_scores_after_game)
5011 SetGameStatus(GAME_MODE_SCORES);
5013 DrawHallOfFame(last_level_nr, hi_pos);
5015 else if (setup.auto_play_next_level && setup.increment_levels &&
5016 last_level_nr < leveldir_current->last_level &&
5019 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5023 SetGameStatus(GAME_MODE_MAIN);
5029 int NewHiScore(int level_nr)
5033 boolean one_score_entry_per_name = !program.many_scores_per_name;
5035 LoadScore(level_nr);
5037 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5038 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5041 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5043 if (game.score_final > highscore[k].Score)
5045 // player has made it to the hall of fame
5047 if (k < MAX_SCORE_ENTRIES - 1)
5049 int m = MAX_SCORE_ENTRIES - 1;
5051 if (one_score_entry_per_name)
5053 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5054 if (strEqual(setup.player_name, highscore[l].Name))
5057 if (m == k) // player's new highscore overwrites his old one
5061 for (l = m; l > k; l--)
5063 strcpy(highscore[l].Name, highscore[l - 1].Name);
5064 highscore[l].Score = highscore[l - 1].Score;
5070 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5071 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5072 highscore[k].Score = game.score_final;
5077 else if (one_score_entry_per_name &&
5078 !strncmp(setup.player_name, highscore[k].Name,
5079 MAX_PLAYER_NAME_LEN))
5080 break; // player already there with a higher score
5084 SaveScore(level_nr);
5089 static int getElementMoveStepsizeExt(int x, int y, int direction)
5091 int element = Tile[x][y];
5092 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5093 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5094 int horiz_move = (dx != 0);
5095 int sign = (horiz_move ? dx : dy);
5096 int step = sign * element_info[element].move_stepsize;
5098 // special values for move stepsize for spring and things on conveyor belt
5101 if (CAN_FALL(element) &&
5102 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5103 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5104 else if (element == EL_SPRING)
5105 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5111 static int getElementMoveStepsize(int x, int y)
5113 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5116 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5118 if (player->GfxAction != action || player->GfxDir != dir)
5120 player->GfxAction = action;
5121 player->GfxDir = dir;
5123 player->StepFrame = 0;
5127 static void ResetGfxFrame(int x, int y)
5129 // profiling showed that "autotest" spends 10~20% of its time in this function
5130 if (DrawingDeactivatedField())
5133 int element = Tile[x][y];
5134 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5136 if (graphic_info[graphic].anim_global_sync)
5137 GfxFrame[x][y] = FrameCounter;
5138 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5139 GfxFrame[x][y] = CustomValue[x][y];
5140 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5141 GfxFrame[x][y] = element_info[element].collect_score;
5142 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5143 GfxFrame[x][y] = ChangeDelay[x][y];
5146 static void ResetGfxAnimation(int x, int y)
5148 GfxAction[x][y] = ACTION_DEFAULT;
5149 GfxDir[x][y] = MovDir[x][y];
5152 ResetGfxFrame(x, y);
5155 static void ResetRandomAnimationValue(int x, int y)
5157 GfxRandom[x][y] = INIT_GFX_RANDOM();
5160 static void InitMovingField(int x, int y, int direction)
5162 int element = Tile[x][y];
5163 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5164 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5167 boolean is_moving_before, is_moving_after;
5169 // check if element was/is moving or being moved before/after mode change
5170 is_moving_before = (WasJustMoving[x][y] != 0);
5171 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5173 // reset animation only for moving elements which change direction of moving
5174 // or which just started or stopped moving
5175 // (else CEs with property "can move" / "not moving" are reset each frame)
5176 if (is_moving_before != is_moving_after ||
5177 direction != MovDir[x][y])
5178 ResetGfxAnimation(x, y);
5180 MovDir[x][y] = direction;
5181 GfxDir[x][y] = direction;
5183 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5184 direction == MV_DOWN && CAN_FALL(element) ?
5185 ACTION_FALLING : ACTION_MOVING);
5187 // this is needed for CEs with property "can move" / "not moving"
5189 if (is_moving_after)
5191 if (Tile[newx][newy] == EL_EMPTY)
5192 Tile[newx][newy] = EL_BLOCKED;
5194 MovDir[newx][newy] = MovDir[x][y];
5196 CustomValue[newx][newy] = CustomValue[x][y];
5198 GfxFrame[newx][newy] = GfxFrame[x][y];
5199 GfxRandom[newx][newy] = GfxRandom[x][y];
5200 GfxAction[newx][newy] = GfxAction[x][y];
5201 GfxDir[newx][newy] = GfxDir[x][y];
5205 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5207 int direction = MovDir[x][y];
5208 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5209 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5215 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5217 int oldx = x, oldy = y;
5218 int direction = MovDir[x][y];
5220 if (direction == MV_LEFT)
5222 else if (direction == MV_RIGHT)
5224 else if (direction == MV_UP)
5226 else if (direction == MV_DOWN)
5229 *comes_from_x = oldx;
5230 *comes_from_y = oldy;
5233 static int MovingOrBlocked2Element(int x, int y)
5235 int element = Tile[x][y];
5237 if (element == EL_BLOCKED)
5241 Blocked2Moving(x, y, &oldx, &oldy);
5242 return Tile[oldx][oldy];
5248 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5250 // like MovingOrBlocked2Element(), but if element is moving
5251 // and (x,y) is the field the moving element is just leaving,
5252 // return EL_BLOCKED instead of the element value
5253 int element = Tile[x][y];
5255 if (IS_MOVING(x, y))
5257 if (element == EL_BLOCKED)
5261 Blocked2Moving(x, y, &oldx, &oldy);
5262 return Tile[oldx][oldy];
5271 static void RemoveField(int x, int y)
5273 Tile[x][y] = EL_EMPTY;
5279 CustomValue[x][y] = 0;
5282 ChangeDelay[x][y] = 0;
5283 ChangePage[x][y] = -1;
5284 Pushed[x][y] = FALSE;
5286 GfxElement[x][y] = EL_UNDEFINED;
5287 GfxAction[x][y] = ACTION_DEFAULT;
5288 GfxDir[x][y] = MV_NONE;
5291 static void RemoveMovingField(int x, int y)
5293 int oldx = x, oldy = y, newx = x, newy = y;
5294 int element = Tile[x][y];
5295 int next_element = EL_UNDEFINED;
5297 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5300 if (IS_MOVING(x, y))
5302 Moving2Blocked(x, y, &newx, &newy);
5304 if (Tile[newx][newy] != EL_BLOCKED)
5306 // element is moving, but target field is not free (blocked), but
5307 // already occupied by something different (example: acid pool);
5308 // in this case, only remove the moving field, but not the target
5310 RemoveField(oldx, oldy);
5312 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5314 TEST_DrawLevelField(oldx, oldy);
5319 else if (element == EL_BLOCKED)
5321 Blocked2Moving(x, y, &oldx, &oldy);
5322 if (!IS_MOVING(oldx, oldy))
5326 if (element == EL_BLOCKED &&
5327 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5328 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5329 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5330 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5331 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5332 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5333 next_element = get_next_element(Tile[oldx][oldy]);
5335 RemoveField(oldx, oldy);
5336 RemoveField(newx, newy);
5338 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5340 if (next_element != EL_UNDEFINED)
5341 Tile[oldx][oldy] = next_element;
5343 TEST_DrawLevelField(oldx, oldy);
5344 TEST_DrawLevelField(newx, newy);
5347 void DrawDynamite(int x, int y)
5349 int sx = SCREENX(x), sy = SCREENY(y);
5350 int graphic = el2img(Tile[x][y]);
5353 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5356 if (IS_WALKABLE_INSIDE(Back[x][y]))
5360 DrawLevelElement(x, y, Back[x][y]);
5361 else if (Store[x][y])
5362 DrawLevelElement(x, y, Store[x][y]);
5363 else if (game.use_masked_elements)
5364 DrawLevelElement(x, y, EL_EMPTY);
5366 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5368 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5369 DrawGraphicThruMask(sx, sy, graphic, frame);
5371 DrawGraphic(sx, sy, graphic, frame);
5374 static void CheckDynamite(int x, int y)
5376 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5380 if (MovDelay[x][y] != 0)
5383 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5389 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5394 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5396 boolean num_checked_players = 0;
5399 for (i = 0; i < MAX_PLAYERS; i++)
5401 if (stored_player[i].active)
5403 int sx = stored_player[i].jx;
5404 int sy = stored_player[i].jy;
5406 if (num_checked_players == 0)
5413 *sx1 = MIN(*sx1, sx);
5414 *sy1 = MIN(*sy1, sy);
5415 *sx2 = MAX(*sx2, sx);
5416 *sy2 = MAX(*sy2, sy);
5419 num_checked_players++;
5424 static boolean checkIfAllPlayersFitToScreen_RND(void)
5426 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5428 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5430 return (sx2 - sx1 < SCR_FIELDX &&
5431 sy2 - sy1 < SCR_FIELDY);
5434 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5436 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5438 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5440 *sx = (sx1 + sx2) / 2;
5441 *sy = (sy1 + sy2) / 2;
5444 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5445 boolean center_screen, boolean quick_relocation)
5447 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5448 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5449 boolean no_delay = (tape.warp_forward);
5450 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5451 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5452 int new_scroll_x, new_scroll_y;
5454 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5456 // case 1: quick relocation inside visible screen (without scrolling)
5463 if (!level.shifted_relocation || center_screen)
5465 // relocation _with_ centering of screen
5467 new_scroll_x = SCROLL_POSITION_X(x);
5468 new_scroll_y = SCROLL_POSITION_Y(y);
5472 // relocation _without_ centering of screen
5474 int center_scroll_x = SCROLL_POSITION_X(old_x);
5475 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5476 int offset_x = x + (scroll_x - center_scroll_x);
5477 int offset_y = y + (scroll_y - center_scroll_y);
5479 // for new screen position, apply previous offset to center position
5480 new_scroll_x = SCROLL_POSITION_X(offset_x);
5481 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5484 if (quick_relocation)
5486 // case 2: quick relocation (redraw without visible scrolling)
5488 scroll_x = new_scroll_x;
5489 scroll_y = new_scroll_y;
5496 // case 3: visible relocation (with scrolling to new position)
5498 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5500 SetVideoFrameDelay(wait_delay_value);
5502 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5504 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5505 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5507 if (dx == 0 && dy == 0) // no scrolling needed at all
5513 // set values for horizontal/vertical screen scrolling (half tile size)
5514 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5515 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5516 int pos_x = dx * TILEX / 2;
5517 int pos_y = dy * TILEY / 2;
5518 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5519 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5521 ScrollLevel(dx, dy);
5524 // scroll in two steps of half tile size to make things smoother
5525 BlitScreenToBitmapExt_RND(window, fx, fy);
5527 // scroll second step to align at full tile size
5528 BlitScreenToBitmap(window);
5534 SetVideoFrameDelay(frame_delay_value_old);
5537 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5539 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5540 int player_nr = GET_PLAYER_NR(el_player);
5541 struct PlayerInfo *player = &stored_player[player_nr];
5542 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5543 boolean no_delay = (tape.warp_forward);
5544 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5545 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5546 int old_jx = player->jx;
5547 int old_jy = player->jy;
5548 int old_element = Tile[old_jx][old_jy];
5549 int element = Tile[jx][jy];
5550 boolean player_relocated = (old_jx != jx || old_jy != jy);
5552 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5553 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5554 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5555 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5556 int leave_side_horiz = move_dir_horiz;
5557 int leave_side_vert = move_dir_vert;
5558 int enter_side = enter_side_horiz | enter_side_vert;
5559 int leave_side = leave_side_horiz | leave_side_vert;
5561 if (player->buried) // do not reanimate dead player
5564 if (!player_relocated) // no need to relocate the player
5567 if (IS_PLAYER(jx, jy)) // player already placed at new position
5569 RemoveField(jx, jy); // temporarily remove newly placed player
5570 DrawLevelField(jx, jy);
5573 if (player->present)
5575 while (player->MovPos)
5577 ScrollPlayer(player, SCROLL_GO_ON);
5578 ScrollScreen(NULL, SCROLL_GO_ON);
5580 AdvanceFrameAndPlayerCounters(player->index_nr);
5584 BackToFront_WithFrameDelay(wait_delay_value);
5587 DrawPlayer(player); // needed here only to cleanup last field
5588 DrawLevelField(player->jx, player->jy); // remove player graphic
5590 player->is_moving = FALSE;
5593 if (IS_CUSTOM_ELEMENT(old_element))
5594 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5596 player->index_bit, leave_side);
5598 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5600 player->index_bit, leave_side);
5602 Tile[jx][jy] = el_player;
5603 InitPlayerField(jx, jy, el_player, TRUE);
5605 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5606 possible that the relocation target field did not contain a player element,
5607 but a walkable element, to which the new player was relocated -- in this
5608 case, restore that (already initialized!) element on the player field */
5609 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5611 Tile[jx][jy] = element; // restore previously existing element
5614 // only visually relocate centered player
5615 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5616 FALSE, level.instant_relocation);
5618 TestIfPlayerTouchesBadThing(jx, jy);
5619 TestIfPlayerTouchesCustomElement(jx, jy);
5621 if (IS_CUSTOM_ELEMENT(element))
5622 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5623 player->index_bit, enter_side);
5625 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5626 player->index_bit, enter_side);
5628 if (player->is_switching)
5630 /* ensure that relocation while still switching an element does not cause
5631 a new element to be treated as also switched directly after relocation
5632 (this is important for teleporter switches that teleport the player to
5633 a place where another teleporter switch is in the same direction, which
5634 would then incorrectly be treated as immediately switched before the
5635 direction key that caused the switch was released) */
5637 player->switch_x += jx - old_jx;
5638 player->switch_y += jy - old_jy;
5642 static void Explode(int ex, int ey, int phase, int mode)
5648 // !!! eliminate this variable !!!
5649 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5651 if (game.explosions_delayed)
5653 ExplodeField[ex][ey] = mode;
5657 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5659 int center_element = Tile[ex][ey];
5660 int artwork_element, explosion_element; // set these values later
5662 // remove things displayed in background while burning dynamite
5663 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5666 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5668 // put moving element to center field (and let it explode there)
5669 center_element = MovingOrBlocked2Element(ex, ey);
5670 RemoveMovingField(ex, ey);
5671 Tile[ex][ey] = center_element;
5674 // now "center_element" is finally determined -- set related values now
5675 artwork_element = center_element; // for custom player artwork
5676 explosion_element = center_element; // for custom player artwork
5678 if (IS_PLAYER(ex, ey))
5680 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5682 artwork_element = stored_player[player_nr].artwork_element;
5684 if (level.use_explosion_element[player_nr])
5686 explosion_element = level.explosion_element[player_nr];
5687 artwork_element = explosion_element;
5691 if (mode == EX_TYPE_NORMAL ||
5692 mode == EX_TYPE_CENTER ||
5693 mode == EX_TYPE_CROSS)
5694 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5696 last_phase = element_info[explosion_element].explosion_delay + 1;
5698 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5700 int xx = x - ex + 1;
5701 int yy = y - ey + 1;
5704 if (!IN_LEV_FIELD(x, y) ||
5705 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5706 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5709 element = Tile[x][y];
5711 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5713 element = MovingOrBlocked2Element(x, y);
5715 if (!IS_EXPLOSION_PROOF(element))
5716 RemoveMovingField(x, y);
5719 // indestructible elements can only explode in center (but not flames)
5720 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5721 mode == EX_TYPE_BORDER)) ||
5722 element == EL_FLAMES)
5725 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5726 behaviour, for example when touching a yamyam that explodes to rocks
5727 with active deadly shield, a rock is created under the player !!! */
5728 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5730 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5731 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5732 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5734 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5737 if (IS_ACTIVE_BOMB(element))
5739 // re-activate things under the bomb like gate or penguin
5740 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5747 // save walkable background elements while explosion on same tile
5748 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5749 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5750 Back[x][y] = element;
5752 // ignite explodable elements reached by other explosion
5753 if (element == EL_EXPLOSION)
5754 element = Store2[x][y];
5756 if (AmoebaNr[x][y] &&
5757 (element == EL_AMOEBA_FULL ||
5758 element == EL_BD_AMOEBA ||
5759 element == EL_AMOEBA_GROWING))
5761 AmoebaCnt[AmoebaNr[x][y]]--;
5762 AmoebaCnt2[AmoebaNr[x][y]]--;
5767 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5769 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5771 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5773 if (PLAYERINFO(ex, ey)->use_murphy)
5774 Store[x][y] = EL_EMPTY;
5777 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5778 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5779 else if (ELEM_IS_PLAYER(center_element))
5780 Store[x][y] = EL_EMPTY;
5781 else if (center_element == EL_YAMYAM)
5782 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5783 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5784 Store[x][y] = element_info[center_element].content.e[xx][yy];
5786 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5787 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5788 // otherwise) -- FIX THIS !!!
5789 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5790 Store[x][y] = element_info[element].content.e[1][1];
5792 else if (!CAN_EXPLODE(element))
5793 Store[x][y] = element_info[element].content.e[1][1];
5796 Store[x][y] = EL_EMPTY;
5798 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5799 center_element == EL_AMOEBA_TO_DIAMOND)
5800 Store2[x][y] = element;
5802 Tile[x][y] = EL_EXPLOSION;
5803 GfxElement[x][y] = artwork_element;
5805 ExplodePhase[x][y] = 1;
5806 ExplodeDelay[x][y] = last_phase;
5811 if (center_element == EL_YAMYAM)
5812 game.yamyam_content_nr =
5813 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5825 GfxFrame[x][y] = 0; // restart explosion animation
5827 last_phase = ExplodeDelay[x][y];
5829 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5831 // this can happen if the player leaves an explosion just in time
5832 if (GfxElement[x][y] == EL_UNDEFINED)
5833 GfxElement[x][y] = EL_EMPTY;
5835 border_element = Store2[x][y];
5836 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5837 border_element = StorePlayer[x][y];
5839 if (phase == element_info[border_element].ignition_delay ||
5840 phase == last_phase)
5842 boolean border_explosion = FALSE;
5844 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5845 !PLAYER_EXPLOSION_PROTECTED(x, y))
5847 KillPlayerUnlessExplosionProtected(x, y);
5848 border_explosion = TRUE;
5850 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5852 Tile[x][y] = Store2[x][y];
5855 border_explosion = TRUE;
5857 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5859 AmoebaToDiamond(x, y);
5861 border_explosion = TRUE;
5864 // if an element just explodes due to another explosion (chain-reaction),
5865 // do not immediately end the new explosion when it was the last frame of
5866 // the explosion (as it would be done in the following "if"-statement!)
5867 if (border_explosion && phase == last_phase)
5871 if (phase == last_phase)
5875 element = Tile[x][y] = Store[x][y];
5876 Store[x][y] = Store2[x][y] = 0;
5877 GfxElement[x][y] = EL_UNDEFINED;
5879 // player can escape from explosions and might therefore be still alive
5880 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5881 element <= EL_PLAYER_IS_EXPLODING_4)
5883 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5884 int explosion_element = EL_PLAYER_1 + player_nr;
5885 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5886 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5888 if (level.use_explosion_element[player_nr])
5889 explosion_element = level.explosion_element[player_nr];
5891 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5892 element_info[explosion_element].content.e[xx][yy]);
5895 // restore probably existing indestructible background element
5896 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5897 element = Tile[x][y] = Back[x][y];
5900 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5901 GfxDir[x][y] = MV_NONE;
5902 ChangeDelay[x][y] = 0;
5903 ChangePage[x][y] = -1;
5905 CustomValue[x][y] = 0;
5907 InitField_WithBug2(x, y, FALSE);
5909 TEST_DrawLevelField(x, y);
5911 TestIfElementTouchesCustomElement(x, y);
5913 if (GFX_CRUMBLED(element))
5914 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5916 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5917 StorePlayer[x][y] = 0;
5919 if (ELEM_IS_PLAYER(element))
5920 RelocatePlayer(x, y, element);
5922 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5924 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5925 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5928 TEST_DrawLevelFieldCrumbled(x, y);
5930 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5932 DrawLevelElement(x, y, Back[x][y]);
5933 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5935 else if (IS_WALKABLE_UNDER(Back[x][y]))
5937 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5938 DrawLevelElementThruMask(x, y, Back[x][y]);
5940 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5941 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5945 static void DynaExplode(int ex, int ey)
5948 int dynabomb_element = Tile[ex][ey];
5949 int dynabomb_size = 1;
5950 boolean dynabomb_xl = FALSE;
5951 struct PlayerInfo *player;
5952 static int xy[4][2] =
5960 if (IS_ACTIVE_BOMB(dynabomb_element))
5962 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5963 dynabomb_size = player->dynabomb_size;
5964 dynabomb_xl = player->dynabomb_xl;
5965 player->dynabombs_left++;
5968 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5970 for (i = 0; i < NUM_DIRECTIONS; i++)
5972 for (j = 1; j <= dynabomb_size; j++)
5974 int x = ex + j * xy[i][0];
5975 int y = ey + j * xy[i][1];
5978 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5981 element = Tile[x][y];
5983 // do not restart explosions of fields with active bombs
5984 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5987 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5989 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5990 !IS_DIGGABLE(element) && !dynabomb_xl)
5996 void Bang(int x, int y)
5998 int element = MovingOrBlocked2Element(x, y);
5999 int explosion_type = EX_TYPE_NORMAL;
6001 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6003 struct PlayerInfo *player = PLAYERINFO(x, y);
6005 element = Tile[x][y] = player->initial_element;
6007 if (level.use_explosion_element[player->index_nr])
6009 int explosion_element = level.explosion_element[player->index_nr];
6011 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6012 explosion_type = EX_TYPE_CROSS;
6013 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6014 explosion_type = EX_TYPE_CENTER;
6022 case EL_BD_BUTTERFLY:
6025 case EL_DARK_YAMYAM:
6029 RaiseScoreElement(element);
6032 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6033 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6034 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6035 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6036 case EL_DYNABOMB_INCREASE_NUMBER:
6037 case EL_DYNABOMB_INCREASE_SIZE:
6038 case EL_DYNABOMB_INCREASE_POWER:
6039 explosion_type = EX_TYPE_DYNA;
6042 case EL_DC_LANDMINE:
6043 explosion_type = EX_TYPE_CENTER;
6048 case EL_LAMP_ACTIVE:
6049 case EL_AMOEBA_TO_DIAMOND:
6050 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6051 explosion_type = EX_TYPE_CENTER;
6055 if (element_info[element].explosion_type == EXPLODES_CROSS)
6056 explosion_type = EX_TYPE_CROSS;
6057 else if (element_info[element].explosion_type == EXPLODES_1X1)
6058 explosion_type = EX_TYPE_CENTER;
6062 if (explosion_type == EX_TYPE_DYNA)
6065 Explode(x, y, EX_PHASE_START, explosion_type);
6067 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6070 static void SplashAcid(int x, int y)
6072 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6073 (!IN_LEV_FIELD(x - 1, y - 2) ||
6074 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6075 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6077 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6078 (!IN_LEV_FIELD(x + 1, y - 2) ||
6079 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6080 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6082 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6085 static void InitBeltMovement(void)
6087 static int belt_base_element[4] =
6089 EL_CONVEYOR_BELT_1_LEFT,
6090 EL_CONVEYOR_BELT_2_LEFT,
6091 EL_CONVEYOR_BELT_3_LEFT,
6092 EL_CONVEYOR_BELT_4_LEFT
6094 static int belt_base_active_element[4] =
6096 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6097 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6098 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6099 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6104 // set frame order for belt animation graphic according to belt direction
6105 for (i = 0; i < NUM_BELTS; i++)
6109 for (j = 0; j < NUM_BELT_PARTS; j++)
6111 int element = belt_base_active_element[belt_nr] + j;
6112 int graphic_1 = el2img(element);
6113 int graphic_2 = el2panelimg(element);
6115 if (game.belt_dir[i] == MV_LEFT)
6117 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6118 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6122 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6123 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6128 SCAN_PLAYFIELD(x, y)
6130 int element = Tile[x][y];
6132 for (i = 0; i < NUM_BELTS; i++)
6134 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6136 int e_belt_nr = getBeltNrFromBeltElement(element);
6139 if (e_belt_nr == belt_nr)
6141 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6143 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6150 static void ToggleBeltSwitch(int x, int y)
6152 static int belt_base_element[4] =
6154 EL_CONVEYOR_BELT_1_LEFT,
6155 EL_CONVEYOR_BELT_2_LEFT,
6156 EL_CONVEYOR_BELT_3_LEFT,
6157 EL_CONVEYOR_BELT_4_LEFT
6159 static int belt_base_active_element[4] =
6161 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6162 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6163 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6164 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6166 static int belt_base_switch_element[4] =
6168 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6169 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6170 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6171 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6173 static int belt_move_dir[4] =
6181 int element = Tile[x][y];
6182 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6183 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6184 int belt_dir = belt_move_dir[belt_dir_nr];
6187 if (!IS_BELT_SWITCH(element))
6190 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6191 game.belt_dir[belt_nr] = belt_dir;
6193 if (belt_dir_nr == 3)
6196 // set frame order for belt animation graphic according to belt direction
6197 for (i = 0; i < NUM_BELT_PARTS; i++)
6199 int element = belt_base_active_element[belt_nr] + i;
6200 int graphic_1 = el2img(element);
6201 int graphic_2 = el2panelimg(element);
6203 if (belt_dir == MV_LEFT)
6205 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6206 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6210 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6211 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6215 SCAN_PLAYFIELD(xx, yy)
6217 int element = Tile[xx][yy];
6219 if (IS_BELT_SWITCH(element))
6221 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6223 if (e_belt_nr == belt_nr)
6225 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6226 TEST_DrawLevelField(xx, yy);
6229 else if (IS_BELT(element) && belt_dir != MV_NONE)
6231 int e_belt_nr = getBeltNrFromBeltElement(element);
6233 if (e_belt_nr == belt_nr)
6235 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6237 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6238 TEST_DrawLevelField(xx, yy);
6241 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6243 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6245 if (e_belt_nr == belt_nr)
6247 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6249 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6250 TEST_DrawLevelField(xx, yy);
6256 static void ToggleSwitchgateSwitch(int x, int y)
6260 game.switchgate_pos = !game.switchgate_pos;
6262 SCAN_PLAYFIELD(xx, yy)
6264 int element = Tile[xx][yy];
6266 if (element == EL_SWITCHGATE_SWITCH_UP)
6268 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6269 TEST_DrawLevelField(xx, yy);
6271 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6273 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6274 TEST_DrawLevelField(xx, yy);
6276 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6278 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6279 TEST_DrawLevelField(xx, yy);
6281 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6283 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6284 TEST_DrawLevelField(xx, yy);
6286 else if (element == EL_SWITCHGATE_OPEN ||
6287 element == EL_SWITCHGATE_OPENING)
6289 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6291 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6293 else if (element == EL_SWITCHGATE_CLOSED ||
6294 element == EL_SWITCHGATE_CLOSING)
6296 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6298 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6303 static int getInvisibleActiveFromInvisibleElement(int element)
6305 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6306 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6307 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6311 static int getInvisibleFromInvisibleActiveElement(int element)
6313 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6314 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6315 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6319 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6323 SCAN_PLAYFIELD(x, y)
6325 int element = Tile[x][y];
6327 if (element == EL_LIGHT_SWITCH &&
6328 game.light_time_left > 0)
6330 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6331 TEST_DrawLevelField(x, y);
6333 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6334 game.light_time_left == 0)
6336 Tile[x][y] = EL_LIGHT_SWITCH;
6337 TEST_DrawLevelField(x, y);
6339 else if (element == EL_EMC_DRIPPER &&
6340 game.light_time_left > 0)
6342 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6343 TEST_DrawLevelField(x, y);
6345 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6346 game.light_time_left == 0)
6348 Tile[x][y] = EL_EMC_DRIPPER;
6349 TEST_DrawLevelField(x, y);
6351 else if (element == EL_INVISIBLE_STEELWALL ||
6352 element == EL_INVISIBLE_WALL ||
6353 element == EL_INVISIBLE_SAND)
6355 if (game.light_time_left > 0)
6356 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6358 TEST_DrawLevelField(x, y);
6360 // uncrumble neighbour fields, if needed
6361 if (element == EL_INVISIBLE_SAND)
6362 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6364 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6365 element == EL_INVISIBLE_WALL_ACTIVE ||
6366 element == EL_INVISIBLE_SAND_ACTIVE)
6368 if (game.light_time_left == 0)
6369 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6371 TEST_DrawLevelField(x, y);
6373 // re-crumble neighbour fields, if needed
6374 if (element == EL_INVISIBLE_SAND)
6375 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6380 static void RedrawAllInvisibleElementsForLenses(void)
6384 SCAN_PLAYFIELD(x, y)
6386 int element = Tile[x][y];
6388 if (element == EL_EMC_DRIPPER &&
6389 game.lenses_time_left > 0)
6391 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6392 TEST_DrawLevelField(x, y);
6394 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6395 game.lenses_time_left == 0)
6397 Tile[x][y] = EL_EMC_DRIPPER;
6398 TEST_DrawLevelField(x, y);
6400 else if (element == EL_INVISIBLE_STEELWALL ||
6401 element == EL_INVISIBLE_WALL ||
6402 element == EL_INVISIBLE_SAND)
6404 if (game.lenses_time_left > 0)
6405 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6407 TEST_DrawLevelField(x, y);
6409 // uncrumble neighbour fields, if needed
6410 if (element == EL_INVISIBLE_SAND)
6411 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6413 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6414 element == EL_INVISIBLE_WALL_ACTIVE ||
6415 element == EL_INVISIBLE_SAND_ACTIVE)
6417 if (game.lenses_time_left == 0)
6418 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6420 TEST_DrawLevelField(x, y);
6422 // re-crumble neighbour fields, if needed
6423 if (element == EL_INVISIBLE_SAND)
6424 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6429 static void RedrawAllInvisibleElementsForMagnifier(void)
6433 SCAN_PLAYFIELD(x, y)
6435 int element = Tile[x][y];
6437 if (element == EL_EMC_FAKE_GRASS &&
6438 game.magnify_time_left > 0)
6440 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6441 TEST_DrawLevelField(x, y);
6443 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6444 game.magnify_time_left == 0)
6446 Tile[x][y] = EL_EMC_FAKE_GRASS;
6447 TEST_DrawLevelField(x, y);
6449 else if (IS_GATE_GRAY(element) &&
6450 game.magnify_time_left > 0)
6452 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6453 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6454 IS_EM_GATE_GRAY(element) ?
6455 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6456 IS_EMC_GATE_GRAY(element) ?
6457 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6458 IS_DC_GATE_GRAY(element) ?
6459 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6461 TEST_DrawLevelField(x, y);
6463 else if (IS_GATE_GRAY_ACTIVE(element) &&
6464 game.magnify_time_left == 0)
6466 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6467 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6468 IS_EM_GATE_GRAY_ACTIVE(element) ?
6469 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6470 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6471 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6472 IS_DC_GATE_GRAY_ACTIVE(element) ?
6473 EL_DC_GATE_WHITE_GRAY :
6475 TEST_DrawLevelField(x, y);
6480 static void ToggleLightSwitch(int x, int y)
6482 int element = Tile[x][y];
6484 game.light_time_left =
6485 (element == EL_LIGHT_SWITCH ?
6486 level.time_light * FRAMES_PER_SECOND : 0);
6488 RedrawAllLightSwitchesAndInvisibleElements();
6491 static void ActivateTimegateSwitch(int x, int y)
6495 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6497 SCAN_PLAYFIELD(xx, yy)
6499 int element = Tile[xx][yy];
6501 if (element == EL_TIMEGATE_CLOSED ||
6502 element == EL_TIMEGATE_CLOSING)
6504 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6505 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6509 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6511 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6512 TEST_DrawLevelField(xx, yy);
6518 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6519 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6522 static void Impact(int x, int y)
6524 boolean last_line = (y == lev_fieldy - 1);
6525 boolean object_hit = FALSE;
6526 boolean impact = (last_line || object_hit);
6527 int element = Tile[x][y];
6528 int smashed = EL_STEELWALL;
6530 if (!last_line) // check if element below was hit
6532 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6535 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6536 MovDir[x][y + 1] != MV_DOWN ||
6537 MovPos[x][y + 1] <= TILEY / 2));
6539 // do not smash moving elements that left the smashed field in time
6540 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6541 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6544 #if USE_QUICKSAND_IMPACT_BUGFIX
6545 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6547 RemoveMovingField(x, y + 1);
6548 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6549 Tile[x][y + 2] = EL_ROCK;
6550 TEST_DrawLevelField(x, y + 2);
6555 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6557 RemoveMovingField(x, y + 1);
6558 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6559 Tile[x][y + 2] = EL_ROCK;
6560 TEST_DrawLevelField(x, y + 2);
6567 smashed = MovingOrBlocked2Element(x, y + 1);
6569 impact = (last_line || object_hit);
6572 if (!last_line && smashed == EL_ACID) // element falls into acid
6574 SplashAcid(x, y + 1);
6578 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6579 // only reset graphic animation if graphic really changes after impact
6581 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6583 ResetGfxAnimation(x, y);
6584 TEST_DrawLevelField(x, y);
6587 if (impact && CAN_EXPLODE_IMPACT(element))
6592 else if (impact && element == EL_PEARL &&
6593 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6595 ResetGfxAnimation(x, y);
6597 Tile[x][y] = EL_PEARL_BREAKING;
6598 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6601 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6603 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6608 if (impact && element == EL_AMOEBA_DROP)
6610 if (object_hit && IS_PLAYER(x, y + 1))
6611 KillPlayerUnlessEnemyProtected(x, y + 1);
6612 else if (object_hit && smashed == EL_PENGUIN)
6616 Tile[x][y] = EL_AMOEBA_GROWING;
6617 Store[x][y] = EL_AMOEBA_WET;
6619 ResetRandomAnimationValue(x, y);
6624 if (object_hit) // check which object was hit
6626 if ((CAN_PASS_MAGIC_WALL(element) &&
6627 (smashed == EL_MAGIC_WALL ||
6628 smashed == EL_BD_MAGIC_WALL)) ||
6629 (CAN_PASS_DC_MAGIC_WALL(element) &&
6630 smashed == EL_DC_MAGIC_WALL))
6633 int activated_magic_wall =
6634 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6635 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6636 EL_DC_MAGIC_WALL_ACTIVE);
6638 // activate magic wall / mill
6639 SCAN_PLAYFIELD(xx, yy)
6641 if (Tile[xx][yy] == smashed)
6642 Tile[xx][yy] = activated_magic_wall;
6645 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6646 game.magic_wall_active = TRUE;
6648 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6649 SND_MAGIC_WALL_ACTIVATING :
6650 smashed == EL_BD_MAGIC_WALL ?
6651 SND_BD_MAGIC_WALL_ACTIVATING :
6652 SND_DC_MAGIC_WALL_ACTIVATING));
6655 if (IS_PLAYER(x, y + 1))
6657 if (CAN_SMASH_PLAYER(element))
6659 KillPlayerUnlessEnemyProtected(x, y + 1);
6663 else if (smashed == EL_PENGUIN)
6665 if (CAN_SMASH_PLAYER(element))
6671 else if (element == EL_BD_DIAMOND)
6673 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6679 else if (((element == EL_SP_INFOTRON ||
6680 element == EL_SP_ZONK) &&
6681 (smashed == EL_SP_SNIKSNAK ||
6682 smashed == EL_SP_ELECTRON ||
6683 smashed == EL_SP_DISK_ORANGE)) ||
6684 (element == EL_SP_INFOTRON &&
6685 smashed == EL_SP_DISK_YELLOW))
6690 else if (CAN_SMASH_EVERYTHING(element))
6692 if (IS_CLASSIC_ENEMY(smashed) ||
6693 CAN_EXPLODE_SMASHED(smashed))
6698 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6700 if (smashed == EL_LAMP ||
6701 smashed == EL_LAMP_ACTIVE)
6706 else if (smashed == EL_NUT)
6708 Tile[x][y + 1] = EL_NUT_BREAKING;
6709 PlayLevelSound(x, y, SND_NUT_BREAKING);
6710 RaiseScoreElement(EL_NUT);
6713 else if (smashed == EL_PEARL)
6715 ResetGfxAnimation(x, y);
6717 Tile[x][y + 1] = EL_PEARL_BREAKING;
6718 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6721 else if (smashed == EL_DIAMOND)
6723 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6724 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6727 else if (IS_BELT_SWITCH(smashed))
6729 ToggleBeltSwitch(x, y + 1);
6731 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6732 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6733 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6734 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6736 ToggleSwitchgateSwitch(x, y + 1);
6738 else if (smashed == EL_LIGHT_SWITCH ||
6739 smashed == EL_LIGHT_SWITCH_ACTIVE)
6741 ToggleLightSwitch(x, y + 1);
6745 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6747 CheckElementChangeBySide(x, y + 1, smashed, element,
6748 CE_SWITCHED, CH_SIDE_TOP);
6749 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6755 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6760 // play sound of magic wall / mill
6762 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6763 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6764 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6766 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6767 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6768 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6769 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6770 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6771 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6776 // play sound of object that hits the ground
6777 if (last_line || object_hit)
6778 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6781 static void TurnRoundExt(int x, int y)
6793 { 0, 0 }, { 0, 0 }, { 0, 0 },
6798 int left, right, back;
6802 { MV_DOWN, MV_UP, MV_RIGHT },
6803 { MV_UP, MV_DOWN, MV_LEFT },
6805 { MV_LEFT, MV_RIGHT, MV_DOWN },
6809 { MV_RIGHT, MV_LEFT, MV_UP }
6812 int element = Tile[x][y];
6813 int move_pattern = element_info[element].move_pattern;
6815 int old_move_dir = MovDir[x][y];
6816 int left_dir = turn[old_move_dir].left;
6817 int right_dir = turn[old_move_dir].right;
6818 int back_dir = turn[old_move_dir].back;
6820 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6821 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6822 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6823 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6825 int left_x = x + left_dx, left_y = y + left_dy;
6826 int right_x = x + right_dx, right_y = y + right_dy;
6827 int move_x = x + move_dx, move_y = y + move_dy;
6831 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6833 TestIfBadThingTouchesOtherBadThing(x, y);
6835 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6836 MovDir[x][y] = right_dir;
6837 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6838 MovDir[x][y] = left_dir;
6840 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6842 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6845 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6847 TestIfBadThingTouchesOtherBadThing(x, y);
6849 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6850 MovDir[x][y] = left_dir;
6851 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6852 MovDir[x][y] = right_dir;
6854 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6856 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6859 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6861 TestIfBadThingTouchesOtherBadThing(x, y);
6863 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6864 MovDir[x][y] = left_dir;
6865 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6866 MovDir[x][y] = right_dir;
6868 if (MovDir[x][y] != old_move_dir)
6871 else if (element == EL_YAMYAM)
6873 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6874 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6876 if (can_turn_left && can_turn_right)
6877 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6878 else if (can_turn_left)
6879 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6880 else if (can_turn_right)
6881 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6883 MovDir[x][y] = back_dir;
6885 MovDelay[x][y] = 16 + 16 * RND(3);
6887 else if (element == EL_DARK_YAMYAM)
6889 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6891 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6894 if (can_turn_left && can_turn_right)
6895 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6896 else if (can_turn_left)
6897 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6898 else if (can_turn_right)
6899 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6901 MovDir[x][y] = back_dir;
6903 MovDelay[x][y] = 16 + 16 * RND(3);
6905 else if (element == EL_PACMAN)
6907 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6908 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6910 if (can_turn_left && can_turn_right)
6911 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6912 else if (can_turn_left)
6913 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6914 else if (can_turn_right)
6915 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6917 MovDir[x][y] = back_dir;
6919 MovDelay[x][y] = 6 + RND(40);
6921 else if (element == EL_PIG)
6923 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6924 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6925 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6926 boolean should_turn_left, should_turn_right, should_move_on;
6928 int rnd = RND(rnd_value);
6930 should_turn_left = (can_turn_left &&
6932 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6933 y + back_dy + left_dy)));
6934 should_turn_right = (can_turn_right &&
6936 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6937 y + back_dy + right_dy)));
6938 should_move_on = (can_move_on &&
6941 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6942 y + move_dy + left_dy) ||
6943 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6944 y + move_dy + right_dy)));
6946 if (should_turn_left || should_turn_right || should_move_on)
6948 if (should_turn_left && should_turn_right && should_move_on)
6949 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6950 rnd < 2 * rnd_value / 3 ? right_dir :
6952 else if (should_turn_left && should_turn_right)
6953 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6954 else if (should_turn_left && should_move_on)
6955 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6956 else if (should_turn_right && should_move_on)
6957 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6958 else if (should_turn_left)
6959 MovDir[x][y] = left_dir;
6960 else if (should_turn_right)
6961 MovDir[x][y] = right_dir;
6962 else if (should_move_on)
6963 MovDir[x][y] = old_move_dir;
6965 else if (can_move_on && rnd > rnd_value / 8)
6966 MovDir[x][y] = old_move_dir;
6967 else if (can_turn_left && can_turn_right)
6968 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6969 else if (can_turn_left && rnd > rnd_value / 8)
6970 MovDir[x][y] = left_dir;
6971 else if (can_turn_right && rnd > rnd_value/8)
6972 MovDir[x][y] = right_dir;
6974 MovDir[x][y] = back_dir;
6976 xx = x + move_xy[MovDir[x][y]].dx;
6977 yy = y + move_xy[MovDir[x][y]].dy;
6979 if (!IN_LEV_FIELD(xx, yy) ||
6980 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6981 MovDir[x][y] = old_move_dir;
6985 else if (element == EL_DRAGON)
6987 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6988 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6989 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6991 int rnd = RND(rnd_value);
6993 if (can_move_on && rnd > rnd_value / 8)
6994 MovDir[x][y] = old_move_dir;
6995 else if (can_turn_left && can_turn_right)
6996 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6997 else if (can_turn_left && rnd > rnd_value / 8)
6998 MovDir[x][y] = left_dir;
6999 else if (can_turn_right && rnd > rnd_value / 8)
7000 MovDir[x][y] = right_dir;
7002 MovDir[x][y] = back_dir;
7004 xx = x + move_xy[MovDir[x][y]].dx;
7005 yy = y + move_xy[MovDir[x][y]].dy;
7007 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7008 MovDir[x][y] = old_move_dir;
7012 else if (element == EL_MOLE)
7014 boolean can_move_on =
7015 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7016 IS_AMOEBOID(Tile[move_x][move_y]) ||
7017 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7020 boolean can_turn_left =
7021 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7022 IS_AMOEBOID(Tile[left_x][left_y])));
7024 boolean can_turn_right =
7025 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7026 IS_AMOEBOID(Tile[right_x][right_y])));
7028 if (can_turn_left && can_turn_right)
7029 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7030 else if (can_turn_left)
7031 MovDir[x][y] = left_dir;
7033 MovDir[x][y] = right_dir;
7036 if (MovDir[x][y] != old_move_dir)
7039 else if (element == EL_BALLOON)
7041 MovDir[x][y] = game.wind_direction;
7044 else if (element == EL_SPRING)
7046 if (MovDir[x][y] & MV_HORIZONTAL)
7048 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7049 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7051 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7052 ResetGfxAnimation(move_x, move_y);
7053 TEST_DrawLevelField(move_x, move_y);
7055 MovDir[x][y] = back_dir;
7057 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7058 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7059 MovDir[x][y] = MV_NONE;
7064 else if (element == EL_ROBOT ||
7065 element == EL_SATELLITE ||
7066 element == EL_PENGUIN ||
7067 element == EL_EMC_ANDROID)
7069 int attr_x = -1, attr_y = -1;
7071 if (game.all_players_gone)
7073 attr_x = game.exit_x;
7074 attr_y = game.exit_y;
7080 for (i = 0; i < MAX_PLAYERS; i++)
7082 struct PlayerInfo *player = &stored_player[i];
7083 int jx = player->jx, jy = player->jy;
7085 if (!player->active)
7089 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7097 if (element == EL_ROBOT &&
7098 game.robot_wheel_x >= 0 &&
7099 game.robot_wheel_y >= 0 &&
7100 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7101 game.engine_version < VERSION_IDENT(3,1,0,0)))
7103 attr_x = game.robot_wheel_x;
7104 attr_y = game.robot_wheel_y;
7107 if (element == EL_PENGUIN)
7110 static int xy[4][2] =
7118 for (i = 0; i < NUM_DIRECTIONS; i++)
7120 int ex = x + xy[i][0];
7121 int ey = y + xy[i][1];
7123 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7124 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7125 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7126 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7135 MovDir[x][y] = MV_NONE;
7137 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7138 else if (attr_x > x)
7139 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7141 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7142 else if (attr_y > y)
7143 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7145 if (element == EL_ROBOT)
7149 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7150 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7151 Moving2Blocked(x, y, &newx, &newy);
7153 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7154 MovDelay[x][y] = 8 + 8 * !RND(3);
7156 MovDelay[x][y] = 16;
7158 else if (element == EL_PENGUIN)
7164 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7166 boolean first_horiz = RND(2);
7167 int new_move_dir = MovDir[x][y];
7170 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7171 Moving2Blocked(x, y, &newx, &newy);
7173 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7177 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7178 Moving2Blocked(x, y, &newx, &newy);
7180 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7183 MovDir[x][y] = old_move_dir;
7187 else if (element == EL_SATELLITE)
7193 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7195 boolean first_horiz = RND(2);
7196 int new_move_dir = MovDir[x][y];
7199 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7200 Moving2Blocked(x, y, &newx, &newy);
7202 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7206 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7207 Moving2Blocked(x, y, &newx, &newy);
7209 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7212 MovDir[x][y] = old_move_dir;
7216 else if (element == EL_EMC_ANDROID)
7218 static int check_pos[16] =
7220 -1, // 0 => (invalid)
7223 -1, // 3 => (invalid)
7225 0, // 5 => MV_LEFT | MV_UP
7226 2, // 6 => MV_RIGHT | MV_UP
7227 -1, // 7 => (invalid)
7229 6, // 9 => MV_LEFT | MV_DOWN
7230 4, // 10 => MV_RIGHT | MV_DOWN
7231 -1, // 11 => (invalid)
7232 -1, // 12 => (invalid)
7233 -1, // 13 => (invalid)
7234 -1, // 14 => (invalid)
7235 -1, // 15 => (invalid)
7243 { -1, -1, MV_LEFT | MV_UP },
7245 { +1, -1, MV_RIGHT | MV_UP },
7246 { +1, 0, MV_RIGHT },
7247 { +1, +1, MV_RIGHT | MV_DOWN },
7249 { -1, +1, MV_LEFT | MV_DOWN },
7252 int start_pos, check_order;
7253 boolean can_clone = FALSE;
7256 // check if there is any free field around current position
7257 for (i = 0; i < 8; i++)
7259 int newx = x + check_xy[i].dx;
7260 int newy = y + check_xy[i].dy;
7262 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7270 if (can_clone) // randomly find an element to clone
7274 start_pos = check_pos[RND(8)];
7275 check_order = (RND(2) ? -1 : +1);
7277 for (i = 0; i < 8; i++)
7279 int pos_raw = start_pos + i * check_order;
7280 int pos = (pos_raw + 8) % 8;
7281 int newx = x + check_xy[pos].dx;
7282 int newy = y + check_xy[pos].dy;
7284 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7286 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7287 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7289 Store[x][y] = Tile[newx][newy];
7298 if (can_clone) // randomly find a direction to move
7302 start_pos = check_pos[RND(8)];
7303 check_order = (RND(2) ? -1 : +1);
7305 for (i = 0; i < 8; i++)
7307 int pos_raw = start_pos + i * check_order;
7308 int pos = (pos_raw + 8) % 8;
7309 int newx = x + check_xy[pos].dx;
7310 int newy = y + check_xy[pos].dy;
7311 int new_move_dir = check_xy[pos].dir;
7313 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7315 MovDir[x][y] = new_move_dir;
7316 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7325 if (can_clone) // cloning and moving successful
7328 // cannot clone -- try to move towards player
7330 start_pos = check_pos[MovDir[x][y] & 0x0f];
7331 check_order = (RND(2) ? -1 : +1);
7333 for (i = 0; i < 3; i++)
7335 // first check start_pos, then previous/next or (next/previous) pos
7336 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7337 int pos = (pos_raw + 8) % 8;
7338 int newx = x + check_xy[pos].dx;
7339 int newy = y + check_xy[pos].dy;
7340 int new_move_dir = check_xy[pos].dir;
7342 if (IS_PLAYER(newx, newy))
7345 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7347 MovDir[x][y] = new_move_dir;
7348 MovDelay[x][y] = level.android_move_time * 8 + 1;
7355 else if (move_pattern == MV_TURNING_LEFT ||
7356 move_pattern == MV_TURNING_RIGHT ||
7357 move_pattern == MV_TURNING_LEFT_RIGHT ||
7358 move_pattern == MV_TURNING_RIGHT_LEFT ||
7359 move_pattern == MV_TURNING_RANDOM ||
7360 move_pattern == MV_ALL_DIRECTIONS)
7362 boolean can_turn_left =
7363 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7364 boolean can_turn_right =
7365 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7367 if (element_info[element].move_stepsize == 0) // "not moving"
7370 if (move_pattern == MV_TURNING_LEFT)
7371 MovDir[x][y] = left_dir;
7372 else if (move_pattern == MV_TURNING_RIGHT)
7373 MovDir[x][y] = right_dir;
7374 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7375 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7376 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7377 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7378 else if (move_pattern == MV_TURNING_RANDOM)
7379 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7380 can_turn_right && !can_turn_left ? right_dir :
7381 RND(2) ? left_dir : right_dir);
7382 else if (can_turn_left && can_turn_right)
7383 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7384 else if (can_turn_left)
7385 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7386 else if (can_turn_right)
7387 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7389 MovDir[x][y] = back_dir;
7391 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7393 else if (move_pattern == MV_HORIZONTAL ||
7394 move_pattern == MV_VERTICAL)
7396 if (move_pattern & old_move_dir)
7397 MovDir[x][y] = back_dir;
7398 else if (move_pattern == MV_HORIZONTAL)
7399 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7400 else if (move_pattern == MV_VERTICAL)
7401 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7403 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7405 else if (move_pattern & MV_ANY_DIRECTION)
7407 MovDir[x][y] = move_pattern;
7408 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7410 else if (move_pattern & MV_WIND_DIRECTION)
7412 MovDir[x][y] = game.wind_direction;
7413 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7415 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7417 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7418 MovDir[x][y] = left_dir;
7419 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7420 MovDir[x][y] = right_dir;
7422 if (MovDir[x][y] != old_move_dir)
7423 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7425 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7427 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7428 MovDir[x][y] = right_dir;
7429 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7430 MovDir[x][y] = left_dir;
7432 if (MovDir[x][y] != old_move_dir)
7433 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7435 else if (move_pattern == MV_TOWARDS_PLAYER ||
7436 move_pattern == MV_AWAY_FROM_PLAYER)
7438 int attr_x = -1, attr_y = -1;
7440 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7442 if (game.all_players_gone)
7444 attr_x = game.exit_x;
7445 attr_y = game.exit_y;
7451 for (i = 0; i < MAX_PLAYERS; i++)
7453 struct PlayerInfo *player = &stored_player[i];
7454 int jx = player->jx, jy = player->jy;
7456 if (!player->active)
7460 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7468 MovDir[x][y] = MV_NONE;
7470 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7471 else if (attr_x > x)
7472 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7474 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7475 else if (attr_y > y)
7476 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7478 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7480 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7482 boolean first_horiz = RND(2);
7483 int new_move_dir = MovDir[x][y];
7485 if (element_info[element].move_stepsize == 0) // "not moving"
7487 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7488 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7494 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7495 Moving2Blocked(x, y, &newx, &newy);
7497 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7501 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7502 Moving2Blocked(x, y, &newx, &newy);
7504 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7507 MovDir[x][y] = old_move_dir;
7510 else if (move_pattern == MV_WHEN_PUSHED ||
7511 move_pattern == MV_WHEN_DROPPED)
7513 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7514 MovDir[x][y] = MV_NONE;
7518 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7520 static int test_xy[7][2] =
7530 static int test_dir[7] =
7540 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7541 int move_preference = -1000000; // start with very low preference
7542 int new_move_dir = MV_NONE;
7543 int start_test = RND(4);
7546 for (i = 0; i < NUM_DIRECTIONS; i++)
7548 int move_dir = test_dir[start_test + i];
7549 int move_dir_preference;
7551 xx = x + test_xy[start_test + i][0];
7552 yy = y + test_xy[start_test + i][1];
7554 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7555 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7557 new_move_dir = move_dir;
7562 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7565 move_dir_preference = -1 * RunnerVisit[xx][yy];
7566 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7567 move_dir_preference = PlayerVisit[xx][yy];
7569 if (move_dir_preference > move_preference)
7571 // prefer field that has not been visited for the longest time
7572 move_preference = move_dir_preference;
7573 new_move_dir = move_dir;
7575 else if (move_dir_preference == move_preference &&
7576 move_dir == old_move_dir)
7578 // prefer last direction when all directions are preferred equally
7579 move_preference = move_dir_preference;
7580 new_move_dir = move_dir;
7584 MovDir[x][y] = new_move_dir;
7585 if (old_move_dir != new_move_dir)
7586 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7590 static void TurnRound(int x, int y)
7592 int direction = MovDir[x][y];
7596 GfxDir[x][y] = MovDir[x][y];
7598 if (direction != MovDir[x][y])
7602 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7604 ResetGfxFrame(x, y);
7607 static boolean JustBeingPushed(int x, int y)
7611 for (i = 0; i < MAX_PLAYERS; i++)
7613 struct PlayerInfo *player = &stored_player[i];
7615 if (player->active && player->is_pushing && player->MovPos)
7617 int next_jx = player->jx + (player->jx - player->last_jx);
7618 int next_jy = player->jy + (player->jy - player->last_jy);
7620 if (x == next_jx && y == next_jy)
7628 static void StartMoving(int x, int y)
7630 boolean started_moving = FALSE; // some elements can fall _and_ move
7631 int element = Tile[x][y];
7636 if (MovDelay[x][y] == 0)
7637 GfxAction[x][y] = ACTION_DEFAULT;
7639 if (CAN_FALL(element) && y < lev_fieldy - 1)
7641 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7642 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7643 if (JustBeingPushed(x, y))
7646 if (element == EL_QUICKSAND_FULL)
7648 if (IS_FREE(x, y + 1))
7650 InitMovingField(x, y, MV_DOWN);
7651 started_moving = TRUE;
7653 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7654 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7655 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7656 Store[x][y] = EL_ROCK;
7658 Store[x][y] = EL_ROCK;
7661 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7663 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7665 if (!MovDelay[x][y])
7667 MovDelay[x][y] = TILEY + 1;
7669 ResetGfxAnimation(x, y);
7670 ResetGfxAnimation(x, y + 1);
7675 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7676 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7683 Tile[x][y] = EL_QUICKSAND_EMPTY;
7684 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7685 Store[x][y + 1] = Store[x][y];
7688 PlayLevelSoundAction(x, y, ACTION_FILLING);
7690 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7692 if (!MovDelay[x][y])
7694 MovDelay[x][y] = TILEY + 1;
7696 ResetGfxAnimation(x, y);
7697 ResetGfxAnimation(x, y + 1);
7702 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7703 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7710 Tile[x][y] = EL_QUICKSAND_EMPTY;
7711 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7712 Store[x][y + 1] = Store[x][y];
7715 PlayLevelSoundAction(x, y, ACTION_FILLING);
7718 else if (element == EL_QUICKSAND_FAST_FULL)
7720 if (IS_FREE(x, y + 1))
7722 InitMovingField(x, y, MV_DOWN);
7723 started_moving = TRUE;
7725 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7726 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7727 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7728 Store[x][y] = EL_ROCK;
7730 Store[x][y] = EL_ROCK;
7733 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7735 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7737 if (!MovDelay[x][y])
7739 MovDelay[x][y] = TILEY + 1;
7741 ResetGfxAnimation(x, y);
7742 ResetGfxAnimation(x, y + 1);
7747 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7748 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7755 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7756 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7757 Store[x][y + 1] = Store[x][y];
7760 PlayLevelSoundAction(x, y, ACTION_FILLING);
7762 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7764 if (!MovDelay[x][y])
7766 MovDelay[x][y] = TILEY + 1;
7768 ResetGfxAnimation(x, y);
7769 ResetGfxAnimation(x, y + 1);
7774 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7775 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7782 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7783 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7784 Store[x][y + 1] = Store[x][y];
7787 PlayLevelSoundAction(x, y, ACTION_FILLING);
7790 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7791 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7793 InitMovingField(x, y, MV_DOWN);
7794 started_moving = TRUE;
7796 Tile[x][y] = EL_QUICKSAND_FILLING;
7797 Store[x][y] = element;
7799 PlayLevelSoundAction(x, y, ACTION_FILLING);
7801 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7802 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7804 InitMovingField(x, y, MV_DOWN);
7805 started_moving = TRUE;
7807 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7808 Store[x][y] = element;
7810 PlayLevelSoundAction(x, y, ACTION_FILLING);
7812 else if (element == EL_MAGIC_WALL_FULL)
7814 if (IS_FREE(x, y + 1))
7816 InitMovingField(x, y, MV_DOWN);
7817 started_moving = TRUE;
7819 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7820 Store[x][y] = EL_CHANGED(Store[x][y]);
7822 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7824 if (!MovDelay[x][y])
7825 MovDelay[x][y] = TILEY / 4 + 1;
7834 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7835 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7836 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7840 else if (element == EL_BD_MAGIC_WALL_FULL)
7842 if (IS_FREE(x, y + 1))
7844 InitMovingField(x, y, MV_DOWN);
7845 started_moving = TRUE;
7847 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7848 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7850 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7852 if (!MovDelay[x][y])
7853 MovDelay[x][y] = TILEY / 4 + 1;
7862 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7863 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7864 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7868 else if (element == EL_DC_MAGIC_WALL_FULL)
7870 if (IS_FREE(x, y + 1))
7872 InitMovingField(x, y, MV_DOWN);
7873 started_moving = TRUE;
7875 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7876 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7878 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7880 if (!MovDelay[x][y])
7881 MovDelay[x][y] = TILEY / 4 + 1;
7890 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7891 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7892 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7896 else if ((CAN_PASS_MAGIC_WALL(element) &&
7897 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7898 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7899 (CAN_PASS_DC_MAGIC_WALL(element) &&
7900 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7903 InitMovingField(x, y, MV_DOWN);
7904 started_moving = TRUE;
7907 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7908 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7909 EL_DC_MAGIC_WALL_FILLING);
7910 Store[x][y] = element;
7912 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7914 SplashAcid(x, y + 1);
7916 InitMovingField(x, y, MV_DOWN);
7917 started_moving = TRUE;
7919 Store[x][y] = EL_ACID;
7922 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7923 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7924 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7925 CAN_FALL(element) && WasJustFalling[x][y] &&
7926 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7928 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7929 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7930 (Tile[x][y + 1] == EL_BLOCKED)))
7932 /* this is needed for a special case not covered by calling "Impact()"
7933 from "ContinueMoving()": if an element moves to a tile directly below
7934 another element which was just falling on that tile (which was empty
7935 in the previous frame), the falling element above would just stop
7936 instead of smashing the element below (in previous version, the above
7937 element was just checked for "moving" instead of "falling", resulting
7938 in incorrect smashes caused by horizontal movement of the above
7939 element; also, the case of the player being the element to smash was
7940 simply not covered here... :-/ ) */
7942 CheckCollision[x][y] = 0;
7943 CheckImpact[x][y] = 0;
7947 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7949 if (MovDir[x][y] == MV_NONE)
7951 InitMovingField(x, y, MV_DOWN);
7952 started_moving = TRUE;
7955 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7957 if (WasJustFalling[x][y]) // prevent animation from being restarted
7958 MovDir[x][y] = MV_DOWN;
7960 InitMovingField(x, y, MV_DOWN);
7961 started_moving = TRUE;
7963 else if (element == EL_AMOEBA_DROP)
7965 Tile[x][y] = EL_AMOEBA_GROWING;
7966 Store[x][y] = EL_AMOEBA_WET;
7968 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7969 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7970 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7971 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7973 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7974 (IS_FREE(x - 1, y + 1) ||
7975 Tile[x - 1][y + 1] == EL_ACID));
7976 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7977 (IS_FREE(x + 1, y + 1) ||
7978 Tile[x + 1][y + 1] == EL_ACID));
7979 boolean can_fall_any = (can_fall_left || can_fall_right);
7980 boolean can_fall_both = (can_fall_left && can_fall_right);
7981 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7983 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7985 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7986 can_fall_right = FALSE;
7987 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7988 can_fall_left = FALSE;
7989 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7990 can_fall_right = FALSE;
7991 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7992 can_fall_left = FALSE;
7994 can_fall_any = (can_fall_left || can_fall_right);
7995 can_fall_both = FALSE;
8000 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8001 can_fall_right = FALSE; // slip down on left side
8003 can_fall_left = !(can_fall_right = RND(2));
8005 can_fall_both = FALSE;
8010 // if not determined otherwise, prefer left side for slipping down
8011 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8012 started_moving = TRUE;
8015 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8017 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8018 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8019 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8020 int belt_dir = game.belt_dir[belt_nr];
8022 if ((belt_dir == MV_LEFT && left_is_free) ||
8023 (belt_dir == MV_RIGHT && right_is_free))
8025 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8027 InitMovingField(x, y, belt_dir);
8028 started_moving = TRUE;
8030 Pushed[x][y] = TRUE;
8031 Pushed[nextx][y] = TRUE;
8033 GfxAction[x][y] = ACTION_DEFAULT;
8037 MovDir[x][y] = 0; // if element was moving, stop it
8042 // not "else if" because of elements that can fall and move (EL_SPRING)
8043 if (CAN_MOVE(element) && !started_moving)
8045 int move_pattern = element_info[element].move_pattern;
8048 Moving2Blocked(x, y, &newx, &newy);
8050 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8053 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8054 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8056 WasJustMoving[x][y] = 0;
8057 CheckCollision[x][y] = 0;
8059 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8061 if (Tile[x][y] != element) // element has changed
8065 if (!MovDelay[x][y]) // start new movement phase
8067 // all objects that can change their move direction after each step
8068 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8070 if (element != EL_YAMYAM &&
8071 element != EL_DARK_YAMYAM &&
8072 element != EL_PACMAN &&
8073 !(move_pattern & MV_ANY_DIRECTION) &&
8074 move_pattern != MV_TURNING_LEFT &&
8075 move_pattern != MV_TURNING_RIGHT &&
8076 move_pattern != MV_TURNING_LEFT_RIGHT &&
8077 move_pattern != MV_TURNING_RIGHT_LEFT &&
8078 move_pattern != MV_TURNING_RANDOM)
8082 if (MovDelay[x][y] && (element == EL_BUG ||
8083 element == EL_SPACESHIP ||
8084 element == EL_SP_SNIKSNAK ||
8085 element == EL_SP_ELECTRON ||
8086 element == EL_MOLE))
8087 TEST_DrawLevelField(x, y);
8091 if (MovDelay[x][y]) // wait some time before next movement
8095 if (element == EL_ROBOT ||
8096 element == EL_YAMYAM ||
8097 element == EL_DARK_YAMYAM)
8099 DrawLevelElementAnimationIfNeeded(x, y, element);
8100 PlayLevelSoundAction(x, y, ACTION_WAITING);
8102 else if (element == EL_SP_ELECTRON)
8103 DrawLevelElementAnimationIfNeeded(x, y, element);
8104 else if (element == EL_DRAGON)
8107 int dir = MovDir[x][y];
8108 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8109 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8110 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8111 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8112 dir == MV_UP ? IMG_FLAMES_1_UP :
8113 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8114 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8116 GfxAction[x][y] = ACTION_ATTACKING;
8118 if (IS_PLAYER(x, y))
8119 DrawPlayerField(x, y);
8121 TEST_DrawLevelField(x, y);
8123 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8125 for (i = 1; i <= 3; i++)
8127 int xx = x + i * dx;
8128 int yy = y + i * dy;
8129 int sx = SCREENX(xx);
8130 int sy = SCREENY(yy);
8131 int flame_graphic = graphic + (i - 1);
8133 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8138 int flamed = MovingOrBlocked2Element(xx, yy);
8140 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8143 RemoveMovingField(xx, yy);
8145 ChangeDelay[xx][yy] = 0;
8147 Tile[xx][yy] = EL_FLAMES;
8149 if (IN_SCR_FIELD(sx, sy))
8151 TEST_DrawLevelFieldCrumbled(xx, yy);
8152 DrawGraphic(sx, sy, flame_graphic, frame);
8157 if (Tile[xx][yy] == EL_FLAMES)
8158 Tile[xx][yy] = EL_EMPTY;
8159 TEST_DrawLevelField(xx, yy);
8164 if (MovDelay[x][y]) // element still has to wait some time
8166 PlayLevelSoundAction(x, y, ACTION_WAITING);
8172 // now make next step
8174 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8176 if (DONT_COLLIDE_WITH(element) &&
8177 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8178 !PLAYER_ENEMY_PROTECTED(newx, newy))
8180 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8185 else if (CAN_MOVE_INTO_ACID(element) &&
8186 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8187 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8188 (MovDir[x][y] == MV_DOWN ||
8189 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8191 SplashAcid(newx, newy);
8192 Store[x][y] = EL_ACID;
8194 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8196 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8197 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8198 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8199 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8202 TEST_DrawLevelField(x, y);
8204 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8205 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8206 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8208 game.friends_still_needed--;
8209 if (!game.friends_still_needed &&
8211 game.all_players_gone)
8216 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8218 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8219 TEST_DrawLevelField(newx, newy);
8221 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8223 else if (!IS_FREE(newx, newy))
8225 GfxAction[x][y] = ACTION_WAITING;
8227 if (IS_PLAYER(x, y))
8228 DrawPlayerField(x, y);
8230 TEST_DrawLevelField(x, y);
8235 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8237 if (IS_FOOD_PIG(Tile[newx][newy]))
8239 if (IS_MOVING(newx, newy))
8240 RemoveMovingField(newx, newy);
8243 Tile[newx][newy] = EL_EMPTY;
8244 TEST_DrawLevelField(newx, newy);
8247 PlayLevelSound(x, y, SND_PIG_DIGGING);
8249 else if (!IS_FREE(newx, newy))
8251 if (IS_PLAYER(x, y))
8252 DrawPlayerField(x, y);
8254 TEST_DrawLevelField(x, y);
8259 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8261 if (Store[x][y] != EL_EMPTY)
8263 boolean can_clone = FALSE;
8266 // check if element to clone is still there
8267 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8269 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8277 // cannot clone or target field not free anymore -- do not clone
8278 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8279 Store[x][y] = EL_EMPTY;
8282 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8284 if (IS_MV_DIAGONAL(MovDir[x][y]))
8286 int diagonal_move_dir = MovDir[x][y];
8287 int stored = Store[x][y];
8288 int change_delay = 8;
8291 // android is moving diagonally
8293 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8295 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8296 GfxElement[x][y] = EL_EMC_ANDROID;
8297 GfxAction[x][y] = ACTION_SHRINKING;
8298 GfxDir[x][y] = diagonal_move_dir;
8299 ChangeDelay[x][y] = change_delay;
8301 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8304 DrawLevelGraphicAnimation(x, y, graphic);
8305 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8307 if (Tile[newx][newy] == EL_ACID)
8309 SplashAcid(newx, newy);
8314 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8316 Store[newx][newy] = EL_EMC_ANDROID;
8317 GfxElement[newx][newy] = EL_EMC_ANDROID;
8318 GfxAction[newx][newy] = ACTION_GROWING;
8319 GfxDir[newx][newy] = diagonal_move_dir;
8320 ChangeDelay[newx][newy] = change_delay;
8322 graphic = el_act_dir2img(GfxElement[newx][newy],
8323 GfxAction[newx][newy], GfxDir[newx][newy]);
8325 DrawLevelGraphicAnimation(newx, newy, graphic);
8326 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8332 Tile[newx][newy] = EL_EMPTY;
8333 TEST_DrawLevelField(newx, newy);
8335 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8338 else if (!IS_FREE(newx, newy))
8343 else if (IS_CUSTOM_ELEMENT(element) &&
8344 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8346 if (!DigFieldByCE(newx, newy, element))
8349 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8351 RunnerVisit[x][y] = FrameCounter;
8352 PlayerVisit[x][y] /= 8; // expire player visit path
8355 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8357 if (!IS_FREE(newx, newy))
8359 if (IS_PLAYER(x, y))
8360 DrawPlayerField(x, y);
8362 TEST_DrawLevelField(x, y);
8368 boolean wanna_flame = !RND(10);
8369 int dx = newx - x, dy = newy - y;
8370 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8371 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8372 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8373 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8374 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8375 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8378 IS_CLASSIC_ENEMY(element1) ||
8379 IS_CLASSIC_ENEMY(element2)) &&
8380 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8381 element1 != EL_FLAMES && element2 != EL_FLAMES)
8383 ResetGfxAnimation(x, y);
8384 GfxAction[x][y] = ACTION_ATTACKING;
8386 if (IS_PLAYER(x, y))
8387 DrawPlayerField(x, y);
8389 TEST_DrawLevelField(x, y);
8391 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8393 MovDelay[x][y] = 50;
8395 Tile[newx][newy] = EL_FLAMES;
8396 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8397 Tile[newx1][newy1] = EL_FLAMES;
8398 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8399 Tile[newx2][newy2] = EL_FLAMES;
8405 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8406 Tile[newx][newy] == EL_DIAMOND)
8408 if (IS_MOVING(newx, newy))
8409 RemoveMovingField(newx, newy);
8412 Tile[newx][newy] = EL_EMPTY;
8413 TEST_DrawLevelField(newx, newy);
8416 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8418 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8419 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8421 if (AmoebaNr[newx][newy])
8423 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8424 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8425 Tile[newx][newy] == EL_BD_AMOEBA)
8426 AmoebaCnt[AmoebaNr[newx][newy]]--;
8429 if (IS_MOVING(newx, newy))
8431 RemoveMovingField(newx, newy);
8435 Tile[newx][newy] = EL_EMPTY;
8436 TEST_DrawLevelField(newx, newy);
8439 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8441 else if ((element == EL_PACMAN || element == EL_MOLE)
8442 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8444 if (AmoebaNr[newx][newy])
8446 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8447 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8448 Tile[newx][newy] == EL_BD_AMOEBA)
8449 AmoebaCnt[AmoebaNr[newx][newy]]--;
8452 if (element == EL_MOLE)
8454 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8455 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8457 ResetGfxAnimation(x, y);
8458 GfxAction[x][y] = ACTION_DIGGING;
8459 TEST_DrawLevelField(x, y);
8461 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8463 return; // wait for shrinking amoeba
8465 else // element == EL_PACMAN
8467 Tile[newx][newy] = EL_EMPTY;
8468 TEST_DrawLevelField(newx, newy);
8469 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8472 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8473 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8474 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8476 // wait for shrinking amoeba to completely disappear
8479 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8481 // object was running against a wall
8485 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8486 DrawLevelElementAnimation(x, y, element);
8488 if (DONT_TOUCH(element))
8489 TestIfBadThingTouchesPlayer(x, y);
8494 InitMovingField(x, y, MovDir[x][y]);
8496 PlayLevelSoundAction(x, y, ACTION_MOVING);
8500 ContinueMoving(x, y);
8503 void ContinueMoving(int x, int y)
8505 int element = Tile[x][y];
8506 struct ElementInfo *ei = &element_info[element];
8507 int direction = MovDir[x][y];
8508 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8509 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8510 int newx = x + dx, newy = y + dy;
8511 int stored = Store[x][y];
8512 int stored_new = Store[newx][newy];
8513 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8514 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8515 boolean last_line = (newy == lev_fieldy - 1);
8516 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8518 if (pushed_by_player) // special case: moving object pushed by player
8520 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8522 else if (use_step_delay) // special case: moving object has step delay
8524 if (!MovDelay[x][y])
8525 MovPos[x][y] += getElementMoveStepsize(x, y);
8530 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8534 TEST_DrawLevelField(x, y);
8536 return; // element is still waiting
8539 else // normal case: generically moving object
8541 MovPos[x][y] += getElementMoveStepsize(x, y);
8544 if (ABS(MovPos[x][y]) < TILEX)
8546 TEST_DrawLevelField(x, y);
8548 return; // element is still moving
8551 // element reached destination field
8553 Tile[x][y] = EL_EMPTY;
8554 Tile[newx][newy] = element;
8555 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8557 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8559 element = Tile[newx][newy] = EL_ACID;
8561 else if (element == EL_MOLE)
8563 Tile[x][y] = EL_SAND;
8565 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8567 else if (element == EL_QUICKSAND_FILLING)
8569 element = Tile[newx][newy] = get_next_element(element);
8570 Store[newx][newy] = Store[x][y];
8572 else if (element == EL_QUICKSAND_EMPTYING)
8574 Tile[x][y] = get_next_element(element);
8575 element = Tile[newx][newy] = Store[x][y];
8577 else if (element == EL_QUICKSAND_FAST_FILLING)
8579 element = Tile[newx][newy] = get_next_element(element);
8580 Store[newx][newy] = Store[x][y];
8582 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8584 Tile[x][y] = get_next_element(element);
8585 element = Tile[newx][newy] = Store[x][y];
8587 else if (element == EL_MAGIC_WALL_FILLING)
8589 element = Tile[newx][newy] = get_next_element(element);
8590 if (!game.magic_wall_active)
8591 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8592 Store[newx][newy] = Store[x][y];
8594 else if (element == EL_MAGIC_WALL_EMPTYING)
8596 Tile[x][y] = get_next_element(element);
8597 if (!game.magic_wall_active)
8598 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8599 element = Tile[newx][newy] = Store[x][y];
8601 InitField(newx, newy, FALSE);
8603 else if (element == EL_BD_MAGIC_WALL_FILLING)
8605 element = Tile[newx][newy] = get_next_element(element);
8606 if (!game.magic_wall_active)
8607 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8608 Store[newx][newy] = Store[x][y];
8610 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8612 Tile[x][y] = get_next_element(element);
8613 if (!game.magic_wall_active)
8614 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8615 element = Tile[newx][newy] = Store[x][y];
8617 InitField(newx, newy, FALSE);
8619 else if (element == EL_DC_MAGIC_WALL_FILLING)
8621 element = Tile[newx][newy] = get_next_element(element);
8622 if (!game.magic_wall_active)
8623 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8624 Store[newx][newy] = Store[x][y];
8626 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8628 Tile[x][y] = get_next_element(element);
8629 if (!game.magic_wall_active)
8630 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8631 element = Tile[newx][newy] = Store[x][y];
8633 InitField(newx, newy, FALSE);
8635 else if (element == EL_AMOEBA_DROPPING)
8637 Tile[x][y] = get_next_element(element);
8638 element = Tile[newx][newy] = Store[x][y];
8640 else if (element == EL_SOKOBAN_OBJECT)
8643 Tile[x][y] = Back[x][y];
8645 if (Back[newx][newy])
8646 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8648 Back[x][y] = Back[newx][newy] = 0;
8651 Store[x][y] = EL_EMPTY;
8656 MovDelay[newx][newy] = 0;
8658 if (CAN_CHANGE_OR_HAS_ACTION(element))
8660 // copy element change control values to new field
8661 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8662 ChangePage[newx][newy] = ChangePage[x][y];
8663 ChangeCount[newx][newy] = ChangeCount[x][y];
8664 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8667 CustomValue[newx][newy] = CustomValue[x][y];
8669 ChangeDelay[x][y] = 0;
8670 ChangePage[x][y] = -1;
8671 ChangeCount[x][y] = 0;
8672 ChangeEvent[x][y] = -1;
8674 CustomValue[x][y] = 0;
8676 // copy animation control values to new field
8677 GfxFrame[newx][newy] = GfxFrame[x][y];
8678 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8679 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8680 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8682 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8684 // some elements can leave other elements behind after moving
8685 if (ei->move_leave_element != EL_EMPTY &&
8686 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8687 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8689 int move_leave_element = ei->move_leave_element;
8691 // this makes it possible to leave the removed element again
8692 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8693 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8695 Tile[x][y] = move_leave_element;
8697 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8698 MovDir[x][y] = direction;
8700 InitField(x, y, FALSE);
8702 if (GFX_CRUMBLED(Tile[x][y]))
8703 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8705 if (ELEM_IS_PLAYER(move_leave_element))
8706 RelocatePlayer(x, y, move_leave_element);
8709 // do this after checking for left-behind element
8710 ResetGfxAnimation(x, y); // reset animation values for old field
8712 if (!CAN_MOVE(element) ||
8713 (CAN_FALL(element) && direction == MV_DOWN &&
8714 (element == EL_SPRING ||
8715 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8716 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8717 GfxDir[x][y] = MovDir[newx][newy] = 0;
8719 TEST_DrawLevelField(x, y);
8720 TEST_DrawLevelField(newx, newy);
8722 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8724 // prevent pushed element from moving on in pushed direction
8725 if (pushed_by_player && CAN_MOVE(element) &&
8726 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8727 !(element_info[element].move_pattern & direction))
8728 TurnRound(newx, newy);
8730 // prevent elements on conveyor belt from moving on in last direction
8731 if (pushed_by_conveyor && CAN_FALL(element) &&
8732 direction & MV_HORIZONTAL)
8733 MovDir[newx][newy] = 0;
8735 if (!pushed_by_player)
8737 int nextx = newx + dx, nexty = newy + dy;
8738 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8740 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8742 if (CAN_FALL(element) && direction == MV_DOWN)
8743 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8745 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8746 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8748 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8749 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8752 if (DONT_TOUCH(element)) // object may be nasty to player or others
8754 TestIfBadThingTouchesPlayer(newx, newy);
8755 TestIfBadThingTouchesFriend(newx, newy);
8757 if (!IS_CUSTOM_ELEMENT(element))
8758 TestIfBadThingTouchesOtherBadThing(newx, newy);
8760 else if (element == EL_PENGUIN)
8761 TestIfFriendTouchesBadThing(newx, newy);
8763 if (DONT_GET_HIT_BY(element))
8765 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8768 // give the player one last chance (one more frame) to move away
8769 if (CAN_FALL(element) && direction == MV_DOWN &&
8770 (last_line || (!IS_FREE(x, newy + 1) &&
8771 (!IS_PLAYER(x, newy + 1) ||
8772 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8775 if (pushed_by_player && !game.use_change_when_pushing_bug)
8777 int push_side = MV_DIR_OPPOSITE(direction);
8778 struct PlayerInfo *player = PLAYERINFO(x, y);
8780 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8781 player->index_bit, push_side);
8782 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8783 player->index_bit, push_side);
8786 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8787 MovDelay[newx][newy] = 1;
8789 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8791 TestIfElementTouchesCustomElement(x, y); // empty or new element
8792 TestIfElementHitsCustomElement(newx, newy, direction);
8793 TestIfPlayerTouchesCustomElement(newx, newy);
8794 TestIfElementTouchesCustomElement(newx, newy);
8796 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8797 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8798 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8799 MV_DIR_OPPOSITE(direction));
8802 int AmoebaNeighbourNr(int ax, int ay)
8805 int element = Tile[ax][ay];
8807 static int xy[4][2] =
8815 for (i = 0; i < NUM_DIRECTIONS; i++)
8817 int x = ax + xy[i][0];
8818 int y = ay + xy[i][1];
8820 if (!IN_LEV_FIELD(x, y))
8823 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8824 group_nr = AmoebaNr[x][y];
8830 static void AmoebaMerge(int ax, int ay)
8832 int i, x, y, xx, yy;
8833 int new_group_nr = AmoebaNr[ax][ay];
8834 static int xy[4][2] =
8842 if (new_group_nr == 0)
8845 for (i = 0; i < NUM_DIRECTIONS; i++)
8850 if (!IN_LEV_FIELD(x, y))
8853 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8854 Tile[x][y] == EL_BD_AMOEBA ||
8855 Tile[x][y] == EL_AMOEBA_DEAD) &&
8856 AmoebaNr[x][y] != new_group_nr)
8858 int old_group_nr = AmoebaNr[x][y];
8860 if (old_group_nr == 0)
8863 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8864 AmoebaCnt[old_group_nr] = 0;
8865 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8866 AmoebaCnt2[old_group_nr] = 0;
8868 SCAN_PLAYFIELD(xx, yy)
8870 if (AmoebaNr[xx][yy] == old_group_nr)
8871 AmoebaNr[xx][yy] = new_group_nr;
8877 void AmoebaToDiamond(int ax, int ay)
8881 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8883 int group_nr = AmoebaNr[ax][ay];
8888 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8889 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8895 SCAN_PLAYFIELD(x, y)
8897 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8900 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8904 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8905 SND_AMOEBA_TURNING_TO_GEM :
8906 SND_AMOEBA_TURNING_TO_ROCK));
8911 static int xy[4][2] =
8919 for (i = 0; i < NUM_DIRECTIONS; i++)
8924 if (!IN_LEV_FIELD(x, y))
8927 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8929 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8930 SND_AMOEBA_TURNING_TO_GEM :
8931 SND_AMOEBA_TURNING_TO_ROCK));
8938 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8941 int group_nr = AmoebaNr[ax][ay];
8942 boolean done = FALSE;
8947 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8948 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8954 SCAN_PLAYFIELD(x, y)
8956 if (AmoebaNr[x][y] == group_nr &&
8957 (Tile[x][y] == EL_AMOEBA_DEAD ||
8958 Tile[x][y] == EL_BD_AMOEBA ||
8959 Tile[x][y] == EL_AMOEBA_GROWING))
8962 Tile[x][y] = new_element;
8963 InitField(x, y, FALSE);
8964 TEST_DrawLevelField(x, y);
8970 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8971 SND_BD_AMOEBA_TURNING_TO_ROCK :
8972 SND_BD_AMOEBA_TURNING_TO_GEM));
8975 static void AmoebaGrowing(int x, int y)
8977 static unsigned int sound_delay = 0;
8978 static unsigned int sound_delay_value = 0;
8980 if (!MovDelay[x][y]) // start new growing cycle
8984 if (DelayReached(&sound_delay, sound_delay_value))
8986 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8987 sound_delay_value = 30;
8991 if (MovDelay[x][y]) // wait some time before growing bigger
8994 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8996 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8997 6 - MovDelay[x][y]);
8999 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9002 if (!MovDelay[x][y])
9004 Tile[x][y] = Store[x][y];
9006 TEST_DrawLevelField(x, y);
9011 static void AmoebaShrinking(int x, int y)
9013 static unsigned int sound_delay = 0;
9014 static unsigned int sound_delay_value = 0;
9016 if (!MovDelay[x][y]) // start new shrinking cycle
9020 if (DelayReached(&sound_delay, sound_delay_value))
9021 sound_delay_value = 30;
9024 if (MovDelay[x][y]) // wait some time before shrinking
9027 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9029 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9030 6 - MovDelay[x][y]);
9032 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9035 if (!MovDelay[x][y])
9037 Tile[x][y] = EL_EMPTY;
9038 TEST_DrawLevelField(x, y);
9040 // don't let mole enter this field in this cycle;
9041 // (give priority to objects falling to this field from above)
9047 static void AmoebaReproduce(int ax, int ay)
9050 int element = Tile[ax][ay];
9051 int graphic = el2img(element);
9052 int newax = ax, neway = ay;
9053 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9054 static int xy[4][2] =
9062 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9064 Tile[ax][ay] = EL_AMOEBA_DEAD;
9065 TEST_DrawLevelField(ax, ay);
9069 if (IS_ANIMATED(graphic))
9070 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9072 if (!MovDelay[ax][ay]) // start making new amoeba field
9073 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9075 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9078 if (MovDelay[ax][ay])
9082 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9085 int x = ax + xy[start][0];
9086 int y = ay + xy[start][1];
9088 if (!IN_LEV_FIELD(x, y))
9091 if (IS_FREE(x, y) ||
9092 CAN_GROW_INTO(Tile[x][y]) ||
9093 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9094 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9100 if (newax == ax && neway == ay)
9103 else // normal or "filled" (BD style) amoeba
9106 boolean waiting_for_player = FALSE;
9108 for (i = 0; i < NUM_DIRECTIONS; i++)
9110 int j = (start + i) % 4;
9111 int x = ax + xy[j][0];
9112 int y = ay + xy[j][1];
9114 if (!IN_LEV_FIELD(x, y))
9117 if (IS_FREE(x, y) ||
9118 CAN_GROW_INTO(Tile[x][y]) ||
9119 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9120 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9126 else if (IS_PLAYER(x, y))
9127 waiting_for_player = TRUE;
9130 if (newax == ax && neway == ay) // amoeba cannot grow
9132 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9134 Tile[ax][ay] = EL_AMOEBA_DEAD;
9135 TEST_DrawLevelField(ax, ay);
9136 AmoebaCnt[AmoebaNr[ax][ay]]--;
9138 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9140 if (element == EL_AMOEBA_FULL)
9141 AmoebaToDiamond(ax, ay);
9142 else if (element == EL_BD_AMOEBA)
9143 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9148 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9150 // amoeba gets larger by growing in some direction
9152 int new_group_nr = AmoebaNr[ax][ay];
9155 if (new_group_nr == 0)
9157 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9159 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9165 AmoebaNr[newax][neway] = new_group_nr;
9166 AmoebaCnt[new_group_nr]++;
9167 AmoebaCnt2[new_group_nr]++;
9169 // if amoeba touches other amoeba(s) after growing, unify them
9170 AmoebaMerge(newax, neway);
9172 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9174 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9180 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9181 (neway == lev_fieldy - 1 && newax != ax))
9183 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9184 Store[newax][neway] = element;
9186 else if (neway == ay || element == EL_EMC_DRIPPER)
9188 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9190 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9194 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9195 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9196 Store[ax][ay] = EL_AMOEBA_DROP;
9197 ContinueMoving(ax, ay);
9201 TEST_DrawLevelField(newax, neway);
9204 static void Life(int ax, int ay)
9208 int element = Tile[ax][ay];
9209 int graphic = el2img(element);
9210 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9212 boolean changed = FALSE;
9214 if (IS_ANIMATED(graphic))
9215 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9220 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9221 MovDelay[ax][ay] = life_time;
9223 if (MovDelay[ax][ay]) // wait some time before next cycle
9226 if (MovDelay[ax][ay])
9230 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9232 int xx = ax+x1, yy = ay+y1;
9233 int old_element = Tile[xx][yy];
9234 int num_neighbours = 0;
9236 if (!IN_LEV_FIELD(xx, yy))
9239 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9241 int x = xx+x2, y = yy+y2;
9243 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9246 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9247 boolean is_neighbour = FALSE;
9249 if (level.use_life_bugs)
9251 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9252 (IS_FREE(x, y) && Stop[x][y]));
9255 (Last[x][y] == element || is_player_cell);
9261 boolean is_free = FALSE;
9263 if (level.use_life_bugs)
9264 is_free = (IS_FREE(xx, yy));
9266 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9268 if (xx == ax && yy == ay) // field in the middle
9270 if (num_neighbours < life_parameter[0] ||
9271 num_neighbours > life_parameter[1])
9273 Tile[xx][yy] = EL_EMPTY;
9274 if (Tile[xx][yy] != old_element)
9275 TEST_DrawLevelField(xx, yy);
9276 Stop[xx][yy] = TRUE;
9280 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9281 { // free border field
9282 if (num_neighbours >= life_parameter[2] &&
9283 num_neighbours <= life_parameter[3])
9285 Tile[xx][yy] = element;
9286 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9287 if (Tile[xx][yy] != old_element)
9288 TEST_DrawLevelField(xx, yy);
9289 Stop[xx][yy] = TRUE;
9296 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9297 SND_GAME_OF_LIFE_GROWING);
9300 static void InitRobotWheel(int x, int y)
9302 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9305 static void RunRobotWheel(int x, int y)
9307 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9310 static void StopRobotWheel(int x, int y)
9312 if (game.robot_wheel_x == x &&
9313 game.robot_wheel_y == y)
9315 game.robot_wheel_x = -1;
9316 game.robot_wheel_y = -1;
9317 game.robot_wheel_active = FALSE;
9321 static void InitTimegateWheel(int x, int y)
9323 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9326 static void RunTimegateWheel(int x, int y)
9328 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9331 static void InitMagicBallDelay(int x, int y)
9333 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9336 static void ActivateMagicBall(int bx, int by)
9340 if (level.ball_random)
9342 int pos_border = RND(8); // select one of the eight border elements
9343 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9344 int xx = pos_content % 3;
9345 int yy = pos_content / 3;
9350 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9351 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9355 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9357 int xx = x - bx + 1;
9358 int yy = y - by + 1;
9360 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9361 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9365 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9368 static void CheckExit(int x, int y)
9370 if (game.gems_still_needed > 0 ||
9371 game.sokoban_fields_still_needed > 0 ||
9372 game.sokoban_objects_still_needed > 0 ||
9373 game.lights_still_needed > 0)
9375 int element = Tile[x][y];
9376 int graphic = el2img(element);
9378 if (IS_ANIMATED(graphic))
9379 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9384 // do not re-open exit door closed after last player
9385 if (game.all_players_gone)
9388 Tile[x][y] = EL_EXIT_OPENING;
9390 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9393 static void CheckExitEM(int x, int y)
9395 if (game.gems_still_needed > 0 ||
9396 game.sokoban_fields_still_needed > 0 ||
9397 game.sokoban_objects_still_needed > 0 ||
9398 game.lights_still_needed > 0)
9400 int element = Tile[x][y];
9401 int graphic = el2img(element);
9403 if (IS_ANIMATED(graphic))
9404 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9409 // do not re-open exit door closed after last player
9410 if (game.all_players_gone)
9413 Tile[x][y] = EL_EM_EXIT_OPENING;
9415 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9418 static void CheckExitSteel(int x, int y)
9420 if (game.gems_still_needed > 0 ||
9421 game.sokoban_fields_still_needed > 0 ||
9422 game.sokoban_objects_still_needed > 0 ||
9423 game.lights_still_needed > 0)
9425 int element = Tile[x][y];
9426 int graphic = el2img(element);
9428 if (IS_ANIMATED(graphic))
9429 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9434 // do not re-open exit door closed after last player
9435 if (game.all_players_gone)
9438 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9440 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9443 static void CheckExitSteelEM(int x, int y)
9445 if (game.gems_still_needed > 0 ||
9446 game.sokoban_fields_still_needed > 0 ||
9447 game.sokoban_objects_still_needed > 0 ||
9448 game.lights_still_needed > 0)
9450 int element = Tile[x][y];
9451 int graphic = el2img(element);
9453 if (IS_ANIMATED(graphic))
9454 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9459 // do not re-open exit door closed after last player
9460 if (game.all_players_gone)
9463 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9465 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9468 static void CheckExitSP(int x, int y)
9470 if (game.gems_still_needed > 0)
9472 int element = Tile[x][y];
9473 int graphic = el2img(element);
9475 if (IS_ANIMATED(graphic))
9476 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9481 // do not re-open exit door closed after last player
9482 if (game.all_players_gone)
9485 Tile[x][y] = EL_SP_EXIT_OPENING;
9487 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9490 static void CloseAllOpenTimegates(void)
9494 SCAN_PLAYFIELD(x, y)
9496 int element = Tile[x][y];
9498 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9500 Tile[x][y] = EL_TIMEGATE_CLOSING;
9502 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9507 static void DrawTwinkleOnField(int x, int y)
9509 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9512 if (Tile[x][y] == EL_BD_DIAMOND)
9515 if (MovDelay[x][y] == 0) // next animation frame
9516 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9518 if (MovDelay[x][y] != 0) // wait some time before next frame
9522 DrawLevelElementAnimation(x, y, Tile[x][y]);
9524 if (MovDelay[x][y] != 0)
9526 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9527 10 - MovDelay[x][y]);
9529 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9534 static void MauerWaechst(int x, int y)
9538 if (!MovDelay[x][y]) // next animation frame
9539 MovDelay[x][y] = 3 * delay;
9541 if (MovDelay[x][y]) // wait some time before next frame
9545 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9547 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9548 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9550 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9553 if (!MovDelay[x][y])
9555 if (MovDir[x][y] == MV_LEFT)
9557 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9558 TEST_DrawLevelField(x - 1, y);
9560 else if (MovDir[x][y] == MV_RIGHT)
9562 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9563 TEST_DrawLevelField(x + 1, y);
9565 else if (MovDir[x][y] == MV_UP)
9567 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9568 TEST_DrawLevelField(x, y - 1);
9572 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9573 TEST_DrawLevelField(x, y + 1);
9576 Tile[x][y] = Store[x][y];
9578 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9579 TEST_DrawLevelField(x, y);
9584 static void MauerAbleger(int ax, int ay)
9586 int element = Tile[ax][ay];
9587 int graphic = el2img(element);
9588 boolean oben_frei = FALSE, unten_frei = FALSE;
9589 boolean links_frei = FALSE, rechts_frei = FALSE;
9590 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9591 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9592 boolean new_wall = FALSE;
9594 if (IS_ANIMATED(graphic))
9595 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9597 if (!MovDelay[ax][ay]) // start building new wall
9598 MovDelay[ax][ay] = 6;
9600 if (MovDelay[ax][ay]) // wait some time before building new wall
9603 if (MovDelay[ax][ay])
9607 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9609 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9611 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9613 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9616 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9617 element == EL_EXPANDABLE_WALL_ANY)
9621 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9622 Store[ax][ay-1] = element;
9623 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9624 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9625 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9626 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9631 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9632 Store[ax][ay+1] = element;
9633 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9634 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9635 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9636 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9641 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9642 element == EL_EXPANDABLE_WALL_ANY ||
9643 element == EL_EXPANDABLE_WALL ||
9644 element == EL_BD_EXPANDABLE_WALL)
9648 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9649 Store[ax-1][ay] = element;
9650 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9651 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9652 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9653 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9659 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9660 Store[ax+1][ay] = element;
9661 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9662 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9663 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9664 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9669 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9670 TEST_DrawLevelField(ax, ay);
9672 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9674 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9675 unten_massiv = TRUE;
9676 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9677 links_massiv = TRUE;
9678 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9679 rechts_massiv = TRUE;
9681 if (((oben_massiv && unten_massiv) ||
9682 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9683 element == EL_EXPANDABLE_WALL) &&
9684 ((links_massiv && rechts_massiv) ||
9685 element == EL_EXPANDABLE_WALL_VERTICAL))
9686 Tile[ax][ay] = EL_WALL;
9689 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9692 static void MauerAblegerStahl(int ax, int ay)
9694 int element = Tile[ax][ay];
9695 int graphic = el2img(element);
9696 boolean oben_frei = FALSE, unten_frei = FALSE;
9697 boolean links_frei = FALSE, rechts_frei = FALSE;
9698 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9699 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9700 boolean new_wall = FALSE;
9702 if (IS_ANIMATED(graphic))
9703 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9705 if (!MovDelay[ax][ay]) // start building new wall
9706 MovDelay[ax][ay] = 6;
9708 if (MovDelay[ax][ay]) // wait some time before building new wall
9711 if (MovDelay[ax][ay])
9715 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9717 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9719 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9721 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9724 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9725 element == EL_EXPANDABLE_STEELWALL_ANY)
9729 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9730 Store[ax][ay-1] = element;
9731 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9732 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9733 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9734 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9739 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9740 Store[ax][ay+1] = element;
9741 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9742 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9743 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9744 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9749 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9750 element == EL_EXPANDABLE_STEELWALL_ANY)
9754 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9755 Store[ax-1][ay] = element;
9756 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9757 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9758 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9759 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9765 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9766 Store[ax+1][ay] = element;
9767 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9768 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9769 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9770 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9775 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9777 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9778 unten_massiv = TRUE;
9779 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9780 links_massiv = TRUE;
9781 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9782 rechts_massiv = TRUE;
9784 if (((oben_massiv && unten_massiv) ||
9785 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9786 ((links_massiv && rechts_massiv) ||
9787 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9788 Tile[ax][ay] = EL_STEELWALL;
9791 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9794 static void CheckForDragon(int x, int y)
9797 boolean dragon_found = FALSE;
9798 static int xy[4][2] =
9806 for (i = 0; i < NUM_DIRECTIONS; i++)
9808 for (j = 0; j < 4; j++)
9810 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9812 if (IN_LEV_FIELD(xx, yy) &&
9813 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9815 if (Tile[xx][yy] == EL_DRAGON)
9816 dragon_found = TRUE;
9825 for (i = 0; i < NUM_DIRECTIONS; i++)
9827 for (j = 0; j < 3; j++)
9829 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9831 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9833 Tile[xx][yy] = EL_EMPTY;
9834 TEST_DrawLevelField(xx, yy);
9843 static void InitBuggyBase(int x, int y)
9845 int element = Tile[x][y];
9846 int activating_delay = FRAMES_PER_SECOND / 4;
9849 (element == EL_SP_BUGGY_BASE ?
9850 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9851 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9853 element == EL_SP_BUGGY_BASE_ACTIVE ?
9854 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9857 static void WarnBuggyBase(int x, int y)
9860 static int xy[4][2] =
9868 for (i = 0; i < NUM_DIRECTIONS; i++)
9870 int xx = x + xy[i][0];
9871 int yy = y + xy[i][1];
9873 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9875 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9882 static void InitTrap(int x, int y)
9884 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9887 static void ActivateTrap(int x, int y)
9889 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9892 static void ChangeActiveTrap(int x, int y)
9894 int graphic = IMG_TRAP_ACTIVE;
9896 // if new animation frame was drawn, correct crumbled sand border
9897 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9898 TEST_DrawLevelFieldCrumbled(x, y);
9901 static int getSpecialActionElement(int element, int number, int base_element)
9903 return (element != EL_EMPTY ? element :
9904 number != -1 ? base_element + number - 1 :
9908 static int getModifiedActionNumber(int value_old, int operator, int operand,
9909 int value_min, int value_max)
9911 int value_new = (operator == CA_MODE_SET ? operand :
9912 operator == CA_MODE_ADD ? value_old + operand :
9913 operator == CA_MODE_SUBTRACT ? value_old - operand :
9914 operator == CA_MODE_MULTIPLY ? value_old * operand :
9915 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9916 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9919 return (value_new < value_min ? value_min :
9920 value_new > value_max ? value_max :
9924 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9926 struct ElementInfo *ei = &element_info[element];
9927 struct ElementChangeInfo *change = &ei->change_page[page];
9928 int target_element = change->target_element;
9929 int action_type = change->action_type;
9930 int action_mode = change->action_mode;
9931 int action_arg = change->action_arg;
9932 int action_element = change->action_element;
9935 if (!change->has_action)
9938 // ---------- determine action paramater values -----------------------------
9940 int level_time_value =
9941 (level.time > 0 ? TimeLeft :
9944 int action_arg_element_raw =
9945 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9946 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9947 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9948 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9949 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9950 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9951 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9953 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9955 int action_arg_direction =
9956 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9957 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9958 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9959 change->actual_trigger_side :
9960 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9961 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9964 int action_arg_number_min =
9965 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9968 int action_arg_number_max =
9969 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9970 action_type == CA_SET_LEVEL_GEMS ? 999 :
9971 action_type == CA_SET_LEVEL_TIME ? 9999 :
9972 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9973 action_type == CA_SET_CE_VALUE ? 9999 :
9974 action_type == CA_SET_CE_SCORE ? 9999 :
9977 int action_arg_number_reset =
9978 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9979 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9980 action_type == CA_SET_LEVEL_TIME ? level.time :
9981 action_type == CA_SET_LEVEL_SCORE ? 0 :
9982 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9983 action_type == CA_SET_CE_SCORE ? 0 :
9986 int action_arg_number =
9987 (action_arg <= CA_ARG_MAX ? action_arg :
9988 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9989 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9990 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9991 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9992 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9993 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9994 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9995 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9996 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9997 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9998 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9999 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10000 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10001 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10002 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10003 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10004 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10005 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10006 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10007 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10008 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10011 int action_arg_number_old =
10012 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10013 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10014 action_type == CA_SET_LEVEL_SCORE ? game.score :
10015 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10016 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10019 int action_arg_number_new =
10020 getModifiedActionNumber(action_arg_number_old,
10021 action_mode, action_arg_number,
10022 action_arg_number_min, action_arg_number_max);
10024 int trigger_player_bits =
10025 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10026 change->actual_trigger_player_bits : change->trigger_player);
10028 int action_arg_player_bits =
10029 (action_arg >= CA_ARG_PLAYER_1 &&
10030 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10031 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10032 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10035 // ---------- execute action -----------------------------------------------
10037 switch (action_type)
10044 // ---------- level actions ----------------------------------------------
10046 case CA_RESTART_LEVEL:
10048 game.restart_level = TRUE;
10053 case CA_SHOW_ENVELOPE:
10055 int element = getSpecialActionElement(action_arg_element,
10056 action_arg_number, EL_ENVELOPE_1);
10058 if (IS_ENVELOPE(element))
10059 local_player->show_envelope = element;
10064 case CA_SET_LEVEL_TIME:
10066 if (level.time > 0) // only modify limited time value
10068 TimeLeft = action_arg_number_new;
10070 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10072 DisplayGameControlValues();
10074 if (!TimeLeft && setup.time_limit)
10075 for (i = 0; i < MAX_PLAYERS; i++)
10076 KillPlayer(&stored_player[i]);
10082 case CA_SET_LEVEL_SCORE:
10084 game.score = action_arg_number_new;
10086 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10088 DisplayGameControlValues();
10093 case CA_SET_LEVEL_GEMS:
10095 game.gems_still_needed = action_arg_number_new;
10097 game.snapshot.collected_item = TRUE;
10099 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10101 DisplayGameControlValues();
10106 case CA_SET_LEVEL_WIND:
10108 game.wind_direction = action_arg_direction;
10113 case CA_SET_LEVEL_RANDOM_SEED:
10115 // ensure that setting a new random seed while playing is predictable
10116 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10121 // ---------- player actions ---------------------------------------------
10123 case CA_MOVE_PLAYER:
10124 case CA_MOVE_PLAYER_NEW:
10126 // automatically move to the next field in specified direction
10127 for (i = 0; i < MAX_PLAYERS; i++)
10128 if (trigger_player_bits & (1 << i))
10129 if (action_type == CA_MOVE_PLAYER ||
10130 stored_player[i].MovPos == 0)
10131 stored_player[i].programmed_action = action_arg_direction;
10136 case CA_EXIT_PLAYER:
10138 for (i = 0; i < MAX_PLAYERS; i++)
10139 if (action_arg_player_bits & (1 << i))
10140 ExitPlayer(&stored_player[i]);
10142 if (game.players_still_needed == 0)
10148 case CA_KILL_PLAYER:
10150 for (i = 0; i < MAX_PLAYERS; i++)
10151 if (action_arg_player_bits & (1 << i))
10152 KillPlayer(&stored_player[i]);
10157 case CA_SET_PLAYER_KEYS:
10159 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10160 int element = getSpecialActionElement(action_arg_element,
10161 action_arg_number, EL_KEY_1);
10163 if (IS_KEY(element))
10165 for (i = 0; i < MAX_PLAYERS; i++)
10167 if (trigger_player_bits & (1 << i))
10169 stored_player[i].key[KEY_NR(element)] = key_state;
10171 DrawGameDoorValues();
10179 case CA_SET_PLAYER_SPEED:
10181 for (i = 0; i < MAX_PLAYERS; i++)
10183 if (trigger_player_bits & (1 << i))
10185 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10187 if (action_arg == CA_ARG_SPEED_FASTER &&
10188 stored_player[i].cannot_move)
10190 action_arg_number = STEPSIZE_VERY_SLOW;
10192 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10193 action_arg == CA_ARG_SPEED_FASTER)
10195 action_arg_number = 2;
10196 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10199 else if (action_arg == CA_ARG_NUMBER_RESET)
10201 action_arg_number = level.initial_player_stepsize[i];
10205 getModifiedActionNumber(move_stepsize,
10208 action_arg_number_min,
10209 action_arg_number_max);
10211 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10218 case CA_SET_PLAYER_SHIELD:
10220 for (i = 0; i < MAX_PLAYERS; i++)
10222 if (trigger_player_bits & (1 << i))
10224 if (action_arg == CA_ARG_SHIELD_OFF)
10226 stored_player[i].shield_normal_time_left = 0;
10227 stored_player[i].shield_deadly_time_left = 0;
10229 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10231 stored_player[i].shield_normal_time_left = 999999;
10233 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10235 stored_player[i].shield_normal_time_left = 999999;
10236 stored_player[i].shield_deadly_time_left = 999999;
10244 case CA_SET_PLAYER_GRAVITY:
10246 for (i = 0; i < MAX_PLAYERS; i++)
10248 if (trigger_player_bits & (1 << i))
10250 stored_player[i].gravity =
10251 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10252 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10253 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10254 stored_player[i].gravity);
10261 case CA_SET_PLAYER_ARTWORK:
10263 for (i = 0; i < MAX_PLAYERS; i++)
10265 if (trigger_player_bits & (1 << i))
10267 int artwork_element = action_arg_element;
10269 if (action_arg == CA_ARG_ELEMENT_RESET)
10271 (level.use_artwork_element[i] ? level.artwork_element[i] :
10272 stored_player[i].element_nr);
10274 if (stored_player[i].artwork_element != artwork_element)
10275 stored_player[i].Frame = 0;
10277 stored_player[i].artwork_element = artwork_element;
10279 SetPlayerWaiting(&stored_player[i], FALSE);
10281 // set number of special actions for bored and sleeping animation
10282 stored_player[i].num_special_action_bored =
10283 get_num_special_action(artwork_element,
10284 ACTION_BORING_1, ACTION_BORING_LAST);
10285 stored_player[i].num_special_action_sleeping =
10286 get_num_special_action(artwork_element,
10287 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10294 case CA_SET_PLAYER_INVENTORY:
10296 for (i = 0; i < MAX_PLAYERS; i++)
10298 struct PlayerInfo *player = &stored_player[i];
10301 if (trigger_player_bits & (1 << i))
10303 int inventory_element = action_arg_element;
10305 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10306 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10307 action_arg == CA_ARG_ELEMENT_ACTION)
10309 int element = inventory_element;
10310 int collect_count = element_info[element].collect_count_initial;
10312 if (!IS_CUSTOM_ELEMENT(element))
10315 if (collect_count == 0)
10316 player->inventory_infinite_element = element;
10318 for (k = 0; k < collect_count; k++)
10319 if (player->inventory_size < MAX_INVENTORY_SIZE)
10320 player->inventory_element[player->inventory_size++] =
10323 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10324 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10325 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10327 if (player->inventory_infinite_element != EL_UNDEFINED &&
10328 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10329 action_arg_element_raw))
10330 player->inventory_infinite_element = EL_UNDEFINED;
10332 for (k = 0, j = 0; j < player->inventory_size; j++)
10334 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10335 action_arg_element_raw))
10336 player->inventory_element[k++] = player->inventory_element[j];
10339 player->inventory_size = k;
10341 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10343 if (player->inventory_size > 0)
10345 for (j = 0; j < player->inventory_size - 1; j++)
10346 player->inventory_element[j] = player->inventory_element[j + 1];
10348 player->inventory_size--;
10351 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10353 if (player->inventory_size > 0)
10354 player->inventory_size--;
10356 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10358 player->inventory_infinite_element = EL_UNDEFINED;
10359 player->inventory_size = 0;
10361 else if (action_arg == CA_ARG_INVENTORY_RESET)
10363 player->inventory_infinite_element = EL_UNDEFINED;
10364 player->inventory_size = 0;
10366 if (level.use_initial_inventory[i])
10368 for (j = 0; j < level.initial_inventory_size[i]; j++)
10370 int element = level.initial_inventory_content[i][j];
10371 int collect_count = element_info[element].collect_count_initial;
10373 if (!IS_CUSTOM_ELEMENT(element))
10376 if (collect_count == 0)
10377 player->inventory_infinite_element = element;
10379 for (k = 0; k < collect_count; k++)
10380 if (player->inventory_size < MAX_INVENTORY_SIZE)
10381 player->inventory_element[player->inventory_size++] =
10392 // ---------- CE actions -------------------------------------------------
10394 case CA_SET_CE_VALUE:
10396 int last_ce_value = CustomValue[x][y];
10398 CustomValue[x][y] = action_arg_number_new;
10400 if (CustomValue[x][y] != last_ce_value)
10402 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10403 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10405 if (CustomValue[x][y] == 0)
10407 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10408 ChangeCount[x][y] = 0; // allow at least one more change
10410 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10411 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10418 case CA_SET_CE_SCORE:
10420 int last_ce_score = ei->collect_score;
10422 ei->collect_score = action_arg_number_new;
10424 if (ei->collect_score != last_ce_score)
10426 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10427 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10429 if (ei->collect_score == 0)
10433 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10434 ChangeCount[x][y] = 0; // allow at least one more change
10436 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10437 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10440 This is a very special case that seems to be a mixture between
10441 CheckElementChange() and CheckTriggeredElementChange(): while
10442 the first one only affects single elements that are triggered
10443 directly, the second one affects multiple elements in the playfield
10444 that are triggered indirectly by another element. This is a third
10445 case: Changing the CE score always affects multiple identical CEs,
10446 so every affected CE must be checked, not only the single CE for
10447 which the CE score was changed in the first place (as every instance
10448 of that CE shares the same CE score, and therefore also can change)!
10450 SCAN_PLAYFIELD(xx, yy)
10452 if (Tile[xx][yy] == element)
10453 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10454 CE_SCORE_GETS_ZERO);
10462 case CA_SET_CE_ARTWORK:
10464 int artwork_element = action_arg_element;
10465 boolean reset_frame = FALSE;
10468 if (action_arg == CA_ARG_ELEMENT_RESET)
10469 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10472 if (ei->gfx_element != artwork_element)
10473 reset_frame = TRUE;
10475 ei->gfx_element = artwork_element;
10477 SCAN_PLAYFIELD(xx, yy)
10479 if (Tile[xx][yy] == element)
10483 ResetGfxAnimation(xx, yy);
10484 ResetRandomAnimationValue(xx, yy);
10487 TEST_DrawLevelField(xx, yy);
10494 // ---------- engine actions ---------------------------------------------
10496 case CA_SET_ENGINE_SCAN_MODE:
10498 InitPlayfieldScanMode(action_arg);
10508 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10510 int old_element = Tile[x][y];
10511 int new_element = GetElementFromGroupElement(element);
10512 int previous_move_direction = MovDir[x][y];
10513 int last_ce_value = CustomValue[x][y];
10514 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10515 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10516 boolean add_player_onto_element = (new_element_is_player &&
10517 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10518 IS_WALKABLE(old_element));
10520 if (!add_player_onto_element)
10522 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10523 RemoveMovingField(x, y);
10527 Tile[x][y] = new_element;
10529 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10530 MovDir[x][y] = previous_move_direction;
10532 if (element_info[new_element].use_last_ce_value)
10533 CustomValue[x][y] = last_ce_value;
10535 InitField_WithBug1(x, y, FALSE);
10537 new_element = Tile[x][y]; // element may have changed
10539 ResetGfxAnimation(x, y);
10540 ResetRandomAnimationValue(x, y);
10542 TEST_DrawLevelField(x, y);
10544 if (GFX_CRUMBLED(new_element))
10545 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10548 // check if element under the player changes from accessible to unaccessible
10549 // (needed for special case of dropping element which then changes)
10550 // (must be checked after creating new element for walkable group elements)
10551 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10552 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10559 // "ChangeCount" not set yet to allow "entered by player" change one time
10560 if (new_element_is_player)
10561 RelocatePlayer(x, y, new_element);
10564 ChangeCount[x][y]++; // count number of changes in the same frame
10566 TestIfBadThingTouchesPlayer(x, y);
10567 TestIfPlayerTouchesCustomElement(x, y);
10568 TestIfElementTouchesCustomElement(x, y);
10571 static void CreateField(int x, int y, int element)
10573 CreateFieldExt(x, y, element, FALSE);
10576 static void CreateElementFromChange(int x, int y, int element)
10578 element = GET_VALID_RUNTIME_ELEMENT(element);
10580 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10582 int old_element = Tile[x][y];
10584 // prevent changed element from moving in same engine frame
10585 // unless both old and new element can either fall or move
10586 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10587 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10591 CreateFieldExt(x, y, element, TRUE);
10594 static boolean ChangeElement(int x, int y, int element, int page)
10596 struct ElementInfo *ei = &element_info[element];
10597 struct ElementChangeInfo *change = &ei->change_page[page];
10598 int ce_value = CustomValue[x][y];
10599 int ce_score = ei->collect_score;
10600 int target_element;
10601 int old_element = Tile[x][y];
10603 // always use default change event to prevent running into a loop
10604 if (ChangeEvent[x][y] == -1)
10605 ChangeEvent[x][y] = CE_DELAY;
10607 if (ChangeEvent[x][y] == CE_DELAY)
10609 // reset actual trigger element, trigger player and action element
10610 change->actual_trigger_element = EL_EMPTY;
10611 change->actual_trigger_player = EL_EMPTY;
10612 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10613 change->actual_trigger_side = CH_SIDE_NONE;
10614 change->actual_trigger_ce_value = 0;
10615 change->actual_trigger_ce_score = 0;
10618 // do not change elements more than a specified maximum number of changes
10619 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10622 ChangeCount[x][y]++; // count number of changes in the same frame
10624 if (change->explode)
10631 if (change->use_target_content)
10633 boolean complete_replace = TRUE;
10634 boolean can_replace[3][3];
10637 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10640 boolean is_walkable;
10641 boolean is_diggable;
10642 boolean is_collectible;
10643 boolean is_removable;
10644 boolean is_destructible;
10645 int ex = x + xx - 1;
10646 int ey = y + yy - 1;
10647 int content_element = change->target_content.e[xx][yy];
10650 can_replace[xx][yy] = TRUE;
10652 if (ex == x && ey == y) // do not check changing element itself
10655 if (content_element == EL_EMPTY_SPACE)
10657 can_replace[xx][yy] = FALSE; // do not replace border with space
10662 if (!IN_LEV_FIELD(ex, ey))
10664 can_replace[xx][yy] = FALSE;
10665 complete_replace = FALSE;
10672 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10673 e = MovingOrBlocked2Element(ex, ey);
10675 is_empty = (IS_FREE(ex, ey) ||
10676 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10678 is_walkable = (is_empty || IS_WALKABLE(e));
10679 is_diggable = (is_empty || IS_DIGGABLE(e));
10680 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10681 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10682 is_removable = (is_diggable || is_collectible);
10684 can_replace[xx][yy] =
10685 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10686 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10687 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10688 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10689 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10690 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10691 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10693 if (!can_replace[xx][yy])
10694 complete_replace = FALSE;
10697 if (!change->only_if_complete || complete_replace)
10699 boolean something_has_changed = FALSE;
10701 if (change->only_if_complete && change->use_random_replace &&
10702 RND(100) < change->random_percentage)
10705 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10707 int ex = x + xx - 1;
10708 int ey = y + yy - 1;
10709 int content_element;
10711 if (can_replace[xx][yy] && (!change->use_random_replace ||
10712 RND(100) < change->random_percentage))
10714 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10715 RemoveMovingField(ex, ey);
10717 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10719 content_element = change->target_content.e[xx][yy];
10720 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10721 ce_value, ce_score);
10723 CreateElementFromChange(ex, ey, target_element);
10725 something_has_changed = TRUE;
10727 // for symmetry reasons, freeze newly created border elements
10728 if (ex != x || ey != y)
10729 Stop[ex][ey] = TRUE; // no more moving in this frame
10733 if (something_has_changed)
10735 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10736 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10742 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10743 ce_value, ce_score);
10745 if (element == EL_DIAGONAL_GROWING ||
10746 element == EL_DIAGONAL_SHRINKING)
10748 target_element = Store[x][y];
10750 Store[x][y] = EL_EMPTY;
10753 CreateElementFromChange(x, y, target_element);
10755 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10756 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10759 // this uses direct change before indirect change
10760 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10765 static void HandleElementChange(int x, int y, int page)
10767 int element = MovingOrBlocked2Element(x, y);
10768 struct ElementInfo *ei = &element_info[element];
10769 struct ElementChangeInfo *change = &ei->change_page[page];
10770 boolean handle_action_before_change = FALSE;
10773 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10774 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10776 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10777 x, y, element, element_info[element].token_name);
10778 Debug("game:playing:HandleElementChange", "This should never happen!");
10782 // this can happen with classic bombs on walkable, changing elements
10783 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10788 if (ChangeDelay[x][y] == 0) // initialize element change
10790 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10792 if (change->can_change)
10794 // !!! not clear why graphic animation should be reset at all here !!!
10795 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10796 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10799 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10801 When using an animation frame delay of 1 (this only happens with
10802 "sp_zonk.moving.left/right" in the classic graphics), the default
10803 (non-moving) animation shows wrong animation frames (while the
10804 moving animation, like "sp_zonk.moving.left/right", is correct,
10805 so this graphical bug never shows up with the classic graphics).
10806 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10807 be drawn instead of the correct frames 0,1,2,3. This is caused by
10808 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10809 an element change: First when the change delay ("ChangeDelay[][]")
10810 counter has reached zero after decrementing, then a second time in
10811 the next frame (after "GfxFrame[][]" was already incremented) when
10812 "ChangeDelay[][]" is reset to the initial delay value again.
10814 This causes frame 0 to be drawn twice, while the last frame won't
10815 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10817 As some animations may already be cleverly designed around this bug
10818 (at least the "Snake Bite" snake tail animation does this), it cannot
10819 simply be fixed here without breaking such existing animations.
10820 Unfortunately, it cannot easily be detected if a graphics set was
10821 designed "before" or "after" the bug was fixed. As a workaround,
10822 a new graphics set option "game.graphics_engine_version" was added
10823 to be able to specify the game's major release version for which the
10824 graphics set was designed, which can then be used to decide if the
10825 bugfix should be used (version 4 and above) or not (version 3 or
10826 below, or if no version was specified at all, as with old sets).
10828 (The wrong/fixed animation frames can be tested with the test level set
10829 "test_gfxframe" and level "000", which contains a specially prepared
10830 custom element at level position (x/y) == (11/9) which uses the zonk
10831 animation mentioned above. Using "game.graphics_engine_version: 4"
10832 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10833 This can also be seen from the debug output for this test element.)
10836 // when a custom element is about to change (for example by change delay),
10837 // do not reset graphic animation when the custom element is moving
10838 if (game.graphics_engine_version < 4 &&
10841 ResetGfxAnimation(x, y);
10842 ResetRandomAnimationValue(x, y);
10845 if (change->pre_change_function)
10846 change->pre_change_function(x, y);
10850 ChangeDelay[x][y]--;
10852 if (ChangeDelay[x][y] != 0) // continue element change
10854 if (change->can_change)
10856 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10858 if (IS_ANIMATED(graphic))
10859 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10861 if (change->change_function)
10862 change->change_function(x, y);
10865 else // finish element change
10867 if (ChangePage[x][y] != -1) // remember page from delayed change
10869 page = ChangePage[x][y];
10870 ChangePage[x][y] = -1;
10872 change = &ei->change_page[page];
10875 if (IS_MOVING(x, y)) // never change a running system ;-)
10877 ChangeDelay[x][y] = 1; // try change after next move step
10878 ChangePage[x][y] = page; // remember page to use for change
10883 // special case: set new level random seed before changing element
10884 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10885 handle_action_before_change = TRUE;
10887 if (change->has_action && handle_action_before_change)
10888 ExecuteCustomElementAction(x, y, element, page);
10890 if (change->can_change)
10892 if (ChangeElement(x, y, element, page))
10894 if (change->post_change_function)
10895 change->post_change_function(x, y);
10899 if (change->has_action && !handle_action_before_change)
10900 ExecuteCustomElementAction(x, y, element, page);
10904 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10905 int trigger_element,
10907 int trigger_player,
10911 boolean change_done_any = FALSE;
10912 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10915 if (!(trigger_events[trigger_element][trigger_event]))
10918 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10920 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10922 int element = EL_CUSTOM_START + i;
10923 boolean change_done = FALSE;
10926 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10927 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10930 for (p = 0; p < element_info[element].num_change_pages; p++)
10932 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10934 if (change->can_change_or_has_action &&
10935 change->has_event[trigger_event] &&
10936 change->trigger_side & trigger_side &&
10937 change->trigger_player & trigger_player &&
10938 change->trigger_page & trigger_page_bits &&
10939 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10941 change->actual_trigger_element = trigger_element;
10942 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10943 change->actual_trigger_player_bits = trigger_player;
10944 change->actual_trigger_side = trigger_side;
10945 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10946 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10948 if ((change->can_change && !change_done) || change->has_action)
10952 SCAN_PLAYFIELD(x, y)
10954 if (Tile[x][y] == element)
10956 if (change->can_change && !change_done)
10958 // if element already changed in this frame, not only prevent
10959 // another element change (checked in ChangeElement()), but
10960 // also prevent additional element actions for this element
10962 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10963 !level.use_action_after_change_bug)
10966 ChangeDelay[x][y] = 1;
10967 ChangeEvent[x][y] = trigger_event;
10969 HandleElementChange(x, y, p);
10971 else if (change->has_action)
10973 // if element already changed in this frame, not only prevent
10974 // another element change (checked in ChangeElement()), but
10975 // also prevent additional element actions for this element
10977 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10978 !level.use_action_after_change_bug)
10981 ExecuteCustomElementAction(x, y, element, p);
10982 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10987 if (change->can_change)
10989 change_done = TRUE;
10990 change_done_any = TRUE;
10997 RECURSION_LOOP_DETECTION_END();
10999 return change_done_any;
11002 static boolean CheckElementChangeExt(int x, int y,
11004 int trigger_element,
11006 int trigger_player,
11009 boolean change_done = FALSE;
11012 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11013 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11016 if (Tile[x][y] == EL_BLOCKED)
11018 Blocked2Moving(x, y, &x, &y);
11019 element = Tile[x][y];
11022 // check if element has already changed or is about to change after moving
11023 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11024 Tile[x][y] != element) ||
11026 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11027 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11028 ChangePage[x][y] != -1)))
11031 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11033 for (p = 0; p < element_info[element].num_change_pages; p++)
11035 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11037 /* check trigger element for all events where the element that is checked
11038 for changing interacts with a directly adjacent element -- this is
11039 different to element changes that affect other elements to change on the
11040 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11041 boolean check_trigger_element =
11042 (trigger_event == CE_TOUCHING_X ||
11043 trigger_event == CE_HITTING_X ||
11044 trigger_event == CE_HIT_BY_X ||
11045 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11047 if (change->can_change_or_has_action &&
11048 change->has_event[trigger_event] &&
11049 change->trigger_side & trigger_side &&
11050 change->trigger_player & trigger_player &&
11051 (!check_trigger_element ||
11052 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11054 change->actual_trigger_element = trigger_element;
11055 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11056 change->actual_trigger_player_bits = trigger_player;
11057 change->actual_trigger_side = trigger_side;
11058 change->actual_trigger_ce_value = CustomValue[x][y];
11059 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11061 // special case: trigger element not at (x,y) position for some events
11062 if (check_trigger_element)
11074 { 0, 0 }, { 0, 0 }, { 0, 0 },
11078 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11079 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11081 change->actual_trigger_ce_value = CustomValue[xx][yy];
11082 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11085 if (change->can_change && !change_done)
11087 ChangeDelay[x][y] = 1;
11088 ChangeEvent[x][y] = trigger_event;
11090 HandleElementChange(x, y, p);
11092 change_done = TRUE;
11094 else if (change->has_action)
11096 ExecuteCustomElementAction(x, y, element, p);
11097 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11102 RECURSION_LOOP_DETECTION_END();
11104 return change_done;
11107 static void PlayPlayerSound(struct PlayerInfo *player)
11109 int jx = player->jx, jy = player->jy;
11110 int sound_element = player->artwork_element;
11111 int last_action = player->last_action_waiting;
11112 int action = player->action_waiting;
11114 if (player->is_waiting)
11116 if (action != last_action)
11117 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11119 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11123 if (action != last_action)
11124 StopSound(element_info[sound_element].sound[last_action]);
11126 if (last_action == ACTION_SLEEPING)
11127 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11131 static void PlayAllPlayersSound(void)
11135 for (i = 0; i < MAX_PLAYERS; i++)
11136 if (stored_player[i].active)
11137 PlayPlayerSound(&stored_player[i]);
11140 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11142 boolean last_waiting = player->is_waiting;
11143 int move_dir = player->MovDir;
11145 player->dir_waiting = move_dir;
11146 player->last_action_waiting = player->action_waiting;
11150 if (!last_waiting) // not waiting -> waiting
11152 player->is_waiting = TRUE;
11154 player->frame_counter_bored =
11156 game.player_boring_delay_fixed +
11157 GetSimpleRandom(game.player_boring_delay_random);
11158 player->frame_counter_sleeping =
11160 game.player_sleeping_delay_fixed +
11161 GetSimpleRandom(game.player_sleeping_delay_random);
11163 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11166 if (game.player_sleeping_delay_fixed +
11167 game.player_sleeping_delay_random > 0 &&
11168 player->anim_delay_counter == 0 &&
11169 player->post_delay_counter == 0 &&
11170 FrameCounter >= player->frame_counter_sleeping)
11171 player->is_sleeping = TRUE;
11172 else if (game.player_boring_delay_fixed +
11173 game.player_boring_delay_random > 0 &&
11174 FrameCounter >= player->frame_counter_bored)
11175 player->is_bored = TRUE;
11177 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11178 player->is_bored ? ACTION_BORING :
11181 if (player->is_sleeping && player->use_murphy)
11183 // special case for sleeping Murphy when leaning against non-free tile
11185 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_LEFT;
11189 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11190 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11191 !IS_MOVING(player->jx + 1, player->jy)))
11192 move_dir = MV_RIGHT;
11194 player->is_sleeping = FALSE;
11196 player->dir_waiting = move_dir;
11199 if (player->is_sleeping)
11201 if (player->num_special_action_sleeping > 0)
11203 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11205 int last_special_action = player->special_action_sleeping;
11206 int num_special_action = player->num_special_action_sleeping;
11207 int special_action =
11208 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11209 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11210 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11211 last_special_action + 1 : ACTION_SLEEPING);
11212 int special_graphic =
11213 el_act_dir2img(player->artwork_element, special_action, move_dir);
11215 player->anim_delay_counter =
11216 graphic_info[special_graphic].anim_delay_fixed +
11217 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11218 player->post_delay_counter =
11219 graphic_info[special_graphic].post_delay_fixed +
11220 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11222 player->special_action_sleeping = special_action;
11225 if (player->anim_delay_counter > 0)
11227 player->action_waiting = player->special_action_sleeping;
11228 player->anim_delay_counter--;
11230 else if (player->post_delay_counter > 0)
11232 player->post_delay_counter--;
11236 else if (player->is_bored)
11238 if (player->num_special_action_bored > 0)
11240 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11242 int special_action =
11243 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11244 int special_graphic =
11245 el_act_dir2img(player->artwork_element, special_action, move_dir);
11247 player->anim_delay_counter =
11248 graphic_info[special_graphic].anim_delay_fixed +
11249 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11250 player->post_delay_counter =
11251 graphic_info[special_graphic].post_delay_fixed +
11252 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11254 player->special_action_bored = special_action;
11257 if (player->anim_delay_counter > 0)
11259 player->action_waiting = player->special_action_bored;
11260 player->anim_delay_counter--;
11262 else if (player->post_delay_counter > 0)
11264 player->post_delay_counter--;
11269 else if (last_waiting) // waiting -> not waiting
11271 player->is_waiting = FALSE;
11272 player->is_bored = FALSE;
11273 player->is_sleeping = FALSE;
11275 player->frame_counter_bored = -1;
11276 player->frame_counter_sleeping = -1;
11278 player->anim_delay_counter = 0;
11279 player->post_delay_counter = 0;
11281 player->dir_waiting = player->MovDir;
11282 player->action_waiting = ACTION_DEFAULT;
11284 player->special_action_bored = ACTION_DEFAULT;
11285 player->special_action_sleeping = ACTION_DEFAULT;
11289 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11291 if ((!player->is_moving && player->was_moving) ||
11292 (player->MovPos == 0 && player->was_moving) ||
11293 (player->is_snapping && !player->was_snapping) ||
11294 (player->is_dropping && !player->was_dropping))
11296 if (!CheckSaveEngineSnapshotToList())
11299 player->was_moving = FALSE;
11300 player->was_snapping = TRUE;
11301 player->was_dropping = TRUE;
11305 if (player->is_moving)
11306 player->was_moving = TRUE;
11308 if (!player->is_snapping)
11309 player->was_snapping = FALSE;
11311 if (!player->is_dropping)
11312 player->was_dropping = FALSE;
11315 static struct MouseActionInfo mouse_action_last = { 0 };
11316 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11317 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11320 CheckSaveEngineSnapshotToList();
11322 mouse_action_last = mouse_action;
11325 static void CheckSingleStepMode(struct PlayerInfo *player)
11327 if (tape.single_step && tape.recording && !tape.pausing)
11329 // as it is called "single step mode", just return to pause mode when the
11330 // player stopped moving after one tile (or never starts moving at all)
11331 // (reverse logic needed here in case single step mode used in team mode)
11332 if (player->is_moving ||
11333 player->is_pushing ||
11334 player->is_dropping_pressed ||
11335 player->effective_mouse_action.button)
11336 game.enter_single_step_mode = FALSE;
11339 CheckSaveEngineSnapshot(player);
11342 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11344 int left = player_action & JOY_LEFT;
11345 int right = player_action & JOY_RIGHT;
11346 int up = player_action & JOY_UP;
11347 int down = player_action & JOY_DOWN;
11348 int button1 = player_action & JOY_BUTTON_1;
11349 int button2 = player_action & JOY_BUTTON_2;
11350 int dx = (left ? -1 : right ? 1 : 0);
11351 int dy = (up ? -1 : down ? 1 : 0);
11353 if (!player->active || tape.pausing)
11359 SnapField(player, dx, dy);
11363 DropElement(player);
11365 MovePlayer(player, dx, dy);
11368 CheckSingleStepMode(player);
11370 SetPlayerWaiting(player, FALSE);
11372 return player_action;
11376 // no actions for this player (no input at player's configured device)
11378 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11379 SnapField(player, 0, 0);
11380 CheckGravityMovementWhenNotMoving(player);
11382 if (player->MovPos == 0)
11383 SetPlayerWaiting(player, TRUE);
11385 if (player->MovPos == 0) // needed for tape.playing
11386 player->is_moving = FALSE;
11388 player->is_dropping = FALSE;
11389 player->is_dropping_pressed = FALSE;
11390 player->drop_pressed_delay = 0;
11392 CheckSingleStepMode(player);
11398 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11401 if (!tape.use_mouse_actions)
11404 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11405 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11406 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11409 static void SetTapeActionFromMouseAction(byte *tape_action,
11410 struct MouseActionInfo *mouse_action)
11412 if (!tape.use_mouse_actions)
11415 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11416 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11417 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11420 static void CheckLevelSolved(void)
11422 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11424 if (game_em.level_solved &&
11425 !game_em.game_over) // game won
11429 game_em.game_over = TRUE;
11431 game.all_players_gone = TRUE;
11434 if (game_em.game_over) // game lost
11435 game.all_players_gone = TRUE;
11437 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11439 if (game_sp.level_solved &&
11440 !game_sp.game_over) // game won
11444 game_sp.game_over = TRUE;
11446 game.all_players_gone = TRUE;
11449 if (game_sp.game_over) // game lost
11450 game.all_players_gone = TRUE;
11452 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11454 if (game_mm.level_solved &&
11455 !game_mm.game_over) // game won
11459 game_mm.game_over = TRUE;
11461 game.all_players_gone = TRUE;
11464 if (game_mm.game_over) // game lost
11465 game.all_players_gone = TRUE;
11469 static void CheckLevelTime(void)
11473 if (TimeFrames >= FRAMES_PER_SECOND)
11478 for (i = 0; i < MAX_PLAYERS; i++)
11480 struct PlayerInfo *player = &stored_player[i];
11482 if (SHIELD_ON(player))
11484 player->shield_normal_time_left--;
11486 if (player->shield_deadly_time_left > 0)
11487 player->shield_deadly_time_left--;
11491 if (!game.LevelSolved && !level.use_step_counter)
11499 if (TimeLeft <= 10 && setup.time_limit)
11500 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11502 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11503 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11505 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11507 if (!TimeLeft && setup.time_limit)
11509 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11510 game_em.lev->killed_out_of_time = TRUE;
11512 for (i = 0; i < MAX_PLAYERS; i++)
11513 KillPlayer(&stored_player[i]);
11516 else if (game.no_time_limit && !game.all_players_gone)
11518 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11521 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11524 if (tape.recording || tape.playing)
11525 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11528 if (tape.recording || tape.playing)
11529 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11531 UpdateAndDisplayGameControlValues();
11534 void AdvanceFrameAndPlayerCounters(int player_nr)
11538 // advance frame counters (global frame counter and time frame counter)
11542 // advance player counters (counters for move delay, move animation etc.)
11543 for (i = 0; i < MAX_PLAYERS; i++)
11545 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11546 int move_delay_value = stored_player[i].move_delay_value;
11547 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11549 if (!advance_player_counters) // not all players may be affected
11552 if (move_frames == 0) // less than one move per game frame
11554 int stepsize = TILEX / move_delay_value;
11555 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11556 int count = (stored_player[i].is_moving ?
11557 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11559 if (count % delay == 0)
11563 stored_player[i].Frame += move_frames;
11565 if (stored_player[i].MovPos != 0)
11566 stored_player[i].StepFrame += move_frames;
11568 if (stored_player[i].move_delay > 0)
11569 stored_player[i].move_delay--;
11571 // due to bugs in previous versions, counter must count up, not down
11572 if (stored_player[i].push_delay != -1)
11573 stored_player[i].push_delay++;
11575 if (stored_player[i].drop_delay > 0)
11576 stored_player[i].drop_delay--;
11578 if (stored_player[i].is_dropping_pressed)
11579 stored_player[i].drop_pressed_delay++;
11583 void StartGameActions(boolean init_network_game, boolean record_tape,
11586 unsigned int new_random_seed = InitRND(random_seed);
11589 TapeStartRecording(new_random_seed);
11591 if (init_network_game)
11593 SendToServer_LevelFile();
11594 SendToServer_StartPlaying();
11602 static void GameActionsExt(void)
11605 static unsigned int game_frame_delay = 0;
11607 unsigned int game_frame_delay_value;
11608 byte *recorded_player_action;
11609 byte summarized_player_action = 0;
11610 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11613 // detect endless loops, caused by custom element programming
11614 if (recursion_loop_detected && recursion_loop_depth == 0)
11616 char *message = getStringCat3("Internal Error! Element ",
11617 EL_NAME(recursion_loop_element),
11618 " caused endless loop! Quit the game?");
11620 Warn("element '%s' caused endless loop in game engine",
11621 EL_NAME(recursion_loop_element));
11623 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11625 recursion_loop_detected = FALSE; // if game should be continued
11632 if (game.restart_level)
11633 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11635 CheckLevelSolved();
11637 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11640 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11643 if (game_status != GAME_MODE_PLAYING) // status might have changed
11646 game_frame_delay_value =
11647 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11649 if (tape.playing && tape.warp_forward && !tape.pausing)
11650 game_frame_delay_value = 0;
11652 SetVideoFrameDelay(game_frame_delay_value);
11654 // (de)activate virtual buttons depending on current game status
11655 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11657 if (game.all_players_gone) // if no players there to be controlled anymore
11658 SetOverlayActive(FALSE);
11659 else if (!tape.playing) // if game continues after tape stopped playing
11660 SetOverlayActive(TRUE);
11665 // ---------- main game synchronization point ----------
11667 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11669 Debug("game:playing:skip", "skip == %d", skip);
11672 // ---------- main game synchronization point ----------
11674 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11678 if (network_playing && !network_player_action_received)
11680 // try to get network player actions in time
11682 // last chance to get network player actions without main loop delay
11683 HandleNetworking();
11685 // game was quit by network peer
11686 if (game_status != GAME_MODE_PLAYING)
11689 // check if network player actions still missing and game still running
11690 if (!network_player_action_received && !checkGameEnded())
11691 return; // failed to get network player actions in time
11693 // do not yet reset "network_player_action_received" (for tape.pausing)
11699 // at this point we know that we really continue executing the game
11701 network_player_action_received = FALSE;
11703 // when playing tape, read previously recorded player input from tape data
11704 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11706 local_player->effective_mouse_action = local_player->mouse_action;
11708 if (recorded_player_action != NULL)
11709 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11710 recorded_player_action);
11712 // TapePlayAction() may return NULL when toggling to "pause before death"
11716 if (tape.set_centered_player)
11718 game.centered_player_nr_next = tape.centered_player_nr_next;
11719 game.set_centered_player = TRUE;
11722 for (i = 0; i < MAX_PLAYERS; i++)
11724 summarized_player_action |= stored_player[i].action;
11726 if (!network_playing && (game.team_mode || tape.playing))
11727 stored_player[i].effective_action = stored_player[i].action;
11730 if (network_playing && !checkGameEnded())
11731 SendToServer_MovePlayer(summarized_player_action);
11733 // summarize all actions at local players mapped input device position
11734 // (this allows using different input devices in single player mode)
11735 if (!network.enabled && !game.team_mode)
11736 stored_player[map_player_action[local_player->index_nr]].effective_action =
11737 summarized_player_action;
11739 // summarize all actions at centered player in local team mode
11740 if (tape.recording &&
11741 setup.team_mode && !network.enabled &&
11742 setup.input_on_focus &&
11743 game.centered_player_nr != -1)
11745 for (i = 0; i < MAX_PLAYERS; i++)
11746 stored_player[map_player_action[i]].effective_action =
11747 (i == game.centered_player_nr ? summarized_player_action : 0);
11750 if (recorded_player_action != NULL)
11751 for (i = 0; i < MAX_PLAYERS; i++)
11752 stored_player[i].effective_action = recorded_player_action[i];
11754 for (i = 0; i < MAX_PLAYERS; i++)
11756 tape_action[i] = stored_player[i].effective_action;
11758 /* (this may happen in the RND game engine if a player was not present on
11759 the playfield on level start, but appeared later from a custom element */
11760 if (setup.team_mode &&
11763 !tape.player_participates[i])
11764 tape.player_participates[i] = TRUE;
11767 SetTapeActionFromMouseAction(tape_action,
11768 &local_player->effective_mouse_action);
11770 // only record actions from input devices, but not programmed actions
11771 if (tape.recording)
11772 TapeRecordAction(tape_action);
11774 // remember if game was played (especially after tape stopped playing)
11775 if (!tape.playing && summarized_player_action)
11776 game.GamePlayed = TRUE;
11778 #if USE_NEW_PLAYER_ASSIGNMENTS
11779 // !!! also map player actions in single player mode !!!
11780 // if (game.team_mode)
11783 byte mapped_action[MAX_PLAYERS];
11785 #if DEBUG_PLAYER_ACTIONS
11786 for (i = 0; i < MAX_PLAYERS; i++)
11787 DebugContinued("", "%d, ", stored_player[i].effective_action);
11790 for (i = 0; i < MAX_PLAYERS; i++)
11791 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11793 for (i = 0; i < MAX_PLAYERS; i++)
11794 stored_player[i].effective_action = mapped_action[i];
11796 #if DEBUG_PLAYER_ACTIONS
11797 DebugContinued("", "=> ");
11798 for (i = 0; i < MAX_PLAYERS; i++)
11799 DebugContinued("", "%d, ", stored_player[i].effective_action);
11800 DebugContinued("game:playing:player", "\n");
11803 #if DEBUG_PLAYER_ACTIONS
11806 for (i = 0; i < MAX_PLAYERS; i++)
11807 DebugContinued("", "%d, ", stored_player[i].effective_action);
11808 DebugContinued("game:playing:player", "\n");
11813 for (i = 0; i < MAX_PLAYERS; i++)
11815 // allow engine snapshot in case of changed movement attempt
11816 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11817 (stored_player[i].effective_action & KEY_MOTION))
11818 game.snapshot.changed_action = TRUE;
11820 // allow engine snapshot in case of snapping/dropping attempt
11821 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11822 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11823 game.snapshot.changed_action = TRUE;
11825 game.snapshot.last_action[i] = stored_player[i].effective_action;
11828 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11830 GameActions_EM_Main();
11832 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11834 GameActions_SP_Main();
11836 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11838 GameActions_MM_Main();
11842 GameActions_RND_Main();
11845 BlitScreenToBitmap(backbuffer);
11847 CheckLevelSolved();
11850 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11852 if (global.show_frames_per_second)
11854 static unsigned int fps_counter = 0;
11855 static int fps_frames = 0;
11856 unsigned int fps_delay_ms = Counter() - fps_counter;
11860 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11862 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11865 fps_counter = Counter();
11867 // always draw FPS to screen after FPS value was updated
11868 redraw_mask |= REDRAW_FPS;
11871 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11872 if (GetDrawDeactivationMask() == REDRAW_NONE)
11873 redraw_mask |= REDRAW_FPS;
11877 static void GameActions_CheckSaveEngineSnapshot(void)
11879 if (!game.snapshot.save_snapshot)
11882 // clear flag for saving snapshot _before_ saving snapshot
11883 game.snapshot.save_snapshot = FALSE;
11885 SaveEngineSnapshotToList();
11888 void GameActions(void)
11892 GameActions_CheckSaveEngineSnapshot();
11895 void GameActions_EM_Main(void)
11897 byte effective_action[MAX_PLAYERS];
11898 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11901 for (i = 0; i < MAX_PLAYERS; i++)
11902 effective_action[i] = stored_player[i].effective_action;
11904 GameActions_EM(effective_action, warp_mode);
11907 void GameActions_SP_Main(void)
11909 byte effective_action[MAX_PLAYERS];
11910 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11913 for (i = 0; i < MAX_PLAYERS; i++)
11914 effective_action[i] = stored_player[i].effective_action;
11916 GameActions_SP(effective_action, warp_mode);
11918 for (i = 0; i < MAX_PLAYERS; i++)
11920 if (stored_player[i].force_dropping)
11921 stored_player[i].action |= KEY_BUTTON_DROP;
11923 stored_player[i].force_dropping = FALSE;
11927 void GameActions_MM_Main(void)
11929 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11931 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11934 void GameActions_RND_Main(void)
11939 void GameActions_RND(void)
11941 static struct MouseActionInfo mouse_action_last = { 0 };
11942 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11943 int magic_wall_x = 0, magic_wall_y = 0;
11944 int i, x, y, element, graphic, last_gfx_frame;
11946 InitPlayfieldScanModeVars();
11948 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11950 SCAN_PLAYFIELD(x, y)
11952 ChangeCount[x][y] = 0;
11953 ChangeEvent[x][y] = -1;
11957 if (game.set_centered_player)
11959 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11961 // switching to "all players" only possible if all players fit to screen
11962 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11964 game.centered_player_nr_next = game.centered_player_nr;
11965 game.set_centered_player = FALSE;
11968 // do not switch focus to non-existing (or non-active) player
11969 if (game.centered_player_nr_next >= 0 &&
11970 !stored_player[game.centered_player_nr_next].active)
11972 game.centered_player_nr_next = game.centered_player_nr;
11973 game.set_centered_player = FALSE;
11977 if (game.set_centered_player &&
11978 ScreenMovPos == 0) // screen currently aligned at tile position
11982 if (game.centered_player_nr_next == -1)
11984 setScreenCenteredToAllPlayers(&sx, &sy);
11988 sx = stored_player[game.centered_player_nr_next].jx;
11989 sy = stored_player[game.centered_player_nr_next].jy;
11992 game.centered_player_nr = game.centered_player_nr_next;
11993 game.set_centered_player = FALSE;
11995 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11996 DrawGameDoorValues();
11999 // check single step mode (set flag and clear again if any player is active)
12000 game.enter_single_step_mode =
12001 (tape.single_step && tape.recording && !tape.pausing);
12003 for (i = 0; i < MAX_PLAYERS; i++)
12005 int actual_player_action = stored_player[i].effective_action;
12008 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12009 - rnd_equinox_tetrachloride 048
12010 - rnd_equinox_tetrachloride_ii 096
12011 - rnd_emanuel_schmieg 002
12012 - doctor_sloan_ww 001, 020
12014 if (stored_player[i].MovPos == 0)
12015 CheckGravityMovement(&stored_player[i]);
12018 // overwrite programmed action with tape action
12019 if (stored_player[i].programmed_action)
12020 actual_player_action = stored_player[i].programmed_action;
12022 PlayerActions(&stored_player[i], actual_player_action);
12024 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12027 // single step pause mode may already have been toggled by "ScrollPlayer()"
12028 if (game.enter_single_step_mode && !tape.pausing)
12029 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12031 ScrollScreen(NULL, SCROLL_GO_ON);
12033 /* for backwards compatibility, the following code emulates a fixed bug that
12034 occured when pushing elements (causing elements that just made their last
12035 pushing step to already (if possible) make their first falling step in the
12036 same game frame, which is bad); this code is also needed to use the famous
12037 "spring push bug" which is used in older levels and might be wanted to be
12038 used also in newer levels, but in this case the buggy pushing code is only
12039 affecting the "spring" element and no other elements */
12041 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12043 for (i = 0; i < MAX_PLAYERS; i++)
12045 struct PlayerInfo *player = &stored_player[i];
12046 int x = player->jx;
12047 int y = player->jy;
12049 if (player->active && player->is_pushing && player->is_moving &&
12051 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12052 Tile[x][y] == EL_SPRING))
12054 ContinueMoving(x, y);
12056 // continue moving after pushing (this is actually a bug)
12057 if (!IS_MOVING(x, y))
12058 Stop[x][y] = FALSE;
12063 SCAN_PLAYFIELD(x, y)
12065 Last[x][y] = Tile[x][y];
12067 ChangeCount[x][y] = 0;
12068 ChangeEvent[x][y] = -1;
12070 // this must be handled before main playfield loop
12071 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12074 if (MovDelay[x][y] <= 0)
12078 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12081 if (MovDelay[x][y] <= 0)
12083 int element = Store[x][y];
12084 int move_direction = MovDir[x][y];
12085 int player_index_bit = Store2[x][y];
12091 TEST_DrawLevelField(x, y);
12093 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12098 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12100 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12102 Debug("game:playing:GameActions_RND", "This should never happen!");
12104 ChangePage[x][y] = -1;
12108 Stop[x][y] = FALSE;
12109 if (WasJustMoving[x][y] > 0)
12110 WasJustMoving[x][y]--;
12111 if (WasJustFalling[x][y] > 0)
12112 WasJustFalling[x][y]--;
12113 if (CheckCollision[x][y] > 0)
12114 CheckCollision[x][y]--;
12115 if (CheckImpact[x][y] > 0)
12116 CheckImpact[x][y]--;
12120 /* reset finished pushing action (not done in ContinueMoving() to allow
12121 continuous pushing animation for elements with zero push delay) */
12122 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12124 ResetGfxAnimation(x, y);
12125 TEST_DrawLevelField(x, y);
12129 if (IS_BLOCKED(x, y))
12133 Blocked2Moving(x, y, &oldx, &oldy);
12134 if (!IS_MOVING(oldx, oldy))
12136 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12137 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12138 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12139 Debug("game:playing:GameActions_RND", "This should never happen!");
12145 if (mouse_action.button)
12147 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12148 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12150 x = mouse_action.lx;
12151 y = mouse_action.ly;
12152 element = Tile[x][y];
12156 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12157 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12161 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12162 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12166 SCAN_PLAYFIELD(x, y)
12168 element = Tile[x][y];
12169 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12170 last_gfx_frame = GfxFrame[x][y];
12172 ResetGfxFrame(x, y);
12174 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12175 DrawLevelGraphicAnimation(x, y, graphic);
12177 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12178 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12179 ResetRandomAnimationValue(x, y);
12181 SetRandomAnimationValue(x, y);
12183 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12185 if (IS_INACTIVE(element))
12187 if (IS_ANIMATED(graphic))
12188 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12193 // this may take place after moving, so 'element' may have changed
12194 if (IS_CHANGING(x, y) &&
12195 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12197 int page = element_info[element].event_page_nr[CE_DELAY];
12199 HandleElementChange(x, y, page);
12201 element = Tile[x][y];
12202 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12205 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12209 element = Tile[x][y];
12210 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12212 if (IS_ANIMATED(graphic) &&
12213 !IS_MOVING(x, y) &&
12215 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12217 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12218 TEST_DrawTwinkleOnField(x, y);
12220 else if (element == EL_ACID)
12223 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12225 else if ((element == EL_EXIT_OPEN ||
12226 element == EL_EM_EXIT_OPEN ||
12227 element == EL_SP_EXIT_OPEN ||
12228 element == EL_STEEL_EXIT_OPEN ||
12229 element == EL_EM_STEEL_EXIT_OPEN ||
12230 element == EL_SP_TERMINAL ||
12231 element == EL_SP_TERMINAL_ACTIVE ||
12232 element == EL_EXTRA_TIME ||
12233 element == EL_SHIELD_NORMAL ||
12234 element == EL_SHIELD_DEADLY) &&
12235 IS_ANIMATED(graphic))
12236 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12237 else if (IS_MOVING(x, y))
12238 ContinueMoving(x, y);
12239 else if (IS_ACTIVE_BOMB(element))
12240 CheckDynamite(x, y);
12241 else if (element == EL_AMOEBA_GROWING)
12242 AmoebaGrowing(x, y);
12243 else if (element == EL_AMOEBA_SHRINKING)
12244 AmoebaShrinking(x, y);
12246 #if !USE_NEW_AMOEBA_CODE
12247 else if (IS_AMOEBALIVE(element))
12248 AmoebaReproduce(x, y);
12251 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12253 else if (element == EL_EXIT_CLOSED)
12255 else if (element == EL_EM_EXIT_CLOSED)
12257 else if (element == EL_STEEL_EXIT_CLOSED)
12258 CheckExitSteel(x, y);
12259 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12260 CheckExitSteelEM(x, y);
12261 else if (element == EL_SP_EXIT_CLOSED)
12263 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12264 element == EL_EXPANDABLE_STEELWALL_GROWING)
12265 MauerWaechst(x, y);
12266 else if (element == EL_EXPANDABLE_WALL ||
12267 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12268 element == EL_EXPANDABLE_WALL_VERTICAL ||
12269 element == EL_EXPANDABLE_WALL_ANY ||
12270 element == EL_BD_EXPANDABLE_WALL)
12271 MauerAbleger(x, y);
12272 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12273 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12274 element == EL_EXPANDABLE_STEELWALL_ANY)
12275 MauerAblegerStahl(x, y);
12276 else if (element == EL_FLAMES)
12277 CheckForDragon(x, y);
12278 else if (element == EL_EXPLOSION)
12279 ; // drawing of correct explosion animation is handled separately
12280 else if (element == EL_ELEMENT_SNAPPING ||
12281 element == EL_DIAGONAL_SHRINKING ||
12282 element == EL_DIAGONAL_GROWING)
12284 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12286 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12288 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12289 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12291 if (IS_BELT_ACTIVE(element))
12292 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12294 if (game.magic_wall_active)
12296 int jx = local_player->jx, jy = local_player->jy;
12298 // play the element sound at the position nearest to the player
12299 if ((element == EL_MAGIC_WALL_FULL ||
12300 element == EL_MAGIC_WALL_ACTIVE ||
12301 element == EL_MAGIC_WALL_EMPTYING ||
12302 element == EL_BD_MAGIC_WALL_FULL ||
12303 element == EL_BD_MAGIC_WALL_ACTIVE ||
12304 element == EL_BD_MAGIC_WALL_EMPTYING ||
12305 element == EL_DC_MAGIC_WALL_FULL ||
12306 element == EL_DC_MAGIC_WALL_ACTIVE ||
12307 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12308 ABS(x - jx) + ABS(y - jy) <
12309 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12317 #if USE_NEW_AMOEBA_CODE
12318 // new experimental amoeba growth stuff
12319 if (!(FrameCounter % 8))
12321 static unsigned int random = 1684108901;
12323 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12325 x = RND(lev_fieldx);
12326 y = RND(lev_fieldy);
12327 element = Tile[x][y];
12329 if (!IS_PLAYER(x,y) &&
12330 (element == EL_EMPTY ||
12331 CAN_GROW_INTO(element) ||
12332 element == EL_QUICKSAND_EMPTY ||
12333 element == EL_QUICKSAND_FAST_EMPTY ||
12334 element == EL_ACID_SPLASH_LEFT ||
12335 element == EL_ACID_SPLASH_RIGHT))
12337 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12338 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12339 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12340 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12341 Tile[x][y] = EL_AMOEBA_DROP;
12344 random = random * 129 + 1;
12349 game.explosions_delayed = FALSE;
12351 SCAN_PLAYFIELD(x, y)
12353 element = Tile[x][y];
12355 if (ExplodeField[x][y])
12356 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12357 else if (element == EL_EXPLOSION)
12358 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12360 ExplodeField[x][y] = EX_TYPE_NONE;
12363 game.explosions_delayed = TRUE;
12365 if (game.magic_wall_active)
12367 if (!(game.magic_wall_time_left % 4))
12369 int element = Tile[magic_wall_x][magic_wall_y];
12371 if (element == EL_BD_MAGIC_WALL_FULL ||
12372 element == EL_BD_MAGIC_WALL_ACTIVE ||
12373 element == EL_BD_MAGIC_WALL_EMPTYING)
12374 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12375 else if (element == EL_DC_MAGIC_WALL_FULL ||
12376 element == EL_DC_MAGIC_WALL_ACTIVE ||
12377 element == EL_DC_MAGIC_WALL_EMPTYING)
12378 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12380 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12383 if (game.magic_wall_time_left > 0)
12385 game.magic_wall_time_left--;
12387 if (!game.magic_wall_time_left)
12389 SCAN_PLAYFIELD(x, y)
12391 element = Tile[x][y];
12393 if (element == EL_MAGIC_WALL_ACTIVE ||
12394 element == EL_MAGIC_WALL_FULL)
12396 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12397 TEST_DrawLevelField(x, y);
12399 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12400 element == EL_BD_MAGIC_WALL_FULL)
12402 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12403 TEST_DrawLevelField(x, y);
12405 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12406 element == EL_DC_MAGIC_WALL_FULL)
12408 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12409 TEST_DrawLevelField(x, y);
12413 game.magic_wall_active = FALSE;
12418 if (game.light_time_left > 0)
12420 game.light_time_left--;
12422 if (game.light_time_left == 0)
12423 RedrawAllLightSwitchesAndInvisibleElements();
12426 if (game.timegate_time_left > 0)
12428 game.timegate_time_left--;
12430 if (game.timegate_time_left == 0)
12431 CloseAllOpenTimegates();
12434 if (game.lenses_time_left > 0)
12436 game.lenses_time_left--;
12438 if (game.lenses_time_left == 0)
12439 RedrawAllInvisibleElementsForLenses();
12442 if (game.magnify_time_left > 0)
12444 game.magnify_time_left--;
12446 if (game.magnify_time_left == 0)
12447 RedrawAllInvisibleElementsForMagnifier();
12450 for (i = 0; i < MAX_PLAYERS; i++)
12452 struct PlayerInfo *player = &stored_player[i];
12454 if (SHIELD_ON(player))
12456 if (player->shield_deadly_time_left)
12457 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12458 else if (player->shield_normal_time_left)
12459 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12463 #if USE_DELAYED_GFX_REDRAW
12464 SCAN_PLAYFIELD(x, y)
12466 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12468 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12469 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12471 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12472 DrawLevelField(x, y);
12474 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12475 DrawLevelFieldCrumbled(x, y);
12477 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12478 DrawLevelFieldCrumbledNeighbours(x, y);
12480 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12481 DrawTwinkleOnField(x, y);
12484 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12489 PlayAllPlayersSound();
12491 for (i = 0; i < MAX_PLAYERS; i++)
12493 struct PlayerInfo *player = &stored_player[i];
12495 if (player->show_envelope != 0 && (!player->active ||
12496 player->MovPos == 0))
12498 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12500 player->show_envelope = 0;
12504 // use random number generator in every frame to make it less predictable
12505 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12508 mouse_action_last = mouse_action;
12511 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12513 int min_x = x, min_y = y, max_x = x, max_y = y;
12514 int scr_fieldx = getScreenFieldSizeX();
12515 int scr_fieldy = getScreenFieldSizeY();
12518 for (i = 0; i < MAX_PLAYERS; i++)
12520 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12522 if (!stored_player[i].active || &stored_player[i] == player)
12525 min_x = MIN(min_x, jx);
12526 min_y = MIN(min_y, jy);
12527 max_x = MAX(max_x, jx);
12528 max_y = MAX(max_y, jy);
12531 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12534 static boolean AllPlayersInVisibleScreen(void)
12538 for (i = 0; i < MAX_PLAYERS; i++)
12540 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12542 if (!stored_player[i].active)
12545 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12552 void ScrollLevel(int dx, int dy)
12554 int scroll_offset = 2 * TILEX_VAR;
12557 BlitBitmap(drawto_field, drawto_field,
12558 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12559 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12560 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12561 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12562 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12563 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12567 x = (dx == 1 ? BX1 : BX2);
12568 for (y = BY1; y <= BY2; y++)
12569 DrawScreenField(x, y);
12574 y = (dy == 1 ? BY1 : BY2);
12575 for (x = BX1; x <= BX2; x++)
12576 DrawScreenField(x, y);
12579 redraw_mask |= REDRAW_FIELD;
12582 static boolean canFallDown(struct PlayerInfo *player)
12584 int jx = player->jx, jy = player->jy;
12586 return (IN_LEV_FIELD(jx, jy + 1) &&
12587 (IS_FREE(jx, jy + 1) ||
12588 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12589 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12590 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12593 static boolean canPassField(int x, int y, int move_dir)
12595 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12596 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12597 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12598 int nextx = x + dx;
12599 int nexty = y + dy;
12600 int element = Tile[x][y];
12602 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12603 !CAN_MOVE(element) &&
12604 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12605 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12606 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12609 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12611 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12612 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12613 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12617 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12618 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12619 (IS_DIGGABLE(Tile[newx][newy]) ||
12620 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12621 canPassField(newx, newy, move_dir)));
12624 static void CheckGravityMovement(struct PlayerInfo *player)
12626 if (player->gravity && !player->programmed_action)
12628 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12629 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12630 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12631 int jx = player->jx, jy = player->jy;
12632 boolean player_is_moving_to_valid_field =
12633 (!player_is_snapping &&
12634 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12635 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12636 boolean player_can_fall_down = canFallDown(player);
12638 if (player_can_fall_down &&
12639 !player_is_moving_to_valid_field)
12640 player->programmed_action = MV_DOWN;
12644 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12646 return CheckGravityMovement(player);
12648 if (player->gravity && !player->programmed_action)
12650 int jx = player->jx, jy = player->jy;
12651 boolean field_under_player_is_free =
12652 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12653 boolean player_is_standing_on_valid_field =
12654 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12655 (IS_WALKABLE(Tile[jx][jy]) &&
12656 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12658 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12659 player->programmed_action = MV_DOWN;
12664 MovePlayerOneStep()
12665 -----------------------------------------------------------------------------
12666 dx, dy: direction (non-diagonal) to try to move the player to
12667 real_dx, real_dy: direction as read from input device (can be diagonal)
12670 boolean MovePlayerOneStep(struct PlayerInfo *player,
12671 int dx, int dy, int real_dx, int real_dy)
12673 int jx = player->jx, jy = player->jy;
12674 int new_jx = jx + dx, new_jy = jy + dy;
12676 boolean player_can_move = !player->cannot_move;
12678 if (!player->active || (!dx && !dy))
12679 return MP_NO_ACTION;
12681 player->MovDir = (dx < 0 ? MV_LEFT :
12682 dx > 0 ? MV_RIGHT :
12684 dy > 0 ? MV_DOWN : MV_NONE);
12686 if (!IN_LEV_FIELD(new_jx, new_jy))
12687 return MP_NO_ACTION;
12689 if (!player_can_move)
12691 if (player->MovPos == 0)
12693 player->is_moving = FALSE;
12694 player->is_digging = FALSE;
12695 player->is_collecting = FALSE;
12696 player->is_snapping = FALSE;
12697 player->is_pushing = FALSE;
12701 if (!network.enabled && game.centered_player_nr == -1 &&
12702 !AllPlayersInSight(player, new_jx, new_jy))
12703 return MP_NO_ACTION;
12705 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12706 if (can_move != MP_MOVING)
12709 // check if DigField() has caused relocation of the player
12710 if (player->jx != jx || player->jy != jy)
12711 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12713 StorePlayer[jx][jy] = 0;
12714 player->last_jx = jx;
12715 player->last_jy = jy;
12716 player->jx = new_jx;
12717 player->jy = new_jy;
12718 StorePlayer[new_jx][new_jy] = player->element_nr;
12720 if (player->move_delay_value_next != -1)
12722 player->move_delay_value = player->move_delay_value_next;
12723 player->move_delay_value_next = -1;
12727 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12729 player->step_counter++;
12731 PlayerVisit[jx][jy] = FrameCounter;
12733 player->is_moving = TRUE;
12736 // should better be called in MovePlayer(), but this breaks some tapes
12737 ScrollPlayer(player, SCROLL_INIT);
12743 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12745 int jx = player->jx, jy = player->jy;
12746 int old_jx = jx, old_jy = jy;
12747 int moved = MP_NO_ACTION;
12749 if (!player->active)
12754 if (player->MovPos == 0)
12756 player->is_moving = FALSE;
12757 player->is_digging = FALSE;
12758 player->is_collecting = FALSE;
12759 player->is_snapping = FALSE;
12760 player->is_pushing = FALSE;
12766 if (player->move_delay > 0)
12769 player->move_delay = -1; // set to "uninitialized" value
12771 // store if player is automatically moved to next field
12772 player->is_auto_moving = (player->programmed_action != MV_NONE);
12774 // remove the last programmed player action
12775 player->programmed_action = 0;
12777 if (player->MovPos)
12779 // should only happen if pre-1.2 tape recordings are played
12780 // this is only for backward compatibility
12782 int original_move_delay_value = player->move_delay_value;
12785 Debug("game:playing:MovePlayer",
12786 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12790 // scroll remaining steps with finest movement resolution
12791 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12793 while (player->MovPos)
12795 ScrollPlayer(player, SCROLL_GO_ON);
12796 ScrollScreen(NULL, SCROLL_GO_ON);
12798 AdvanceFrameAndPlayerCounters(player->index_nr);
12801 BackToFront_WithFrameDelay(0);
12804 player->move_delay_value = original_move_delay_value;
12807 player->is_active = FALSE;
12809 if (player->last_move_dir & MV_HORIZONTAL)
12811 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12812 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12816 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12817 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12820 if (!moved && !player->is_active)
12822 player->is_moving = FALSE;
12823 player->is_digging = FALSE;
12824 player->is_collecting = FALSE;
12825 player->is_snapping = FALSE;
12826 player->is_pushing = FALSE;
12832 if (moved & MP_MOVING && !ScreenMovPos &&
12833 (player->index_nr == game.centered_player_nr ||
12834 game.centered_player_nr == -1))
12836 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12838 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12840 // actual player has left the screen -- scroll in that direction
12841 if (jx != old_jx) // player has moved horizontally
12842 scroll_x += (jx - old_jx);
12843 else // player has moved vertically
12844 scroll_y += (jy - old_jy);
12848 int offset_raw = game.scroll_delay_value;
12850 if (jx != old_jx) // player has moved horizontally
12852 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12853 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12854 int new_scroll_x = jx - MIDPOSX + offset_x;
12856 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12857 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12858 scroll_x = new_scroll_x;
12860 // don't scroll over playfield boundaries
12861 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12863 // don't scroll more than one field at a time
12864 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12866 // don't scroll against the player's moving direction
12867 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12868 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12869 scroll_x = old_scroll_x;
12871 else // player has moved vertically
12873 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12874 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12875 int new_scroll_y = jy - MIDPOSY + offset_y;
12877 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12878 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12879 scroll_y = new_scroll_y;
12881 // don't scroll over playfield boundaries
12882 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12884 // don't scroll more than one field at a time
12885 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12887 // don't scroll against the player's moving direction
12888 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12889 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12890 scroll_y = old_scroll_y;
12894 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12896 if (!network.enabled && game.centered_player_nr == -1 &&
12897 !AllPlayersInVisibleScreen())
12899 scroll_x = old_scroll_x;
12900 scroll_y = old_scroll_y;
12904 ScrollScreen(player, SCROLL_INIT);
12905 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12910 player->StepFrame = 0;
12912 if (moved & MP_MOVING)
12914 if (old_jx != jx && old_jy == jy)
12915 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12916 else if (old_jx == jx && old_jy != jy)
12917 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12919 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12921 player->last_move_dir = player->MovDir;
12922 player->is_moving = TRUE;
12923 player->is_snapping = FALSE;
12924 player->is_switching = FALSE;
12925 player->is_dropping = FALSE;
12926 player->is_dropping_pressed = FALSE;
12927 player->drop_pressed_delay = 0;
12930 // should better be called here than above, but this breaks some tapes
12931 ScrollPlayer(player, SCROLL_INIT);
12936 CheckGravityMovementWhenNotMoving(player);
12938 player->is_moving = FALSE;
12940 /* at this point, the player is allowed to move, but cannot move right now
12941 (e.g. because of something blocking the way) -- ensure that the player
12942 is also allowed to move in the next frame (in old versions before 3.1.1,
12943 the player was forced to wait again for eight frames before next try) */
12945 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12946 player->move_delay = 0; // allow direct movement in the next frame
12949 if (player->move_delay == -1) // not yet initialized by DigField()
12950 player->move_delay = player->move_delay_value;
12952 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12954 TestIfPlayerTouchesBadThing(jx, jy);
12955 TestIfPlayerTouchesCustomElement(jx, jy);
12958 if (!player->active)
12959 RemovePlayer(player);
12964 void ScrollPlayer(struct PlayerInfo *player, int mode)
12966 int jx = player->jx, jy = player->jy;
12967 int last_jx = player->last_jx, last_jy = player->last_jy;
12968 int move_stepsize = TILEX / player->move_delay_value;
12970 if (!player->active)
12973 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12976 if (mode == SCROLL_INIT)
12978 player->actual_frame_counter = FrameCounter;
12979 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12981 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12982 Tile[last_jx][last_jy] == EL_EMPTY)
12984 int last_field_block_delay = 0; // start with no blocking at all
12985 int block_delay_adjustment = player->block_delay_adjustment;
12987 // if player blocks last field, add delay for exactly one move
12988 if (player->block_last_field)
12990 last_field_block_delay += player->move_delay_value;
12992 // when blocking enabled, prevent moving up despite gravity
12993 if (player->gravity && player->MovDir == MV_UP)
12994 block_delay_adjustment = -1;
12997 // add block delay adjustment (also possible when not blocking)
12998 last_field_block_delay += block_delay_adjustment;
13000 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13001 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13004 if (player->MovPos != 0) // player has not yet reached destination
13007 else if (!FrameReached(&player->actual_frame_counter, 1))
13010 if (player->MovPos != 0)
13012 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13013 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13015 // before DrawPlayer() to draw correct player graphic for this case
13016 if (player->MovPos == 0)
13017 CheckGravityMovement(player);
13020 if (player->MovPos == 0) // player reached destination field
13022 if (player->move_delay_reset_counter > 0)
13024 player->move_delay_reset_counter--;
13026 if (player->move_delay_reset_counter == 0)
13028 // continue with normal speed after quickly moving through gate
13029 HALVE_PLAYER_SPEED(player);
13031 // be able to make the next move without delay
13032 player->move_delay = 0;
13036 player->last_jx = jx;
13037 player->last_jy = jy;
13039 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13040 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13041 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13042 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13043 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13044 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13045 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13046 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13048 ExitPlayer(player);
13050 if (game.players_still_needed == 0 &&
13051 (game.friends_still_needed == 0 ||
13052 IS_SP_ELEMENT(Tile[jx][jy])))
13056 // this breaks one level: "machine", level 000
13058 int move_direction = player->MovDir;
13059 int enter_side = MV_DIR_OPPOSITE(move_direction);
13060 int leave_side = move_direction;
13061 int old_jx = last_jx;
13062 int old_jy = last_jy;
13063 int old_element = Tile[old_jx][old_jy];
13064 int new_element = Tile[jx][jy];
13066 if (IS_CUSTOM_ELEMENT(old_element))
13067 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13069 player->index_bit, leave_side);
13071 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13072 CE_PLAYER_LEAVES_X,
13073 player->index_bit, leave_side);
13075 if (IS_CUSTOM_ELEMENT(new_element))
13076 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13077 player->index_bit, enter_side);
13079 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13080 CE_PLAYER_ENTERS_X,
13081 player->index_bit, enter_side);
13083 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13084 CE_MOVE_OF_X, move_direction);
13087 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13089 TestIfPlayerTouchesBadThing(jx, jy);
13090 TestIfPlayerTouchesCustomElement(jx, jy);
13092 /* needed because pushed element has not yet reached its destination,
13093 so it would trigger a change event at its previous field location */
13094 if (!player->is_pushing)
13095 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13097 if (level.finish_dig_collect &&
13098 (player->is_digging || player->is_collecting))
13100 int last_element = player->last_removed_element;
13101 int move_direction = player->MovDir;
13102 int enter_side = MV_DIR_OPPOSITE(move_direction);
13103 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13104 CE_PLAYER_COLLECTS_X);
13106 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13107 player->index_bit, enter_side);
13109 player->last_removed_element = EL_UNDEFINED;
13112 if (!player->active)
13113 RemovePlayer(player);
13116 if (!game.LevelSolved && level.use_step_counter)
13126 if (TimeLeft <= 10 && setup.time_limit)
13127 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13129 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13131 DisplayGameControlValues();
13133 if (!TimeLeft && setup.time_limit)
13134 for (i = 0; i < MAX_PLAYERS; i++)
13135 KillPlayer(&stored_player[i]);
13137 else if (game.no_time_limit && !game.all_players_gone)
13139 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13141 DisplayGameControlValues();
13145 if (tape.single_step && tape.recording && !tape.pausing &&
13146 !player->programmed_action)
13147 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13149 if (!player->programmed_action)
13150 CheckSaveEngineSnapshot(player);
13154 void ScrollScreen(struct PlayerInfo *player, int mode)
13156 static unsigned int screen_frame_counter = 0;
13158 if (mode == SCROLL_INIT)
13160 // set scrolling step size according to actual player's moving speed
13161 ScrollStepSize = TILEX / player->move_delay_value;
13163 screen_frame_counter = FrameCounter;
13164 ScreenMovDir = player->MovDir;
13165 ScreenMovPos = player->MovPos;
13166 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13169 else if (!FrameReached(&screen_frame_counter, 1))
13174 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13175 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13176 redraw_mask |= REDRAW_FIELD;
13179 ScreenMovDir = MV_NONE;
13182 void TestIfPlayerTouchesCustomElement(int x, int y)
13184 static int xy[4][2] =
13191 static int trigger_sides[4][2] =
13193 // center side border side
13194 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13195 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13196 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13197 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13199 static int touch_dir[4] =
13201 MV_LEFT | MV_RIGHT,
13206 int center_element = Tile[x][y]; // should always be non-moving!
13209 for (i = 0; i < NUM_DIRECTIONS; i++)
13211 int xx = x + xy[i][0];
13212 int yy = y + xy[i][1];
13213 int center_side = trigger_sides[i][0];
13214 int border_side = trigger_sides[i][1];
13215 int border_element;
13217 if (!IN_LEV_FIELD(xx, yy))
13220 if (IS_PLAYER(x, y)) // player found at center element
13222 struct PlayerInfo *player = PLAYERINFO(x, y);
13224 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13225 border_element = Tile[xx][yy]; // may be moving!
13226 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13227 border_element = Tile[xx][yy];
13228 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13229 border_element = MovingOrBlocked2Element(xx, yy);
13231 continue; // center and border element do not touch
13233 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13234 player->index_bit, border_side);
13235 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13236 CE_PLAYER_TOUCHES_X,
13237 player->index_bit, border_side);
13240 /* use player element that is initially defined in the level playfield,
13241 not the player element that corresponds to the runtime player number
13242 (example: a level that contains EL_PLAYER_3 as the only player would
13243 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13244 int player_element = PLAYERINFO(x, y)->initial_element;
13246 CheckElementChangeBySide(xx, yy, border_element, player_element,
13247 CE_TOUCHING_X, border_side);
13250 else if (IS_PLAYER(xx, yy)) // player found at border element
13252 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13254 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13256 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13257 continue; // center and border element do not touch
13260 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13261 player->index_bit, center_side);
13262 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13263 CE_PLAYER_TOUCHES_X,
13264 player->index_bit, center_side);
13267 /* use player element that is initially defined in the level playfield,
13268 not the player element that corresponds to the runtime player number
13269 (example: a level that contains EL_PLAYER_3 as the only player would
13270 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13271 int player_element = PLAYERINFO(xx, yy)->initial_element;
13273 CheckElementChangeBySide(x, y, center_element, player_element,
13274 CE_TOUCHING_X, center_side);
13282 void TestIfElementTouchesCustomElement(int x, int y)
13284 static int xy[4][2] =
13291 static int trigger_sides[4][2] =
13293 // center side border side
13294 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13295 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13296 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13297 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13299 static int touch_dir[4] =
13301 MV_LEFT | MV_RIGHT,
13306 boolean change_center_element = FALSE;
13307 int center_element = Tile[x][y]; // should always be non-moving!
13308 int border_element_old[NUM_DIRECTIONS];
13311 for (i = 0; i < NUM_DIRECTIONS; i++)
13313 int xx = x + xy[i][0];
13314 int yy = y + xy[i][1];
13315 int border_element;
13317 border_element_old[i] = -1;
13319 if (!IN_LEV_FIELD(xx, yy))
13322 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13323 border_element = Tile[xx][yy]; // may be moving!
13324 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13325 border_element = Tile[xx][yy];
13326 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13327 border_element = MovingOrBlocked2Element(xx, yy);
13329 continue; // center and border element do not touch
13331 border_element_old[i] = border_element;
13334 for (i = 0; i < NUM_DIRECTIONS; i++)
13336 int xx = x + xy[i][0];
13337 int yy = y + xy[i][1];
13338 int center_side = trigger_sides[i][0];
13339 int border_element = border_element_old[i];
13341 if (border_element == -1)
13344 // check for change of border element
13345 CheckElementChangeBySide(xx, yy, border_element, center_element,
13346 CE_TOUCHING_X, center_side);
13348 // (center element cannot be player, so we dont have to check this here)
13351 for (i = 0; i < NUM_DIRECTIONS; i++)
13353 int xx = x + xy[i][0];
13354 int yy = y + xy[i][1];
13355 int border_side = trigger_sides[i][1];
13356 int border_element = border_element_old[i];
13358 if (border_element == -1)
13361 // check for change of center element (but change it only once)
13362 if (!change_center_element)
13363 change_center_element =
13364 CheckElementChangeBySide(x, y, center_element, border_element,
13365 CE_TOUCHING_X, border_side);
13367 if (IS_PLAYER(xx, yy))
13369 /* use player element that is initially defined in the level playfield,
13370 not the player element that corresponds to the runtime player number
13371 (example: a level that contains EL_PLAYER_3 as the only player would
13372 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13373 int player_element = PLAYERINFO(xx, yy)->initial_element;
13375 CheckElementChangeBySide(x, y, center_element, player_element,
13376 CE_TOUCHING_X, border_side);
13381 void TestIfElementHitsCustomElement(int x, int y, int direction)
13383 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13384 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13385 int hitx = x + dx, hity = y + dy;
13386 int hitting_element = Tile[x][y];
13387 int touched_element;
13389 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13392 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13393 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13395 if (IN_LEV_FIELD(hitx, hity))
13397 int opposite_direction = MV_DIR_OPPOSITE(direction);
13398 int hitting_side = direction;
13399 int touched_side = opposite_direction;
13400 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13401 MovDir[hitx][hity] != direction ||
13402 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13408 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13409 CE_HITTING_X, touched_side);
13411 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13412 CE_HIT_BY_X, hitting_side);
13414 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13415 CE_HIT_BY_SOMETHING, opposite_direction);
13417 if (IS_PLAYER(hitx, hity))
13419 /* use player element that is initially defined in the level playfield,
13420 not the player element that corresponds to the runtime player number
13421 (example: a level that contains EL_PLAYER_3 as the only player would
13422 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13423 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13425 CheckElementChangeBySide(x, y, hitting_element, player_element,
13426 CE_HITTING_X, touched_side);
13431 // "hitting something" is also true when hitting the playfield border
13432 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13433 CE_HITTING_SOMETHING, direction);
13436 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13438 int i, kill_x = -1, kill_y = -1;
13440 int bad_element = -1;
13441 static int test_xy[4][2] =
13448 static int test_dir[4] =
13456 for (i = 0; i < NUM_DIRECTIONS; i++)
13458 int test_x, test_y, test_move_dir, test_element;
13460 test_x = good_x + test_xy[i][0];
13461 test_y = good_y + test_xy[i][1];
13463 if (!IN_LEV_FIELD(test_x, test_y))
13467 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13469 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13471 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13472 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13474 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13475 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13479 bad_element = test_element;
13485 if (kill_x != -1 || kill_y != -1)
13487 if (IS_PLAYER(good_x, good_y))
13489 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13491 if (player->shield_deadly_time_left > 0 &&
13492 !IS_INDESTRUCTIBLE(bad_element))
13493 Bang(kill_x, kill_y);
13494 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13495 KillPlayer(player);
13498 Bang(good_x, good_y);
13502 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13504 int i, kill_x = -1, kill_y = -1;
13505 int bad_element = Tile[bad_x][bad_y];
13506 static int test_xy[4][2] =
13513 static int touch_dir[4] =
13515 MV_LEFT | MV_RIGHT,
13520 static int test_dir[4] =
13528 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13531 for (i = 0; i < NUM_DIRECTIONS; i++)
13533 int test_x, test_y, test_move_dir, test_element;
13535 test_x = bad_x + test_xy[i][0];
13536 test_y = bad_y + test_xy[i][1];
13538 if (!IN_LEV_FIELD(test_x, test_y))
13542 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13544 test_element = Tile[test_x][test_y];
13546 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13547 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13549 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13550 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13552 // good thing is player or penguin that does not move away
13553 if (IS_PLAYER(test_x, test_y))
13555 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13557 if (bad_element == EL_ROBOT && player->is_moving)
13558 continue; // robot does not kill player if he is moving
13560 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13562 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13563 continue; // center and border element do not touch
13571 else if (test_element == EL_PENGUIN)
13581 if (kill_x != -1 || kill_y != -1)
13583 if (IS_PLAYER(kill_x, kill_y))
13585 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13587 if (player->shield_deadly_time_left > 0 &&
13588 !IS_INDESTRUCTIBLE(bad_element))
13589 Bang(bad_x, bad_y);
13590 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13591 KillPlayer(player);
13594 Bang(kill_x, kill_y);
13598 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13600 int bad_element = Tile[bad_x][bad_y];
13601 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13602 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13603 int test_x = bad_x + dx, test_y = bad_y + dy;
13604 int test_move_dir, test_element;
13605 int kill_x = -1, kill_y = -1;
13607 if (!IN_LEV_FIELD(test_x, test_y))
13611 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13613 test_element = Tile[test_x][test_y];
13615 if (test_move_dir != bad_move_dir)
13617 // good thing can be player or penguin that does not move away
13618 if (IS_PLAYER(test_x, test_y))
13620 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13622 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13623 player as being hit when he is moving towards the bad thing, because
13624 the "get hit by" condition would be lost after the player stops) */
13625 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13626 return; // player moves away from bad thing
13631 else if (test_element == EL_PENGUIN)
13638 if (kill_x != -1 || kill_y != -1)
13640 if (IS_PLAYER(kill_x, kill_y))
13642 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13644 if (player->shield_deadly_time_left > 0 &&
13645 !IS_INDESTRUCTIBLE(bad_element))
13646 Bang(bad_x, bad_y);
13647 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13648 KillPlayer(player);
13651 Bang(kill_x, kill_y);
13655 void TestIfPlayerTouchesBadThing(int x, int y)
13657 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13660 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13662 TestIfGoodThingHitsBadThing(x, y, move_dir);
13665 void TestIfBadThingTouchesPlayer(int x, int y)
13667 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13670 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13672 TestIfBadThingHitsGoodThing(x, y, move_dir);
13675 void TestIfFriendTouchesBadThing(int x, int y)
13677 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13680 void TestIfBadThingTouchesFriend(int x, int y)
13682 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13685 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13687 int i, kill_x = bad_x, kill_y = bad_y;
13688 static int xy[4][2] =
13696 for (i = 0; i < NUM_DIRECTIONS; i++)
13700 x = bad_x + xy[i][0];
13701 y = bad_y + xy[i][1];
13702 if (!IN_LEV_FIELD(x, y))
13705 element = Tile[x][y];
13706 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13707 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13715 if (kill_x != bad_x || kill_y != bad_y)
13716 Bang(bad_x, bad_y);
13719 void KillPlayer(struct PlayerInfo *player)
13721 int jx = player->jx, jy = player->jy;
13723 if (!player->active)
13727 Debug("game:playing:KillPlayer",
13728 "0: killed == %d, active == %d, reanimated == %d",
13729 player->killed, player->active, player->reanimated);
13732 /* the following code was introduced to prevent an infinite loop when calling
13734 -> CheckTriggeredElementChangeExt()
13735 -> ExecuteCustomElementAction()
13737 -> (infinitely repeating the above sequence of function calls)
13738 which occurs when killing the player while having a CE with the setting
13739 "kill player X when explosion of <player X>"; the solution using a new
13740 field "player->killed" was chosen for backwards compatibility, although
13741 clever use of the fields "player->active" etc. would probably also work */
13743 if (player->killed)
13747 player->killed = TRUE;
13749 // remove accessible field at the player's position
13750 Tile[jx][jy] = EL_EMPTY;
13752 // deactivate shield (else Bang()/Explode() would not work right)
13753 player->shield_normal_time_left = 0;
13754 player->shield_deadly_time_left = 0;
13757 Debug("game:playing:KillPlayer",
13758 "1: killed == %d, active == %d, reanimated == %d",
13759 player->killed, player->active, player->reanimated);
13765 Debug("game:playing:KillPlayer",
13766 "2: killed == %d, active == %d, reanimated == %d",
13767 player->killed, player->active, player->reanimated);
13770 if (player->reanimated) // killed player may have been reanimated
13771 player->killed = player->reanimated = FALSE;
13773 BuryPlayer(player);
13776 static void KillPlayerUnlessEnemyProtected(int x, int y)
13778 if (!PLAYER_ENEMY_PROTECTED(x, y))
13779 KillPlayer(PLAYERINFO(x, y));
13782 static void KillPlayerUnlessExplosionProtected(int x, int y)
13784 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13785 KillPlayer(PLAYERINFO(x, y));
13788 void BuryPlayer(struct PlayerInfo *player)
13790 int jx = player->jx, jy = player->jy;
13792 if (!player->active)
13795 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13796 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13798 RemovePlayer(player);
13800 player->buried = TRUE;
13802 if (game.all_players_gone)
13803 game.GameOver = TRUE;
13806 void RemovePlayer(struct PlayerInfo *player)
13808 int jx = player->jx, jy = player->jy;
13809 int i, found = FALSE;
13811 player->present = FALSE;
13812 player->active = FALSE;
13814 // required for some CE actions (even if the player is not active anymore)
13815 player->MovPos = 0;
13817 if (!ExplodeField[jx][jy])
13818 StorePlayer[jx][jy] = 0;
13820 if (player->is_moving)
13821 TEST_DrawLevelField(player->last_jx, player->last_jy);
13823 for (i = 0; i < MAX_PLAYERS; i++)
13824 if (stored_player[i].active)
13829 game.all_players_gone = TRUE;
13830 game.GameOver = TRUE;
13833 game.exit_x = game.robot_wheel_x = jx;
13834 game.exit_y = game.robot_wheel_y = jy;
13837 void ExitPlayer(struct PlayerInfo *player)
13839 DrawPlayer(player); // needed here only to cleanup last field
13840 RemovePlayer(player);
13842 if (game.players_still_needed > 0)
13843 game.players_still_needed--;
13846 static void SetFieldForSnapping(int x, int y, int element, int direction,
13847 int player_index_bit)
13849 struct ElementInfo *ei = &element_info[element];
13850 int direction_bit = MV_DIR_TO_BIT(direction);
13851 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13852 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13853 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13855 Tile[x][y] = EL_ELEMENT_SNAPPING;
13856 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13857 MovDir[x][y] = direction;
13858 Store[x][y] = element;
13859 Store2[x][y] = player_index_bit;
13861 ResetGfxAnimation(x, y);
13863 GfxElement[x][y] = element;
13864 GfxAction[x][y] = action;
13865 GfxDir[x][y] = direction;
13866 GfxFrame[x][y] = -1;
13869 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13870 int player_index_bit)
13872 TestIfElementTouchesCustomElement(x, y); // for empty space
13874 if (level.finish_dig_collect)
13876 int dig_side = MV_DIR_OPPOSITE(direction);
13878 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13879 player_index_bit, dig_side);
13884 =============================================================================
13885 checkDiagonalPushing()
13886 -----------------------------------------------------------------------------
13887 check if diagonal input device direction results in pushing of object
13888 (by checking if the alternative direction is walkable, diggable, ...)
13889 =============================================================================
13892 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13893 int x, int y, int real_dx, int real_dy)
13895 int jx, jy, dx, dy, xx, yy;
13897 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13900 // diagonal direction: check alternative direction
13905 xx = jx + (dx == 0 ? real_dx : 0);
13906 yy = jy + (dy == 0 ? real_dy : 0);
13908 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13912 =============================================================================
13914 -----------------------------------------------------------------------------
13915 x, y: field next to player (non-diagonal) to try to dig to
13916 real_dx, real_dy: direction as read from input device (can be diagonal)
13917 =============================================================================
13920 static int DigField(struct PlayerInfo *player,
13921 int oldx, int oldy, int x, int y,
13922 int real_dx, int real_dy, int mode)
13924 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13925 boolean player_was_pushing = player->is_pushing;
13926 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13927 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13928 int jx = oldx, jy = oldy;
13929 int dx = x - jx, dy = y - jy;
13930 int nextx = x + dx, nexty = y + dy;
13931 int move_direction = (dx == -1 ? MV_LEFT :
13932 dx == +1 ? MV_RIGHT :
13934 dy == +1 ? MV_DOWN : MV_NONE);
13935 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13936 int dig_side = MV_DIR_OPPOSITE(move_direction);
13937 int old_element = Tile[jx][jy];
13938 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13941 if (is_player) // function can also be called by EL_PENGUIN
13943 if (player->MovPos == 0)
13945 player->is_digging = FALSE;
13946 player->is_collecting = FALSE;
13949 if (player->MovPos == 0) // last pushing move finished
13950 player->is_pushing = FALSE;
13952 if (mode == DF_NO_PUSH) // player just stopped pushing
13954 player->is_switching = FALSE;
13955 player->push_delay = -1;
13957 return MP_NO_ACTION;
13961 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13962 old_element = Back[jx][jy];
13964 // in case of element dropped at player position, check background
13965 else if (Back[jx][jy] != EL_EMPTY &&
13966 game.engine_version >= VERSION_IDENT(2,2,0,0))
13967 old_element = Back[jx][jy];
13969 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13970 return MP_NO_ACTION; // field has no opening in this direction
13972 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13973 return MP_NO_ACTION; // field has no opening in this direction
13975 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13979 Tile[jx][jy] = player->artwork_element;
13980 InitMovingField(jx, jy, MV_DOWN);
13981 Store[jx][jy] = EL_ACID;
13982 ContinueMoving(jx, jy);
13983 BuryPlayer(player);
13985 return MP_DONT_RUN_INTO;
13988 if (player_can_move && DONT_RUN_INTO(element))
13990 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13992 return MP_DONT_RUN_INTO;
13995 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13996 return MP_NO_ACTION;
13998 collect_count = element_info[element].collect_count_initial;
14000 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14001 return MP_NO_ACTION;
14003 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14004 player_can_move = player_can_move_or_snap;
14006 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14007 game.engine_version >= VERSION_IDENT(2,2,0,0))
14009 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14010 player->index_bit, dig_side);
14011 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14012 player->index_bit, dig_side);
14014 if (element == EL_DC_LANDMINE)
14017 if (Tile[x][y] != element) // field changed by snapping
14020 return MP_NO_ACTION;
14023 if (player->gravity && is_player && !player->is_auto_moving &&
14024 canFallDown(player) && move_direction != MV_DOWN &&
14025 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14026 return MP_NO_ACTION; // player cannot walk here due to gravity
14028 if (player_can_move &&
14029 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14031 int sound_element = SND_ELEMENT(element);
14032 int sound_action = ACTION_WALKING;
14034 if (IS_RND_GATE(element))
14036 if (!player->key[RND_GATE_NR(element)])
14037 return MP_NO_ACTION;
14039 else if (IS_RND_GATE_GRAY(element))
14041 if (!player->key[RND_GATE_GRAY_NR(element)])
14042 return MP_NO_ACTION;
14044 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14046 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14047 return MP_NO_ACTION;
14049 else if (element == EL_EXIT_OPEN ||
14050 element == EL_EM_EXIT_OPEN ||
14051 element == EL_EM_EXIT_OPENING ||
14052 element == EL_STEEL_EXIT_OPEN ||
14053 element == EL_EM_STEEL_EXIT_OPEN ||
14054 element == EL_EM_STEEL_EXIT_OPENING ||
14055 element == EL_SP_EXIT_OPEN ||
14056 element == EL_SP_EXIT_OPENING)
14058 sound_action = ACTION_PASSING; // player is passing exit
14060 else if (element == EL_EMPTY)
14062 sound_action = ACTION_MOVING; // nothing to walk on
14065 // play sound from background or player, whatever is available
14066 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14067 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14069 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14071 else if (player_can_move &&
14072 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14074 if (!ACCESS_FROM(element, opposite_direction))
14075 return MP_NO_ACTION; // field not accessible from this direction
14077 if (CAN_MOVE(element)) // only fixed elements can be passed!
14078 return MP_NO_ACTION;
14080 if (IS_EM_GATE(element))
14082 if (!player->key[EM_GATE_NR(element)])
14083 return MP_NO_ACTION;
14085 else if (IS_EM_GATE_GRAY(element))
14087 if (!player->key[EM_GATE_GRAY_NR(element)])
14088 return MP_NO_ACTION;
14090 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14092 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14093 return MP_NO_ACTION;
14095 else if (IS_EMC_GATE(element))
14097 if (!player->key[EMC_GATE_NR(element)])
14098 return MP_NO_ACTION;
14100 else if (IS_EMC_GATE_GRAY(element))
14102 if (!player->key[EMC_GATE_GRAY_NR(element)])
14103 return MP_NO_ACTION;
14105 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14107 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14108 return MP_NO_ACTION;
14110 else if (element == EL_DC_GATE_WHITE ||
14111 element == EL_DC_GATE_WHITE_GRAY ||
14112 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14114 if (player->num_white_keys == 0)
14115 return MP_NO_ACTION;
14117 player->num_white_keys--;
14119 else if (IS_SP_PORT(element))
14121 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14122 element == EL_SP_GRAVITY_PORT_RIGHT ||
14123 element == EL_SP_GRAVITY_PORT_UP ||
14124 element == EL_SP_GRAVITY_PORT_DOWN)
14125 player->gravity = !player->gravity;
14126 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14127 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14128 element == EL_SP_GRAVITY_ON_PORT_UP ||
14129 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14130 player->gravity = TRUE;
14131 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14132 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14133 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14134 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14135 player->gravity = FALSE;
14138 // automatically move to the next field with double speed
14139 player->programmed_action = move_direction;
14141 if (player->move_delay_reset_counter == 0)
14143 player->move_delay_reset_counter = 2; // two double speed steps
14145 DOUBLE_PLAYER_SPEED(player);
14148 PlayLevelSoundAction(x, y, ACTION_PASSING);
14150 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14154 if (mode != DF_SNAP)
14156 GfxElement[x][y] = GFX_ELEMENT(element);
14157 player->is_digging = TRUE;
14160 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14162 // use old behaviour for old levels (digging)
14163 if (!level.finish_dig_collect)
14165 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14166 player->index_bit, dig_side);
14168 // if digging triggered player relocation, finish digging tile
14169 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14170 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14173 if (mode == DF_SNAP)
14175 if (level.block_snap_field)
14176 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14178 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14180 // use old behaviour for old levels (snapping)
14181 if (!level.finish_dig_collect)
14182 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14183 player->index_bit, dig_side);
14186 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14190 if (is_player && mode != DF_SNAP)
14192 GfxElement[x][y] = element;
14193 player->is_collecting = TRUE;
14196 if (element == EL_SPEED_PILL)
14198 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14200 else if (element == EL_EXTRA_TIME && level.time > 0)
14202 TimeLeft += level.extra_time;
14204 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14206 DisplayGameControlValues();
14208 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14210 player->shield_normal_time_left += level.shield_normal_time;
14211 if (element == EL_SHIELD_DEADLY)
14212 player->shield_deadly_time_left += level.shield_deadly_time;
14214 else if (element == EL_DYNAMITE ||
14215 element == EL_EM_DYNAMITE ||
14216 element == EL_SP_DISK_RED)
14218 if (player->inventory_size < MAX_INVENTORY_SIZE)
14219 player->inventory_element[player->inventory_size++] = element;
14221 DrawGameDoorValues();
14223 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14225 player->dynabomb_count++;
14226 player->dynabombs_left++;
14228 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14230 player->dynabomb_size++;
14232 else if (element == EL_DYNABOMB_INCREASE_POWER)
14234 player->dynabomb_xl = TRUE;
14236 else if (IS_KEY(element))
14238 player->key[KEY_NR(element)] = TRUE;
14240 DrawGameDoorValues();
14242 else if (element == EL_DC_KEY_WHITE)
14244 player->num_white_keys++;
14246 // display white keys?
14247 // DrawGameDoorValues();
14249 else if (IS_ENVELOPE(element))
14251 player->show_envelope = element;
14253 else if (element == EL_EMC_LENSES)
14255 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14257 RedrawAllInvisibleElementsForLenses();
14259 else if (element == EL_EMC_MAGNIFIER)
14261 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14263 RedrawAllInvisibleElementsForMagnifier();
14265 else if (IS_DROPPABLE(element) ||
14266 IS_THROWABLE(element)) // can be collected and dropped
14270 if (collect_count == 0)
14271 player->inventory_infinite_element = element;
14273 for (i = 0; i < collect_count; i++)
14274 if (player->inventory_size < MAX_INVENTORY_SIZE)
14275 player->inventory_element[player->inventory_size++] = element;
14277 DrawGameDoorValues();
14279 else if (collect_count > 0)
14281 game.gems_still_needed -= collect_count;
14282 if (game.gems_still_needed < 0)
14283 game.gems_still_needed = 0;
14285 game.snapshot.collected_item = TRUE;
14287 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14289 DisplayGameControlValues();
14292 RaiseScoreElement(element);
14293 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14295 // use old behaviour for old levels (collecting)
14296 if (!level.finish_dig_collect && is_player)
14298 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14299 player->index_bit, dig_side);
14301 // if collecting triggered player relocation, finish collecting tile
14302 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14303 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14306 if (mode == DF_SNAP)
14308 if (level.block_snap_field)
14309 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14311 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14313 // use old behaviour for old levels (snapping)
14314 if (!level.finish_dig_collect)
14315 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14316 player->index_bit, dig_side);
14319 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14321 if (mode == DF_SNAP && element != EL_BD_ROCK)
14322 return MP_NO_ACTION;
14324 if (CAN_FALL(element) && dy)
14325 return MP_NO_ACTION;
14327 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14328 !(element == EL_SPRING && level.use_spring_bug))
14329 return MP_NO_ACTION;
14331 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14332 ((move_direction & MV_VERTICAL &&
14333 ((element_info[element].move_pattern & MV_LEFT &&
14334 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14335 (element_info[element].move_pattern & MV_RIGHT &&
14336 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14337 (move_direction & MV_HORIZONTAL &&
14338 ((element_info[element].move_pattern & MV_UP &&
14339 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14340 (element_info[element].move_pattern & MV_DOWN &&
14341 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14342 return MP_NO_ACTION;
14344 // do not push elements already moving away faster than player
14345 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14346 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14347 return MP_NO_ACTION;
14349 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14351 if (player->push_delay_value == -1 || !player_was_pushing)
14352 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14354 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14356 if (player->push_delay_value == -1)
14357 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14359 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14361 if (!player->is_pushing)
14362 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14365 player->is_pushing = TRUE;
14366 player->is_active = TRUE;
14368 if (!(IN_LEV_FIELD(nextx, nexty) &&
14369 (IS_FREE(nextx, nexty) ||
14370 (IS_SB_ELEMENT(element) &&
14371 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14372 (IS_CUSTOM_ELEMENT(element) &&
14373 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14374 return MP_NO_ACTION;
14376 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14377 return MP_NO_ACTION;
14379 if (player->push_delay == -1) // new pushing; restart delay
14380 player->push_delay = 0;
14382 if (player->push_delay < player->push_delay_value &&
14383 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14384 element != EL_SPRING && element != EL_BALLOON)
14386 // make sure that there is no move delay before next try to push
14387 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14388 player->move_delay = 0;
14390 return MP_NO_ACTION;
14393 if (IS_CUSTOM_ELEMENT(element) &&
14394 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14396 if (!DigFieldByCE(nextx, nexty, element))
14397 return MP_NO_ACTION;
14400 if (IS_SB_ELEMENT(element))
14402 boolean sokoban_task_solved = FALSE;
14404 if (element == EL_SOKOBAN_FIELD_FULL)
14406 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14408 IncrementSokobanFieldsNeeded();
14409 IncrementSokobanObjectsNeeded();
14412 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14414 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14416 DecrementSokobanFieldsNeeded();
14417 DecrementSokobanObjectsNeeded();
14419 // sokoban object was pushed from empty field to sokoban field
14420 if (Back[x][y] == EL_EMPTY)
14421 sokoban_task_solved = TRUE;
14424 Tile[x][y] = EL_SOKOBAN_OBJECT;
14426 if (Back[x][y] == Back[nextx][nexty])
14427 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14428 else if (Back[x][y] != 0)
14429 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14432 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14435 if (sokoban_task_solved &&
14436 game.sokoban_fields_still_needed == 0 &&
14437 game.sokoban_objects_still_needed == 0 &&
14438 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14440 game.players_still_needed = 0;
14444 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14448 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14450 InitMovingField(x, y, move_direction);
14451 GfxAction[x][y] = ACTION_PUSHING;
14453 if (mode == DF_SNAP)
14454 ContinueMoving(x, y);
14456 MovPos[x][y] = (dx != 0 ? dx : dy);
14458 Pushed[x][y] = TRUE;
14459 Pushed[nextx][nexty] = TRUE;
14461 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14462 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14464 player->push_delay_value = -1; // get new value later
14466 // check for element change _after_ element has been pushed
14467 if (game.use_change_when_pushing_bug)
14469 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14470 player->index_bit, dig_side);
14471 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14472 player->index_bit, dig_side);
14475 else if (IS_SWITCHABLE(element))
14477 if (PLAYER_SWITCHING(player, x, y))
14479 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14480 player->index_bit, dig_side);
14485 player->is_switching = TRUE;
14486 player->switch_x = x;
14487 player->switch_y = y;
14489 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14491 if (element == EL_ROBOT_WHEEL)
14493 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14495 game.robot_wheel_x = x;
14496 game.robot_wheel_y = y;
14497 game.robot_wheel_active = TRUE;
14499 TEST_DrawLevelField(x, y);
14501 else if (element == EL_SP_TERMINAL)
14505 SCAN_PLAYFIELD(xx, yy)
14507 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14511 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14513 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14515 ResetGfxAnimation(xx, yy);
14516 TEST_DrawLevelField(xx, yy);
14520 else if (IS_BELT_SWITCH(element))
14522 ToggleBeltSwitch(x, y);
14524 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14525 element == EL_SWITCHGATE_SWITCH_DOWN ||
14526 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14527 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14529 ToggleSwitchgateSwitch(x, y);
14531 else if (element == EL_LIGHT_SWITCH ||
14532 element == EL_LIGHT_SWITCH_ACTIVE)
14534 ToggleLightSwitch(x, y);
14536 else if (element == EL_TIMEGATE_SWITCH ||
14537 element == EL_DC_TIMEGATE_SWITCH)
14539 ActivateTimegateSwitch(x, y);
14541 else if (element == EL_BALLOON_SWITCH_LEFT ||
14542 element == EL_BALLOON_SWITCH_RIGHT ||
14543 element == EL_BALLOON_SWITCH_UP ||
14544 element == EL_BALLOON_SWITCH_DOWN ||
14545 element == EL_BALLOON_SWITCH_NONE ||
14546 element == EL_BALLOON_SWITCH_ANY)
14548 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14549 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14550 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14551 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14552 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14555 else if (element == EL_LAMP)
14557 Tile[x][y] = EL_LAMP_ACTIVE;
14558 game.lights_still_needed--;
14560 ResetGfxAnimation(x, y);
14561 TEST_DrawLevelField(x, y);
14563 else if (element == EL_TIME_ORB_FULL)
14565 Tile[x][y] = EL_TIME_ORB_EMPTY;
14567 if (level.time > 0 || level.use_time_orb_bug)
14569 TimeLeft += level.time_orb_time;
14570 game.no_time_limit = FALSE;
14572 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14574 DisplayGameControlValues();
14577 ResetGfxAnimation(x, y);
14578 TEST_DrawLevelField(x, y);
14580 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14581 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14585 game.ball_active = !game.ball_active;
14587 SCAN_PLAYFIELD(xx, yy)
14589 int e = Tile[xx][yy];
14591 if (game.ball_active)
14593 if (e == EL_EMC_MAGIC_BALL)
14594 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14595 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14596 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14600 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14601 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14602 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14603 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14608 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14609 player->index_bit, dig_side);
14611 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14612 player->index_bit, dig_side);
14614 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14615 player->index_bit, dig_side);
14621 if (!PLAYER_SWITCHING(player, x, y))
14623 player->is_switching = TRUE;
14624 player->switch_x = x;
14625 player->switch_y = y;
14627 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14628 player->index_bit, dig_side);
14629 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14630 player->index_bit, dig_side);
14632 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14633 player->index_bit, dig_side);
14634 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14635 player->index_bit, dig_side);
14638 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14639 player->index_bit, dig_side);
14640 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14641 player->index_bit, dig_side);
14643 return MP_NO_ACTION;
14646 player->push_delay = -1;
14648 if (is_player) // function can also be called by EL_PENGUIN
14650 if (Tile[x][y] != element) // really digged/collected something
14652 player->is_collecting = !player->is_digging;
14653 player->is_active = TRUE;
14655 player->last_removed_element = element;
14662 static boolean DigFieldByCE(int x, int y, int digging_element)
14664 int element = Tile[x][y];
14666 if (!IS_FREE(x, y))
14668 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14669 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14672 // no element can dig solid indestructible elements
14673 if (IS_INDESTRUCTIBLE(element) &&
14674 !IS_DIGGABLE(element) &&
14675 !IS_COLLECTIBLE(element))
14678 if (AmoebaNr[x][y] &&
14679 (element == EL_AMOEBA_FULL ||
14680 element == EL_BD_AMOEBA ||
14681 element == EL_AMOEBA_GROWING))
14683 AmoebaCnt[AmoebaNr[x][y]]--;
14684 AmoebaCnt2[AmoebaNr[x][y]]--;
14687 if (IS_MOVING(x, y))
14688 RemoveMovingField(x, y);
14692 TEST_DrawLevelField(x, y);
14695 // if digged element was about to explode, prevent the explosion
14696 ExplodeField[x][y] = EX_TYPE_NONE;
14698 PlayLevelSoundAction(x, y, action);
14701 Store[x][y] = EL_EMPTY;
14703 // this makes it possible to leave the removed element again
14704 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14705 Store[x][y] = element;
14710 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14712 int jx = player->jx, jy = player->jy;
14713 int x = jx + dx, y = jy + dy;
14714 int snap_direction = (dx == -1 ? MV_LEFT :
14715 dx == +1 ? MV_RIGHT :
14717 dy == +1 ? MV_DOWN : MV_NONE);
14718 boolean can_continue_snapping = (level.continuous_snapping &&
14719 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14721 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14724 if (!player->active || !IN_LEV_FIELD(x, y))
14732 if (player->MovPos == 0)
14733 player->is_pushing = FALSE;
14735 player->is_snapping = FALSE;
14737 if (player->MovPos == 0)
14739 player->is_moving = FALSE;
14740 player->is_digging = FALSE;
14741 player->is_collecting = FALSE;
14747 // prevent snapping with already pressed snap key when not allowed
14748 if (player->is_snapping && !can_continue_snapping)
14751 player->MovDir = snap_direction;
14753 if (player->MovPos == 0)
14755 player->is_moving = FALSE;
14756 player->is_digging = FALSE;
14757 player->is_collecting = FALSE;
14760 player->is_dropping = FALSE;
14761 player->is_dropping_pressed = FALSE;
14762 player->drop_pressed_delay = 0;
14764 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14767 player->is_snapping = TRUE;
14768 player->is_active = TRUE;
14770 if (player->MovPos == 0)
14772 player->is_moving = FALSE;
14773 player->is_digging = FALSE;
14774 player->is_collecting = FALSE;
14777 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14778 TEST_DrawLevelField(player->last_jx, player->last_jy);
14780 TEST_DrawLevelField(x, y);
14785 static boolean DropElement(struct PlayerInfo *player)
14787 int old_element, new_element;
14788 int dropx = player->jx, dropy = player->jy;
14789 int drop_direction = player->MovDir;
14790 int drop_side = drop_direction;
14791 int drop_element = get_next_dropped_element(player);
14793 /* do not drop an element on top of another element; when holding drop key
14794 pressed without moving, dropped element must move away before the next
14795 element can be dropped (this is especially important if the next element
14796 is dynamite, which can be placed on background for historical reasons) */
14797 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14800 if (IS_THROWABLE(drop_element))
14802 dropx += GET_DX_FROM_DIR(drop_direction);
14803 dropy += GET_DY_FROM_DIR(drop_direction);
14805 if (!IN_LEV_FIELD(dropx, dropy))
14809 old_element = Tile[dropx][dropy]; // old element at dropping position
14810 new_element = drop_element; // default: no change when dropping
14812 // check if player is active, not moving and ready to drop
14813 if (!player->active || player->MovPos || player->drop_delay > 0)
14816 // check if player has anything that can be dropped
14817 if (new_element == EL_UNDEFINED)
14820 // only set if player has anything that can be dropped
14821 player->is_dropping_pressed = TRUE;
14823 // check if drop key was pressed long enough for EM style dynamite
14824 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14827 // check if anything can be dropped at the current position
14828 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14831 // collected custom elements can only be dropped on empty fields
14832 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14835 if (old_element != EL_EMPTY)
14836 Back[dropx][dropy] = old_element; // store old element on this field
14838 ResetGfxAnimation(dropx, dropy);
14839 ResetRandomAnimationValue(dropx, dropy);
14841 if (player->inventory_size > 0 ||
14842 player->inventory_infinite_element != EL_UNDEFINED)
14844 if (player->inventory_size > 0)
14846 player->inventory_size--;
14848 DrawGameDoorValues();
14850 if (new_element == EL_DYNAMITE)
14851 new_element = EL_DYNAMITE_ACTIVE;
14852 else if (new_element == EL_EM_DYNAMITE)
14853 new_element = EL_EM_DYNAMITE_ACTIVE;
14854 else if (new_element == EL_SP_DISK_RED)
14855 new_element = EL_SP_DISK_RED_ACTIVE;
14858 Tile[dropx][dropy] = new_element;
14860 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14861 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14862 el2img(Tile[dropx][dropy]), 0);
14864 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14866 // needed if previous element just changed to "empty" in the last frame
14867 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14869 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14870 player->index_bit, drop_side);
14871 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14873 player->index_bit, drop_side);
14875 TestIfElementTouchesCustomElement(dropx, dropy);
14877 else // player is dropping a dyna bomb
14879 player->dynabombs_left--;
14881 Tile[dropx][dropy] = new_element;
14883 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14884 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14885 el2img(Tile[dropx][dropy]), 0);
14887 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14890 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14891 InitField_WithBug1(dropx, dropy, FALSE);
14893 new_element = Tile[dropx][dropy]; // element might have changed
14895 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14896 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14898 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14899 MovDir[dropx][dropy] = drop_direction;
14901 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14903 // do not cause impact style collision by dropping elements that can fall
14904 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14907 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14908 player->is_dropping = TRUE;
14910 player->drop_pressed_delay = 0;
14911 player->is_dropping_pressed = FALSE;
14913 player->drop_x = dropx;
14914 player->drop_y = dropy;
14919 // ----------------------------------------------------------------------------
14920 // game sound playing functions
14921 // ----------------------------------------------------------------------------
14923 static int *loop_sound_frame = NULL;
14924 static int *loop_sound_volume = NULL;
14926 void InitPlayLevelSound(void)
14928 int num_sounds = getSoundListSize();
14930 checked_free(loop_sound_frame);
14931 checked_free(loop_sound_volume);
14933 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14934 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14937 static void PlayLevelSound(int x, int y, int nr)
14939 int sx = SCREENX(x), sy = SCREENY(y);
14940 int volume, stereo_position;
14941 int max_distance = 8;
14942 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14944 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14945 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14948 if (!IN_LEV_FIELD(x, y) ||
14949 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14950 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14953 volume = SOUND_MAX_VOLUME;
14955 if (!IN_SCR_FIELD(sx, sy))
14957 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14958 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14960 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14963 stereo_position = (SOUND_MAX_LEFT +
14964 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14965 (SCR_FIELDX + 2 * max_distance));
14967 if (IS_LOOP_SOUND(nr))
14969 /* This assures that quieter loop sounds do not overwrite louder ones,
14970 while restarting sound volume comparison with each new game frame. */
14972 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14975 loop_sound_volume[nr] = volume;
14976 loop_sound_frame[nr] = FrameCounter;
14979 PlaySoundExt(nr, volume, stereo_position, type);
14982 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14984 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14985 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14986 y < LEVELY(BY1) ? LEVELY(BY1) :
14987 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14991 static void PlayLevelSoundAction(int x, int y, int action)
14993 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14996 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14998 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15000 if (sound_effect != SND_UNDEFINED)
15001 PlayLevelSound(x, y, sound_effect);
15004 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15007 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15009 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15010 PlayLevelSound(x, y, sound_effect);
15013 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15015 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15017 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15018 PlayLevelSound(x, y, sound_effect);
15021 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15023 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15025 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15026 StopSound(sound_effect);
15029 static int getLevelMusicNr(void)
15031 if (levelset.music[level_nr] != MUS_UNDEFINED)
15032 return levelset.music[level_nr]; // from config file
15034 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15037 static void FadeLevelSounds(void)
15042 static void FadeLevelMusic(void)
15044 int music_nr = getLevelMusicNr();
15045 char *curr_music = getCurrentlyPlayingMusicFilename();
15046 char *next_music = getMusicInfoEntryFilename(music_nr);
15048 if (!strEqual(curr_music, next_music))
15052 void FadeLevelSoundsAndMusic(void)
15058 static void PlayLevelMusic(void)
15060 int music_nr = getLevelMusicNr();
15061 char *curr_music = getCurrentlyPlayingMusicFilename();
15062 char *next_music = getMusicInfoEntryFilename(music_nr);
15064 if (!strEqual(curr_music, next_music))
15065 PlayMusicLoop(music_nr);
15068 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15070 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15072 int x = xx - offset;
15073 int y = yy - offset;
15078 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15082 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15086 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15090 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15094 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15098 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15102 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15105 case SOUND_android_clone:
15106 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15109 case SOUND_android_move:
15110 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15114 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15118 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15122 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15125 case SOUND_eater_eat:
15126 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15130 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15133 case SOUND_collect:
15134 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15137 case SOUND_diamond:
15138 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15142 // !!! CHECK THIS !!!
15144 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15146 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15150 case SOUND_wonderfall:
15151 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15155 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15159 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15163 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15167 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15171 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15175 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15179 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15183 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15186 case SOUND_exit_open:
15187 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15190 case SOUND_exit_leave:
15191 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15194 case SOUND_dynamite:
15195 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15199 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15203 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15207 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15211 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15215 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15219 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15223 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15228 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15230 int element = map_element_SP_to_RND(element_sp);
15231 int action = map_action_SP_to_RND(action_sp);
15232 int offset = (setup.sp_show_border_elements ? 0 : 1);
15233 int x = xx - offset;
15234 int y = yy - offset;
15236 PlayLevelSoundElementAction(x, y, element, action);
15239 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15241 int element = map_element_MM_to_RND(element_mm);
15242 int action = map_action_MM_to_RND(action_mm);
15244 int x = xx - offset;
15245 int y = yy - offset;
15247 if (!IS_MM_ELEMENT(element))
15248 element = EL_MM_DEFAULT;
15250 PlayLevelSoundElementAction(x, y, element, action);
15253 void PlaySound_MM(int sound_mm)
15255 int sound = map_sound_MM_to_RND(sound_mm);
15257 if (sound == SND_UNDEFINED)
15263 void PlaySoundLoop_MM(int sound_mm)
15265 int sound = map_sound_MM_to_RND(sound_mm);
15267 if (sound == SND_UNDEFINED)
15270 PlaySoundLoop(sound);
15273 void StopSound_MM(int sound_mm)
15275 int sound = map_sound_MM_to_RND(sound_mm);
15277 if (sound == SND_UNDEFINED)
15283 void RaiseScore(int value)
15285 game.score += value;
15287 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15289 DisplayGameControlValues();
15292 void RaiseScoreElement(int element)
15297 case EL_BD_DIAMOND:
15298 case EL_EMERALD_YELLOW:
15299 case EL_EMERALD_RED:
15300 case EL_EMERALD_PURPLE:
15301 case EL_SP_INFOTRON:
15302 RaiseScore(level.score[SC_EMERALD]);
15305 RaiseScore(level.score[SC_DIAMOND]);
15308 RaiseScore(level.score[SC_CRYSTAL]);
15311 RaiseScore(level.score[SC_PEARL]);
15314 case EL_BD_BUTTERFLY:
15315 case EL_SP_ELECTRON:
15316 RaiseScore(level.score[SC_BUG]);
15319 case EL_BD_FIREFLY:
15320 case EL_SP_SNIKSNAK:
15321 RaiseScore(level.score[SC_SPACESHIP]);
15324 case EL_DARK_YAMYAM:
15325 RaiseScore(level.score[SC_YAMYAM]);
15328 RaiseScore(level.score[SC_ROBOT]);
15331 RaiseScore(level.score[SC_PACMAN]);
15334 RaiseScore(level.score[SC_NUT]);
15337 case EL_EM_DYNAMITE:
15338 case EL_SP_DISK_RED:
15339 case EL_DYNABOMB_INCREASE_NUMBER:
15340 case EL_DYNABOMB_INCREASE_SIZE:
15341 case EL_DYNABOMB_INCREASE_POWER:
15342 RaiseScore(level.score[SC_DYNAMITE]);
15344 case EL_SHIELD_NORMAL:
15345 case EL_SHIELD_DEADLY:
15346 RaiseScore(level.score[SC_SHIELD]);
15348 case EL_EXTRA_TIME:
15349 RaiseScore(level.extra_time_score);
15363 case EL_DC_KEY_WHITE:
15364 RaiseScore(level.score[SC_KEY]);
15367 RaiseScore(element_info[element].collect_score);
15372 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15374 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15376 // closing door required in case of envelope style request dialogs
15379 // prevent short reactivation of overlay buttons while closing door
15380 SetOverlayActive(FALSE);
15382 CloseDoor(DOOR_CLOSE_1);
15385 if (network.enabled)
15386 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15390 FadeSkipNextFadeIn();
15392 SetGameStatus(GAME_MODE_MAIN);
15397 else // continue playing the game
15399 if (tape.playing && tape.deactivate_display)
15400 TapeDeactivateDisplayOff(TRUE);
15402 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15404 if (tape.playing && tape.deactivate_display)
15405 TapeDeactivateDisplayOn();
15409 void RequestQuitGame(boolean ask_if_really_quit)
15411 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15412 boolean skip_request = game.all_players_gone || quick_quit;
15414 RequestQuitGameExt(skip_request, quick_quit,
15415 "Do you really want to quit the game?");
15418 void RequestRestartGame(char *message)
15420 game.restart_game_message = NULL;
15422 boolean has_started_game = hasStartedNetworkGame();
15423 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15425 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15427 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15431 // needed in case of envelope request to close game panel
15432 CloseDoor(DOOR_CLOSE_1);
15434 SetGameStatus(GAME_MODE_MAIN);
15440 void CheckGameOver(void)
15442 static boolean last_game_over = FALSE;
15443 static int game_over_delay = 0;
15444 int game_over_delay_value = 50;
15445 boolean game_over = checkGameFailed();
15447 // do not handle game over if request dialog is already active
15448 if (game.request_active)
15451 // do not ask to play again if game was never actually played
15452 if (!game.GamePlayed)
15457 last_game_over = FALSE;
15458 game_over_delay = game_over_delay_value;
15463 if (game_over_delay > 0)
15470 if (last_game_over != game_over)
15471 game.restart_game_message = (hasStartedNetworkGame() ?
15472 "Game over! Play it again?" :
15475 last_game_over = game_over;
15478 boolean checkGameSolved(void)
15480 // set for all game engines if level was solved
15481 return game.LevelSolved_GameEnd;
15484 boolean checkGameFailed(void)
15486 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15487 return (game_em.game_over && !game_em.level_solved);
15488 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15489 return (game_sp.game_over && !game_sp.level_solved);
15490 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15491 return (game_mm.game_over && !game_mm.level_solved);
15492 else // GAME_ENGINE_TYPE_RND
15493 return (game.GameOver && !game.LevelSolved);
15496 boolean checkGameEnded(void)
15498 return (checkGameSolved() || checkGameFailed());
15502 // ----------------------------------------------------------------------------
15503 // random generator functions
15504 // ----------------------------------------------------------------------------
15506 unsigned int InitEngineRandom_RND(int seed)
15508 game.num_random_calls = 0;
15510 return InitEngineRandom(seed);
15513 unsigned int RND(int max)
15517 game.num_random_calls++;
15519 return GetEngineRandom(max);
15526 // ----------------------------------------------------------------------------
15527 // game engine snapshot handling functions
15528 // ----------------------------------------------------------------------------
15530 struct EngineSnapshotInfo
15532 // runtime values for custom element collect score
15533 int collect_score[NUM_CUSTOM_ELEMENTS];
15535 // runtime values for group element choice position
15536 int choice_pos[NUM_GROUP_ELEMENTS];
15538 // runtime values for belt position animations
15539 int belt_graphic[4][NUM_BELT_PARTS];
15540 int belt_anim_mode[4][NUM_BELT_PARTS];
15543 static struct EngineSnapshotInfo engine_snapshot_rnd;
15544 static char *snapshot_level_identifier = NULL;
15545 static int snapshot_level_nr = -1;
15547 static void SaveEngineSnapshotValues_RND(void)
15549 static int belt_base_active_element[4] =
15551 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15552 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15553 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15554 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15558 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15560 int element = EL_CUSTOM_START + i;
15562 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15565 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15567 int element = EL_GROUP_START + i;
15569 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15572 for (i = 0; i < 4; i++)
15574 for (j = 0; j < NUM_BELT_PARTS; j++)
15576 int element = belt_base_active_element[i] + j;
15577 int graphic = el2img(element);
15578 int anim_mode = graphic_info[graphic].anim_mode;
15580 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15581 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15586 static void LoadEngineSnapshotValues_RND(void)
15588 unsigned int num_random_calls = game.num_random_calls;
15591 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15593 int element = EL_CUSTOM_START + i;
15595 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15598 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15600 int element = EL_GROUP_START + i;
15602 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15605 for (i = 0; i < 4; i++)
15607 for (j = 0; j < NUM_BELT_PARTS; j++)
15609 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15610 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15612 graphic_info[graphic].anim_mode = anim_mode;
15616 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15618 InitRND(tape.random_seed);
15619 for (i = 0; i < num_random_calls; i++)
15623 if (game.num_random_calls != num_random_calls)
15625 Error("number of random calls out of sync");
15626 Error("number of random calls should be %d", num_random_calls);
15627 Error("number of random calls is %d", game.num_random_calls);
15629 Fail("this should not happen -- please debug");
15633 void FreeEngineSnapshotSingle(void)
15635 FreeSnapshotSingle();
15637 setString(&snapshot_level_identifier, NULL);
15638 snapshot_level_nr = -1;
15641 void FreeEngineSnapshotList(void)
15643 FreeSnapshotList();
15646 static ListNode *SaveEngineSnapshotBuffers(void)
15648 ListNode *buffers = NULL;
15650 // copy some special values to a structure better suited for the snapshot
15652 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15653 SaveEngineSnapshotValues_RND();
15654 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15655 SaveEngineSnapshotValues_EM();
15656 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15657 SaveEngineSnapshotValues_SP(&buffers);
15658 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15659 SaveEngineSnapshotValues_MM(&buffers);
15661 // save values stored in special snapshot structure
15663 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15665 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15667 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15669 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15672 // save further RND engine values
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15676 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15679 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15682 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15686 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15695 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15696 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15697 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15698 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15699 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15700 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15701 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15702 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15703 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15704 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15705 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15706 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15707 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15708 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15709 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15710 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15712 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15713 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15715 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15716 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15717 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15719 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15720 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15722 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15723 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15724 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15725 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15726 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15728 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15729 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15732 ListNode *node = engine_snapshot_list_rnd;
15735 while (node != NULL)
15737 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15742 Debug("game:playing:SaveEngineSnapshotBuffers",
15743 "size of engine snapshot: %d bytes", num_bytes);
15749 void SaveEngineSnapshotSingle(void)
15751 ListNode *buffers = SaveEngineSnapshotBuffers();
15753 // finally save all snapshot buffers to single snapshot
15754 SaveSnapshotSingle(buffers);
15756 // save level identification information
15757 setString(&snapshot_level_identifier, leveldir_current->identifier);
15758 snapshot_level_nr = level_nr;
15761 boolean CheckSaveEngineSnapshotToList(void)
15763 boolean save_snapshot =
15764 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15765 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15766 game.snapshot.changed_action) ||
15767 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15768 game.snapshot.collected_item));
15770 game.snapshot.changed_action = FALSE;
15771 game.snapshot.collected_item = FALSE;
15772 game.snapshot.save_snapshot = save_snapshot;
15774 return save_snapshot;
15777 void SaveEngineSnapshotToList(void)
15779 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15783 ListNode *buffers = SaveEngineSnapshotBuffers();
15785 // finally save all snapshot buffers to snapshot list
15786 SaveSnapshotToList(buffers);
15789 void SaveEngineSnapshotToListInitial(void)
15791 FreeEngineSnapshotList();
15793 SaveEngineSnapshotToList();
15796 static void LoadEngineSnapshotValues(void)
15798 // restore special values from snapshot structure
15800 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15801 LoadEngineSnapshotValues_RND();
15802 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15803 LoadEngineSnapshotValues_EM();
15804 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15805 LoadEngineSnapshotValues_SP();
15806 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15807 LoadEngineSnapshotValues_MM();
15810 void LoadEngineSnapshotSingle(void)
15812 LoadSnapshotSingle();
15814 LoadEngineSnapshotValues();
15817 static void LoadEngineSnapshot_Undo(int steps)
15819 LoadSnapshotFromList_Older(steps);
15821 LoadEngineSnapshotValues();
15824 static void LoadEngineSnapshot_Redo(int steps)
15826 LoadSnapshotFromList_Newer(steps);
15828 LoadEngineSnapshotValues();
15831 boolean CheckEngineSnapshotSingle(void)
15833 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15834 snapshot_level_nr == level_nr);
15837 boolean CheckEngineSnapshotList(void)
15839 return CheckSnapshotList();
15843 // ---------- new game button stuff -------------------------------------------
15850 boolean *setup_value;
15851 boolean allowed_on_tape;
15852 boolean is_touch_button;
15854 } gamebutton_info[NUM_GAME_BUTTONS] =
15857 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15858 GAME_CTRL_ID_STOP, NULL,
15859 TRUE, FALSE, "stop game"
15862 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15863 GAME_CTRL_ID_PAUSE, NULL,
15864 TRUE, FALSE, "pause game"
15867 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15868 GAME_CTRL_ID_PLAY, NULL,
15869 TRUE, FALSE, "play game"
15872 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15873 GAME_CTRL_ID_UNDO, NULL,
15874 TRUE, FALSE, "undo step"
15877 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15878 GAME_CTRL_ID_REDO, NULL,
15879 TRUE, FALSE, "redo step"
15882 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15883 GAME_CTRL_ID_SAVE, NULL,
15884 TRUE, FALSE, "save game"
15887 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15888 GAME_CTRL_ID_PAUSE2, NULL,
15889 TRUE, FALSE, "pause game"
15892 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15893 GAME_CTRL_ID_LOAD, NULL,
15894 TRUE, FALSE, "load game"
15897 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15898 GAME_CTRL_ID_PANEL_STOP, NULL,
15899 FALSE, FALSE, "stop game"
15902 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15903 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15904 FALSE, FALSE, "pause game"
15907 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15908 GAME_CTRL_ID_PANEL_PLAY, NULL,
15909 FALSE, FALSE, "play game"
15912 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15913 GAME_CTRL_ID_TOUCH_STOP, NULL,
15914 FALSE, TRUE, "stop game"
15917 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15918 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15919 FALSE, TRUE, "pause game"
15922 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15923 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15924 TRUE, FALSE, "background music on/off"
15927 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15928 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15929 TRUE, FALSE, "sound loops on/off"
15932 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15933 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15934 TRUE, FALSE, "normal sounds on/off"
15937 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15938 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15939 FALSE, FALSE, "background music on/off"
15942 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15943 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15944 FALSE, FALSE, "sound loops on/off"
15947 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15948 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15949 FALSE, FALSE, "normal sounds on/off"
15953 void CreateGameButtons(void)
15957 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15959 int graphic = gamebutton_info[i].graphic;
15960 struct GraphicInfo *gfx = &graphic_info[graphic];
15961 struct XY *pos = gamebutton_info[i].pos;
15962 struct GadgetInfo *gi;
15965 unsigned int event_mask;
15966 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15967 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15968 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15969 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15970 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15971 int gd_x = gfx->src_x;
15972 int gd_y = gfx->src_y;
15973 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15974 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15975 int gd_xa = gfx->src_x + gfx->active_xoffset;
15976 int gd_ya = gfx->src_y + gfx->active_yoffset;
15977 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15978 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15979 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15980 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15983 if (gfx->bitmap == NULL)
15985 game_gadget[id] = NULL;
15990 if (id == GAME_CTRL_ID_STOP ||
15991 id == GAME_CTRL_ID_PANEL_STOP ||
15992 id == GAME_CTRL_ID_TOUCH_STOP ||
15993 id == GAME_CTRL_ID_PLAY ||
15994 id == GAME_CTRL_ID_PANEL_PLAY ||
15995 id == GAME_CTRL_ID_SAVE ||
15996 id == GAME_CTRL_ID_LOAD)
15998 button_type = GD_TYPE_NORMAL_BUTTON;
16000 event_mask = GD_EVENT_RELEASED;
16002 else if (id == GAME_CTRL_ID_UNDO ||
16003 id == GAME_CTRL_ID_REDO)
16005 button_type = GD_TYPE_NORMAL_BUTTON;
16007 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16011 button_type = GD_TYPE_CHECK_BUTTON;
16012 checked = (gamebutton_info[i].setup_value != NULL ?
16013 *gamebutton_info[i].setup_value : FALSE);
16014 event_mask = GD_EVENT_PRESSED;
16017 gi = CreateGadget(GDI_CUSTOM_ID, id,
16018 GDI_IMAGE_ID, graphic,
16019 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16022 GDI_WIDTH, gfx->width,
16023 GDI_HEIGHT, gfx->height,
16024 GDI_TYPE, button_type,
16025 GDI_STATE, GD_BUTTON_UNPRESSED,
16026 GDI_CHECKED, checked,
16027 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16028 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16029 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16030 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16031 GDI_DIRECT_DRAW, FALSE,
16032 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16033 GDI_EVENT_MASK, event_mask,
16034 GDI_CALLBACK_ACTION, HandleGameButtons,
16038 Fail("cannot create gadget");
16040 game_gadget[id] = gi;
16044 void FreeGameButtons(void)
16048 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16049 FreeGadget(game_gadget[i]);
16052 static void UnmapGameButtonsAtSamePosition(int id)
16056 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16058 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16059 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16060 UnmapGadget(game_gadget[i]);
16063 static void UnmapGameButtonsAtSamePosition_All(void)
16065 if (setup.show_snapshot_buttons)
16067 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16068 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16069 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16073 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16074 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16075 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16077 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16078 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16079 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16083 static void MapGameButtonsAtSamePosition(int id)
16087 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16089 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16090 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16091 MapGadget(game_gadget[i]);
16093 UnmapGameButtonsAtSamePosition_All();
16096 void MapUndoRedoButtons(void)
16098 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16099 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16101 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16102 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16105 void UnmapUndoRedoButtons(void)
16107 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16108 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16110 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16111 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16114 void ModifyPauseButtons(void)
16118 GAME_CTRL_ID_PAUSE,
16119 GAME_CTRL_ID_PAUSE2,
16120 GAME_CTRL_ID_PANEL_PAUSE,
16121 GAME_CTRL_ID_TOUCH_PAUSE,
16126 for (i = 0; ids[i] > -1; i++)
16127 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16130 static void MapGameButtonsExt(boolean on_tape)
16134 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16135 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16136 i != GAME_CTRL_ID_UNDO &&
16137 i != GAME_CTRL_ID_REDO)
16138 MapGadget(game_gadget[i]);
16140 UnmapGameButtonsAtSamePosition_All();
16142 RedrawGameButtons();
16145 static void UnmapGameButtonsExt(boolean on_tape)
16149 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16150 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16151 UnmapGadget(game_gadget[i]);
16154 static void RedrawGameButtonsExt(boolean on_tape)
16158 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16159 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16160 RedrawGadget(game_gadget[i]);
16163 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16168 gi->checked = state;
16171 static void RedrawSoundButtonGadget(int id)
16173 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16174 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16175 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16176 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16177 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16178 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16181 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16182 RedrawGadget(game_gadget[id2]);
16185 void MapGameButtons(void)
16187 MapGameButtonsExt(FALSE);
16190 void UnmapGameButtons(void)
16192 UnmapGameButtonsExt(FALSE);
16195 void RedrawGameButtons(void)
16197 RedrawGameButtonsExt(FALSE);
16200 void MapGameButtonsOnTape(void)
16202 MapGameButtonsExt(TRUE);
16205 void UnmapGameButtonsOnTape(void)
16207 UnmapGameButtonsExt(TRUE);
16210 void RedrawGameButtonsOnTape(void)
16212 RedrawGameButtonsExt(TRUE);
16215 static void GameUndoRedoExt(void)
16217 ClearPlayerAction();
16219 tape.pausing = TRUE;
16222 UpdateAndDisplayGameControlValues();
16224 DrawCompleteVideoDisplay();
16225 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16226 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16227 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16232 static void GameUndo(int steps)
16234 if (!CheckEngineSnapshotList())
16237 LoadEngineSnapshot_Undo(steps);
16242 static void GameRedo(int steps)
16244 if (!CheckEngineSnapshotList())
16247 LoadEngineSnapshot_Redo(steps);
16252 static void HandleGameButtonsExt(int id, int button)
16254 static boolean game_undo_executed = FALSE;
16255 int steps = BUTTON_STEPSIZE(button);
16256 boolean handle_game_buttons =
16257 (game_status == GAME_MODE_PLAYING ||
16258 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16260 if (!handle_game_buttons)
16265 case GAME_CTRL_ID_STOP:
16266 case GAME_CTRL_ID_PANEL_STOP:
16267 case GAME_CTRL_ID_TOUCH_STOP:
16268 if (game_status == GAME_MODE_MAIN)
16274 RequestQuitGame(TRUE);
16278 case GAME_CTRL_ID_PAUSE:
16279 case GAME_CTRL_ID_PAUSE2:
16280 case GAME_CTRL_ID_PANEL_PAUSE:
16281 case GAME_CTRL_ID_TOUCH_PAUSE:
16282 if (network.enabled && game_status == GAME_MODE_PLAYING)
16285 SendToServer_ContinuePlaying();
16287 SendToServer_PausePlaying();
16290 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16292 game_undo_executed = FALSE;
16296 case GAME_CTRL_ID_PLAY:
16297 case GAME_CTRL_ID_PANEL_PLAY:
16298 if (game_status == GAME_MODE_MAIN)
16300 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16302 else if (tape.pausing)
16304 if (network.enabled)
16305 SendToServer_ContinuePlaying();
16307 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16311 case GAME_CTRL_ID_UNDO:
16312 // Important: When using "save snapshot when collecting an item" mode,
16313 // load last (current) snapshot for first "undo" after pressing "pause"
16314 // (else the last-but-one snapshot would be loaded, because the snapshot
16315 // pointer already points to the last snapshot when pressing "pause",
16316 // which is fine for "every step/move" mode, but not for "every collect")
16317 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16318 !game_undo_executed)
16321 game_undo_executed = TRUE;
16326 case GAME_CTRL_ID_REDO:
16330 case GAME_CTRL_ID_SAVE:
16334 case GAME_CTRL_ID_LOAD:
16338 case SOUND_CTRL_ID_MUSIC:
16339 case SOUND_CTRL_ID_PANEL_MUSIC:
16340 if (setup.sound_music)
16342 setup.sound_music = FALSE;
16346 else if (audio.music_available)
16348 setup.sound = setup.sound_music = TRUE;
16350 SetAudioMode(setup.sound);
16352 if (game_status == GAME_MODE_PLAYING)
16356 RedrawSoundButtonGadget(id);
16360 case SOUND_CTRL_ID_LOOPS:
16361 case SOUND_CTRL_ID_PANEL_LOOPS:
16362 if (setup.sound_loops)
16363 setup.sound_loops = FALSE;
16364 else if (audio.loops_available)
16366 setup.sound = setup.sound_loops = TRUE;
16368 SetAudioMode(setup.sound);
16371 RedrawSoundButtonGadget(id);
16375 case SOUND_CTRL_ID_SIMPLE:
16376 case SOUND_CTRL_ID_PANEL_SIMPLE:
16377 if (setup.sound_simple)
16378 setup.sound_simple = FALSE;
16379 else if (audio.sound_available)
16381 setup.sound = setup.sound_simple = TRUE;
16383 SetAudioMode(setup.sound);
16386 RedrawSoundButtonGadget(id);
16395 static void HandleGameButtons(struct GadgetInfo *gi)
16397 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16400 void HandleSoundButtonKeys(Key key)
16402 if (key == setup.shortcut.sound_simple)
16403 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16404 else if (key == setup.shortcut.sound_loops)
16405 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16406 else if (key == setup.shortcut.sound_music)
16407 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);