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_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
883 RND(element_info[e].ce_value_random_initial))
884 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
885 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
886 RND((c)->delay_random * (c)->delay_frames))
887 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
888 RND((c)->delay_random))
891 #define GET_VALID_RUNTIME_ELEMENT(e) \
892 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
894 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
895 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
896 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
897 (be) + (e) - EL_SELF)
899 #define GET_PLAYER_FROM_BITS(p) \
900 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
902 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
903 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
904 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
905 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
906 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
907 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
908 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
909 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
910 RESOLVED_REFERENCE_ELEMENT(be, e) : \
913 #define CAN_GROW_INTO(e) \
914 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
916 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
917 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
922 (CAN_MOVE_INTO_ACID(e) && \
923 Tile[x][y] == EL_ACID) || \
926 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
927 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
928 (CAN_MOVE_INTO_ACID(e) && \
929 Tile[x][y] == EL_ACID) || \
932 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
933 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
935 (CAN_MOVE_INTO_ACID(e) && \
936 Tile[x][y] == EL_ACID) || \
937 (DONT_COLLIDE_WITH(e) && \
939 !PLAYER_ENEMY_PROTECTED(x, y))))
941 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
942 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
944 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
945 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
947 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
948 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
950 #define ANDROID_CAN_CLONE_FIELD(x, y) \
951 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
952 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
954 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
955 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
957 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
958 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
960 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
961 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
963 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
964 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
966 #define PIG_CAN_ENTER_FIELD(e, x, y) \
967 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
969 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
970 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
971 Tile[x][y] == EL_EM_EXIT_OPEN || \
972 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
973 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
974 IS_FOOD_PENGUIN(Tile[x][y])))
975 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
976 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
978 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
979 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
981 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
982 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
984 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
985 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
986 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
988 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
990 #define CE_ENTER_FIELD_COND(e, x, y) \
991 (!IS_PLAYER(x, y) && \
992 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
994 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
995 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
997 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
998 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1000 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1001 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1002 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1003 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1005 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1007 // game button identifiers
1008 #define GAME_CTRL_ID_STOP 0
1009 #define GAME_CTRL_ID_PAUSE 1
1010 #define GAME_CTRL_ID_PLAY 2
1011 #define GAME_CTRL_ID_UNDO 3
1012 #define GAME_CTRL_ID_REDO 4
1013 #define GAME_CTRL_ID_SAVE 5
1014 #define GAME_CTRL_ID_PAUSE2 6
1015 #define GAME_CTRL_ID_LOAD 7
1016 #define GAME_CTRL_ID_PANEL_STOP 8
1017 #define GAME_CTRL_ID_PANEL_PAUSE 9
1018 #define GAME_CTRL_ID_PANEL_PLAY 10
1019 #define GAME_CTRL_ID_TOUCH_STOP 11
1020 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1021 #define SOUND_CTRL_ID_MUSIC 13
1022 #define SOUND_CTRL_ID_LOOPS 14
1023 #define SOUND_CTRL_ID_SIMPLE 15
1024 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1025 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1026 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1028 #define NUM_GAME_BUTTONS 19
1031 // forward declaration for internal use
1033 static void CreateField(int, int, int);
1035 static void ResetGfxAnimation(int, int);
1037 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1038 static void AdvanceFrameAndPlayerCounters(int);
1040 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1041 static boolean MovePlayer(struct PlayerInfo *, int, int);
1042 static void ScrollPlayer(struct PlayerInfo *, int);
1043 static void ScrollScreen(struct PlayerInfo *, int);
1045 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1046 static boolean DigFieldByCE(int, int, int);
1047 static boolean SnapField(struct PlayerInfo *, int, int);
1048 static boolean DropElement(struct PlayerInfo *);
1050 static void InitBeltMovement(void);
1051 static void CloseAllOpenTimegates(void);
1052 static void CheckGravityMovement(struct PlayerInfo *);
1053 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1054 static void KillPlayerUnlessEnemyProtected(int, int);
1055 static void KillPlayerUnlessExplosionProtected(int, int);
1057 static void TestIfPlayerTouchesCustomElement(int, int);
1058 static void TestIfElementTouchesCustomElement(int, int);
1059 static void TestIfElementHitsCustomElement(int, int, int);
1061 static void HandleElementChange(int, int, int);
1062 static void ExecuteCustomElementAction(int, int, int, int);
1063 static boolean ChangeElement(int, int, int, int);
1065 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1066 #define CheckTriggeredElementChange(x, y, e, ev) \
1067 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1068 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1069 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1070 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1071 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1072 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1073 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1075 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1076 #define CheckElementChange(x, y, e, te, ev) \
1077 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1078 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1079 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1080 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1081 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1083 static void PlayLevelSound(int, int, int);
1084 static void PlayLevelSoundNearest(int, int, int);
1085 static void PlayLevelSoundAction(int, int, int);
1086 static void PlayLevelSoundElementAction(int, int, int, int);
1087 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1088 static void PlayLevelSoundActionIfLoop(int, int, int);
1089 static void StopLevelSoundActionIfLoop(int, int, int);
1090 static void PlayLevelMusic(void);
1091 static void FadeLevelSoundsAndMusic(void);
1093 static void HandleGameButtons(struct GadgetInfo *);
1095 int AmoebaNeighbourNr(int, int);
1096 void AmoebaToDiamond(int, int);
1097 void ContinueMoving(int, int);
1098 void Bang(int, int);
1099 void InitMovDir(int, int);
1100 void InitAmoebaNr(int, int);
1101 int NewHiScore(int);
1103 void TestIfGoodThingHitsBadThing(int, int, int);
1104 void TestIfBadThingHitsGoodThing(int, int, int);
1105 void TestIfPlayerTouchesBadThing(int, int);
1106 void TestIfPlayerRunsIntoBadThing(int, int, int);
1107 void TestIfBadThingTouchesPlayer(int, int);
1108 void TestIfBadThingRunsIntoPlayer(int, int, int);
1109 void TestIfFriendTouchesBadThing(int, int);
1110 void TestIfBadThingTouchesFriend(int, int);
1111 void TestIfBadThingTouchesOtherBadThing(int, int);
1112 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1114 void KillPlayer(struct PlayerInfo *);
1115 void BuryPlayer(struct PlayerInfo *);
1116 void RemovePlayer(struct PlayerInfo *);
1117 void ExitPlayer(struct PlayerInfo *);
1119 static int getInvisibleActiveFromInvisibleElement(int);
1120 static int getInvisibleFromInvisibleActiveElement(int);
1122 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1124 // for detection of endless loops, caused by custom element programming
1125 // (using maximal playfield width x 10 is just a rough approximation)
1126 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1128 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1130 if (recursion_loop_detected) \
1133 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1135 recursion_loop_detected = TRUE; \
1136 recursion_loop_element = (e); \
1139 recursion_loop_depth++; \
1142 #define RECURSION_LOOP_DETECTION_END() \
1144 recursion_loop_depth--; \
1147 static int recursion_loop_depth;
1148 static boolean recursion_loop_detected;
1149 static boolean recursion_loop_element;
1151 static int map_player_action[MAX_PLAYERS];
1154 // ----------------------------------------------------------------------------
1155 // definition of elements that automatically change to other elements after
1156 // a specified time, eventually calling a function when changing
1157 // ----------------------------------------------------------------------------
1159 // forward declaration for changer functions
1160 static void InitBuggyBase(int, int);
1161 static void WarnBuggyBase(int, int);
1163 static void InitTrap(int, int);
1164 static void ActivateTrap(int, int);
1165 static void ChangeActiveTrap(int, int);
1167 static void InitRobotWheel(int, int);
1168 static void RunRobotWheel(int, int);
1169 static void StopRobotWheel(int, int);
1171 static void InitTimegateWheel(int, int);
1172 static void RunTimegateWheel(int, int);
1174 static void InitMagicBallDelay(int, int);
1175 static void ActivateMagicBall(int, int);
1177 struct ChangingElementInfo
1182 void (*pre_change_function)(int x, int y);
1183 void (*change_function)(int x, int y);
1184 void (*post_change_function)(int x, int y);
1187 static struct ChangingElementInfo change_delay_list[] =
1222 EL_STEEL_EXIT_OPENING,
1230 EL_STEEL_EXIT_CLOSING,
1231 EL_STEEL_EXIT_CLOSED,
1254 EL_EM_STEEL_EXIT_OPENING,
1255 EL_EM_STEEL_EXIT_OPEN,
1262 EL_EM_STEEL_EXIT_CLOSING,
1286 EL_SWITCHGATE_OPENING,
1294 EL_SWITCHGATE_CLOSING,
1295 EL_SWITCHGATE_CLOSED,
1302 EL_TIMEGATE_OPENING,
1310 EL_TIMEGATE_CLOSING,
1319 EL_ACID_SPLASH_LEFT,
1327 EL_ACID_SPLASH_RIGHT,
1336 EL_SP_BUGGY_BASE_ACTIVATING,
1343 EL_SP_BUGGY_BASE_ACTIVATING,
1344 EL_SP_BUGGY_BASE_ACTIVE,
1351 EL_SP_BUGGY_BASE_ACTIVE,
1375 EL_ROBOT_WHEEL_ACTIVE,
1383 EL_TIMEGATE_SWITCH_ACTIVE,
1391 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1392 EL_DC_TIMEGATE_SWITCH,
1399 EL_EMC_MAGIC_BALL_ACTIVE,
1400 EL_EMC_MAGIC_BALL_ACTIVE,
1407 EL_EMC_SPRING_BUMPER_ACTIVE,
1408 EL_EMC_SPRING_BUMPER,
1415 EL_DIAGONAL_SHRINKING,
1423 EL_DIAGONAL_GROWING,
1444 int push_delay_fixed, push_delay_random;
1448 { EL_SPRING, 0, 0 },
1449 { EL_BALLOON, 0, 0 },
1451 { EL_SOKOBAN_OBJECT, 2, 0 },
1452 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1453 { EL_SATELLITE, 2, 0 },
1454 { EL_SP_DISK_YELLOW, 2, 0 },
1456 { EL_UNDEFINED, 0, 0 },
1464 move_stepsize_list[] =
1466 { EL_AMOEBA_DROP, 2 },
1467 { EL_AMOEBA_DROPPING, 2 },
1468 { EL_QUICKSAND_FILLING, 1 },
1469 { EL_QUICKSAND_EMPTYING, 1 },
1470 { EL_QUICKSAND_FAST_FILLING, 2 },
1471 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1472 { EL_MAGIC_WALL_FILLING, 2 },
1473 { EL_MAGIC_WALL_EMPTYING, 2 },
1474 { EL_BD_MAGIC_WALL_FILLING, 2 },
1475 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_DC_MAGIC_WALL_FILLING, 2 },
1477 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1479 { EL_UNDEFINED, 0 },
1487 collect_count_list[] =
1490 { EL_BD_DIAMOND, 1 },
1491 { EL_EMERALD_YELLOW, 1 },
1492 { EL_EMERALD_RED, 1 },
1493 { EL_EMERALD_PURPLE, 1 },
1495 { EL_SP_INFOTRON, 1 },
1499 { EL_UNDEFINED, 0 },
1507 access_direction_list[] =
1509 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1510 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1511 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1512 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1513 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1514 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1515 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1516 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1517 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1518 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1519 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1521 { EL_SP_PORT_LEFT, MV_RIGHT },
1522 { EL_SP_PORT_RIGHT, MV_LEFT },
1523 { EL_SP_PORT_UP, MV_DOWN },
1524 { EL_SP_PORT_DOWN, MV_UP },
1525 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1526 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1527 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1528 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1529 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1530 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1531 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1532 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1533 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1534 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1535 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1536 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1537 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1538 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1539 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1541 { EL_UNDEFINED, MV_NONE }
1544 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1546 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1547 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1548 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1549 IS_JUST_CHANGING(x, y))
1551 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1553 // static variables for playfield scan mode (scanning forward or backward)
1554 static int playfield_scan_start_x = 0;
1555 static int playfield_scan_start_y = 0;
1556 static int playfield_scan_delta_x = 1;
1557 static int playfield_scan_delta_y = 1;
1559 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1560 (y) >= 0 && (y) <= lev_fieldy - 1; \
1561 (y) += playfield_scan_delta_y) \
1562 for ((x) = playfield_scan_start_x; \
1563 (x) >= 0 && (x) <= lev_fieldx - 1; \
1564 (x) += playfield_scan_delta_x)
1567 void DEBUG_SetMaximumDynamite(void)
1571 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1572 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1573 local_player->inventory_element[local_player->inventory_size++] =
1578 static void InitPlayfieldScanModeVars(void)
1580 if (game.use_reverse_scan_direction)
1582 playfield_scan_start_x = lev_fieldx - 1;
1583 playfield_scan_start_y = lev_fieldy - 1;
1585 playfield_scan_delta_x = -1;
1586 playfield_scan_delta_y = -1;
1590 playfield_scan_start_x = 0;
1591 playfield_scan_start_y = 0;
1593 playfield_scan_delta_x = 1;
1594 playfield_scan_delta_y = 1;
1598 static void InitPlayfieldScanMode(int mode)
1600 game.use_reverse_scan_direction =
1601 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1603 InitPlayfieldScanModeVars();
1606 static int get_move_delay_from_stepsize(int move_stepsize)
1609 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1611 // make sure that stepsize value is always a power of 2
1612 move_stepsize = (1 << log_2(move_stepsize));
1614 return TILEX / move_stepsize;
1617 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1620 int player_nr = player->index_nr;
1621 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1622 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1624 // do no immediately change move delay -- the player might just be moving
1625 player->move_delay_value_next = move_delay;
1627 // information if player can move must be set separately
1628 player->cannot_move = cannot_move;
1632 player->move_delay = game.initial_move_delay[player_nr];
1633 player->move_delay_value = game.initial_move_delay_value[player_nr];
1635 player->move_delay_value_next = -1;
1637 player->move_delay_reset_counter = 0;
1641 void GetPlayerConfig(void)
1643 GameFrameDelay = setup.game_frame_delay;
1645 if (!audio.sound_available)
1646 setup.sound_simple = FALSE;
1648 if (!audio.loops_available)
1649 setup.sound_loops = FALSE;
1651 if (!audio.music_available)
1652 setup.sound_music = FALSE;
1654 if (!video.fullscreen_available)
1655 setup.fullscreen = FALSE;
1657 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1659 SetAudioMode(setup.sound);
1662 int GetElementFromGroupElement(int element)
1664 if (IS_GROUP_ELEMENT(element))
1666 struct ElementGroupInfo *group = element_info[element].group;
1667 int last_anim_random_frame = gfx.anim_random_frame;
1670 if (group->choice_mode == ANIM_RANDOM)
1671 gfx.anim_random_frame = RND(group->num_elements_resolved);
1673 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1674 group->choice_mode, 0,
1677 if (group->choice_mode == ANIM_RANDOM)
1678 gfx.anim_random_frame = last_anim_random_frame;
1680 group->choice_pos++;
1682 element = group->element_resolved[element_pos];
1688 static void IncrementSokobanFieldsNeeded(void)
1690 if (level.sb_fields_needed)
1691 game.sokoban_fields_still_needed++;
1694 static void IncrementSokobanObjectsNeeded(void)
1696 if (level.sb_objects_needed)
1697 game.sokoban_objects_still_needed++;
1700 static void DecrementSokobanFieldsNeeded(void)
1702 if (game.sokoban_fields_still_needed > 0)
1703 game.sokoban_fields_still_needed--;
1706 static void DecrementSokobanObjectsNeeded(void)
1708 if (game.sokoban_objects_still_needed > 0)
1709 game.sokoban_objects_still_needed--;
1712 static void InitPlayerField(int x, int y, int element, boolean init_game)
1714 if (element == EL_SP_MURPHY)
1718 if (stored_player[0].present)
1720 Tile[x][y] = EL_SP_MURPHY_CLONE;
1726 stored_player[0].initial_element = element;
1727 stored_player[0].use_murphy = TRUE;
1729 if (!level.use_artwork_element[0])
1730 stored_player[0].artwork_element = EL_SP_MURPHY;
1733 Tile[x][y] = EL_PLAYER_1;
1739 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1740 int jx = player->jx, jy = player->jy;
1742 player->present = TRUE;
1744 player->block_last_field = (element == EL_SP_MURPHY ?
1745 level.sp_block_last_field :
1746 level.block_last_field);
1748 // ---------- initialize player's last field block delay ------------------
1750 // always start with reliable default value (no adjustment needed)
1751 player->block_delay_adjustment = 0;
1753 // special case 1: in Supaplex, Murphy blocks last field one more frame
1754 if (player->block_last_field && element == EL_SP_MURPHY)
1755 player->block_delay_adjustment = 1;
1757 // special case 2: in game engines before 3.1.1, blocking was different
1758 if (game.use_block_last_field_bug)
1759 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1761 if (!network.enabled || player->connected_network)
1763 player->active = TRUE;
1765 // remove potentially duplicate players
1766 if (StorePlayer[jx][jy] == Tile[x][y])
1767 StorePlayer[jx][jy] = 0;
1769 StorePlayer[x][y] = Tile[x][y];
1771 #if DEBUG_INIT_PLAYER
1772 Debug("game:init:player", "- player element %d activated",
1773 player->element_nr);
1774 Debug("game:init:player", " (local player is %d and currently %s)",
1775 local_player->element_nr,
1776 local_player->active ? "active" : "not active");
1780 Tile[x][y] = EL_EMPTY;
1782 player->jx = player->last_jx = x;
1783 player->jy = player->last_jy = y;
1786 // always check if player was just killed and should be reanimated
1788 int player_nr = GET_PLAYER_NR(element);
1789 struct PlayerInfo *player = &stored_player[player_nr];
1791 if (player->active && player->killed)
1792 player->reanimated = TRUE; // if player was just killed, reanimate him
1796 static void InitField(int x, int y, boolean init_game)
1798 int element = Tile[x][y];
1807 InitPlayerField(x, y, element, init_game);
1810 case EL_SOKOBAN_FIELD_PLAYER:
1811 element = Tile[x][y] = EL_PLAYER_1;
1812 InitField(x, y, init_game);
1814 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1815 InitField(x, y, init_game);
1818 case EL_SOKOBAN_FIELD_EMPTY:
1819 IncrementSokobanFieldsNeeded();
1822 case EL_SOKOBAN_OBJECT:
1823 IncrementSokobanObjectsNeeded();
1827 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1828 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1829 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1831 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1832 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1845 case EL_SPACESHIP_RIGHT:
1846 case EL_SPACESHIP_UP:
1847 case EL_SPACESHIP_LEFT:
1848 case EL_SPACESHIP_DOWN:
1849 case EL_BD_BUTTERFLY:
1850 case EL_BD_BUTTERFLY_RIGHT:
1851 case EL_BD_BUTTERFLY_UP:
1852 case EL_BD_BUTTERFLY_LEFT:
1853 case EL_BD_BUTTERFLY_DOWN:
1855 case EL_BD_FIREFLY_RIGHT:
1856 case EL_BD_FIREFLY_UP:
1857 case EL_BD_FIREFLY_LEFT:
1858 case EL_BD_FIREFLY_DOWN:
1859 case EL_PACMAN_RIGHT:
1861 case EL_PACMAN_LEFT:
1862 case EL_PACMAN_DOWN:
1864 case EL_YAMYAM_LEFT:
1865 case EL_YAMYAM_RIGHT:
1867 case EL_YAMYAM_DOWN:
1868 case EL_DARK_YAMYAM:
1871 case EL_SP_SNIKSNAK:
1872 case EL_SP_ELECTRON:
1878 case EL_SPRING_LEFT:
1879 case EL_SPRING_RIGHT:
1883 case EL_AMOEBA_FULL:
1888 case EL_AMOEBA_DROP:
1889 if (y == lev_fieldy - 1)
1891 Tile[x][y] = EL_AMOEBA_GROWING;
1892 Store[x][y] = EL_AMOEBA_WET;
1896 case EL_DYNAMITE_ACTIVE:
1897 case EL_SP_DISK_RED_ACTIVE:
1898 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1899 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1902 MovDelay[x][y] = 96;
1905 case EL_EM_DYNAMITE_ACTIVE:
1906 MovDelay[x][y] = 32;
1910 game.lights_still_needed++;
1914 game.friends_still_needed++;
1919 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1922 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1923 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1924 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1925 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1926 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1927 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1928 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1929 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1930 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1931 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1932 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1933 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1936 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1937 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1938 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1940 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1942 game.belt_dir[belt_nr] = belt_dir;
1943 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1945 else // more than one switch -- set it like the first switch
1947 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1952 case EL_LIGHT_SWITCH_ACTIVE:
1954 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1957 case EL_INVISIBLE_STEELWALL:
1958 case EL_INVISIBLE_WALL:
1959 case EL_INVISIBLE_SAND:
1960 if (game.light_time_left > 0 ||
1961 game.lenses_time_left > 0)
1962 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1965 case EL_EMC_MAGIC_BALL:
1966 if (game.ball_active)
1967 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1970 case EL_EMC_MAGIC_BALL_SWITCH:
1971 if (game.ball_active)
1972 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1975 case EL_TRIGGER_PLAYER:
1976 case EL_TRIGGER_ELEMENT:
1977 case EL_TRIGGER_CE_VALUE:
1978 case EL_TRIGGER_CE_SCORE:
1980 case EL_ANY_ELEMENT:
1981 case EL_CURRENT_CE_VALUE:
1982 case EL_CURRENT_CE_SCORE:
1999 // reference elements should not be used on the playfield
2000 Tile[x][y] = EL_EMPTY;
2004 if (IS_CUSTOM_ELEMENT(element))
2006 if (CAN_MOVE(element))
2009 if (!element_info[element].use_last_ce_value || init_game)
2010 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2012 else if (IS_GROUP_ELEMENT(element))
2014 Tile[x][y] = GetElementFromGroupElement(element);
2016 InitField(x, y, init_game);
2023 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2026 static void InitField_WithBug1(int x, int y, boolean init_game)
2028 InitField(x, y, init_game);
2030 // not needed to call InitMovDir() -- already done by InitField()!
2031 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2032 CAN_MOVE(Tile[x][y]))
2036 static void InitField_WithBug2(int x, int y, boolean init_game)
2038 int old_element = Tile[x][y];
2040 InitField(x, y, init_game);
2042 // not needed to call InitMovDir() -- already done by InitField()!
2043 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2044 CAN_MOVE(old_element) &&
2045 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2048 /* this case is in fact a combination of not less than three bugs:
2049 first, it calls InitMovDir() for elements that can move, although this is
2050 already done by InitField(); then, it checks the element that was at this
2051 field _before_ the call to InitField() (which can change it); lastly, it
2052 was not called for "mole with direction" elements, which were treated as
2053 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2057 static int get_key_element_from_nr(int key_nr)
2059 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2060 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2061 EL_EM_KEY_1 : EL_KEY_1);
2063 return key_base_element + key_nr;
2066 static int get_next_dropped_element(struct PlayerInfo *player)
2068 return (player->inventory_size > 0 ?
2069 player->inventory_element[player->inventory_size - 1] :
2070 player->inventory_infinite_element != EL_UNDEFINED ?
2071 player->inventory_infinite_element :
2072 player->dynabombs_left > 0 ?
2073 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2077 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2079 // pos >= 0: get element from bottom of the stack;
2080 // pos < 0: get element from top of the stack
2084 int min_inventory_size = -pos;
2085 int inventory_pos = player->inventory_size - min_inventory_size;
2086 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2088 return (player->inventory_size >= min_inventory_size ?
2089 player->inventory_element[inventory_pos] :
2090 player->inventory_infinite_element != EL_UNDEFINED ?
2091 player->inventory_infinite_element :
2092 player->dynabombs_left >= min_dynabombs_left ?
2093 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2098 int min_dynabombs_left = pos + 1;
2099 int min_inventory_size = pos + 1 - player->dynabombs_left;
2100 int inventory_pos = pos - player->dynabombs_left;
2102 return (player->inventory_infinite_element != EL_UNDEFINED ?
2103 player->inventory_infinite_element :
2104 player->dynabombs_left >= min_dynabombs_left ?
2105 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2106 player->inventory_size >= min_inventory_size ?
2107 player->inventory_element[inventory_pos] :
2112 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2114 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2115 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2118 if (gpo1->sort_priority != gpo2->sort_priority)
2119 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2121 compare_result = gpo1->nr - gpo2->nr;
2123 return compare_result;
2126 int getPlayerInventorySize(int player_nr)
2128 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2129 return game_em.ply[player_nr]->dynamite;
2130 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2131 return game_sp.red_disk_count;
2133 return stored_player[player_nr].inventory_size;
2136 static void InitGameControlValues(void)
2140 for (i = 0; game_panel_controls[i].nr != -1; i++)
2142 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2143 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2144 struct TextPosInfo *pos = gpc->pos;
2146 int type = gpc->type;
2150 Error("'game_panel_controls' structure corrupted at %d", i);
2152 Fail("this should not happen -- please debug");
2155 // force update of game controls after initialization
2156 gpc->value = gpc->last_value = -1;
2157 gpc->frame = gpc->last_frame = -1;
2158 gpc->gfx_frame = -1;
2160 // determine panel value width for later calculation of alignment
2161 if (type == TYPE_INTEGER || type == TYPE_STRING)
2163 pos->width = pos->size * getFontWidth(pos->font);
2164 pos->height = getFontHeight(pos->font);
2166 else if (type == TYPE_ELEMENT)
2168 pos->width = pos->size;
2169 pos->height = pos->size;
2172 // fill structure for game panel draw order
2174 gpo->sort_priority = pos->sort_priority;
2177 // sort game panel controls according to sort_priority and control number
2178 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2179 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2182 static void UpdatePlayfieldElementCount(void)
2184 boolean use_element_count = FALSE;
2187 // first check if it is needed at all to calculate playfield element count
2188 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2189 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2190 use_element_count = TRUE;
2192 if (!use_element_count)
2195 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2196 element_info[i].element_count = 0;
2198 SCAN_PLAYFIELD(x, y)
2200 element_info[Tile[x][y]].element_count++;
2203 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2204 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2205 if (IS_IN_GROUP(j, i))
2206 element_info[EL_GROUP_START + i].element_count +=
2207 element_info[j].element_count;
2210 static void UpdateGameControlValues(void)
2213 int time = (game.LevelSolved ?
2214 game.LevelSolved_CountingTime :
2215 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2217 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2218 game_sp.time_played :
2219 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2220 game_mm.energy_left :
2221 game.no_time_limit ? TimePlayed : TimeLeft);
2222 int score = (game.LevelSolved ?
2223 game.LevelSolved_CountingScore :
2224 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2225 game_em.lev->score :
2226 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2231 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2232 game_em.lev->gems_needed :
2233 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2234 game_sp.infotrons_still_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2236 game_mm.kettles_still_needed :
2237 game.gems_still_needed);
2238 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2239 game_em.lev->gems_needed > 0 :
2240 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2241 game_sp.infotrons_still_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2243 game_mm.kettles_still_needed > 0 ||
2244 game_mm.lights_still_needed > 0 :
2245 game.gems_still_needed > 0 ||
2246 game.sokoban_fields_still_needed > 0 ||
2247 game.sokoban_objects_still_needed > 0 ||
2248 game.lights_still_needed > 0);
2249 int health = (game.LevelSolved ?
2250 game.LevelSolved_CountingHealth :
2251 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 MM_HEALTH(game_mm.laser_overload_value) :
2254 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2256 UpdatePlayfieldElementCount();
2258 // update game panel control values
2260 // used instead of "level_nr" (for network games)
2261 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2262 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2264 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2265 for (i = 0; i < MAX_NUM_KEYS; i++)
2266 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2268 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2270 if (game.centered_player_nr == -1)
2272 for (i = 0; i < MAX_PLAYERS; i++)
2274 // only one player in Supaplex game engine
2275 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2278 for (k = 0; k < MAX_NUM_KEYS; k++)
2280 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2282 if (game_em.ply[i]->keys & (1 << k))
2283 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2284 get_key_element_from_nr(k);
2286 else if (stored_player[i].key[k])
2287 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2288 get_key_element_from_nr(k);
2291 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2292 getPlayerInventorySize(i);
2294 if (stored_player[i].num_white_keys > 0)
2295 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2298 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2299 stored_player[i].num_white_keys;
2304 int player_nr = game.centered_player_nr;
2306 for (k = 0; k < MAX_NUM_KEYS; k++)
2308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2310 if (game_em.ply[player_nr]->keys & (1 << k))
2311 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2312 get_key_element_from_nr(k);
2314 else if (stored_player[player_nr].key[k])
2315 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2316 get_key_element_from_nr(k);
2319 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2320 getPlayerInventorySize(player_nr);
2322 if (stored_player[player_nr].num_white_keys > 0)
2323 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2325 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2326 stored_player[player_nr].num_white_keys;
2329 // move keys to leftmost position in game panel, if defined by style settings
2330 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2332 int nr = GAME_PANEL_KEY_1 + i;
2333 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2334 struct TextPosInfo *pos = gpc->pos;
2336 if (gpc->value == EL_EMPTY)
2339 if (pos->style != STYLE_LEFTMOST_POSITION)
2342 // check previous key positions (left from current key)
2343 for (k = 0; k < i; k++)
2345 int nr_new = GAME_PANEL_KEY_1 + k;
2347 if (game_panel_controls[nr_new].value == EL_EMPTY)
2349 game_panel_controls[nr_new].value = gpc->value;
2350 gpc->value = EL_EMPTY;
2357 // try to display as many collected keys as possible in the default game panel
2358 for (i = STD_NUM_KEYS; i < MAX_NUM_KEYS + 1; i++) // EMC keys + white key
2360 int nr = GAME_PANEL_KEY_1 + i;
2361 int emc_key = get_key_element_from_nr(i);
2362 int element = (i < MAX_NUM_KEYS ? emc_key : EL_DC_KEY_WHITE);
2363 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2364 struct TextPosInfo *pos = gpc->pos;
2366 // check if panel position is undefined for a certain EMC key or white key
2367 if (gpc->value != EL_EMPTY && pos->x == -1 && pos->y == -1)
2369 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2371 // 1st try: display key at the same position as normal or EM keys
2372 if (game_panel_controls[nr_new].value == EL_EMPTY)
2374 game_panel_controls[nr_new].value = element;
2378 // 2nd try: display key at the next free position in the key panel
2379 for (k = 0; k < STD_NUM_KEYS; k++)
2381 nr_new = GAME_PANEL_KEY_1 + k;
2383 if (game_panel_controls[nr_new].value == EL_EMPTY)
2385 game_panel_controls[nr_new].value = element;
2394 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2396 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2397 get_inventory_element_from_pos(local_player, i);
2398 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, -i - 1);
2402 game_panel_controls[GAME_PANEL_SCORE].value = score;
2403 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2405 game_panel_controls[GAME_PANEL_TIME].value = time;
2407 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2408 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2409 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2411 if (level.time == 0)
2412 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2416 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2417 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2419 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2421 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2422 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2424 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2425 local_player->shield_normal_time_left;
2426 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2427 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2429 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2430 local_player->shield_deadly_time_left;
2432 game_panel_controls[GAME_PANEL_EXIT].value =
2433 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2435 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2436 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2439 EL_EMC_MAGIC_BALL_SWITCH);
2441 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2442 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2444 game.light_time_left;
2446 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2447 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2449 game.timegate_time_left;
2451 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2452 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2454 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2455 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2456 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2457 game.lenses_time_left;
2459 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2460 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2462 game.magnify_time_left;
2464 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2465 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2466 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2467 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2468 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2469 EL_BALLOON_SWITCH_NONE);
2471 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2472 local_player->dynabomb_count;
2473 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2474 local_player->dynabomb_size;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2476 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2478 game_panel_controls[GAME_PANEL_PENGUINS].value =
2479 game.friends_still_needed;
2481 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2482 game.sokoban_objects_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2484 game.sokoban_fields_still_needed;
2486 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2487 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2489 for (i = 0; i < NUM_BELTS; i++)
2491 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2492 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2493 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2494 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2495 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2498 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2499 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2501 game.magic_wall_time_left;
2503 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2504 local_player->gravity;
2506 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2507 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2509 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2510 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2511 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2512 game.panel.element[i].id : EL_UNDEFINED);
2514 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2515 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2516 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2517 element_info[game.panel.element_count[i].id].element_count : 0);
2519 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2520 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2521 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2522 element_info[game.panel.ce_score[i].id].collect_score : 0);
2524 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2525 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2526 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2527 element_info[game.panel.ce_score_element[i].id].collect_score :
2530 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2531 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2532 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2534 // update game panel control frames
2536 for (i = 0; game_panel_controls[i].nr != -1; i++)
2538 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2540 if (gpc->type == TYPE_ELEMENT)
2542 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2544 int last_anim_random_frame = gfx.anim_random_frame;
2545 int element = gpc->value;
2546 int graphic = el2panelimg(element);
2547 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2548 sync_random_frame : INIT_GFX_RANDOM());
2550 if (gpc->value != gpc->last_value)
2553 gpc->gfx_random = init_gfx_random;
2559 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2560 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2561 gpc->gfx_random = init_gfx_random;
2564 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2565 gfx.anim_random_frame = gpc->gfx_random;
2567 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2568 gpc->gfx_frame = element_info[element].collect_score;
2570 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2573 gfx.anim_random_frame = last_anim_random_frame;
2576 else if (gpc->type == TYPE_GRAPHIC)
2578 if (gpc->graphic != IMG_UNDEFINED)
2580 int last_anim_random_frame = gfx.anim_random_frame;
2581 int graphic = gpc->graphic;
2582 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2583 sync_random_frame : INIT_GFX_RANDOM());
2585 if (gpc->value != gpc->last_value)
2588 gpc->gfx_random = init_gfx_random;
2594 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2595 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2596 gpc->gfx_random = init_gfx_random;
2599 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2600 gfx.anim_random_frame = gpc->gfx_random;
2602 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2604 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2605 gfx.anim_random_frame = last_anim_random_frame;
2611 static void DisplayGameControlValues(void)
2613 boolean redraw_panel = FALSE;
2616 for (i = 0; game_panel_controls[i].nr != -1; i++)
2618 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2620 if (PANEL_DEACTIVATED(gpc->pos))
2623 if (gpc->value == gpc->last_value &&
2624 gpc->frame == gpc->last_frame)
2627 redraw_panel = TRUE;
2633 // copy default game door content to main double buffer
2635 // !!! CHECK AGAIN !!!
2636 SetPanelBackground();
2637 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2638 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2640 // redraw game control buttons
2641 RedrawGameButtons();
2643 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2645 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2647 int nr = game_panel_order[i].nr;
2648 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2649 struct TextPosInfo *pos = gpc->pos;
2650 int type = gpc->type;
2651 int value = gpc->value;
2652 int frame = gpc->frame;
2653 int size = pos->size;
2654 int font = pos->font;
2655 boolean draw_masked = pos->draw_masked;
2656 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2658 if (PANEL_DEACTIVATED(pos))
2661 if (pos->class == get_hash_from_key("extra_panel_items") &&
2662 !setup.prefer_extra_panel_items)
2665 gpc->last_value = value;
2666 gpc->last_frame = frame;
2668 if (type == TYPE_INTEGER)
2670 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2671 nr == GAME_PANEL_TIME)
2673 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2675 if (use_dynamic_size) // use dynamic number of digits
2677 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2678 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2679 int size2 = size1 + 1;
2680 int font1 = pos->font;
2681 int font2 = pos->font_alt;
2683 size = (value < value_change ? size1 : size2);
2684 font = (value < value_change ? font1 : font2);
2688 // correct text size if "digits" is zero or less
2690 size = strlen(int2str(value, size));
2692 // dynamically correct text alignment
2693 pos->width = size * getFontWidth(font);
2695 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2696 int2str(value, size), font, mask_mode);
2698 else if (type == TYPE_ELEMENT)
2700 int element, graphic;
2704 int dst_x = PANEL_XPOS(pos);
2705 int dst_y = PANEL_YPOS(pos);
2707 if (value != EL_UNDEFINED && value != EL_EMPTY)
2710 graphic = el2panelimg(value);
2713 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2714 element, EL_NAME(element), size);
2717 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2720 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2723 width = graphic_info[graphic].width * size / TILESIZE;
2724 height = graphic_info[graphic].height * size / TILESIZE;
2727 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2730 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2734 else if (type == TYPE_GRAPHIC)
2736 int graphic = gpc->graphic;
2737 int graphic_active = gpc->graphic_active;
2741 int dst_x = PANEL_XPOS(pos);
2742 int dst_y = PANEL_YPOS(pos);
2743 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2744 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2746 if (graphic != IMG_UNDEFINED && !skip)
2748 if (pos->style == STYLE_REVERSE)
2749 value = 100 - value;
2751 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2753 if (pos->direction & MV_HORIZONTAL)
2755 width = graphic_info[graphic_active].width * value / 100;
2756 height = graphic_info[graphic_active].height;
2758 if (pos->direction == MV_LEFT)
2760 src_x += graphic_info[graphic_active].width - width;
2761 dst_x += graphic_info[graphic_active].width - width;
2766 width = graphic_info[graphic_active].width;
2767 height = graphic_info[graphic_active].height * value / 100;
2769 if (pos->direction == MV_UP)
2771 src_y += graphic_info[graphic_active].height - height;
2772 dst_y += graphic_info[graphic_active].height - height;
2777 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2780 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2783 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2785 if (pos->direction & MV_HORIZONTAL)
2787 if (pos->direction == MV_RIGHT)
2794 dst_x = PANEL_XPOS(pos);
2797 width = graphic_info[graphic].width - width;
2801 if (pos->direction == MV_DOWN)
2808 dst_y = PANEL_YPOS(pos);
2811 height = graphic_info[graphic].height - height;
2815 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2818 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2822 else if (type == TYPE_STRING)
2824 boolean active = (value != 0);
2825 char *state_normal = "off";
2826 char *state_active = "on";
2827 char *state = (active ? state_active : state_normal);
2828 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2829 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2830 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2831 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2833 if (nr == GAME_PANEL_GRAVITY_STATE)
2835 int font1 = pos->font; // (used for normal state)
2836 int font2 = pos->font_alt; // (used for active state)
2838 font = (active ? font2 : font1);
2847 // don't truncate output if "chars" is zero or less
2850 // dynamically correct text alignment
2851 pos->width = size * getFontWidth(font);
2854 s_cut = getStringCopyN(s, size);
2856 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2857 s_cut, font, mask_mode);
2863 redraw_mask |= REDRAW_DOOR_1;
2866 SetGameStatus(GAME_MODE_PLAYING);
2869 void UpdateAndDisplayGameControlValues(void)
2871 if (tape.deactivate_display)
2874 UpdateGameControlValues();
2875 DisplayGameControlValues();
2879 static void UpdateGameDoorValues(void)
2881 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();
3587 // 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;
3594 // don't play tapes over network
3595 network_playing = (network.enabled && !tape.playing);
3597 for (i = 0; i < MAX_PLAYERS; i++)
3599 struct PlayerInfo *player = &stored_player[i];
3601 player->index_nr = i;
3602 player->index_bit = (1 << i);
3603 player->element_nr = EL_PLAYER_1 + i;
3605 player->present = FALSE;
3606 player->active = FALSE;
3607 player->mapped = FALSE;
3609 player->killed = FALSE;
3610 player->reanimated = FALSE;
3611 player->buried = FALSE;
3614 player->effective_action = 0;
3615 player->programmed_action = 0;
3616 player->snap_action = 0;
3618 player->mouse_action.lx = 0;
3619 player->mouse_action.ly = 0;
3620 player->mouse_action.button = 0;
3621 player->mouse_action.button_hint = 0;
3623 player->effective_mouse_action.lx = 0;
3624 player->effective_mouse_action.ly = 0;
3625 player->effective_mouse_action.button = 0;
3626 player->effective_mouse_action.button_hint = 0;
3628 for (j = 0; j < MAX_NUM_KEYS; j++)
3629 player->key[j] = FALSE;
3631 player->num_white_keys = 0;
3633 player->dynabomb_count = 0;
3634 player->dynabomb_size = 1;
3635 player->dynabombs_left = 0;
3636 player->dynabomb_xl = FALSE;
3638 player->MovDir = initial_move_dir;
3641 player->GfxDir = initial_move_dir;
3642 player->GfxAction = ACTION_DEFAULT;
3644 player->StepFrame = 0;
3646 player->initial_element = player->element_nr;
3647 player->artwork_element =
3648 (level.use_artwork_element[i] ? level.artwork_element[i] :
3649 player->element_nr);
3650 player->use_murphy = FALSE;
3652 player->block_last_field = FALSE; // initialized in InitPlayerField()
3653 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3655 player->gravity = level.initial_player_gravity[i];
3657 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3659 player->actual_frame_counter = 0;
3661 player->step_counter = 0;
3663 player->last_move_dir = initial_move_dir;
3665 player->is_active = FALSE;
3667 player->is_waiting = FALSE;
3668 player->is_moving = FALSE;
3669 player->is_auto_moving = FALSE;
3670 player->is_digging = FALSE;
3671 player->is_snapping = FALSE;
3672 player->is_collecting = FALSE;
3673 player->is_pushing = FALSE;
3674 player->is_switching = FALSE;
3675 player->is_dropping = FALSE;
3676 player->is_dropping_pressed = FALSE;
3678 player->is_bored = FALSE;
3679 player->is_sleeping = FALSE;
3681 player->was_waiting = TRUE;
3682 player->was_moving = FALSE;
3683 player->was_snapping = FALSE;
3684 player->was_dropping = FALSE;
3686 player->force_dropping = FALSE;
3688 player->frame_counter_bored = -1;
3689 player->frame_counter_sleeping = -1;
3691 player->anim_delay_counter = 0;
3692 player->post_delay_counter = 0;
3694 player->dir_waiting = initial_move_dir;
3695 player->action_waiting = ACTION_DEFAULT;
3696 player->last_action_waiting = ACTION_DEFAULT;
3697 player->special_action_bored = ACTION_DEFAULT;
3698 player->special_action_sleeping = ACTION_DEFAULT;
3700 player->switch_x = -1;
3701 player->switch_y = -1;
3703 player->drop_x = -1;
3704 player->drop_y = -1;
3706 player->show_envelope = 0;
3708 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3710 player->push_delay = -1; // initialized when pushing starts
3711 player->push_delay_value = game.initial_push_delay_value;
3713 player->drop_delay = 0;
3714 player->drop_pressed_delay = 0;
3716 player->last_jx = -1;
3717 player->last_jy = -1;
3721 player->shield_normal_time_left = 0;
3722 player->shield_deadly_time_left = 0;
3724 player->inventory_infinite_element = EL_UNDEFINED;
3725 player->inventory_size = 0;
3727 if (level.use_initial_inventory[i])
3729 for (j = 0; j < level.initial_inventory_size[i]; j++)
3731 int element = level.initial_inventory_content[i][j];
3732 int collect_count = element_info[element].collect_count_initial;
3735 if (!IS_CUSTOM_ELEMENT(element))
3738 if (collect_count == 0)
3739 player->inventory_infinite_element = element;
3741 for (k = 0; k < collect_count; k++)
3742 if (player->inventory_size < MAX_INVENTORY_SIZE)
3743 player->inventory_element[player->inventory_size++] = element;
3747 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3748 SnapField(player, 0, 0);
3750 map_player_action[i] = i;
3753 network_player_action_received = FALSE;
3755 // initial null action
3756 if (network_playing)
3757 SendToServer_MovePlayer(MV_NONE);
3762 TimeLeft = level.time;
3765 ScreenMovDir = MV_NONE;
3769 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3771 game.robot_wheel_x = -1;
3772 game.robot_wheel_y = -1;
3777 game.all_players_gone = FALSE;
3779 game.LevelSolved = FALSE;
3780 game.GameOver = FALSE;
3782 game.GamePlayed = !tape.playing;
3784 game.LevelSolved_GameWon = FALSE;
3785 game.LevelSolved_GameEnd = FALSE;
3786 game.LevelSolved_SaveTape = FALSE;
3787 game.LevelSolved_SaveScore = FALSE;
3789 game.LevelSolved_CountingTime = 0;
3790 game.LevelSolved_CountingScore = 0;
3791 game.LevelSolved_CountingHealth = 0;
3793 game.panel.active = TRUE;
3795 game.no_time_limit = (level.time == 0);
3797 game.yamyam_content_nr = 0;
3798 game.robot_wheel_active = FALSE;
3799 game.magic_wall_active = FALSE;
3800 game.magic_wall_time_left = 0;
3801 game.light_time_left = 0;
3802 game.timegate_time_left = 0;
3803 game.switchgate_pos = 0;
3804 game.wind_direction = level.wind_direction_initial;
3807 game.score_final = 0;
3809 game.health = MAX_HEALTH;
3810 game.health_final = MAX_HEALTH;
3812 game.gems_still_needed = level.gems_needed;
3813 game.sokoban_fields_still_needed = 0;
3814 game.sokoban_objects_still_needed = 0;
3815 game.lights_still_needed = 0;
3816 game.players_still_needed = 0;
3817 game.friends_still_needed = 0;
3819 game.lenses_time_left = 0;
3820 game.magnify_time_left = 0;
3822 game.ball_active = level.ball_active_initial;
3823 game.ball_content_nr = 0;
3825 game.explosions_delayed = TRUE;
3827 game.envelope_active = FALSE;
3829 for (i = 0; i < NUM_BELTS; i++)
3831 game.belt_dir[i] = MV_NONE;
3832 game.belt_dir_nr[i] = 3; // not moving, next moving left
3835 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3836 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3838 #if DEBUG_INIT_PLAYER
3839 DebugPrintPlayerStatus("Player status at level initialization");
3842 SCAN_PLAYFIELD(x, y)
3844 Tile[x][y] = Last[x][y] = level.field[x][y];
3845 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3846 ChangeDelay[x][y] = 0;
3847 ChangePage[x][y] = -1;
3848 CustomValue[x][y] = 0; // initialized in InitField()
3849 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3851 WasJustMoving[x][y] = 0;
3852 WasJustFalling[x][y] = 0;
3853 CheckCollision[x][y] = 0;
3854 CheckImpact[x][y] = 0;
3856 Pushed[x][y] = FALSE;
3858 ChangeCount[x][y] = 0;
3859 ChangeEvent[x][y] = -1;
3861 ExplodePhase[x][y] = 0;
3862 ExplodeDelay[x][y] = 0;
3863 ExplodeField[x][y] = EX_TYPE_NONE;
3865 RunnerVisit[x][y] = 0;
3866 PlayerVisit[x][y] = 0;
3869 GfxRandom[x][y] = INIT_GFX_RANDOM();
3870 GfxElement[x][y] = EL_UNDEFINED;
3871 GfxAction[x][y] = ACTION_DEFAULT;
3872 GfxDir[x][y] = MV_NONE;
3873 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3876 SCAN_PLAYFIELD(x, y)
3878 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3880 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3882 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3885 InitField(x, y, TRUE);
3887 ResetGfxAnimation(x, y);
3892 for (i = 0; i < MAX_PLAYERS; i++)
3894 struct PlayerInfo *player = &stored_player[i];
3896 // set number of special actions for bored and sleeping animation
3897 player->num_special_action_bored =
3898 get_num_special_action(player->artwork_element,
3899 ACTION_BORING_1, ACTION_BORING_LAST);
3900 player->num_special_action_sleeping =
3901 get_num_special_action(player->artwork_element,
3902 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3905 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3906 emulate_sb ? EMU_SOKOBAN :
3907 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3909 // initialize type of slippery elements
3910 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3912 if (!IS_CUSTOM_ELEMENT(i))
3914 // default: elements slip down either to the left or right randomly
3915 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3917 // SP style elements prefer to slip down on the left side
3918 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3919 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3921 // BD style elements prefer to slip down on the left side
3922 if (game.emulation == EMU_BOULDERDASH)
3923 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3927 // initialize explosion and ignition delay
3928 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3930 if (!IS_CUSTOM_ELEMENT(i))
3933 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3934 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3935 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3936 int last_phase = (num_phase + 1) * delay;
3937 int half_phase = (num_phase / 2) * delay;
3939 element_info[i].explosion_delay = last_phase - 1;
3940 element_info[i].ignition_delay = half_phase;
3942 if (i == EL_BLACK_ORB)
3943 element_info[i].ignition_delay = 1;
3947 // correct non-moving belts to start moving left
3948 for (i = 0; i < NUM_BELTS; i++)
3949 if (game.belt_dir[i] == MV_NONE)
3950 game.belt_dir_nr[i] = 3; // not moving, next moving left
3952 #if USE_NEW_PLAYER_ASSIGNMENTS
3953 // use preferred player also in local single-player mode
3954 if (!network.enabled && !game.team_mode)
3956 int new_index_nr = setup.network_player_nr;
3958 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3960 for (i = 0; i < MAX_PLAYERS; i++)
3961 stored_player[i].connected_locally = FALSE;
3963 stored_player[new_index_nr].connected_locally = TRUE;
3967 for (i = 0; i < MAX_PLAYERS; i++)
3969 stored_player[i].connected = FALSE;
3971 // in network game mode, the local player might not be the first player
3972 if (stored_player[i].connected_locally)
3973 local_player = &stored_player[i];
3976 if (!network.enabled)
3977 local_player->connected = TRUE;
3981 for (i = 0; i < MAX_PLAYERS; i++)
3982 stored_player[i].connected = tape.player_participates[i];
3984 else if (network.enabled)
3986 // add team mode players connected over the network (needed for correct
3987 // assignment of player figures from level to locally playing players)
3989 for (i = 0; i < MAX_PLAYERS; i++)
3990 if (stored_player[i].connected_network)
3991 stored_player[i].connected = TRUE;
3993 else if (game.team_mode)
3995 // try to guess locally connected team mode players (needed for correct
3996 // assignment of player figures from level to locally playing players)
3998 for (i = 0; i < MAX_PLAYERS; i++)
3999 if (setup.input[i].use_joystick ||
4000 setup.input[i].key.left != KSYM_UNDEFINED)
4001 stored_player[i].connected = TRUE;
4004 #if DEBUG_INIT_PLAYER
4005 DebugPrintPlayerStatus("Player status after level initialization");
4008 #if DEBUG_INIT_PLAYER
4009 Debug("game:init:player", "Reassigning players ...");
4012 // check if any connected player was not found in playfield
4013 for (i = 0; i < MAX_PLAYERS; i++)
4015 struct PlayerInfo *player = &stored_player[i];
4017 if (player->connected && !player->present)
4019 struct PlayerInfo *field_player = NULL;
4021 #if DEBUG_INIT_PLAYER
4022 Debug("game:init:player",
4023 "- looking for field player for player %d ...", i + 1);
4026 // assign first free player found that is present in the playfield
4028 // first try: look for unmapped playfield player that is not connected
4029 for (j = 0; j < MAX_PLAYERS; j++)
4030 if (field_player == NULL &&
4031 stored_player[j].present &&
4032 !stored_player[j].mapped &&
4033 !stored_player[j].connected)
4034 field_player = &stored_player[j];
4036 // second try: look for *any* unmapped playfield player
4037 for (j = 0; j < MAX_PLAYERS; j++)
4038 if (field_player == NULL &&
4039 stored_player[j].present &&
4040 !stored_player[j].mapped)
4041 field_player = &stored_player[j];
4043 if (field_player != NULL)
4045 int jx = field_player->jx, jy = field_player->jy;
4047 #if DEBUG_INIT_PLAYER
4048 Debug("game:init:player", "- found player %d",
4049 field_player->index_nr + 1);
4052 player->present = FALSE;
4053 player->active = FALSE;
4055 field_player->present = TRUE;
4056 field_player->active = TRUE;
4059 player->initial_element = field_player->initial_element;
4060 player->artwork_element = field_player->artwork_element;
4062 player->block_last_field = field_player->block_last_field;
4063 player->block_delay_adjustment = field_player->block_delay_adjustment;
4066 StorePlayer[jx][jy] = field_player->element_nr;
4068 field_player->jx = field_player->last_jx = jx;
4069 field_player->jy = field_player->last_jy = jy;
4071 if (local_player == player)
4072 local_player = field_player;
4074 map_player_action[field_player->index_nr] = i;
4076 field_player->mapped = TRUE;
4078 #if DEBUG_INIT_PLAYER
4079 Debug("game:init:player", "- map_player_action[%d] == %d",
4080 field_player->index_nr + 1, i + 1);
4085 if (player->connected && player->present)
4086 player->mapped = TRUE;
4089 #if DEBUG_INIT_PLAYER
4090 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4095 // check if any connected player was not found in playfield
4096 for (i = 0; i < MAX_PLAYERS; i++)
4098 struct PlayerInfo *player = &stored_player[i];
4100 if (player->connected && !player->present)
4102 for (j = 0; j < MAX_PLAYERS; j++)
4104 struct PlayerInfo *field_player = &stored_player[j];
4105 int jx = field_player->jx, jy = field_player->jy;
4107 // assign first free player found that is present in the playfield
4108 if (field_player->present && !field_player->connected)
4110 player->present = TRUE;
4111 player->active = TRUE;
4113 field_player->present = FALSE;
4114 field_player->active = FALSE;
4116 player->initial_element = field_player->initial_element;
4117 player->artwork_element = field_player->artwork_element;
4119 player->block_last_field = field_player->block_last_field;
4120 player->block_delay_adjustment = field_player->block_delay_adjustment;
4122 StorePlayer[jx][jy] = player->element_nr;
4124 player->jx = player->last_jx = jx;
4125 player->jy = player->last_jy = jy;
4135 Debug("game:init:player", "local_player->present == %d",
4136 local_player->present);
4139 // set focus to local player for network games, else to all players
4140 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4141 game.centered_player_nr_next = game.centered_player_nr;
4142 game.set_centered_player = FALSE;
4143 game.set_centered_player_wrap = FALSE;
4145 if (network_playing && tape.recording)
4147 // store client dependent player focus when recording network games
4148 tape.centered_player_nr_next = game.centered_player_nr_next;
4149 tape.set_centered_player = TRUE;
4154 // when playing a tape, eliminate all players who do not participate
4156 #if USE_NEW_PLAYER_ASSIGNMENTS
4158 if (!game.team_mode)
4160 for (i = 0; i < MAX_PLAYERS; i++)
4162 if (stored_player[i].active &&
4163 !tape.player_participates[map_player_action[i]])
4165 struct PlayerInfo *player = &stored_player[i];
4166 int jx = player->jx, jy = player->jy;
4168 #if DEBUG_INIT_PLAYER
4169 Debug("game:init:player", "Removing player %d at (%d, %d)",
4173 player->active = FALSE;
4174 StorePlayer[jx][jy] = 0;
4175 Tile[jx][jy] = EL_EMPTY;
4182 for (i = 0; i < MAX_PLAYERS; i++)
4184 if (stored_player[i].active &&
4185 !tape.player_participates[i])
4187 struct PlayerInfo *player = &stored_player[i];
4188 int jx = player->jx, jy = player->jy;
4190 player->active = FALSE;
4191 StorePlayer[jx][jy] = 0;
4192 Tile[jx][jy] = EL_EMPTY;
4197 else if (!network.enabled && !game.team_mode) // && !tape.playing
4199 // when in single player mode, eliminate all but the local player
4201 for (i = 0; i < MAX_PLAYERS; i++)
4203 struct PlayerInfo *player = &stored_player[i];
4205 if (player->active && player != local_player)
4207 int jx = player->jx, jy = player->jy;
4209 player->active = FALSE;
4210 player->present = FALSE;
4212 StorePlayer[jx][jy] = 0;
4213 Tile[jx][jy] = EL_EMPTY;
4218 for (i = 0; i < MAX_PLAYERS; i++)
4219 if (stored_player[i].active)
4220 game.players_still_needed++;
4222 if (level.solved_by_one_player)
4223 game.players_still_needed = 1;
4225 // when recording the game, store which players take part in the game
4228 #if USE_NEW_PLAYER_ASSIGNMENTS
4229 for (i = 0; i < MAX_PLAYERS; i++)
4230 if (stored_player[i].connected)
4231 tape.player_participates[i] = TRUE;
4233 for (i = 0; i < MAX_PLAYERS; i++)
4234 if (stored_player[i].active)
4235 tape.player_participates[i] = TRUE;
4239 #if DEBUG_INIT_PLAYER
4240 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4243 if (BorderElement == EL_EMPTY)
4246 SBX_Right = lev_fieldx - SCR_FIELDX;
4248 SBY_Lower = lev_fieldy - SCR_FIELDY;
4253 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4255 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4258 if (full_lev_fieldx <= SCR_FIELDX)
4259 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4260 if (full_lev_fieldy <= SCR_FIELDY)
4261 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4263 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4265 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4268 // if local player not found, look for custom element that might create
4269 // the player (make some assumptions about the right custom element)
4270 if (!local_player->present)
4272 int start_x = 0, start_y = 0;
4273 int found_rating = 0;
4274 int found_element = EL_UNDEFINED;
4275 int player_nr = local_player->index_nr;
4277 SCAN_PLAYFIELD(x, y)
4279 int element = Tile[x][y];
4284 if (level.use_start_element[player_nr] &&
4285 level.start_element[player_nr] == element &&
4292 found_element = element;
4295 if (!IS_CUSTOM_ELEMENT(element))
4298 if (CAN_CHANGE(element))
4300 for (i = 0; i < element_info[element].num_change_pages; i++)
4302 // check for player created from custom element as single target
4303 content = element_info[element].change_page[i].target_element;
4304 is_player = ELEM_IS_PLAYER(content);
4306 if (is_player && (found_rating < 3 ||
4307 (found_rating == 3 && element < found_element)))
4313 found_element = element;
4318 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4320 // check for player created from custom element as explosion content
4321 content = element_info[element].content.e[xx][yy];
4322 is_player = ELEM_IS_PLAYER(content);
4324 if (is_player && (found_rating < 2 ||
4325 (found_rating == 2 && element < found_element)))
4327 start_x = x + xx - 1;
4328 start_y = y + yy - 1;
4331 found_element = element;
4334 if (!CAN_CHANGE(element))
4337 for (i = 0; i < element_info[element].num_change_pages; i++)
4339 // check for player created from custom element as extended target
4341 element_info[element].change_page[i].target_content.e[xx][yy];
4343 is_player = ELEM_IS_PLAYER(content);
4345 if (is_player && (found_rating < 1 ||
4346 (found_rating == 1 && element < found_element)))
4348 start_x = x + xx - 1;
4349 start_y = y + yy - 1;
4352 found_element = element;
4358 scroll_x = SCROLL_POSITION_X(start_x);
4359 scroll_y = SCROLL_POSITION_Y(start_y);
4363 scroll_x = SCROLL_POSITION_X(local_player->jx);
4364 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4367 // !!! FIX THIS (START) !!!
4368 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4370 InitGameEngine_EM();
4372 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4374 InitGameEngine_SP();
4376 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4378 InitGameEngine_MM();
4382 DrawLevel(REDRAW_FIELD);
4385 // after drawing the level, correct some elements
4386 if (game.timegate_time_left == 0)
4387 CloseAllOpenTimegates();
4390 // blit playfield from scroll buffer to normal back buffer for fading in
4391 BlitScreenToBitmap(backbuffer);
4392 // !!! FIX THIS (END) !!!
4394 DrawMaskedBorder(fade_mask);
4399 // full screen redraw is required at this point in the following cases:
4400 // - special editor door undrawn when game was started from level editor
4401 // - drawing area (playfield) was changed and has to be removed completely
4402 redraw_mask = REDRAW_ALL;
4406 if (!game.restart_level)
4408 // copy default game door content to main double buffer
4410 // !!! CHECK AGAIN !!!
4411 SetPanelBackground();
4412 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4413 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4416 SetPanelBackground();
4417 SetDrawBackgroundMask(REDRAW_DOOR_1);
4419 UpdateAndDisplayGameControlValues();
4421 if (!game.restart_level)
4427 CreateGameButtons();
4432 // copy actual game door content to door double buffer for OpenDoor()
4433 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4435 OpenDoor(DOOR_OPEN_ALL);
4437 KeyboardAutoRepeatOffUnlessAutoplay();
4439 #if DEBUG_INIT_PLAYER
4440 DebugPrintPlayerStatus("Player status (final)");
4449 if (!game.restart_level && !tape.playing)
4451 LevelStats_incPlayed(level_nr);
4453 SaveLevelSetup_SeriesInfo();
4456 game.restart_level = FALSE;
4457 game.restart_game_message = NULL;
4458 game.request_active = FALSE;
4460 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4461 InitGameActions_MM();
4463 SaveEngineSnapshotToListInitial();
4465 if (!game.restart_level)
4467 PlaySound(SND_GAME_STARTING);
4469 if (setup.sound_music)
4474 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4475 int actual_player_x, int actual_player_y)
4477 // this is used for non-R'n'D game engines to update certain engine values
4479 // needed to determine if sounds are played within the visible screen area
4480 scroll_x = actual_scroll_x;
4481 scroll_y = actual_scroll_y;
4483 // needed to get player position for "follow finger" playing input method
4484 local_player->jx = actual_player_x;
4485 local_player->jy = actual_player_y;
4488 void InitMovDir(int x, int y)
4490 int i, element = Tile[x][y];
4491 static int xy[4][2] =
4498 static int direction[3][4] =
4500 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4501 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4502 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4511 Tile[x][y] = EL_BUG;
4512 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4515 case EL_SPACESHIP_RIGHT:
4516 case EL_SPACESHIP_UP:
4517 case EL_SPACESHIP_LEFT:
4518 case EL_SPACESHIP_DOWN:
4519 Tile[x][y] = EL_SPACESHIP;
4520 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4523 case EL_BD_BUTTERFLY_RIGHT:
4524 case EL_BD_BUTTERFLY_UP:
4525 case EL_BD_BUTTERFLY_LEFT:
4526 case EL_BD_BUTTERFLY_DOWN:
4527 Tile[x][y] = EL_BD_BUTTERFLY;
4528 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4531 case EL_BD_FIREFLY_RIGHT:
4532 case EL_BD_FIREFLY_UP:
4533 case EL_BD_FIREFLY_LEFT:
4534 case EL_BD_FIREFLY_DOWN:
4535 Tile[x][y] = EL_BD_FIREFLY;
4536 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4539 case EL_PACMAN_RIGHT:
4541 case EL_PACMAN_LEFT:
4542 case EL_PACMAN_DOWN:
4543 Tile[x][y] = EL_PACMAN;
4544 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4547 case EL_YAMYAM_LEFT:
4548 case EL_YAMYAM_RIGHT:
4550 case EL_YAMYAM_DOWN:
4551 Tile[x][y] = EL_YAMYAM;
4552 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4555 case EL_SP_SNIKSNAK:
4556 MovDir[x][y] = MV_UP;
4559 case EL_SP_ELECTRON:
4560 MovDir[x][y] = MV_LEFT;
4567 Tile[x][y] = EL_MOLE;
4568 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4571 case EL_SPRING_LEFT:
4572 case EL_SPRING_RIGHT:
4573 Tile[x][y] = EL_SPRING;
4574 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4578 if (IS_CUSTOM_ELEMENT(element))
4580 struct ElementInfo *ei = &element_info[element];
4581 int move_direction_initial = ei->move_direction_initial;
4582 int move_pattern = ei->move_pattern;
4584 if (move_direction_initial == MV_START_PREVIOUS)
4586 if (MovDir[x][y] != MV_NONE)
4589 move_direction_initial = MV_START_AUTOMATIC;
4592 if (move_direction_initial == MV_START_RANDOM)
4593 MovDir[x][y] = 1 << RND(4);
4594 else if (move_direction_initial & MV_ANY_DIRECTION)
4595 MovDir[x][y] = move_direction_initial;
4596 else if (move_pattern == MV_ALL_DIRECTIONS ||
4597 move_pattern == MV_TURNING_LEFT ||
4598 move_pattern == MV_TURNING_RIGHT ||
4599 move_pattern == MV_TURNING_LEFT_RIGHT ||
4600 move_pattern == MV_TURNING_RIGHT_LEFT ||
4601 move_pattern == MV_TURNING_RANDOM)
4602 MovDir[x][y] = 1 << RND(4);
4603 else if (move_pattern == MV_HORIZONTAL)
4604 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4605 else if (move_pattern == MV_VERTICAL)
4606 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4607 else if (move_pattern & MV_ANY_DIRECTION)
4608 MovDir[x][y] = element_info[element].move_pattern;
4609 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4610 move_pattern == MV_ALONG_RIGHT_SIDE)
4612 // use random direction as default start direction
4613 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4614 MovDir[x][y] = 1 << RND(4);
4616 for (i = 0; i < NUM_DIRECTIONS; i++)
4618 int x1 = x + xy[i][0];
4619 int y1 = y + xy[i][1];
4621 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4623 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4624 MovDir[x][y] = direction[0][i];
4626 MovDir[x][y] = direction[1][i];
4635 MovDir[x][y] = 1 << RND(4);
4637 if (element != EL_BUG &&
4638 element != EL_SPACESHIP &&
4639 element != EL_BD_BUTTERFLY &&
4640 element != EL_BD_FIREFLY)
4643 for (i = 0; i < NUM_DIRECTIONS; i++)
4645 int x1 = x + xy[i][0];
4646 int y1 = y + xy[i][1];
4648 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4650 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4652 MovDir[x][y] = direction[0][i];
4655 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4656 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4658 MovDir[x][y] = direction[1][i];
4667 GfxDir[x][y] = MovDir[x][y];
4670 void InitAmoebaNr(int x, int y)
4673 int group_nr = AmoebaNeighbourNr(x, y);
4677 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4679 if (AmoebaCnt[i] == 0)
4687 AmoebaNr[x][y] = group_nr;
4688 AmoebaCnt[group_nr]++;
4689 AmoebaCnt2[group_nr]++;
4692 static void LevelSolved(void)
4694 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4695 game.players_still_needed > 0)
4698 game.LevelSolved = TRUE;
4699 game.GameOver = TRUE;
4701 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4702 game_em.lev->score :
4703 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4706 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4707 MM_HEALTH(game_mm.laser_overload_value) :
4710 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4711 game.LevelSolved_CountingScore = game.score_final;
4712 game.LevelSolved_CountingHealth = game.health_final;
4717 static int time_count_steps;
4718 static int time, time_final;
4719 static int score, score_final;
4720 static int health, health_final;
4721 static int game_over_delay_1 = 0;
4722 static int game_over_delay_2 = 0;
4723 static int game_over_delay_3 = 0;
4724 int game_over_delay_value_1 = 50;
4725 int game_over_delay_value_2 = 25;
4726 int game_over_delay_value_3 = 50;
4728 if (!game.LevelSolved_GameWon)
4732 // do not start end game actions before the player stops moving (to exit)
4733 if (local_player->active && local_player->MovPos)
4736 game.LevelSolved_GameWon = TRUE;
4737 game.LevelSolved_SaveTape = tape.recording;
4738 game.LevelSolved_SaveScore = !tape.playing;
4742 LevelStats_incSolved(level_nr);
4744 SaveLevelSetup_SeriesInfo();
4747 if (tape.auto_play) // tape might already be stopped here
4748 tape.auto_play_level_solved = TRUE;
4752 game_over_delay_1 = 0;
4753 game_over_delay_2 = 0;
4754 game_over_delay_3 = game_over_delay_value_3;
4756 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4757 score = score_final = game.score_final;
4758 health = health_final = game.health_final;
4760 if (level.score[SC_TIME_BONUS] > 0)
4765 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4767 else if (game.no_time_limit && TimePlayed < 999)
4770 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4773 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4775 game_over_delay_1 = game_over_delay_value_1;
4777 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4780 score_final += health * level.score[SC_TIME_BONUS];
4782 game_over_delay_2 = game_over_delay_value_2;
4785 game.score_final = score_final;
4786 game.health_final = health_final;
4789 if (level_editor_test_game)
4792 score = score_final;
4794 game.LevelSolved_CountingTime = time;
4795 game.LevelSolved_CountingScore = score;
4797 game_panel_controls[GAME_PANEL_TIME].value = time;
4798 game_panel_controls[GAME_PANEL_SCORE].value = score;
4800 DisplayGameControlValues();
4803 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4805 // check if last player has left the level
4806 if (game.exit_x >= 0 &&
4809 int x = game.exit_x;
4810 int y = game.exit_y;
4811 int element = Tile[x][y];
4813 // close exit door after last player
4814 if ((game.all_players_gone &&
4815 (element == EL_EXIT_OPEN ||
4816 element == EL_SP_EXIT_OPEN ||
4817 element == EL_STEEL_EXIT_OPEN)) ||
4818 element == EL_EM_EXIT_OPEN ||
4819 element == EL_EM_STEEL_EXIT_OPEN)
4823 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4824 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4825 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4826 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4827 EL_EM_STEEL_EXIT_CLOSING);
4829 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4832 // player disappears
4833 DrawLevelField(x, y);
4836 for (i = 0; i < MAX_PLAYERS; i++)
4838 struct PlayerInfo *player = &stored_player[i];
4840 if (player->present)
4842 RemovePlayer(player);
4844 // player disappears
4845 DrawLevelField(player->jx, player->jy);
4850 PlaySound(SND_GAME_WINNING);
4853 if (game_over_delay_1 > 0)
4855 game_over_delay_1--;
4860 if (time != time_final)
4862 int time_to_go = ABS(time_final - time);
4863 int time_count_dir = (time < time_final ? +1 : -1);
4865 if (time_to_go < time_count_steps)
4866 time_count_steps = 1;
4868 time += time_count_steps * time_count_dir;
4869 score += time_count_steps * level.score[SC_TIME_BONUS];
4871 game.LevelSolved_CountingTime = time;
4872 game.LevelSolved_CountingScore = score;
4874 game_panel_controls[GAME_PANEL_TIME].value = time;
4875 game_panel_controls[GAME_PANEL_SCORE].value = score;
4877 DisplayGameControlValues();
4879 if (time == time_final)
4880 StopSound(SND_GAME_LEVELTIME_BONUS);
4881 else if (setup.sound_loops)
4882 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4884 PlaySound(SND_GAME_LEVELTIME_BONUS);
4889 if (game_over_delay_2 > 0)
4891 game_over_delay_2--;
4896 if (health != health_final)
4898 int health_count_dir = (health < health_final ? +1 : -1);
4900 health += health_count_dir;
4901 score += level.score[SC_TIME_BONUS];
4903 game.LevelSolved_CountingHealth = health;
4904 game.LevelSolved_CountingScore = score;
4906 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4907 game_panel_controls[GAME_PANEL_SCORE].value = score;
4909 DisplayGameControlValues();
4911 if (health == health_final)
4912 StopSound(SND_GAME_LEVELTIME_BONUS);
4913 else if (setup.sound_loops)
4914 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4916 PlaySound(SND_GAME_LEVELTIME_BONUS);
4921 game.panel.active = FALSE;
4923 if (game_over_delay_3 > 0)
4925 game_over_delay_3--;
4935 // used instead of "level_nr" (needed for network games)
4936 int last_level_nr = levelset.level_nr;
4939 game.LevelSolved_GameEnd = TRUE;
4941 if (game.LevelSolved_SaveTape)
4943 // make sure that request dialog to save tape does not open door again
4944 if (!global.use_envelope_request)
4945 CloseDoor(DOOR_CLOSE_1);
4947 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4950 // if no tape is to be saved, close both doors simultaneously
4951 CloseDoor(DOOR_CLOSE_ALL);
4953 if (level_editor_test_game)
4955 SetGameStatus(GAME_MODE_MAIN);
4962 if (!game.LevelSolved_SaveScore)
4964 SetGameStatus(GAME_MODE_MAIN);
4971 if (level_nr == leveldir_current->handicap_level)
4973 leveldir_current->handicap_level++;
4975 SaveLevelSetup_SeriesInfo();
4978 if (setup.increment_levels &&
4979 level_nr < leveldir_current->last_level &&
4982 level_nr++; // advance to next level
4983 TapeErase(); // start with empty tape
4985 if (setup.auto_play_next_level)
4987 LoadLevel(level_nr);
4989 SaveLevelSetup_SeriesInfo();
4993 hi_pos = NewHiScore(last_level_nr);
4995 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4997 SetGameStatus(GAME_MODE_SCORES);
4999 DrawHallOfFame(last_level_nr, hi_pos);
5001 else if (setup.auto_play_next_level && setup.increment_levels &&
5002 last_level_nr < leveldir_current->last_level &&
5005 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5009 SetGameStatus(GAME_MODE_MAIN);
5015 int NewHiScore(int level_nr)
5019 boolean one_score_entry_per_name = !program.many_scores_per_name;
5021 LoadScore(level_nr);
5023 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5024 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5027 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5029 if (game.score_final > highscore[k].Score)
5031 // player has made it to the hall of fame
5033 if (k < MAX_SCORE_ENTRIES - 1)
5035 int m = MAX_SCORE_ENTRIES - 1;
5037 if (one_score_entry_per_name)
5039 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5040 if (strEqual(setup.player_name, highscore[l].Name))
5043 if (m == k) // player's new highscore overwrites his old one
5047 for (l = m; l > k; l--)
5049 strcpy(highscore[l].Name, highscore[l - 1].Name);
5050 highscore[l].Score = highscore[l - 1].Score;
5056 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5057 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5058 highscore[k].Score = game.score_final;
5063 else if (one_score_entry_per_name &&
5064 !strncmp(setup.player_name, highscore[k].Name,
5065 MAX_PLAYER_NAME_LEN))
5066 break; // player already there with a higher score
5070 SaveScore(level_nr);
5075 static int getElementMoveStepsizeExt(int x, int y, int direction)
5077 int element = Tile[x][y];
5078 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5079 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5080 int horiz_move = (dx != 0);
5081 int sign = (horiz_move ? dx : dy);
5082 int step = sign * element_info[element].move_stepsize;
5084 // special values for move stepsize for spring and things on conveyor belt
5087 if (CAN_FALL(element) &&
5088 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5089 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5090 else if (element == EL_SPRING)
5091 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5097 static int getElementMoveStepsize(int x, int y)
5099 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5102 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5104 if (player->GfxAction != action || player->GfxDir != dir)
5106 player->GfxAction = action;
5107 player->GfxDir = dir;
5109 player->StepFrame = 0;
5113 static void ResetGfxFrame(int x, int y)
5115 // profiling showed that "autotest" spends 10~20% of its time in this function
5116 if (DrawingDeactivatedField())
5119 int element = Tile[x][y];
5120 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5122 if (graphic_info[graphic].anim_global_sync)
5123 GfxFrame[x][y] = FrameCounter;
5124 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5125 GfxFrame[x][y] = CustomValue[x][y];
5126 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5127 GfxFrame[x][y] = element_info[element].collect_score;
5128 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5129 GfxFrame[x][y] = ChangeDelay[x][y];
5132 static void ResetGfxAnimation(int x, int y)
5134 GfxAction[x][y] = ACTION_DEFAULT;
5135 GfxDir[x][y] = MovDir[x][y];
5138 ResetGfxFrame(x, y);
5141 static void ResetRandomAnimationValue(int x, int y)
5143 GfxRandom[x][y] = INIT_GFX_RANDOM();
5146 static void InitMovingField(int x, int y, int direction)
5148 int element = Tile[x][y];
5149 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5150 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5153 boolean is_moving_before, is_moving_after;
5155 // check if element was/is moving or being moved before/after mode change
5156 is_moving_before = (WasJustMoving[x][y] != 0);
5157 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5159 // reset animation only for moving elements which change direction of moving
5160 // or which just started or stopped moving
5161 // (else CEs with property "can move" / "not moving" are reset each frame)
5162 if (is_moving_before != is_moving_after ||
5163 direction != MovDir[x][y])
5164 ResetGfxAnimation(x, y);
5166 MovDir[x][y] = direction;
5167 GfxDir[x][y] = direction;
5169 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5170 direction == MV_DOWN && CAN_FALL(element) ?
5171 ACTION_FALLING : ACTION_MOVING);
5173 // this is needed for CEs with property "can move" / "not moving"
5175 if (is_moving_after)
5177 if (Tile[newx][newy] == EL_EMPTY)
5178 Tile[newx][newy] = EL_BLOCKED;
5180 MovDir[newx][newy] = MovDir[x][y];
5182 CustomValue[newx][newy] = CustomValue[x][y];
5184 GfxFrame[newx][newy] = GfxFrame[x][y];
5185 GfxRandom[newx][newy] = GfxRandom[x][y];
5186 GfxAction[newx][newy] = GfxAction[x][y];
5187 GfxDir[newx][newy] = GfxDir[x][y];
5191 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5193 int direction = MovDir[x][y];
5194 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5195 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5201 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5203 int oldx = x, oldy = y;
5204 int direction = MovDir[x][y];
5206 if (direction == MV_LEFT)
5208 else if (direction == MV_RIGHT)
5210 else if (direction == MV_UP)
5212 else if (direction == MV_DOWN)
5215 *comes_from_x = oldx;
5216 *comes_from_y = oldy;
5219 static int MovingOrBlocked2Element(int x, int y)
5221 int element = Tile[x][y];
5223 if (element == EL_BLOCKED)
5227 Blocked2Moving(x, y, &oldx, &oldy);
5228 return Tile[oldx][oldy];
5234 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5236 // like MovingOrBlocked2Element(), but if element is moving
5237 // and (x,y) is the field the moving element is just leaving,
5238 // return EL_BLOCKED instead of the element value
5239 int element = Tile[x][y];
5241 if (IS_MOVING(x, y))
5243 if (element == EL_BLOCKED)
5247 Blocked2Moving(x, y, &oldx, &oldy);
5248 return Tile[oldx][oldy];
5257 static void RemoveField(int x, int y)
5259 Tile[x][y] = EL_EMPTY;
5265 CustomValue[x][y] = 0;
5268 ChangeDelay[x][y] = 0;
5269 ChangePage[x][y] = -1;
5270 Pushed[x][y] = FALSE;
5272 GfxElement[x][y] = EL_UNDEFINED;
5273 GfxAction[x][y] = ACTION_DEFAULT;
5274 GfxDir[x][y] = MV_NONE;
5277 static void RemoveMovingField(int x, int y)
5279 int oldx = x, oldy = y, newx = x, newy = y;
5280 int element = Tile[x][y];
5281 int next_element = EL_UNDEFINED;
5283 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5286 if (IS_MOVING(x, y))
5288 Moving2Blocked(x, y, &newx, &newy);
5290 if (Tile[newx][newy] != EL_BLOCKED)
5292 // element is moving, but target field is not free (blocked), but
5293 // already occupied by something different (example: acid pool);
5294 // in this case, only remove the moving field, but not the target
5296 RemoveField(oldx, oldy);
5298 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5300 TEST_DrawLevelField(oldx, oldy);
5305 else if (element == EL_BLOCKED)
5307 Blocked2Moving(x, y, &oldx, &oldy);
5308 if (!IS_MOVING(oldx, oldy))
5312 if (element == EL_BLOCKED &&
5313 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5314 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5315 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5316 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5317 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5318 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5319 next_element = get_next_element(Tile[oldx][oldy]);
5321 RemoveField(oldx, oldy);
5322 RemoveField(newx, newy);
5324 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5326 if (next_element != EL_UNDEFINED)
5327 Tile[oldx][oldy] = next_element;
5329 TEST_DrawLevelField(oldx, oldy);
5330 TEST_DrawLevelField(newx, newy);
5333 void DrawDynamite(int x, int y)
5335 int sx = SCREENX(x), sy = SCREENY(y);
5336 int graphic = el2img(Tile[x][y]);
5339 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5342 if (IS_WALKABLE_INSIDE(Back[x][y]))
5346 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5347 else if (Store[x][y])
5348 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5350 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5352 if (Back[x][y] || Store[x][y])
5353 DrawGraphicThruMask(sx, sy, graphic, frame);
5355 DrawGraphic(sx, sy, graphic, frame);
5358 static void CheckDynamite(int x, int y)
5360 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5364 if (MovDelay[x][y] != 0)
5367 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5373 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5378 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5380 boolean num_checked_players = 0;
5383 for (i = 0; i < MAX_PLAYERS; i++)
5385 if (stored_player[i].active)
5387 int sx = stored_player[i].jx;
5388 int sy = stored_player[i].jy;
5390 if (num_checked_players == 0)
5397 *sx1 = MIN(*sx1, sx);
5398 *sy1 = MIN(*sy1, sy);
5399 *sx2 = MAX(*sx2, sx);
5400 *sy2 = MAX(*sy2, sy);
5403 num_checked_players++;
5408 static boolean checkIfAllPlayersFitToScreen_RND(void)
5410 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5412 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5414 return (sx2 - sx1 < SCR_FIELDX &&
5415 sy2 - sy1 < SCR_FIELDY);
5418 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5420 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5422 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5424 *sx = (sx1 + sx2) / 2;
5425 *sy = (sy1 + sy2) / 2;
5428 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5429 boolean center_screen, boolean quick_relocation)
5431 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5432 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5433 boolean no_delay = (tape.warp_forward);
5434 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5435 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5436 int new_scroll_x, new_scroll_y;
5438 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5440 // case 1: quick relocation inside visible screen (without scrolling)
5447 if (!level.shifted_relocation || center_screen)
5449 // relocation _with_ centering of screen
5451 new_scroll_x = SCROLL_POSITION_X(x);
5452 new_scroll_y = SCROLL_POSITION_Y(y);
5456 // relocation _without_ centering of screen
5458 int center_scroll_x = SCROLL_POSITION_X(old_x);
5459 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5460 int offset_x = x + (scroll_x - center_scroll_x);
5461 int offset_y = y + (scroll_y - center_scroll_y);
5463 // for new screen position, apply previous offset to center position
5464 new_scroll_x = SCROLL_POSITION_X(offset_x);
5465 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5468 if (quick_relocation)
5470 // case 2: quick relocation (redraw without visible scrolling)
5472 scroll_x = new_scroll_x;
5473 scroll_y = new_scroll_y;
5480 // case 3: visible relocation (with scrolling to new position)
5482 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5484 SetVideoFrameDelay(wait_delay_value);
5486 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5488 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5489 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5491 if (dx == 0 && dy == 0) // no scrolling needed at all
5497 // set values for horizontal/vertical screen scrolling (half tile size)
5498 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5499 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5500 int pos_x = dx * TILEX / 2;
5501 int pos_y = dy * TILEY / 2;
5502 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5503 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5505 ScrollLevel(dx, dy);
5508 // scroll in two steps of half tile size to make things smoother
5509 BlitScreenToBitmapExt_RND(window, fx, fy);
5511 // scroll second step to align at full tile size
5512 BlitScreenToBitmap(window);
5518 SetVideoFrameDelay(frame_delay_value_old);
5521 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5523 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5524 int player_nr = GET_PLAYER_NR(el_player);
5525 struct PlayerInfo *player = &stored_player[player_nr];
5526 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5527 boolean no_delay = (tape.warp_forward);
5528 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5529 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5530 int old_jx = player->jx;
5531 int old_jy = player->jy;
5532 int old_element = Tile[old_jx][old_jy];
5533 int element = Tile[jx][jy];
5534 boolean player_relocated = (old_jx != jx || old_jy != jy);
5536 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5537 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5538 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5539 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5540 int leave_side_horiz = move_dir_horiz;
5541 int leave_side_vert = move_dir_vert;
5542 int enter_side = enter_side_horiz | enter_side_vert;
5543 int leave_side = leave_side_horiz | leave_side_vert;
5545 if (player->buried) // do not reanimate dead player
5548 if (!player_relocated) // no need to relocate the player
5551 if (IS_PLAYER(jx, jy)) // player already placed at new position
5553 RemoveField(jx, jy); // temporarily remove newly placed player
5554 DrawLevelField(jx, jy);
5557 if (player->present)
5559 while (player->MovPos)
5561 ScrollPlayer(player, SCROLL_GO_ON);
5562 ScrollScreen(NULL, SCROLL_GO_ON);
5564 AdvanceFrameAndPlayerCounters(player->index_nr);
5568 BackToFront_WithFrameDelay(wait_delay_value);
5571 DrawPlayer(player); // needed here only to cleanup last field
5572 DrawLevelField(player->jx, player->jy); // remove player graphic
5574 player->is_moving = FALSE;
5577 if (IS_CUSTOM_ELEMENT(old_element))
5578 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5580 player->index_bit, leave_side);
5582 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5584 player->index_bit, leave_side);
5586 Tile[jx][jy] = el_player;
5587 InitPlayerField(jx, jy, el_player, TRUE);
5589 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5590 possible that the relocation target field did not contain a player element,
5591 but a walkable element, to which the new player was relocated -- in this
5592 case, restore that (already initialized!) element on the player field */
5593 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5595 Tile[jx][jy] = element; // restore previously existing element
5598 // only visually relocate centered player
5599 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5600 FALSE, level.instant_relocation);
5602 TestIfPlayerTouchesBadThing(jx, jy);
5603 TestIfPlayerTouchesCustomElement(jx, jy);
5605 if (IS_CUSTOM_ELEMENT(element))
5606 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5607 player->index_bit, enter_side);
5609 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5610 player->index_bit, enter_side);
5612 if (player->is_switching)
5614 /* ensure that relocation while still switching an element does not cause
5615 a new element to be treated as also switched directly after relocation
5616 (this is important for teleporter switches that teleport the player to
5617 a place where another teleporter switch is in the same direction, which
5618 would then incorrectly be treated as immediately switched before the
5619 direction key that caused the switch was released) */
5621 player->switch_x += jx - old_jx;
5622 player->switch_y += jy - old_jy;
5626 static void Explode(int ex, int ey, int phase, int mode)
5632 // !!! eliminate this variable !!!
5633 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5635 if (game.explosions_delayed)
5637 ExplodeField[ex][ey] = mode;
5641 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5643 int center_element = Tile[ex][ey];
5644 int artwork_element, explosion_element; // set these values later
5646 // remove things displayed in background while burning dynamite
5647 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5650 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5652 // put moving element to center field (and let it explode there)
5653 center_element = MovingOrBlocked2Element(ex, ey);
5654 RemoveMovingField(ex, ey);
5655 Tile[ex][ey] = center_element;
5658 // now "center_element" is finally determined -- set related values now
5659 artwork_element = center_element; // for custom player artwork
5660 explosion_element = center_element; // for custom player artwork
5662 if (IS_PLAYER(ex, ey))
5664 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5666 artwork_element = stored_player[player_nr].artwork_element;
5668 if (level.use_explosion_element[player_nr])
5670 explosion_element = level.explosion_element[player_nr];
5671 artwork_element = explosion_element;
5675 if (mode == EX_TYPE_NORMAL ||
5676 mode == EX_TYPE_CENTER ||
5677 mode == EX_TYPE_CROSS)
5678 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5680 last_phase = element_info[explosion_element].explosion_delay + 1;
5682 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5684 int xx = x - ex + 1;
5685 int yy = y - ey + 1;
5688 if (!IN_LEV_FIELD(x, y) ||
5689 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5690 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5693 element = Tile[x][y];
5695 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5697 element = MovingOrBlocked2Element(x, y);
5699 if (!IS_EXPLOSION_PROOF(element))
5700 RemoveMovingField(x, y);
5703 // indestructible elements can only explode in center (but not flames)
5704 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5705 mode == EX_TYPE_BORDER)) ||
5706 element == EL_FLAMES)
5709 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5710 behaviour, for example when touching a yamyam that explodes to rocks
5711 with active deadly shield, a rock is created under the player !!! */
5712 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5714 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5715 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5716 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5718 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5721 if (IS_ACTIVE_BOMB(element))
5723 // re-activate things under the bomb like gate or penguin
5724 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5731 // save walkable background elements while explosion on same tile
5732 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5733 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5734 Back[x][y] = element;
5736 // ignite explodable elements reached by other explosion
5737 if (element == EL_EXPLOSION)
5738 element = Store2[x][y];
5740 if (AmoebaNr[x][y] &&
5741 (element == EL_AMOEBA_FULL ||
5742 element == EL_BD_AMOEBA ||
5743 element == EL_AMOEBA_GROWING))
5745 AmoebaCnt[AmoebaNr[x][y]]--;
5746 AmoebaCnt2[AmoebaNr[x][y]]--;
5751 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5753 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5755 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5757 if (PLAYERINFO(ex, ey)->use_murphy)
5758 Store[x][y] = EL_EMPTY;
5761 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5762 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5763 else if (ELEM_IS_PLAYER(center_element))
5764 Store[x][y] = EL_EMPTY;
5765 else if (center_element == EL_YAMYAM)
5766 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5767 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5768 Store[x][y] = element_info[center_element].content.e[xx][yy];
5770 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5771 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5772 // otherwise) -- FIX THIS !!!
5773 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5774 Store[x][y] = element_info[element].content.e[1][1];
5776 else if (!CAN_EXPLODE(element))
5777 Store[x][y] = element_info[element].content.e[1][1];
5780 Store[x][y] = EL_EMPTY;
5782 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5783 center_element == EL_AMOEBA_TO_DIAMOND)
5784 Store2[x][y] = element;
5786 Tile[x][y] = EL_EXPLOSION;
5787 GfxElement[x][y] = artwork_element;
5789 ExplodePhase[x][y] = 1;
5790 ExplodeDelay[x][y] = last_phase;
5795 if (center_element == EL_YAMYAM)
5796 game.yamyam_content_nr =
5797 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5809 GfxFrame[x][y] = 0; // restart explosion animation
5811 last_phase = ExplodeDelay[x][y];
5813 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5815 // this can happen if the player leaves an explosion just in time
5816 if (GfxElement[x][y] == EL_UNDEFINED)
5817 GfxElement[x][y] = EL_EMPTY;
5819 border_element = Store2[x][y];
5820 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5821 border_element = StorePlayer[x][y];
5823 if (phase == element_info[border_element].ignition_delay ||
5824 phase == last_phase)
5826 boolean border_explosion = FALSE;
5828 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5829 !PLAYER_EXPLOSION_PROTECTED(x, y))
5831 KillPlayerUnlessExplosionProtected(x, y);
5832 border_explosion = TRUE;
5834 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5836 Tile[x][y] = Store2[x][y];
5839 border_explosion = TRUE;
5841 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5843 AmoebaToDiamond(x, y);
5845 border_explosion = TRUE;
5848 // if an element just explodes due to another explosion (chain-reaction),
5849 // do not immediately end the new explosion when it was the last frame of
5850 // the explosion (as it would be done in the following "if"-statement!)
5851 if (border_explosion && phase == last_phase)
5855 if (phase == last_phase)
5859 element = Tile[x][y] = Store[x][y];
5860 Store[x][y] = Store2[x][y] = 0;
5861 GfxElement[x][y] = EL_UNDEFINED;
5863 // player can escape from explosions and might therefore be still alive
5864 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5865 element <= EL_PLAYER_IS_EXPLODING_4)
5867 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5868 int explosion_element = EL_PLAYER_1 + player_nr;
5869 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5870 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5872 if (level.use_explosion_element[player_nr])
5873 explosion_element = level.explosion_element[player_nr];
5875 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5876 element_info[explosion_element].content.e[xx][yy]);
5879 // restore probably existing indestructible background element
5880 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5881 element = Tile[x][y] = Back[x][y];
5884 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5885 GfxDir[x][y] = MV_NONE;
5886 ChangeDelay[x][y] = 0;
5887 ChangePage[x][y] = -1;
5889 CustomValue[x][y] = 0;
5891 InitField_WithBug2(x, y, FALSE);
5893 TEST_DrawLevelField(x, y);
5895 TestIfElementTouchesCustomElement(x, y);
5897 if (GFX_CRUMBLED(element))
5898 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5900 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5901 StorePlayer[x][y] = 0;
5903 if (ELEM_IS_PLAYER(element))
5904 RelocatePlayer(x, y, element);
5906 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5908 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5909 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5912 TEST_DrawLevelFieldCrumbled(x, y);
5914 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5916 DrawLevelElement(x, y, Back[x][y]);
5917 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5919 else if (IS_WALKABLE_UNDER(Back[x][y]))
5921 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5922 DrawLevelElementThruMask(x, y, Back[x][y]);
5924 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5925 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5929 static void DynaExplode(int ex, int ey)
5932 int dynabomb_element = Tile[ex][ey];
5933 int dynabomb_size = 1;
5934 boolean dynabomb_xl = FALSE;
5935 struct PlayerInfo *player;
5936 static int xy[4][2] =
5944 if (IS_ACTIVE_BOMB(dynabomb_element))
5946 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5947 dynabomb_size = player->dynabomb_size;
5948 dynabomb_xl = player->dynabomb_xl;
5949 player->dynabombs_left++;
5952 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5954 for (i = 0; i < NUM_DIRECTIONS; i++)
5956 for (j = 1; j <= dynabomb_size; j++)
5958 int x = ex + j * xy[i][0];
5959 int y = ey + j * xy[i][1];
5962 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5965 element = Tile[x][y];
5967 // do not restart explosions of fields with active bombs
5968 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5971 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5973 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5974 !IS_DIGGABLE(element) && !dynabomb_xl)
5980 void Bang(int x, int y)
5982 int element = MovingOrBlocked2Element(x, y);
5983 int explosion_type = EX_TYPE_NORMAL;
5985 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5987 struct PlayerInfo *player = PLAYERINFO(x, y);
5989 element = Tile[x][y] = player->initial_element;
5991 if (level.use_explosion_element[player->index_nr])
5993 int explosion_element = level.explosion_element[player->index_nr];
5995 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5996 explosion_type = EX_TYPE_CROSS;
5997 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5998 explosion_type = EX_TYPE_CENTER;
6006 case EL_BD_BUTTERFLY:
6009 case EL_DARK_YAMYAM:
6013 RaiseScoreElement(element);
6016 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6017 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6018 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6019 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6020 case EL_DYNABOMB_INCREASE_NUMBER:
6021 case EL_DYNABOMB_INCREASE_SIZE:
6022 case EL_DYNABOMB_INCREASE_POWER:
6023 explosion_type = EX_TYPE_DYNA;
6026 case EL_DC_LANDMINE:
6027 explosion_type = EX_TYPE_CENTER;
6032 case EL_LAMP_ACTIVE:
6033 case EL_AMOEBA_TO_DIAMOND:
6034 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6035 explosion_type = EX_TYPE_CENTER;
6039 if (element_info[element].explosion_type == EXPLODES_CROSS)
6040 explosion_type = EX_TYPE_CROSS;
6041 else if (element_info[element].explosion_type == EXPLODES_1X1)
6042 explosion_type = EX_TYPE_CENTER;
6046 if (explosion_type == EX_TYPE_DYNA)
6049 Explode(x, y, EX_PHASE_START, explosion_type);
6051 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6054 static void SplashAcid(int x, int y)
6056 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6057 (!IN_LEV_FIELD(x - 1, y - 2) ||
6058 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6059 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6061 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6062 (!IN_LEV_FIELD(x + 1, y - 2) ||
6063 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6064 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6066 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6069 static void InitBeltMovement(void)
6071 static int belt_base_element[4] =
6073 EL_CONVEYOR_BELT_1_LEFT,
6074 EL_CONVEYOR_BELT_2_LEFT,
6075 EL_CONVEYOR_BELT_3_LEFT,
6076 EL_CONVEYOR_BELT_4_LEFT
6078 static int belt_base_active_element[4] =
6080 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6081 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6082 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6083 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6088 // set frame order for belt animation graphic according to belt direction
6089 for (i = 0; i < NUM_BELTS; i++)
6093 for (j = 0; j < NUM_BELT_PARTS; j++)
6095 int element = belt_base_active_element[belt_nr] + j;
6096 int graphic_1 = el2img(element);
6097 int graphic_2 = el2panelimg(element);
6099 if (game.belt_dir[i] == MV_LEFT)
6101 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6102 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6106 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6107 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6112 SCAN_PLAYFIELD(x, y)
6114 int element = Tile[x][y];
6116 for (i = 0; i < NUM_BELTS; i++)
6118 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6120 int e_belt_nr = getBeltNrFromBeltElement(element);
6123 if (e_belt_nr == belt_nr)
6125 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6127 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6134 static void ToggleBeltSwitch(int x, int y)
6136 static int belt_base_element[4] =
6138 EL_CONVEYOR_BELT_1_LEFT,
6139 EL_CONVEYOR_BELT_2_LEFT,
6140 EL_CONVEYOR_BELT_3_LEFT,
6141 EL_CONVEYOR_BELT_4_LEFT
6143 static int belt_base_active_element[4] =
6145 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6146 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6147 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6148 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6150 static int belt_base_switch_element[4] =
6152 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6153 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6154 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6155 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6157 static int belt_move_dir[4] =
6165 int element = Tile[x][y];
6166 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6167 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6168 int belt_dir = belt_move_dir[belt_dir_nr];
6171 if (!IS_BELT_SWITCH(element))
6174 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6175 game.belt_dir[belt_nr] = belt_dir;
6177 if (belt_dir_nr == 3)
6180 // set frame order for belt animation graphic according to belt direction
6181 for (i = 0; i < NUM_BELT_PARTS; i++)
6183 int element = belt_base_active_element[belt_nr] + i;
6184 int graphic_1 = el2img(element);
6185 int graphic_2 = el2panelimg(element);
6187 if (belt_dir == MV_LEFT)
6189 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6190 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6194 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6195 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6199 SCAN_PLAYFIELD(xx, yy)
6201 int element = Tile[xx][yy];
6203 if (IS_BELT_SWITCH(element))
6205 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6207 if (e_belt_nr == belt_nr)
6209 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6210 TEST_DrawLevelField(xx, yy);
6213 else if (IS_BELT(element) && belt_dir != MV_NONE)
6215 int e_belt_nr = getBeltNrFromBeltElement(element);
6217 if (e_belt_nr == belt_nr)
6219 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6221 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6222 TEST_DrawLevelField(xx, yy);
6225 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6227 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6229 if (e_belt_nr == belt_nr)
6231 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6233 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6234 TEST_DrawLevelField(xx, yy);
6240 static void ToggleSwitchgateSwitch(int x, int y)
6244 game.switchgate_pos = !game.switchgate_pos;
6246 SCAN_PLAYFIELD(xx, yy)
6248 int element = Tile[xx][yy];
6250 if (element == EL_SWITCHGATE_SWITCH_UP)
6252 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6253 TEST_DrawLevelField(xx, yy);
6255 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6257 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6258 TEST_DrawLevelField(xx, yy);
6260 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6262 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6263 TEST_DrawLevelField(xx, yy);
6265 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6267 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6268 TEST_DrawLevelField(xx, yy);
6270 else if (element == EL_SWITCHGATE_OPEN ||
6271 element == EL_SWITCHGATE_OPENING)
6273 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6275 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6277 else if (element == EL_SWITCHGATE_CLOSED ||
6278 element == EL_SWITCHGATE_CLOSING)
6280 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6282 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6287 static int getInvisibleActiveFromInvisibleElement(int element)
6289 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6290 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6291 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6295 static int getInvisibleFromInvisibleActiveElement(int element)
6297 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6298 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6299 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6303 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6307 SCAN_PLAYFIELD(x, y)
6309 int element = Tile[x][y];
6311 if (element == EL_LIGHT_SWITCH &&
6312 game.light_time_left > 0)
6314 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6315 TEST_DrawLevelField(x, y);
6317 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6318 game.light_time_left == 0)
6320 Tile[x][y] = EL_LIGHT_SWITCH;
6321 TEST_DrawLevelField(x, y);
6323 else if (element == EL_EMC_DRIPPER &&
6324 game.light_time_left > 0)
6326 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6327 TEST_DrawLevelField(x, y);
6329 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6330 game.light_time_left == 0)
6332 Tile[x][y] = EL_EMC_DRIPPER;
6333 TEST_DrawLevelField(x, y);
6335 else if (element == EL_INVISIBLE_STEELWALL ||
6336 element == EL_INVISIBLE_WALL ||
6337 element == EL_INVISIBLE_SAND)
6339 if (game.light_time_left > 0)
6340 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6342 TEST_DrawLevelField(x, y);
6344 // uncrumble neighbour fields, if needed
6345 if (element == EL_INVISIBLE_SAND)
6346 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6348 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6349 element == EL_INVISIBLE_WALL_ACTIVE ||
6350 element == EL_INVISIBLE_SAND_ACTIVE)
6352 if (game.light_time_left == 0)
6353 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6355 TEST_DrawLevelField(x, y);
6357 // re-crumble neighbour fields, if needed
6358 if (element == EL_INVISIBLE_SAND)
6359 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6364 static void RedrawAllInvisibleElementsForLenses(void)
6368 SCAN_PLAYFIELD(x, y)
6370 int element = Tile[x][y];
6372 if (element == EL_EMC_DRIPPER &&
6373 game.lenses_time_left > 0)
6375 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6376 TEST_DrawLevelField(x, y);
6378 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6379 game.lenses_time_left == 0)
6381 Tile[x][y] = EL_EMC_DRIPPER;
6382 TEST_DrawLevelField(x, y);
6384 else if (element == EL_INVISIBLE_STEELWALL ||
6385 element == EL_INVISIBLE_WALL ||
6386 element == EL_INVISIBLE_SAND)
6388 if (game.lenses_time_left > 0)
6389 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6391 TEST_DrawLevelField(x, y);
6393 // uncrumble neighbour fields, if needed
6394 if (element == EL_INVISIBLE_SAND)
6395 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6397 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6398 element == EL_INVISIBLE_WALL_ACTIVE ||
6399 element == EL_INVISIBLE_SAND_ACTIVE)
6401 if (game.lenses_time_left == 0)
6402 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6404 TEST_DrawLevelField(x, y);
6406 // re-crumble neighbour fields, if needed
6407 if (element == EL_INVISIBLE_SAND)
6408 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6413 static void RedrawAllInvisibleElementsForMagnifier(void)
6417 SCAN_PLAYFIELD(x, y)
6419 int element = Tile[x][y];
6421 if (element == EL_EMC_FAKE_GRASS &&
6422 game.magnify_time_left > 0)
6424 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6425 TEST_DrawLevelField(x, y);
6427 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6428 game.magnify_time_left == 0)
6430 Tile[x][y] = EL_EMC_FAKE_GRASS;
6431 TEST_DrawLevelField(x, y);
6433 else if (IS_GATE_GRAY(element) &&
6434 game.magnify_time_left > 0)
6436 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6437 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6438 IS_EM_GATE_GRAY(element) ?
6439 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6440 IS_EMC_GATE_GRAY(element) ?
6441 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6442 IS_DC_GATE_GRAY(element) ?
6443 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6445 TEST_DrawLevelField(x, y);
6447 else if (IS_GATE_GRAY_ACTIVE(element) &&
6448 game.magnify_time_left == 0)
6450 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6451 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6452 IS_EM_GATE_GRAY_ACTIVE(element) ?
6453 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6454 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6455 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6456 IS_DC_GATE_GRAY_ACTIVE(element) ?
6457 EL_DC_GATE_WHITE_GRAY :
6459 TEST_DrawLevelField(x, y);
6464 static void ToggleLightSwitch(int x, int y)
6466 int element = Tile[x][y];
6468 game.light_time_left =
6469 (element == EL_LIGHT_SWITCH ?
6470 level.time_light * FRAMES_PER_SECOND : 0);
6472 RedrawAllLightSwitchesAndInvisibleElements();
6475 static void ActivateTimegateSwitch(int x, int y)
6479 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6481 SCAN_PLAYFIELD(xx, yy)
6483 int element = Tile[xx][yy];
6485 if (element == EL_TIMEGATE_CLOSED ||
6486 element == EL_TIMEGATE_CLOSING)
6488 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6489 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6493 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6495 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6496 TEST_DrawLevelField(xx, yy);
6502 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6503 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6506 static void Impact(int x, int y)
6508 boolean last_line = (y == lev_fieldy - 1);
6509 boolean object_hit = FALSE;
6510 boolean impact = (last_line || object_hit);
6511 int element = Tile[x][y];
6512 int smashed = EL_STEELWALL;
6514 if (!last_line) // check if element below was hit
6516 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6519 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6520 MovDir[x][y + 1] != MV_DOWN ||
6521 MovPos[x][y + 1] <= TILEY / 2));
6523 // do not smash moving elements that left the smashed field in time
6524 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6525 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6528 #if USE_QUICKSAND_IMPACT_BUGFIX
6529 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6531 RemoveMovingField(x, y + 1);
6532 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6533 Tile[x][y + 2] = EL_ROCK;
6534 TEST_DrawLevelField(x, y + 2);
6539 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6541 RemoveMovingField(x, y + 1);
6542 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6543 Tile[x][y + 2] = EL_ROCK;
6544 TEST_DrawLevelField(x, y + 2);
6551 smashed = MovingOrBlocked2Element(x, y + 1);
6553 impact = (last_line || object_hit);
6556 if (!last_line && smashed == EL_ACID) // element falls into acid
6558 SplashAcid(x, y + 1);
6562 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6563 // only reset graphic animation if graphic really changes after impact
6565 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6567 ResetGfxAnimation(x, y);
6568 TEST_DrawLevelField(x, y);
6571 if (impact && CAN_EXPLODE_IMPACT(element))
6576 else if (impact && element == EL_PEARL &&
6577 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6579 ResetGfxAnimation(x, y);
6581 Tile[x][y] = EL_PEARL_BREAKING;
6582 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6585 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6587 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6592 if (impact && element == EL_AMOEBA_DROP)
6594 if (object_hit && IS_PLAYER(x, y + 1))
6595 KillPlayerUnlessEnemyProtected(x, y + 1);
6596 else if (object_hit && smashed == EL_PENGUIN)
6600 Tile[x][y] = EL_AMOEBA_GROWING;
6601 Store[x][y] = EL_AMOEBA_WET;
6603 ResetRandomAnimationValue(x, y);
6608 if (object_hit) // check which object was hit
6610 if ((CAN_PASS_MAGIC_WALL(element) &&
6611 (smashed == EL_MAGIC_WALL ||
6612 smashed == EL_BD_MAGIC_WALL)) ||
6613 (CAN_PASS_DC_MAGIC_WALL(element) &&
6614 smashed == EL_DC_MAGIC_WALL))
6617 int activated_magic_wall =
6618 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6619 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6620 EL_DC_MAGIC_WALL_ACTIVE);
6622 // activate magic wall / mill
6623 SCAN_PLAYFIELD(xx, yy)
6625 if (Tile[xx][yy] == smashed)
6626 Tile[xx][yy] = activated_magic_wall;
6629 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6630 game.magic_wall_active = TRUE;
6632 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6633 SND_MAGIC_WALL_ACTIVATING :
6634 smashed == EL_BD_MAGIC_WALL ?
6635 SND_BD_MAGIC_WALL_ACTIVATING :
6636 SND_DC_MAGIC_WALL_ACTIVATING));
6639 if (IS_PLAYER(x, y + 1))
6641 if (CAN_SMASH_PLAYER(element))
6643 KillPlayerUnlessEnemyProtected(x, y + 1);
6647 else if (smashed == EL_PENGUIN)
6649 if (CAN_SMASH_PLAYER(element))
6655 else if (element == EL_BD_DIAMOND)
6657 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6663 else if (((element == EL_SP_INFOTRON ||
6664 element == EL_SP_ZONK) &&
6665 (smashed == EL_SP_SNIKSNAK ||
6666 smashed == EL_SP_ELECTRON ||
6667 smashed == EL_SP_DISK_ORANGE)) ||
6668 (element == EL_SP_INFOTRON &&
6669 smashed == EL_SP_DISK_YELLOW))
6674 else if (CAN_SMASH_EVERYTHING(element))
6676 if (IS_CLASSIC_ENEMY(smashed) ||
6677 CAN_EXPLODE_SMASHED(smashed))
6682 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6684 if (smashed == EL_LAMP ||
6685 smashed == EL_LAMP_ACTIVE)
6690 else if (smashed == EL_NUT)
6692 Tile[x][y + 1] = EL_NUT_BREAKING;
6693 PlayLevelSound(x, y, SND_NUT_BREAKING);
6694 RaiseScoreElement(EL_NUT);
6697 else if (smashed == EL_PEARL)
6699 ResetGfxAnimation(x, y);
6701 Tile[x][y + 1] = EL_PEARL_BREAKING;
6702 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6705 else if (smashed == EL_DIAMOND)
6707 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6708 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6711 else if (IS_BELT_SWITCH(smashed))
6713 ToggleBeltSwitch(x, y + 1);
6715 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6716 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6717 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6718 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6720 ToggleSwitchgateSwitch(x, y + 1);
6722 else if (smashed == EL_LIGHT_SWITCH ||
6723 smashed == EL_LIGHT_SWITCH_ACTIVE)
6725 ToggleLightSwitch(x, y + 1);
6729 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6731 CheckElementChangeBySide(x, y + 1, smashed, element,
6732 CE_SWITCHED, CH_SIDE_TOP);
6733 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6739 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6744 // play sound of magic wall / mill
6746 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6747 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6748 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6750 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6751 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6752 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6753 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6754 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6755 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6760 // play sound of object that hits the ground
6761 if (last_line || object_hit)
6762 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6765 static void TurnRoundExt(int x, int y)
6777 { 0, 0 }, { 0, 0 }, { 0, 0 },
6782 int left, right, back;
6786 { MV_DOWN, MV_UP, MV_RIGHT },
6787 { MV_UP, MV_DOWN, MV_LEFT },
6789 { MV_LEFT, MV_RIGHT, MV_DOWN },
6793 { MV_RIGHT, MV_LEFT, MV_UP }
6796 int element = Tile[x][y];
6797 int move_pattern = element_info[element].move_pattern;
6799 int old_move_dir = MovDir[x][y];
6800 int left_dir = turn[old_move_dir].left;
6801 int right_dir = turn[old_move_dir].right;
6802 int back_dir = turn[old_move_dir].back;
6804 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6805 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6806 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6807 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6809 int left_x = x + left_dx, left_y = y + left_dy;
6810 int right_x = x + right_dx, right_y = y + right_dy;
6811 int move_x = x + move_dx, move_y = y + move_dy;
6815 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6817 TestIfBadThingTouchesOtherBadThing(x, y);
6819 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6820 MovDir[x][y] = right_dir;
6821 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6822 MovDir[x][y] = left_dir;
6824 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6826 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6829 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6831 TestIfBadThingTouchesOtherBadThing(x, y);
6833 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6834 MovDir[x][y] = left_dir;
6835 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6836 MovDir[x][y] = right_dir;
6838 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6840 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6843 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6845 TestIfBadThingTouchesOtherBadThing(x, y);
6847 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6848 MovDir[x][y] = left_dir;
6849 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6850 MovDir[x][y] = right_dir;
6852 if (MovDir[x][y] != old_move_dir)
6855 else if (element == EL_YAMYAM)
6857 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6858 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6860 if (can_turn_left && can_turn_right)
6861 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6862 else if (can_turn_left)
6863 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6864 else if (can_turn_right)
6865 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6867 MovDir[x][y] = back_dir;
6869 MovDelay[x][y] = 16 + 16 * RND(3);
6871 else if (element == EL_DARK_YAMYAM)
6873 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6875 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6878 if (can_turn_left && can_turn_right)
6879 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6880 else if (can_turn_left)
6881 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6882 else if (can_turn_right)
6883 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6885 MovDir[x][y] = back_dir;
6887 MovDelay[x][y] = 16 + 16 * RND(3);
6889 else if (element == EL_PACMAN)
6891 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6892 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
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] = 6 + RND(40);
6905 else if (element == EL_PIG)
6907 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6908 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6909 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6910 boolean should_turn_left, should_turn_right, should_move_on;
6912 int rnd = RND(rnd_value);
6914 should_turn_left = (can_turn_left &&
6916 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6917 y + back_dy + left_dy)));
6918 should_turn_right = (can_turn_right &&
6920 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6921 y + back_dy + right_dy)));
6922 should_move_on = (can_move_on &&
6925 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6926 y + move_dy + left_dy) ||
6927 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6928 y + move_dy + right_dy)));
6930 if (should_turn_left || should_turn_right || should_move_on)
6932 if (should_turn_left && should_turn_right && should_move_on)
6933 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6934 rnd < 2 * rnd_value / 3 ? right_dir :
6936 else if (should_turn_left && should_turn_right)
6937 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6938 else if (should_turn_left && should_move_on)
6939 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6940 else if (should_turn_right && should_move_on)
6941 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6942 else if (should_turn_left)
6943 MovDir[x][y] = left_dir;
6944 else if (should_turn_right)
6945 MovDir[x][y] = right_dir;
6946 else if (should_move_on)
6947 MovDir[x][y] = old_move_dir;
6949 else if (can_move_on && rnd > rnd_value / 8)
6950 MovDir[x][y] = old_move_dir;
6951 else if (can_turn_left && can_turn_right)
6952 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6953 else if (can_turn_left && rnd > rnd_value / 8)
6954 MovDir[x][y] = left_dir;
6955 else if (can_turn_right && rnd > rnd_value/8)
6956 MovDir[x][y] = right_dir;
6958 MovDir[x][y] = back_dir;
6960 xx = x + move_xy[MovDir[x][y]].dx;
6961 yy = y + move_xy[MovDir[x][y]].dy;
6963 if (!IN_LEV_FIELD(xx, yy) ||
6964 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6965 MovDir[x][y] = old_move_dir;
6969 else if (element == EL_DRAGON)
6971 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6972 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6973 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6975 int rnd = RND(rnd_value);
6977 if (can_move_on && rnd > rnd_value / 8)
6978 MovDir[x][y] = old_move_dir;
6979 else if (can_turn_left && can_turn_right)
6980 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6981 else if (can_turn_left && rnd > rnd_value / 8)
6982 MovDir[x][y] = left_dir;
6983 else if (can_turn_right && rnd > rnd_value / 8)
6984 MovDir[x][y] = right_dir;
6986 MovDir[x][y] = back_dir;
6988 xx = x + move_xy[MovDir[x][y]].dx;
6989 yy = y + move_xy[MovDir[x][y]].dy;
6991 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6992 MovDir[x][y] = old_move_dir;
6996 else if (element == EL_MOLE)
6998 boolean can_move_on =
6999 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7000 IS_AMOEBOID(Tile[move_x][move_y]) ||
7001 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7004 boolean can_turn_left =
7005 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7006 IS_AMOEBOID(Tile[left_x][left_y])));
7008 boolean can_turn_right =
7009 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7010 IS_AMOEBOID(Tile[right_x][right_y])));
7012 if (can_turn_left && can_turn_right)
7013 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7014 else if (can_turn_left)
7015 MovDir[x][y] = left_dir;
7017 MovDir[x][y] = right_dir;
7020 if (MovDir[x][y] != old_move_dir)
7023 else if (element == EL_BALLOON)
7025 MovDir[x][y] = game.wind_direction;
7028 else if (element == EL_SPRING)
7030 if (MovDir[x][y] & MV_HORIZONTAL)
7032 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7033 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7035 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7036 ResetGfxAnimation(move_x, move_y);
7037 TEST_DrawLevelField(move_x, move_y);
7039 MovDir[x][y] = back_dir;
7041 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7042 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7043 MovDir[x][y] = MV_NONE;
7048 else if (element == EL_ROBOT ||
7049 element == EL_SATELLITE ||
7050 element == EL_PENGUIN ||
7051 element == EL_EMC_ANDROID)
7053 int attr_x = -1, attr_y = -1;
7055 if (game.all_players_gone)
7057 attr_x = game.exit_x;
7058 attr_y = game.exit_y;
7064 for (i = 0; i < MAX_PLAYERS; i++)
7066 struct PlayerInfo *player = &stored_player[i];
7067 int jx = player->jx, jy = player->jy;
7069 if (!player->active)
7073 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7081 if (element == EL_ROBOT &&
7082 game.robot_wheel_x >= 0 &&
7083 game.robot_wheel_y >= 0 &&
7084 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7085 game.engine_version < VERSION_IDENT(3,1,0,0)))
7087 attr_x = game.robot_wheel_x;
7088 attr_y = game.robot_wheel_y;
7091 if (element == EL_PENGUIN)
7094 static int xy[4][2] =
7102 for (i = 0; i < NUM_DIRECTIONS; i++)
7104 int ex = x + xy[i][0];
7105 int ey = y + xy[i][1];
7107 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7108 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7109 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7110 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7119 MovDir[x][y] = MV_NONE;
7121 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7122 else if (attr_x > x)
7123 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7125 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7126 else if (attr_y > y)
7127 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7129 if (element == EL_ROBOT)
7133 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7134 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7135 Moving2Blocked(x, y, &newx, &newy);
7137 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7138 MovDelay[x][y] = 8 + 8 * !RND(3);
7140 MovDelay[x][y] = 16;
7142 else if (element == EL_PENGUIN)
7148 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7150 boolean first_horiz = RND(2);
7151 int new_move_dir = MovDir[x][y];
7154 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7155 Moving2Blocked(x, y, &newx, &newy);
7157 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7161 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7162 Moving2Blocked(x, y, &newx, &newy);
7164 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7167 MovDir[x][y] = old_move_dir;
7171 else if (element == EL_SATELLITE)
7177 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7179 boolean first_horiz = RND(2);
7180 int new_move_dir = MovDir[x][y];
7183 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7184 Moving2Blocked(x, y, &newx, &newy);
7186 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7190 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7191 Moving2Blocked(x, y, &newx, &newy);
7193 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7196 MovDir[x][y] = old_move_dir;
7200 else if (element == EL_EMC_ANDROID)
7202 static int check_pos[16] =
7204 -1, // 0 => (invalid)
7207 -1, // 3 => (invalid)
7209 0, // 5 => MV_LEFT | MV_UP
7210 2, // 6 => MV_RIGHT | MV_UP
7211 -1, // 7 => (invalid)
7213 6, // 9 => MV_LEFT | MV_DOWN
7214 4, // 10 => MV_RIGHT | MV_DOWN
7215 -1, // 11 => (invalid)
7216 -1, // 12 => (invalid)
7217 -1, // 13 => (invalid)
7218 -1, // 14 => (invalid)
7219 -1, // 15 => (invalid)
7227 { -1, -1, MV_LEFT | MV_UP },
7229 { +1, -1, MV_RIGHT | MV_UP },
7230 { +1, 0, MV_RIGHT },
7231 { +1, +1, MV_RIGHT | MV_DOWN },
7233 { -1, +1, MV_LEFT | MV_DOWN },
7236 int start_pos, check_order;
7237 boolean can_clone = FALSE;
7240 // check if there is any free field around current position
7241 for (i = 0; i < 8; i++)
7243 int newx = x + check_xy[i].dx;
7244 int newy = y + check_xy[i].dy;
7246 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7254 if (can_clone) // randomly find an element to clone
7258 start_pos = check_pos[RND(8)];
7259 check_order = (RND(2) ? -1 : +1);
7261 for (i = 0; i < 8; i++)
7263 int pos_raw = start_pos + i * check_order;
7264 int pos = (pos_raw + 8) % 8;
7265 int newx = x + check_xy[pos].dx;
7266 int newy = y + check_xy[pos].dy;
7268 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7270 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7271 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7273 Store[x][y] = Tile[newx][newy];
7282 if (can_clone) // randomly find a direction to move
7286 start_pos = check_pos[RND(8)];
7287 check_order = (RND(2) ? -1 : +1);
7289 for (i = 0; i < 8; i++)
7291 int pos_raw = start_pos + i * check_order;
7292 int pos = (pos_raw + 8) % 8;
7293 int newx = x + check_xy[pos].dx;
7294 int newy = y + check_xy[pos].dy;
7295 int new_move_dir = check_xy[pos].dir;
7297 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7299 MovDir[x][y] = new_move_dir;
7300 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7309 if (can_clone) // cloning and moving successful
7312 // cannot clone -- try to move towards player
7314 start_pos = check_pos[MovDir[x][y] & 0x0f];
7315 check_order = (RND(2) ? -1 : +1);
7317 for (i = 0; i < 3; i++)
7319 // first check start_pos, then previous/next or (next/previous) pos
7320 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7321 int pos = (pos_raw + 8) % 8;
7322 int newx = x + check_xy[pos].dx;
7323 int newy = y + check_xy[pos].dy;
7324 int new_move_dir = check_xy[pos].dir;
7326 if (IS_PLAYER(newx, newy))
7329 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7331 MovDir[x][y] = new_move_dir;
7332 MovDelay[x][y] = level.android_move_time * 8 + 1;
7339 else if (move_pattern == MV_TURNING_LEFT ||
7340 move_pattern == MV_TURNING_RIGHT ||
7341 move_pattern == MV_TURNING_LEFT_RIGHT ||
7342 move_pattern == MV_TURNING_RIGHT_LEFT ||
7343 move_pattern == MV_TURNING_RANDOM ||
7344 move_pattern == MV_ALL_DIRECTIONS)
7346 boolean can_turn_left =
7347 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7348 boolean can_turn_right =
7349 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7351 if (element_info[element].move_stepsize == 0) // "not moving"
7354 if (move_pattern == MV_TURNING_LEFT)
7355 MovDir[x][y] = left_dir;
7356 else if (move_pattern == MV_TURNING_RIGHT)
7357 MovDir[x][y] = right_dir;
7358 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7359 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7360 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7361 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7362 else if (move_pattern == MV_TURNING_RANDOM)
7363 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7364 can_turn_right && !can_turn_left ? right_dir :
7365 RND(2) ? left_dir : right_dir);
7366 else if (can_turn_left && can_turn_right)
7367 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7368 else if (can_turn_left)
7369 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7370 else if (can_turn_right)
7371 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7373 MovDir[x][y] = back_dir;
7375 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7377 else if (move_pattern == MV_HORIZONTAL ||
7378 move_pattern == MV_VERTICAL)
7380 if (move_pattern & old_move_dir)
7381 MovDir[x][y] = back_dir;
7382 else if (move_pattern == MV_HORIZONTAL)
7383 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7384 else if (move_pattern == MV_VERTICAL)
7385 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7387 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7389 else if (move_pattern & MV_ANY_DIRECTION)
7391 MovDir[x][y] = move_pattern;
7392 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7394 else if (move_pattern & MV_WIND_DIRECTION)
7396 MovDir[x][y] = game.wind_direction;
7397 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7399 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7401 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7402 MovDir[x][y] = left_dir;
7403 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7404 MovDir[x][y] = right_dir;
7406 if (MovDir[x][y] != old_move_dir)
7407 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7409 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7411 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7412 MovDir[x][y] = right_dir;
7413 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7414 MovDir[x][y] = left_dir;
7416 if (MovDir[x][y] != old_move_dir)
7417 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7419 else if (move_pattern == MV_TOWARDS_PLAYER ||
7420 move_pattern == MV_AWAY_FROM_PLAYER)
7422 int attr_x = -1, attr_y = -1;
7424 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7426 if (game.all_players_gone)
7428 attr_x = game.exit_x;
7429 attr_y = game.exit_y;
7435 for (i = 0; i < MAX_PLAYERS; i++)
7437 struct PlayerInfo *player = &stored_player[i];
7438 int jx = player->jx, jy = player->jy;
7440 if (!player->active)
7444 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7452 MovDir[x][y] = MV_NONE;
7454 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7455 else if (attr_x > x)
7456 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7458 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7459 else if (attr_y > y)
7460 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7462 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7464 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7466 boolean first_horiz = RND(2);
7467 int new_move_dir = MovDir[x][y];
7469 if (element_info[element].move_stepsize == 0) // "not moving"
7471 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7472 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7478 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7479 Moving2Blocked(x, y, &newx, &newy);
7481 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7485 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7486 Moving2Blocked(x, y, &newx, &newy);
7488 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7491 MovDir[x][y] = old_move_dir;
7494 else if (move_pattern == MV_WHEN_PUSHED ||
7495 move_pattern == MV_WHEN_DROPPED)
7497 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7498 MovDir[x][y] = MV_NONE;
7502 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7504 static int test_xy[7][2] =
7514 static int test_dir[7] =
7524 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7525 int move_preference = -1000000; // start with very low preference
7526 int new_move_dir = MV_NONE;
7527 int start_test = RND(4);
7530 for (i = 0; i < NUM_DIRECTIONS; i++)
7532 int move_dir = test_dir[start_test + i];
7533 int move_dir_preference;
7535 xx = x + test_xy[start_test + i][0];
7536 yy = y + test_xy[start_test + i][1];
7538 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7539 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7541 new_move_dir = move_dir;
7546 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7549 move_dir_preference = -1 * RunnerVisit[xx][yy];
7550 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7551 move_dir_preference = PlayerVisit[xx][yy];
7553 if (move_dir_preference > move_preference)
7555 // prefer field that has not been visited for the longest time
7556 move_preference = move_dir_preference;
7557 new_move_dir = move_dir;
7559 else if (move_dir_preference == move_preference &&
7560 move_dir == old_move_dir)
7562 // prefer last direction when all directions are preferred equally
7563 move_preference = move_dir_preference;
7564 new_move_dir = move_dir;
7568 MovDir[x][y] = new_move_dir;
7569 if (old_move_dir != new_move_dir)
7570 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7574 static void TurnRound(int x, int y)
7576 int direction = MovDir[x][y];
7580 GfxDir[x][y] = MovDir[x][y];
7582 if (direction != MovDir[x][y])
7586 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7588 ResetGfxFrame(x, y);
7591 static boolean JustBeingPushed(int x, int y)
7595 for (i = 0; i < MAX_PLAYERS; i++)
7597 struct PlayerInfo *player = &stored_player[i];
7599 if (player->active && player->is_pushing && player->MovPos)
7601 int next_jx = player->jx + (player->jx - player->last_jx);
7602 int next_jy = player->jy + (player->jy - player->last_jy);
7604 if (x == next_jx && y == next_jy)
7612 static void StartMoving(int x, int y)
7614 boolean started_moving = FALSE; // some elements can fall _and_ move
7615 int element = Tile[x][y];
7620 if (MovDelay[x][y] == 0)
7621 GfxAction[x][y] = ACTION_DEFAULT;
7623 if (CAN_FALL(element) && y < lev_fieldy - 1)
7625 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7626 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7627 if (JustBeingPushed(x, y))
7630 if (element == EL_QUICKSAND_FULL)
7632 if (IS_FREE(x, y + 1))
7634 InitMovingField(x, y, MV_DOWN);
7635 started_moving = TRUE;
7637 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7638 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7639 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7640 Store[x][y] = EL_ROCK;
7642 Store[x][y] = EL_ROCK;
7645 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7647 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7649 if (!MovDelay[x][y])
7651 MovDelay[x][y] = TILEY + 1;
7653 ResetGfxAnimation(x, y);
7654 ResetGfxAnimation(x, y + 1);
7659 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7660 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7667 Tile[x][y] = EL_QUICKSAND_EMPTY;
7668 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7669 Store[x][y + 1] = Store[x][y];
7672 PlayLevelSoundAction(x, y, ACTION_FILLING);
7674 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7676 if (!MovDelay[x][y])
7678 MovDelay[x][y] = TILEY + 1;
7680 ResetGfxAnimation(x, y);
7681 ResetGfxAnimation(x, y + 1);
7686 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7687 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7694 Tile[x][y] = EL_QUICKSAND_EMPTY;
7695 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7696 Store[x][y + 1] = Store[x][y];
7699 PlayLevelSoundAction(x, y, ACTION_FILLING);
7702 else if (element == EL_QUICKSAND_FAST_FULL)
7704 if (IS_FREE(x, y + 1))
7706 InitMovingField(x, y, MV_DOWN);
7707 started_moving = TRUE;
7709 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7710 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7711 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7712 Store[x][y] = EL_ROCK;
7714 Store[x][y] = EL_ROCK;
7717 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7719 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7721 if (!MovDelay[x][y])
7723 MovDelay[x][y] = TILEY + 1;
7725 ResetGfxAnimation(x, y);
7726 ResetGfxAnimation(x, y + 1);
7731 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7732 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7739 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7740 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7741 Store[x][y + 1] = Store[x][y];
7744 PlayLevelSoundAction(x, y, ACTION_FILLING);
7746 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7748 if (!MovDelay[x][y])
7750 MovDelay[x][y] = TILEY + 1;
7752 ResetGfxAnimation(x, y);
7753 ResetGfxAnimation(x, y + 1);
7758 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7759 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7766 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7767 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7768 Store[x][y + 1] = Store[x][y];
7771 PlayLevelSoundAction(x, y, ACTION_FILLING);
7774 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7775 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7777 InitMovingField(x, y, MV_DOWN);
7778 started_moving = TRUE;
7780 Tile[x][y] = EL_QUICKSAND_FILLING;
7781 Store[x][y] = element;
7783 PlayLevelSoundAction(x, y, ACTION_FILLING);
7785 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7786 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7788 InitMovingField(x, y, MV_DOWN);
7789 started_moving = TRUE;
7791 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7792 Store[x][y] = element;
7794 PlayLevelSoundAction(x, y, ACTION_FILLING);
7796 else if (element == EL_MAGIC_WALL_FULL)
7798 if (IS_FREE(x, y + 1))
7800 InitMovingField(x, y, MV_DOWN);
7801 started_moving = TRUE;
7803 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7804 Store[x][y] = EL_CHANGED(Store[x][y]);
7806 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7808 if (!MovDelay[x][y])
7809 MovDelay[x][y] = TILEY / 4 + 1;
7818 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7819 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7820 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7824 else if (element == EL_BD_MAGIC_WALL_FULL)
7826 if (IS_FREE(x, y + 1))
7828 InitMovingField(x, y, MV_DOWN);
7829 started_moving = TRUE;
7831 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7832 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7834 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7836 if (!MovDelay[x][y])
7837 MovDelay[x][y] = TILEY / 4 + 1;
7846 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7847 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7848 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7852 else if (element == EL_DC_MAGIC_WALL_FULL)
7854 if (IS_FREE(x, y + 1))
7856 InitMovingField(x, y, MV_DOWN);
7857 started_moving = TRUE;
7859 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7860 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7862 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7864 if (!MovDelay[x][y])
7865 MovDelay[x][y] = TILEY / 4 + 1;
7874 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7875 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7876 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7880 else if ((CAN_PASS_MAGIC_WALL(element) &&
7881 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7882 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7883 (CAN_PASS_DC_MAGIC_WALL(element) &&
7884 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7887 InitMovingField(x, y, MV_DOWN);
7888 started_moving = TRUE;
7891 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7892 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7893 EL_DC_MAGIC_WALL_FILLING);
7894 Store[x][y] = element;
7896 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7898 SplashAcid(x, y + 1);
7900 InitMovingField(x, y, MV_DOWN);
7901 started_moving = TRUE;
7903 Store[x][y] = EL_ACID;
7906 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7907 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7908 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7909 CAN_FALL(element) && WasJustFalling[x][y] &&
7910 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7912 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7913 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7914 (Tile[x][y + 1] == EL_BLOCKED)))
7916 /* this is needed for a special case not covered by calling "Impact()"
7917 from "ContinueMoving()": if an element moves to a tile directly below
7918 another element which was just falling on that tile (which was empty
7919 in the previous frame), the falling element above would just stop
7920 instead of smashing the element below (in previous version, the above
7921 element was just checked for "moving" instead of "falling", resulting
7922 in incorrect smashes caused by horizontal movement of the above
7923 element; also, the case of the player being the element to smash was
7924 simply not covered here... :-/ ) */
7926 CheckCollision[x][y] = 0;
7927 CheckImpact[x][y] = 0;
7931 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7933 if (MovDir[x][y] == MV_NONE)
7935 InitMovingField(x, y, MV_DOWN);
7936 started_moving = TRUE;
7939 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7941 if (WasJustFalling[x][y]) // prevent animation from being restarted
7942 MovDir[x][y] = MV_DOWN;
7944 InitMovingField(x, y, MV_DOWN);
7945 started_moving = TRUE;
7947 else if (element == EL_AMOEBA_DROP)
7949 Tile[x][y] = EL_AMOEBA_GROWING;
7950 Store[x][y] = EL_AMOEBA_WET;
7952 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7953 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7954 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7955 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7957 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7958 (IS_FREE(x - 1, y + 1) ||
7959 Tile[x - 1][y + 1] == EL_ACID));
7960 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7961 (IS_FREE(x + 1, y + 1) ||
7962 Tile[x + 1][y + 1] == EL_ACID));
7963 boolean can_fall_any = (can_fall_left || can_fall_right);
7964 boolean can_fall_both = (can_fall_left && can_fall_right);
7965 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7967 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7969 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7970 can_fall_right = FALSE;
7971 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7972 can_fall_left = FALSE;
7973 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7974 can_fall_right = FALSE;
7975 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7976 can_fall_left = FALSE;
7978 can_fall_any = (can_fall_left || can_fall_right);
7979 can_fall_both = FALSE;
7984 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7985 can_fall_right = FALSE; // slip down on left side
7987 can_fall_left = !(can_fall_right = RND(2));
7989 can_fall_both = FALSE;
7994 // if not determined otherwise, prefer left side for slipping down
7995 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7996 started_moving = TRUE;
7999 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8001 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8002 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8003 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8004 int belt_dir = game.belt_dir[belt_nr];
8006 if ((belt_dir == MV_LEFT && left_is_free) ||
8007 (belt_dir == MV_RIGHT && right_is_free))
8009 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8011 InitMovingField(x, y, belt_dir);
8012 started_moving = TRUE;
8014 Pushed[x][y] = TRUE;
8015 Pushed[nextx][y] = TRUE;
8017 GfxAction[x][y] = ACTION_DEFAULT;
8021 MovDir[x][y] = 0; // if element was moving, stop it
8026 // not "else if" because of elements that can fall and move (EL_SPRING)
8027 if (CAN_MOVE(element) && !started_moving)
8029 int move_pattern = element_info[element].move_pattern;
8032 Moving2Blocked(x, y, &newx, &newy);
8034 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8037 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8038 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8040 WasJustMoving[x][y] = 0;
8041 CheckCollision[x][y] = 0;
8043 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8045 if (Tile[x][y] != element) // element has changed
8049 if (!MovDelay[x][y]) // start new movement phase
8051 // all objects that can change their move direction after each step
8052 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8054 if (element != EL_YAMYAM &&
8055 element != EL_DARK_YAMYAM &&
8056 element != EL_PACMAN &&
8057 !(move_pattern & MV_ANY_DIRECTION) &&
8058 move_pattern != MV_TURNING_LEFT &&
8059 move_pattern != MV_TURNING_RIGHT &&
8060 move_pattern != MV_TURNING_LEFT_RIGHT &&
8061 move_pattern != MV_TURNING_RIGHT_LEFT &&
8062 move_pattern != MV_TURNING_RANDOM)
8066 if (MovDelay[x][y] && (element == EL_BUG ||
8067 element == EL_SPACESHIP ||
8068 element == EL_SP_SNIKSNAK ||
8069 element == EL_SP_ELECTRON ||
8070 element == EL_MOLE))
8071 TEST_DrawLevelField(x, y);
8075 if (MovDelay[x][y]) // wait some time before next movement
8079 if (element == EL_ROBOT ||
8080 element == EL_YAMYAM ||
8081 element == EL_DARK_YAMYAM)
8083 DrawLevelElementAnimationIfNeeded(x, y, element);
8084 PlayLevelSoundAction(x, y, ACTION_WAITING);
8086 else if (element == EL_SP_ELECTRON)
8087 DrawLevelElementAnimationIfNeeded(x, y, element);
8088 else if (element == EL_DRAGON)
8091 int dir = MovDir[x][y];
8092 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8093 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8094 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8095 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8096 dir == MV_UP ? IMG_FLAMES_1_UP :
8097 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8098 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8100 GfxAction[x][y] = ACTION_ATTACKING;
8102 if (IS_PLAYER(x, y))
8103 DrawPlayerField(x, y);
8105 TEST_DrawLevelField(x, y);
8107 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8109 for (i = 1; i <= 3; i++)
8111 int xx = x + i * dx;
8112 int yy = y + i * dy;
8113 int sx = SCREENX(xx);
8114 int sy = SCREENY(yy);
8115 int flame_graphic = graphic + (i - 1);
8117 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8122 int flamed = MovingOrBlocked2Element(xx, yy);
8124 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8127 RemoveMovingField(xx, yy);
8129 ChangeDelay[xx][yy] = 0;
8131 Tile[xx][yy] = EL_FLAMES;
8133 if (IN_SCR_FIELD(sx, sy))
8135 TEST_DrawLevelFieldCrumbled(xx, yy);
8136 DrawGraphic(sx, sy, flame_graphic, frame);
8141 if (Tile[xx][yy] == EL_FLAMES)
8142 Tile[xx][yy] = EL_EMPTY;
8143 TEST_DrawLevelField(xx, yy);
8148 if (MovDelay[x][y]) // element still has to wait some time
8150 PlayLevelSoundAction(x, y, ACTION_WAITING);
8156 // now make next step
8158 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8160 if (DONT_COLLIDE_WITH(element) &&
8161 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8162 !PLAYER_ENEMY_PROTECTED(newx, newy))
8164 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8169 else if (CAN_MOVE_INTO_ACID(element) &&
8170 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8171 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8172 (MovDir[x][y] == MV_DOWN ||
8173 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8175 SplashAcid(newx, newy);
8176 Store[x][y] = EL_ACID;
8178 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8180 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8181 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8182 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8183 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8186 TEST_DrawLevelField(x, y);
8188 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8189 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8190 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8192 game.friends_still_needed--;
8193 if (!game.friends_still_needed &&
8195 game.all_players_gone)
8200 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8202 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8203 TEST_DrawLevelField(newx, newy);
8205 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8207 else if (!IS_FREE(newx, newy))
8209 GfxAction[x][y] = ACTION_WAITING;
8211 if (IS_PLAYER(x, y))
8212 DrawPlayerField(x, y);
8214 TEST_DrawLevelField(x, y);
8219 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8221 if (IS_FOOD_PIG(Tile[newx][newy]))
8223 if (IS_MOVING(newx, newy))
8224 RemoveMovingField(newx, newy);
8227 Tile[newx][newy] = EL_EMPTY;
8228 TEST_DrawLevelField(newx, newy);
8231 PlayLevelSound(x, y, SND_PIG_DIGGING);
8233 else if (!IS_FREE(newx, newy))
8235 if (IS_PLAYER(x, y))
8236 DrawPlayerField(x, y);
8238 TEST_DrawLevelField(x, y);
8243 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8245 if (Store[x][y] != EL_EMPTY)
8247 boolean can_clone = FALSE;
8250 // check if element to clone is still there
8251 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8253 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8261 // cannot clone or target field not free anymore -- do not clone
8262 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8263 Store[x][y] = EL_EMPTY;
8266 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8268 if (IS_MV_DIAGONAL(MovDir[x][y]))
8270 int diagonal_move_dir = MovDir[x][y];
8271 int stored = Store[x][y];
8272 int change_delay = 8;
8275 // android is moving diagonally
8277 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8279 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8280 GfxElement[x][y] = EL_EMC_ANDROID;
8281 GfxAction[x][y] = ACTION_SHRINKING;
8282 GfxDir[x][y] = diagonal_move_dir;
8283 ChangeDelay[x][y] = change_delay;
8285 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8288 DrawLevelGraphicAnimation(x, y, graphic);
8289 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8291 if (Tile[newx][newy] == EL_ACID)
8293 SplashAcid(newx, newy);
8298 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8300 Store[newx][newy] = EL_EMC_ANDROID;
8301 GfxElement[newx][newy] = EL_EMC_ANDROID;
8302 GfxAction[newx][newy] = ACTION_GROWING;
8303 GfxDir[newx][newy] = diagonal_move_dir;
8304 ChangeDelay[newx][newy] = change_delay;
8306 graphic = el_act_dir2img(GfxElement[newx][newy],
8307 GfxAction[newx][newy], GfxDir[newx][newy]);
8309 DrawLevelGraphicAnimation(newx, newy, graphic);
8310 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8316 Tile[newx][newy] = EL_EMPTY;
8317 TEST_DrawLevelField(newx, newy);
8319 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8322 else if (!IS_FREE(newx, newy))
8327 else if (IS_CUSTOM_ELEMENT(element) &&
8328 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8330 if (!DigFieldByCE(newx, newy, element))
8333 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8335 RunnerVisit[x][y] = FrameCounter;
8336 PlayerVisit[x][y] /= 8; // expire player visit path
8339 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8341 if (!IS_FREE(newx, newy))
8343 if (IS_PLAYER(x, y))
8344 DrawPlayerField(x, y);
8346 TEST_DrawLevelField(x, y);
8352 boolean wanna_flame = !RND(10);
8353 int dx = newx - x, dy = newy - y;
8354 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8355 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8356 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8357 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8358 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8359 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8362 IS_CLASSIC_ENEMY(element1) ||
8363 IS_CLASSIC_ENEMY(element2)) &&
8364 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8365 element1 != EL_FLAMES && element2 != EL_FLAMES)
8367 ResetGfxAnimation(x, y);
8368 GfxAction[x][y] = ACTION_ATTACKING;
8370 if (IS_PLAYER(x, y))
8371 DrawPlayerField(x, y);
8373 TEST_DrawLevelField(x, y);
8375 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8377 MovDelay[x][y] = 50;
8379 Tile[newx][newy] = EL_FLAMES;
8380 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8381 Tile[newx1][newy1] = EL_FLAMES;
8382 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8383 Tile[newx2][newy2] = EL_FLAMES;
8389 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8390 Tile[newx][newy] == EL_DIAMOND)
8392 if (IS_MOVING(newx, newy))
8393 RemoveMovingField(newx, newy);
8396 Tile[newx][newy] = EL_EMPTY;
8397 TEST_DrawLevelField(newx, newy);
8400 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8402 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8403 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8405 if (AmoebaNr[newx][newy])
8407 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8408 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8409 Tile[newx][newy] == EL_BD_AMOEBA)
8410 AmoebaCnt[AmoebaNr[newx][newy]]--;
8413 if (IS_MOVING(newx, newy))
8415 RemoveMovingField(newx, newy);
8419 Tile[newx][newy] = EL_EMPTY;
8420 TEST_DrawLevelField(newx, newy);
8423 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8425 else if ((element == EL_PACMAN || element == EL_MOLE)
8426 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8428 if (AmoebaNr[newx][newy])
8430 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8431 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8432 Tile[newx][newy] == EL_BD_AMOEBA)
8433 AmoebaCnt[AmoebaNr[newx][newy]]--;
8436 if (element == EL_MOLE)
8438 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8439 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8441 ResetGfxAnimation(x, y);
8442 GfxAction[x][y] = ACTION_DIGGING;
8443 TEST_DrawLevelField(x, y);
8445 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8447 return; // wait for shrinking amoeba
8449 else // element == EL_PACMAN
8451 Tile[newx][newy] = EL_EMPTY;
8452 TEST_DrawLevelField(newx, newy);
8453 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8456 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8457 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8458 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8460 // wait for shrinking amoeba to completely disappear
8463 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8465 // object was running against a wall
8469 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8470 DrawLevelElementAnimation(x, y, element);
8472 if (DONT_TOUCH(element))
8473 TestIfBadThingTouchesPlayer(x, y);
8478 InitMovingField(x, y, MovDir[x][y]);
8480 PlayLevelSoundAction(x, y, ACTION_MOVING);
8484 ContinueMoving(x, y);
8487 void ContinueMoving(int x, int y)
8489 int element = Tile[x][y];
8490 struct ElementInfo *ei = &element_info[element];
8491 int direction = MovDir[x][y];
8492 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8493 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8494 int newx = x + dx, newy = y + dy;
8495 int stored = Store[x][y];
8496 int stored_new = Store[newx][newy];
8497 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8498 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8499 boolean last_line = (newy == lev_fieldy - 1);
8501 MovPos[x][y] += getElementMoveStepsize(x, y);
8503 if (pushed_by_player) // special case: moving object pushed by player
8504 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8506 if (ABS(MovPos[x][y]) < TILEX)
8508 TEST_DrawLevelField(x, y);
8510 return; // element is still moving
8513 // element reached destination field
8515 Tile[x][y] = EL_EMPTY;
8516 Tile[newx][newy] = element;
8517 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8519 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8521 element = Tile[newx][newy] = EL_ACID;
8523 else if (element == EL_MOLE)
8525 Tile[x][y] = EL_SAND;
8527 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8529 else if (element == EL_QUICKSAND_FILLING)
8531 element = Tile[newx][newy] = get_next_element(element);
8532 Store[newx][newy] = Store[x][y];
8534 else if (element == EL_QUICKSAND_EMPTYING)
8536 Tile[x][y] = get_next_element(element);
8537 element = Tile[newx][newy] = Store[x][y];
8539 else if (element == EL_QUICKSAND_FAST_FILLING)
8541 element = Tile[newx][newy] = get_next_element(element);
8542 Store[newx][newy] = Store[x][y];
8544 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8546 Tile[x][y] = get_next_element(element);
8547 element = Tile[newx][newy] = Store[x][y];
8549 else if (element == EL_MAGIC_WALL_FILLING)
8551 element = Tile[newx][newy] = get_next_element(element);
8552 if (!game.magic_wall_active)
8553 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8554 Store[newx][newy] = Store[x][y];
8556 else if (element == EL_MAGIC_WALL_EMPTYING)
8558 Tile[x][y] = get_next_element(element);
8559 if (!game.magic_wall_active)
8560 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8561 element = Tile[newx][newy] = Store[x][y];
8563 InitField(newx, newy, FALSE);
8565 else if (element == EL_BD_MAGIC_WALL_FILLING)
8567 element = Tile[newx][newy] = get_next_element(element);
8568 if (!game.magic_wall_active)
8569 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8570 Store[newx][newy] = Store[x][y];
8572 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8574 Tile[x][y] = get_next_element(element);
8575 if (!game.magic_wall_active)
8576 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8577 element = Tile[newx][newy] = Store[x][y];
8579 InitField(newx, newy, FALSE);
8581 else if (element == EL_DC_MAGIC_WALL_FILLING)
8583 element = Tile[newx][newy] = get_next_element(element);
8584 if (!game.magic_wall_active)
8585 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8586 Store[newx][newy] = Store[x][y];
8588 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8590 Tile[x][y] = get_next_element(element);
8591 if (!game.magic_wall_active)
8592 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8593 element = Tile[newx][newy] = Store[x][y];
8595 InitField(newx, newy, FALSE);
8597 else if (element == EL_AMOEBA_DROPPING)
8599 Tile[x][y] = get_next_element(element);
8600 element = Tile[newx][newy] = Store[x][y];
8602 else if (element == EL_SOKOBAN_OBJECT)
8605 Tile[x][y] = Back[x][y];
8607 if (Back[newx][newy])
8608 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8610 Back[x][y] = Back[newx][newy] = 0;
8613 Store[x][y] = EL_EMPTY;
8618 MovDelay[newx][newy] = 0;
8620 if (CAN_CHANGE_OR_HAS_ACTION(element))
8622 // copy element change control values to new field
8623 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8624 ChangePage[newx][newy] = ChangePage[x][y];
8625 ChangeCount[newx][newy] = ChangeCount[x][y];
8626 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8629 CustomValue[newx][newy] = CustomValue[x][y];
8631 ChangeDelay[x][y] = 0;
8632 ChangePage[x][y] = -1;
8633 ChangeCount[x][y] = 0;
8634 ChangeEvent[x][y] = -1;
8636 CustomValue[x][y] = 0;
8638 // copy animation control values to new field
8639 GfxFrame[newx][newy] = GfxFrame[x][y];
8640 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8641 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8642 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8644 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8646 // some elements can leave other elements behind after moving
8647 if (ei->move_leave_element != EL_EMPTY &&
8648 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8649 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8651 int move_leave_element = ei->move_leave_element;
8653 // this makes it possible to leave the removed element again
8654 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8655 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8657 Tile[x][y] = move_leave_element;
8659 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8660 MovDir[x][y] = direction;
8662 InitField(x, y, FALSE);
8664 if (GFX_CRUMBLED(Tile[x][y]))
8665 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8667 if (ELEM_IS_PLAYER(move_leave_element))
8668 RelocatePlayer(x, y, move_leave_element);
8671 // do this after checking for left-behind element
8672 ResetGfxAnimation(x, y); // reset animation values for old field
8674 if (!CAN_MOVE(element) ||
8675 (CAN_FALL(element) && direction == MV_DOWN &&
8676 (element == EL_SPRING ||
8677 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8678 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8679 GfxDir[x][y] = MovDir[newx][newy] = 0;
8681 TEST_DrawLevelField(x, y);
8682 TEST_DrawLevelField(newx, newy);
8684 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8686 // prevent pushed element from moving on in pushed direction
8687 if (pushed_by_player && CAN_MOVE(element) &&
8688 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8689 !(element_info[element].move_pattern & direction))
8690 TurnRound(newx, newy);
8692 // prevent elements on conveyor belt from moving on in last direction
8693 if (pushed_by_conveyor && CAN_FALL(element) &&
8694 direction & MV_HORIZONTAL)
8695 MovDir[newx][newy] = 0;
8697 if (!pushed_by_player)
8699 int nextx = newx + dx, nexty = newy + dy;
8700 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8702 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8704 if (CAN_FALL(element) && direction == MV_DOWN)
8705 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8707 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8708 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8710 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8711 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8714 if (DONT_TOUCH(element)) // object may be nasty to player or others
8716 TestIfBadThingTouchesPlayer(newx, newy);
8717 TestIfBadThingTouchesFriend(newx, newy);
8719 if (!IS_CUSTOM_ELEMENT(element))
8720 TestIfBadThingTouchesOtherBadThing(newx, newy);
8722 else if (element == EL_PENGUIN)
8723 TestIfFriendTouchesBadThing(newx, newy);
8725 if (DONT_GET_HIT_BY(element))
8727 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8730 // give the player one last chance (one more frame) to move away
8731 if (CAN_FALL(element) && direction == MV_DOWN &&
8732 (last_line || (!IS_FREE(x, newy + 1) &&
8733 (!IS_PLAYER(x, newy + 1) ||
8734 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8737 if (pushed_by_player && !game.use_change_when_pushing_bug)
8739 int push_side = MV_DIR_OPPOSITE(direction);
8740 struct PlayerInfo *player = PLAYERINFO(x, y);
8742 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8743 player->index_bit, push_side);
8744 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8745 player->index_bit, push_side);
8748 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8749 MovDelay[newx][newy] = 1;
8751 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8753 TestIfElementTouchesCustomElement(x, y); // empty or new element
8754 TestIfElementHitsCustomElement(newx, newy, direction);
8755 TestIfPlayerTouchesCustomElement(newx, newy);
8756 TestIfElementTouchesCustomElement(newx, newy);
8758 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8759 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8760 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8761 MV_DIR_OPPOSITE(direction));
8764 int AmoebaNeighbourNr(int ax, int ay)
8767 int element = Tile[ax][ay];
8769 static int xy[4][2] =
8777 for (i = 0; i < NUM_DIRECTIONS; i++)
8779 int x = ax + xy[i][0];
8780 int y = ay + xy[i][1];
8782 if (!IN_LEV_FIELD(x, y))
8785 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8786 group_nr = AmoebaNr[x][y];
8792 static void AmoebaMerge(int ax, int ay)
8794 int i, x, y, xx, yy;
8795 int new_group_nr = AmoebaNr[ax][ay];
8796 static int xy[4][2] =
8804 if (new_group_nr == 0)
8807 for (i = 0; i < NUM_DIRECTIONS; i++)
8812 if (!IN_LEV_FIELD(x, y))
8815 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8816 Tile[x][y] == EL_BD_AMOEBA ||
8817 Tile[x][y] == EL_AMOEBA_DEAD) &&
8818 AmoebaNr[x][y] != new_group_nr)
8820 int old_group_nr = AmoebaNr[x][y];
8822 if (old_group_nr == 0)
8825 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8826 AmoebaCnt[old_group_nr] = 0;
8827 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8828 AmoebaCnt2[old_group_nr] = 0;
8830 SCAN_PLAYFIELD(xx, yy)
8832 if (AmoebaNr[xx][yy] == old_group_nr)
8833 AmoebaNr[xx][yy] = new_group_nr;
8839 void AmoebaToDiamond(int ax, int ay)
8843 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8845 int group_nr = AmoebaNr[ax][ay];
8850 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8851 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8857 SCAN_PLAYFIELD(x, y)
8859 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8862 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8866 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8867 SND_AMOEBA_TURNING_TO_GEM :
8868 SND_AMOEBA_TURNING_TO_ROCK));
8873 static int xy[4][2] =
8881 for (i = 0; i < NUM_DIRECTIONS; i++)
8886 if (!IN_LEV_FIELD(x, y))
8889 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8891 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8892 SND_AMOEBA_TURNING_TO_GEM :
8893 SND_AMOEBA_TURNING_TO_ROCK));
8900 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8903 int group_nr = AmoebaNr[ax][ay];
8904 boolean done = FALSE;
8909 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8910 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8916 SCAN_PLAYFIELD(x, y)
8918 if (AmoebaNr[x][y] == group_nr &&
8919 (Tile[x][y] == EL_AMOEBA_DEAD ||
8920 Tile[x][y] == EL_BD_AMOEBA ||
8921 Tile[x][y] == EL_AMOEBA_GROWING))
8924 Tile[x][y] = new_element;
8925 InitField(x, y, FALSE);
8926 TEST_DrawLevelField(x, y);
8932 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8933 SND_BD_AMOEBA_TURNING_TO_ROCK :
8934 SND_BD_AMOEBA_TURNING_TO_GEM));
8937 static void AmoebaGrowing(int x, int y)
8939 static unsigned int sound_delay = 0;
8940 static unsigned int sound_delay_value = 0;
8942 if (!MovDelay[x][y]) // start new growing cycle
8946 if (DelayReached(&sound_delay, sound_delay_value))
8948 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8949 sound_delay_value = 30;
8953 if (MovDelay[x][y]) // wait some time before growing bigger
8956 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8958 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8959 6 - MovDelay[x][y]);
8961 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8964 if (!MovDelay[x][y])
8966 Tile[x][y] = Store[x][y];
8968 TEST_DrawLevelField(x, y);
8973 static void AmoebaShrinking(int x, int y)
8975 static unsigned int sound_delay = 0;
8976 static unsigned int sound_delay_value = 0;
8978 if (!MovDelay[x][y]) // start new shrinking cycle
8982 if (DelayReached(&sound_delay, sound_delay_value))
8983 sound_delay_value = 30;
8986 if (MovDelay[x][y]) // wait some time before shrinking
8989 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8991 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8992 6 - MovDelay[x][y]);
8994 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8997 if (!MovDelay[x][y])
8999 Tile[x][y] = EL_EMPTY;
9000 TEST_DrawLevelField(x, y);
9002 // don't let mole enter this field in this cycle;
9003 // (give priority to objects falling to this field from above)
9009 static void AmoebaReproduce(int ax, int ay)
9012 int element = Tile[ax][ay];
9013 int graphic = el2img(element);
9014 int newax = ax, neway = ay;
9015 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9016 static int xy[4][2] =
9024 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9026 Tile[ax][ay] = EL_AMOEBA_DEAD;
9027 TEST_DrawLevelField(ax, ay);
9031 if (IS_ANIMATED(graphic))
9032 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9034 if (!MovDelay[ax][ay]) // start making new amoeba field
9035 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9037 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9040 if (MovDelay[ax][ay])
9044 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9047 int x = ax + xy[start][0];
9048 int y = ay + xy[start][1];
9050 if (!IN_LEV_FIELD(x, y))
9053 if (IS_FREE(x, y) ||
9054 CAN_GROW_INTO(Tile[x][y]) ||
9055 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9056 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9062 if (newax == ax && neway == ay)
9065 else // normal or "filled" (BD style) amoeba
9068 boolean waiting_for_player = FALSE;
9070 for (i = 0; i < NUM_DIRECTIONS; i++)
9072 int j = (start + i) % 4;
9073 int x = ax + xy[j][0];
9074 int y = ay + xy[j][1];
9076 if (!IN_LEV_FIELD(x, y))
9079 if (IS_FREE(x, y) ||
9080 CAN_GROW_INTO(Tile[x][y]) ||
9081 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9082 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9088 else if (IS_PLAYER(x, y))
9089 waiting_for_player = TRUE;
9092 if (newax == ax && neway == ay) // amoeba cannot grow
9094 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9096 Tile[ax][ay] = EL_AMOEBA_DEAD;
9097 TEST_DrawLevelField(ax, ay);
9098 AmoebaCnt[AmoebaNr[ax][ay]]--;
9100 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9102 if (element == EL_AMOEBA_FULL)
9103 AmoebaToDiamond(ax, ay);
9104 else if (element == EL_BD_AMOEBA)
9105 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9110 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9112 // amoeba gets larger by growing in some direction
9114 int new_group_nr = AmoebaNr[ax][ay];
9117 if (new_group_nr == 0)
9119 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9121 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9127 AmoebaNr[newax][neway] = new_group_nr;
9128 AmoebaCnt[new_group_nr]++;
9129 AmoebaCnt2[new_group_nr]++;
9131 // if amoeba touches other amoeba(s) after growing, unify them
9132 AmoebaMerge(newax, neway);
9134 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9136 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9142 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9143 (neway == lev_fieldy - 1 && newax != ax))
9145 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9146 Store[newax][neway] = element;
9148 else if (neway == ay || element == EL_EMC_DRIPPER)
9150 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9152 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9156 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9157 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9158 Store[ax][ay] = EL_AMOEBA_DROP;
9159 ContinueMoving(ax, ay);
9163 TEST_DrawLevelField(newax, neway);
9166 static void Life(int ax, int ay)
9170 int element = Tile[ax][ay];
9171 int graphic = el2img(element);
9172 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9174 boolean changed = FALSE;
9176 if (IS_ANIMATED(graphic))
9177 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9182 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9183 MovDelay[ax][ay] = life_time;
9185 if (MovDelay[ax][ay]) // wait some time before next cycle
9188 if (MovDelay[ax][ay])
9192 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9194 int xx = ax+x1, yy = ay+y1;
9195 int old_element = Tile[xx][yy];
9196 int num_neighbours = 0;
9198 if (!IN_LEV_FIELD(xx, yy))
9201 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9203 int x = xx+x2, y = yy+y2;
9205 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9208 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9209 boolean is_neighbour = FALSE;
9211 if (level.use_life_bugs)
9213 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9214 (IS_FREE(x, y) && Stop[x][y]));
9217 (Last[x][y] == element || is_player_cell);
9223 boolean is_free = FALSE;
9225 if (level.use_life_bugs)
9226 is_free = (IS_FREE(xx, yy));
9228 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9230 if (xx == ax && yy == ay) // field in the middle
9232 if (num_neighbours < life_parameter[0] ||
9233 num_neighbours > life_parameter[1])
9235 Tile[xx][yy] = EL_EMPTY;
9236 if (Tile[xx][yy] != old_element)
9237 TEST_DrawLevelField(xx, yy);
9238 Stop[xx][yy] = TRUE;
9242 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9243 { // free border field
9244 if (num_neighbours >= life_parameter[2] &&
9245 num_neighbours <= life_parameter[3])
9247 Tile[xx][yy] = element;
9248 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9249 if (Tile[xx][yy] != old_element)
9250 TEST_DrawLevelField(xx, yy);
9251 Stop[xx][yy] = TRUE;
9258 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9259 SND_GAME_OF_LIFE_GROWING);
9262 static void InitRobotWheel(int x, int y)
9264 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9267 static void RunRobotWheel(int x, int y)
9269 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9272 static void StopRobotWheel(int x, int y)
9274 if (game.robot_wheel_x == x &&
9275 game.robot_wheel_y == y)
9277 game.robot_wheel_x = -1;
9278 game.robot_wheel_y = -1;
9279 game.robot_wheel_active = FALSE;
9283 static void InitTimegateWheel(int x, int y)
9285 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9288 static void RunTimegateWheel(int x, int y)
9290 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9293 static void InitMagicBallDelay(int x, int y)
9295 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9298 static void ActivateMagicBall(int bx, int by)
9302 if (level.ball_random)
9304 int pos_border = RND(8); // select one of the eight border elements
9305 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9306 int xx = pos_content % 3;
9307 int yy = pos_content / 3;
9312 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9313 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9317 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9319 int xx = x - bx + 1;
9320 int yy = y - by + 1;
9322 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9323 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9327 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9330 static void CheckExit(int x, int y)
9332 if (game.gems_still_needed > 0 ||
9333 game.sokoban_fields_still_needed > 0 ||
9334 game.sokoban_objects_still_needed > 0 ||
9335 game.lights_still_needed > 0)
9337 int element = Tile[x][y];
9338 int graphic = el2img(element);
9340 if (IS_ANIMATED(graphic))
9341 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9346 // do not re-open exit door closed after last player
9347 if (game.all_players_gone)
9350 Tile[x][y] = EL_EXIT_OPENING;
9352 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9355 static void CheckExitEM(int x, int y)
9357 if (game.gems_still_needed > 0 ||
9358 game.sokoban_fields_still_needed > 0 ||
9359 game.sokoban_objects_still_needed > 0 ||
9360 game.lights_still_needed > 0)
9362 int element = Tile[x][y];
9363 int graphic = el2img(element);
9365 if (IS_ANIMATED(graphic))
9366 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9371 // do not re-open exit door closed after last player
9372 if (game.all_players_gone)
9375 Tile[x][y] = EL_EM_EXIT_OPENING;
9377 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9380 static void CheckExitSteel(int x, int y)
9382 if (game.gems_still_needed > 0 ||
9383 game.sokoban_fields_still_needed > 0 ||
9384 game.sokoban_objects_still_needed > 0 ||
9385 game.lights_still_needed > 0)
9387 int element = Tile[x][y];
9388 int graphic = el2img(element);
9390 if (IS_ANIMATED(graphic))
9391 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9396 // do not re-open exit door closed after last player
9397 if (game.all_players_gone)
9400 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9402 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9405 static void CheckExitSteelEM(int x, int y)
9407 if (game.gems_still_needed > 0 ||
9408 game.sokoban_fields_still_needed > 0 ||
9409 game.sokoban_objects_still_needed > 0 ||
9410 game.lights_still_needed > 0)
9412 int element = Tile[x][y];
9413 int graphic = el2img(element);
9415 if (IS_ANIMATED(graphic))
9416 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9421 // do not re-open exit door closed after last player
9422 if (game.all_players_gone)
9425 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9427 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9430 static void CheckExitSP(int x, int y)
9432 if (game.gems_still_needed > 0)
9434 int element = Tile[x][y];
9435 int graphic = el2img(element);
9437 if (IS_ANIMATED(graphic))
9438 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9443 // do not re-open exit door closed after last player
9444 if (game.all_players_gone)
9447 Tile[x][y] = EL_SP_EXIT_OPENING;
9449 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9452 static void CloseAllOpenTimegates(void)
9456 SCAN_PLAYFIELD(x, y)
9458 int element = Tile[x][y];
9460 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9462 Tile[x][y] = EL_TIMEGATE_CLOSING;
9464 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9469 static void DrawTwinkleOnField(int x, int y)
9471 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9474 if (Tile[x][y] == EL_BD_DIAMOND)
9477 if (MovDelay[x][y] == 0) // next animation frame
9478 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9480 if (MovDelay[x][y] != 0) // wait some time before next frame
9484 DrawLevelElementAnimation(x, y, Tile[x][y]);
9486 if (MovDelay[x][y] != 0)
9488 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9489 10 - MovDelay[x][y]);
9491 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9496 static void MauerWaechst(int x, int y)
9500 if (!MovDelay[x][y]) // next animation frame
9501 MovDelay[x][y] = 3 * delay;
9503 if (MovDelay[x][y]) // wait some time before next frame
9507 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9509 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9510 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9512 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9515 if (!MovDelay[x][y])
9517 if (MovDir[x][y] == MV_LEFT)
9519 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9520 TEST_DrawLevelField(x - 1, y);
9522 else if (MovDir[x][y] == MV_RIGHT)
9524 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9525 TEST_DrawLevelField(x + 1, y);
9527 else if (MovDir[x][y] == MV_UP)
9529 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9530 TEST_DrawLevelField(x, y - 1);
9534 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9535 TEST_DrawLevelField(x, y + 1);
9538 Tile[x][y] = Store[x][y];
9540 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9541 TEST_DrawLevelField(x, y);
9546 static void MauerAbleger(int ax, int ay)
9548 int element = Tile[ax][ay];
9549 int graphic = el2img(element);
9550 boolean oben_frei = FALSE, unten_frei = FALSE;
9551 boolean links_frei = FALSE, rechts_frei = FALSE;
9552 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9553 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9554 boolean new_wall = FALSE;
9556 if (IS_ANIMATED(graphic))
9557 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9559 if (!MovDelay[ax][ay]) // start building new wall
9560 MovDelay[ax][ay] = 6;
9562 if (MovDelay[ax][ay]) // wait some time before building new wall
9565 if (MovDelay[ax][ay])
9569 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9571 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9573 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9575 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9578 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9579 element == EL_EXPANDABLE_WALL_ANY)
9583 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9584 Store[ax][ay-1] = element;
9585 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9586 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9587 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9588 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9593 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9594 Store[ax][ay+1] = element;
9595 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9596 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9597 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9598 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9603 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9604 element == EL_EXPANDABLE_WALL_ANY ||
9605 element == EL_EXPANDABLE_WALL ||
9606 element == EL_BD_EXPANDABLE_WALL)
9610 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9611 Store[ax-1][ay] = element;
9612 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9613 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9614 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9615 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9621 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9622 Store[ax+1][ay] = element;
9623 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9624 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9625 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9626 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9631 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9632 TEST_DrawLevelField(ax, ay);
9634 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9636 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9637 unten_massiv = TRUE;
9638 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9639 links_massiv = TRUE;
9640 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9641 rechts_massiv = TRUE;
9643 if (((oben_massiv && unten_massiv) ||
9644 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9645 element == EL_EXPANDABLE_WALL) &&
9646 ((links_massiv && rechts_massiv) ||
9647 element == EL_EXPANDABLE_WALL_VERTICAL))
9648 Tile[ax][ay] = EL_WALL;
9651 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9654 static void MauerAblegerStahl(int ax, int ay)
9656 int element = Tile[ax][ay];
9657 int graphic = el2img(element);
9658 boolean oben_frei = FALSE, unten_frei = FALSE;
9659 boolean links_frei = FALSE, rechts_frei = FALSE;
9660 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9661 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9662 boolean new_wall = FALSE;
9664 if (IS_ANIMATED(graphic))
9665 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9667 if (!MovDelay[ax][ay]) // start building new wall
9668 MovDelay[ax][ay] = 6;
9670 if (MovDelay[ax][ay]) // wait some time before building new wall
9673 if (MovDelay[ax][ay])
9677 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9679 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9681 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9683 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9686 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9687 element == EL_EXPANDABLE_STEELWALL_ANY)
9691 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9692 Store[ax][ay-1] = element;
9693 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9694 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9695 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9696 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9701 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9702 Store[ax][ay+1] = element;
9703 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9704 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9705 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9706 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9711 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9712 element == EL_EXPANDABLE_STEELWALL_ANY)
9716 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9717 Store[ax-1][ay] = element;
9718 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9719 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9720 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9721 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9727 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9728 Store[ax+1][ay] = element;
9729 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9730 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9731 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9732 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9737 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9739 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9740 unten_massiv = TRUE;
9741 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9742 links_massiv = TRUE;
9743 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9744 rechts_massiv = TRUE;
9746 if (((oben_massiv && unten_massiv) ||
9747 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9748 ((links_massiv && rechts_massiv) ||
9749 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9750 Tile[ax][ay] = EL_STEELWALL;
9753 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9756 static void CheckForDragon(int x, int y)
9759 boolean dragon_found = FALSE;
9760 static int xy[4][2] =
9768 for (i = 0; i < NUM_DIRECTIONS; i++)
9770 for (j = 0; j < 4; j++)
9772 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9774 if (IN_LEV_FIELD(xx, yy) &&
9775 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9777 if (Tile[xx][yy] == EL_DRAGON)
9778 dragon_found = TRUE;
9787 for (i = 0; i < NUM_DIRECTIONS; i++)
9789 for (j = 0; j < 3; j++)
9791 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9793 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9795 Tile[xx][yy] = EL_EMPTY;
9796 TEST_DrawLevelField(xx, yy);
9805 static void InitBuggyBase(int x, int y)
9807 int element = Tile[x][y];
9808 int activating_delay = FRAMES_PER_SECOND / 4;
9811 (element == EL_SP_BUGGY_BASE ?
9812 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9813 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9815 element == EL_SP_BUGGY_BASE_ACTIVE ?
9816 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9819 static void WarnBuggyBase(int x, int y)
9822 static int xy[4][2] =
9830 for (i = 0; i < NUM_DIRECTIONS; i++)
9832 int xx = x + xy[i][0];
9833 int yy = y + xy[i][1];
9835 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9837 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9844 static void InitTrap(int x, int y)
9846 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9849 static void ActivateTrap(int x, int y)
9851 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9854 static void ChangeActiveTrap(int x, int y)
9856 int graphic = IMG_TRAP_ACTIVE;
9858 // if new animation frame was drawn, correct crumbled sand border
9859 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9860 TEST_DrawLevelFieldCrumbled(x, y);
9863 static int getSpecialActionElement(int element, int number, int base_element)
9865 return (element != EL_EMPTY ? element :
9866 number != -1 ? base_element + number - 1 :
9870 static int getModifiedActionNumber(int value_old, int operator, int operand,
9871 int value_min, int value_max)
9873 int value_new = (operator == CA_MODE_SET ? operand :
9874 operator == CA_MODE_ADD ? value_old + operand :
9875 operator == CA_MODE_SUBTRACT ? value_old - operand :
9876 operator == CA_MODE_MULTIPLY ? value_old * operand :
9877 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9878 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9881 return (value_new < value_min ? value_min :
9882 value_new > value_max ? value_max :
9886 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9888 struct ElementInfo *ei = &element_info[element];
9889 struct ElementChangeInfo *change = &ei->change_page[page];
9890 int target_element = change->target_element;
9891 int action_type = change->action_type;
9892 int action_mode = change->action_mode;
9893 int action_arg = change->action_arg;
9894 int action_element = change->action_element;
9897 if (!change->has_action)
9900 // ---------- determine action paramater values -----------------------------
9902 int level_time_value =
9903 (level.time > 0 ? TimeLeft :
9906 int action_arg_element_raw =
9907 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9908 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9909 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9910 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9911 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9912 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9913 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9915 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9917 int action_arg_direction =
9918 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9919 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9920 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9921 change->actual_trigger_side :
9922 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9923 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9926 int action_arg_number_min =
9927 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9930 int action_arg_number_max =
9931 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9932 action_type == CA_SET_LEVEL_GEMS ? 999 :
9933 action_type == CA_SET_LEVEL_TIME ? 9999 :
9934 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9935 action_type == CA_SET_CE_VALUE ? 9999 :
9936 action_type == CA_SET_CE_SCORE ? 9999 :
9939 int action_arg_number_reset =
9940 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9941 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9942 action_type == CA_SET_LEVEL_TIME ? level.time :
9943 action_type == CA_SET_LEVEL_SCORE ? 0 :
9944 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9945 action_type == CA_SET_CE_SCORE ? 0 :
9948 int action_arg_number =
9949 (action_arg <= CA_ARG_MAX ? action_arg :
9950 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9951 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9952 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9953 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9954 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9955 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9956 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9957 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9958 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9959 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9960 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9961 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9962 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9963 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9964 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9965 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9966 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9967 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9968 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9969 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9970 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9973 int action_arg_number_old =
9974 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9975 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9976 action_type == CA_SET_LEVEL_SCORE ? game.score :
9977 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9978 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9981 int action_arg_number_new =
9982 getModifiedActionNumber(action_arg_number_old,
9983 action_mode, action_arg_number,
9984 action_arg_number_min, action_arg_number_max);
9986 int trigger_player_bits =
9987 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9988 change->actual_trigger_player_bits : change->trigger_player);
9990 int action_arg_player_bits =
9991 (action_arg >= CA_ARG_PLAYER_1 &&
9992 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9993 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9994 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9997 // ---------- execute action -----------------------------------------------
9999 switch (action_type)
10006 // ---------- level actions ----------------------------------------------
10008 case CA_RESTART_LEVEL:
10010 game.restart_level = TRUE;
10015 case CA_SHOW_ENVELOPE:
10017 int element = getSpecialActionElement(action_arg_element,
10018 action_arg_number, EL_ENVELOPE_1);
10020 if (IS_ENVELOPE(element))
10021 local_player->show_envelope = element;
10026 case CA_SET_LEVEL_TIME:
10028 if (level.time > 0) // only modify limited time value
10030 TimeLeft = action_arg_number_new;
10032 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10034 DisplayGameControlValues();
10036 if (!TimeLeft && setup.time_limit)
10037 for (i = 0; i < MAX_PLAYERS; i++)
10038 KillPlayer(&stored_player[i]);
10044 case CA_SET_LEVEL_SCORE:
10046 game.score = action_arg_number_new;
10048 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10050 DisplayGameControlValues();
10055 case CA_SET_LEVEL_GEMS:
10057 game.gems_still_needed = action_arg_number_new;
10059 game.snapshot.collected_item = TRUE;
10061 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10063 DisplayGameControlValues();
10068 case CA_SET_LEVEL_WIND:
10070 game.wind_direction = action_arg_direction;
10075 case CA_SET_LEVEL_RANDOM_SEED:
10077 // ensure that setting a new random seed while playing is predictable
10078 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10083 // ---------- player actions ---------------------------------------------
10085 case CA_MOVE_PLAYER:
10086 case CA_MOVE_PLAYER_NEW:
10088 // automatically move to the next field in specified direction
10089 for (i = 0; i < MAX_PLAYERS; i++)
10090 if (trigger_player_bits & (1 << i))
10091 if (action_type == CA_MOVE_PLAYER ||
10092 stored_player[i].MovPos == 0)
10093 stored_player[i].programmed_action = action_arg_direction;
10098 case CA_EXIT_PLAYER:
10100 for (i = 0; i < MAX_PLAYERS; i++)
10101 if (action_arg_player_bits & (1 << i))
10102 ExitPlayer(&stored_player[i]);
10104 if (game.players_still_needed == 0)
10110 case CA_KILL_PLAYER:
10112 for (i = 0; i < MAX_PLAYERS; i++)
10113 if (action_arg_player_bits & (1 << i))
10114 KillPlayer(&stored_player[i]);
10119 case CA_SET_PLAYER_KEYS:
10121 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10122 int element = getSpecialActionElement(action_arg_element,
10123 action_arg_number, EL_KEY_1);
10125 if (IS_KEY(element))
10127 for (i = 0; i < MAX_PLAYERS; i++)
10129 if (trigger_player_bits & (1 << i))
10131 stored_player[i].key[KEY_NR(element)] = key_state;
10133 DrawGameDoorValues();
10141 case CA_SET_PLAYER_SPEED:
10143 for (i = 0; i < MAX_PLAYERS; i++)
10145 if (trigger_player_bits & (1 << i))
10147 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10149 if (action_arg == CA_ARG_SPEED_FASTER &&
10150 stored_player[i].cannot_move)
10152 action_arg_number = STEPSIZE_VERY_SLOW;
10154 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10155 action_arg == CA_ARG_SPEED_FASTER)
10157 action_arg_number = 2;
10158 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10161 else if (action_arg == CA_ARG_NUMBER_RESET)
10163 action_arg_number = level.initial_player_stepsize[i];
10167 getModifiedActionNumber(move_stepsize,
10170 action_arg_number_min,
10171 action_arg_number_max);
10173 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10180 case CA_SET_PLAYER_SHIELD:
10182 for (i = 0; i < MAX_PLAYERS; i++)
10184 if (trigger_player_bits & (1 << i))
10186 if (action_arg == CA_ARG_SHIELD_OFF)
10188 stored_player[i].shield_normal_time_left = 0;
10189 stored_player[i].shield_deadly_time_left = 0;
10191 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10193 stored_player[i].shield_normal_time_left = 999999;
10195 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10197 stored_player[i].shield_normal_time_left = 999999;
10198 stored_player[i].shield_deadly_time_left = 999999;
10206 case CA_SET_PLAYER_GRAVITY:
10208 for (i = 0; i < MAX_PLAYERS; i++)
10210 if (trigger_player_bits & (1 << i))
10212 stored_player[i].gravity =
10213 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10214 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10215 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10216 stored_player[i].gravity);
10223 case CA_SET_PLAYER_ARTWORK:
10225 for (i = 0; i < MAX_PLAYERS; i++)
10227 if (trigger_player_bits & (1 << i))
10229 int artwork_element = action_arg_element;
10231 if (action_arg == CA_ARG_ELEMENT_RESET)
10233 (level.use_artwork_element[i] ? level.artwork_element[i] :
10234 stored_player[i].element_nr);
10236 if (stored_player[i].artwork_element != artwork_element)
10237 stored_player[i].Frame = 0;
10239 stored_player[i].artwork_element = artwork_element;
10241 SetPlayerWaiting(&stored_player[i], FALSE);
10243 // set number of special actions for bored and sleeping animation
10244 stored_player[i].num_special_action_bored =
10245 get_num_special_action(artwork_element,
10246 ACTION_BORING_1, ACTION_BORING_LAST);
10247 stored_player[i].num_special_action_sleeping =
10248 get_num_special_action(artwork_element,
10249 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10256 case CA_SET_PLAYER_INVENTORY:
10258 for (i = 0; i < MAX_PLAYERS; i++)
10260 struct PlayerInfo *player = &stored_player[i];
10263 if (trigger_player_bits & (1 << i))
10265 int inventory_element = action_arg_element;
10267 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10268 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10269 action_arg == CA_ARG_ELEMENT_ACTION)
10271 int element = inventory_element;
10272 int collect_count = element_info[element].collect_count_initial;
10274 if (!IS_CUSTOM_ELEMENT(element))
10277 if (collect_count == 0)
10278 player->inventory_infinite_element = element;
10280 for (k = 0; k < collect_count; k++)
10281 if (player->inventory_size < MAX_INVENTORY_SIZE)
10282 player->inventory_element[player->inventory_size++] =
10285 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10286 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10287 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10289 if (player->inventory_infinite_element != EL_UNDEFINED &&
10290 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10291 action_arg_element_raw))
10292 player->inventory_infinite_element = EL_UNDEFINED;
10294 for (k = 0, j = 0; j < player->inventory_size; j++)
10296 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10297 action_arg_element_raw))
10298 player->inventory_element[k++] = player->inventory_element[j];
10301 player->inventory_size = k;
10303 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10305 if (player->inventory_size > 0)
10307 for (j = 0; j < player->inventory_size - 1; j++)
10308 player->inventory_element[j] = player->inventory_element[j + 1];
10310 player->inventory_size--;
10313 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10315 if (player->inventory_size > 0)
10316 player->inventory_size--;
10318 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10320 player->inventory_infinite_element = EL_UNDEFINED;
10321 player->inventory_size = 0;
10323 else if (action_arg == CA_ARG_INVENTORY_RESET)
10325 player->inventory_infinite_element = EL_UNDEFINED;
10326 player->inventory_size = 0;
10328 if (level.use_initial_inventory[i])
10330 for (j = 0; j < level.initial_inventory_size[i]; j++)
10332 int element = level.initial_inventory_content[i][j];
10333 int collect_count = element_info[element].collect_count_initial;
10335 if (!IS_CUSTOM_ELEMENT(element))
10338 if (collect_count == 0)
10339 player->inventory_infinite_element = element;
10341 for (k = 0; k < collect_count; k++)
10342 if (player->inventory_size < MAX_INVENTORY_SIZE)
10343 player->inventory_element[player->inventory_size++] =
10354 // ---------- CE actions -------------------------------------------------
10356 case CA_SET_CE_VALUE:
10358 int last_ce_value = CustomValue[x][y];
10360 CustomValue[x][y] = action_arg_number_new;
10362 if (CustomValue[x][y] != last_ce_value)
10364 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10365 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10367 if (CustomValue[x][y] == 0)
10369 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10370 ChangeCount[x][y] = 0; // allow at least one more change
10372 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10373 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10380 case CA_SET_CE_SCORE:
10382 int last_ce_score = ei->collect_score;
10384 ei->collect_score = action_arg_number_new;
10386 if (ei->collect_score != last_ce_score)
10388 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10389 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10391 if (ei->collect_score == 0)
10395 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10396 ChangeCount[x][y] = 0; // allow at least one more change
10398 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10399 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10402 This is a very special case that seems to be a mixture between
10403 CheckElementChange() and CheckTriggeredElementChange(): while
10404 the first one only affects single elements that are triggered
10405 directly, the second one affects multiple elements in the playfield
10406 that are triggered indirectly by another element. This is a third
10407 case: Changing the CE score always affects multiple identical CEs,
10408 so every affected CE must be checked, not only the single CE for
10409 which the CE score was changed in the first place (as every instance
10410 of that CE shares the same CE score, and therefore also can change)!
10412 SCAN_PLAYFIELD(xx, yy)
10414 if (Tile[xx][yy] == element)
10415 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10416 CE_SCORE_GETS_ZERO);
10424 case CA_SET_CE_ARTWORK:
10426 int artwork_element = action_arg_element;
10427 boolean reset_frame = FALSE;
10430 if (action_arg == CA_ARG_ELEMENT_RESET)
10431 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10434 if (ei->gfx_element != artwork_element)
10435 reset_frame = TRUE;
10437 ei->gfx_element = artwork_element;
10439 SCAN_PLAYFIELD(xx, yy)
10441 if (Tile[xx][yy] == element)
10445 ResetGfxAnimation(xx, yy);
10446 ResetRandomAnimationValue(xx, yy);
10449 TEST_DrawLevelField(xx, yy);
10456 // ---------- engine actions ---------------------------------------------
10458 case CA_SET_ENGINE_SCAN_MODE:
10460 InitPlayfieldScanMode(action_arg);
10470 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10472 int old_element = Tile[x][y];
10473 int new_element = GetElementFromGroupElement(element);
10474 int previous_move_direction = MovDir[x][y];
10475 int last_ce_value = CustomValue[x][y];
10476 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10477 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10478 boolean add_player_onto_element = (new_element_is_player &&
10479 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10480 IS_WALKABLE(old_element));
10482 if (!add_player_onto_element)
10484 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10485 RemoveMovingField(x, y);
10489 Tile[x][y] = new_element;
10491 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10492 MovDir[x][y] = previous_move_direction;
10494 if (element_info[new_element].use_last_ce_value)
10495 CustomValue[x][y] = last_ce_value;
10497 InitField_WithBug1(x, y, FALSE);
10499 new_element = Tile[x][y]; // element may have changed
10501 ResetGfxAnimation(x, y);
10502 ResetRandomAnimationValue(x, y);
10504 TEST_DrawLevelField(x, y);
10506 if (GFX_CRUMBLED(new_element))
10507 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10510 // check if element under the player changes from accessible to unaccessible
10511 // (needed for special case of dropping element which then changes)
10512 // (must be checked after creating new element for walkable group elements)
10513 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10514 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10521 // "ChangeCount" not set yet to allow "entered by player" change one time
10522 if (new_element_is_player)
10523 RelocatePlayer(x, y, new_element);
10526 ChangeCount[x][y]++; // count number of changes in the same frame
10528 TestIfBadThingTouchesPlayer(x, y);
10529 TestIfPlayerTouchesCustomElement(x, y);
10530 TestIfElementTouchesCustomElement(x, y);
10533 static void CreateField(int x, int y, int element)
10535 CreateFieldExt(x, y, element, FALSE);
10538 static void CreateElementFromChange(int x, int y, int element)
10540 element = GET_VALID_RUNTIME_ELEMENT(element);
10542 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10544 int old_element = Tile[x][y];
10546 // prevent changed element from moving in same engine frame
10547 // unless both old and new element can either fall or move
10548 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10549 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10553 CreateFieldExt(x, y, element, TRUE);
10556 static boolean ChangeElement(int x, int y, int element, int page)
10558 struct ElementInfo *ei = &element_info[element];
10559 struct ElementChangeInfo *change = &ei->change_page[page];
10560 int ce_value = CustomValue[x][y];
10561 int ce_score = ei->collect_score;
10562 int target_element;
10563 int old_element = Tile[x][y];
10565 // always use default change event to prevent running into a loop
10566 if (ChangeEvent[x][y] == -1)
10567 ChangeEvent[x][y] = CE_DELAY;
10569 if (ChangeEvent[x][y] == CE_DELAY)
10571 // reset actual trigger element, trigger player and action element
10572 change->actual_trigger_element = EL_EMPTY;
10573 change->actual_trigger_player = EL_EMPTY;
10574 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10575 change->actual_trigger_side = CH_SIDE_NONE;
10576 change->actual_trigger_ce_value = 0;
10577 change->actual_trigger_ce_score = 0;
10580 // do not change elements more than a specified maximum number of changes
10581 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10584 ChangeCount[x][y]++; // count number of changes in the same frame
10586 if (change->explode)
10593 if (change->use_target_content)
10595 boolean complete_replace = TRUE;
10596 boolean can_replace[3][3];
10599 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10602 boolean is_walkable;
10603 boolean is_diggable;
10604 boolean is_collectible;
10605 boolean is_removable;
10606 boolean is_destructible;
10607 int ex = x + xx - 1;
10608 int ey = y + yy - 1;
10609 int content_element = change->target_content.e[xx][yy];
10612 can_replace[xx][yy] = TRUE;
10614 if (ex == x && ey == y) // do not check changing element itself
10617 if (content_element == EL_EMPTY_SPACE)
10619 can_replace[xx][yy] = FALSE; // do not replace border with space
10624 if (!IN_LEV_FIELD(ex, ey))
10626 can_replace[xx][yy] = FALSE;
10627 complete_replace = FALSE;
10634 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10635 e = MovingOrBlocked2Element(ex, ey);
10637 is_empty = (IS_FREE(ex, ey) ||
10638 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10640 is_walkable = (is_empty || IS_WALKABLE(e));
10641 is_diggable = (is_empty || IS_DIGGABLE(e));
10642 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10643 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10644 is_removable = (is_diggable || is_collectible);
10646 can_replace[xx][yy] =
10647 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10648 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10649 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10650 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10651 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10652 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10653 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10655 if (!can_replace[xx][yy])
10656 complete_replace = FALSE;
10659 if (!change->only_if_complete || complete_replace)
10661 boolean something_has_changed = FALSE;
10663 if (change->only_if_complete && change->use_random_replace &&
10664 RND(100) < change->random_percentage)
10667 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10669 int ex = x + xx - 1;
10670 int ey = y + yy - 1;
10671 int content_element;
10673 if (can_replace[xx][yy] && (!change->use_random_replace ||
10674 RND(100) < change->random_percentage))
10676 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10677 RemoveMovingField(ex, ey);
10679 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10681 content_element = change->target_content.e[xx][yy];
10682 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10683 ce_value, ce_score);
10685 CreateElementFromChange(ex, ey, target_element);
10687 something_has_changed = TRUE;
10689 // for symmetry reasons, freeze newly created border elements
10690 if (ex != x || ey != y)
10691 Stop[ex][ey] = TRUE; // no more moving in this frame
10695 if (something_has_changed)
10697 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10698 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10704 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10705 ce_value, ce_score);
10707 if (element == EL_DIAGONAL_GROWING ||
10708 element == EL_DIAGONAL_SHRINKING)
10710 target_element = Store[x][y];
10712 Store[x][y] = EL_EMPTY;
10715 CreateElementFromChange(x, y, target_element);
10717 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10718 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10721 // this uses direct change before indirect change
10722 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10727 static void HandleElementChange(int x, int y, int page)
10729 int element = MovingOrBlocked2Element(x, y);
10730 struct ElementInfo *ei = &element_info[element];
10731 struct ElementChangeInfo *change = &ei->change_page[page];
10732 boolean handle_action_before_change = FALSE;
10735 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10736 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10738 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10739 x, y, element, element_info[element].token_name);
10740 Debug("game:playing:HandleElementChange", "This should never happen!");
10744 // this can happen with classic bombs on walkable, changing elements
10745 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10750 if (ChangeDelay[x][y] == 0) // initialize element change
10752 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10754 if (change->can_change)
10756 // !!! not clear why graphic animation should be reset at all here !!!
10757 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10758 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10761 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10763 When using an animation frame delay of 1 (this only happens with
10764 "sp_zonk.moving.left/right" in the classic graphics), the default
10765 (non-moving) animation shows wrong animation frames (while the
10766 moving animation, like "sp_zonk.moving.left/right", is correct,
10767 so this graphical bug never shows up with the classic graphics).
10768 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10769 be drawn instead of the correct frames 0,1,2,3. This is caused by
10770 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10771 an element change: First when the change delay ("ChangeDelay[][]")
10772 counter has reached zero after decrementing, then a second time in
10773 the next frame (after "GfxFrame[][]" was already incremented) when
10774 "ChangeDelay[][]" is reset to the initial delay value again.
10776 This causes frame 0 to be drawn twice, while the last frame won't
10777 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10779 As some animations may already be cleverly designed around this bug
10780 (at least the "Snake Bite" snake tail animation does this), it cannot
10781 simply be fixed here without breaking such existing animations.
10782 Unfortunately, it cannot easily be detected if a graphics set was
10783 designed "before" or "after" the bug was fixed. As a workaround,
10784 a new graphics set option "game.graphics_engine_version" was added
10785 to be able to specify the game's major release version for which the
10786 graphics set was designed, which can then be used to decide if the
10787 bugfix should be used (version 4 and above) or not (version 3 or
10788 below, or if no version was specified at all, as with old sets).
10790 (The wrong/fixed animation frames can be tested with the test level set
10791 "test_gfxframe" and level "000", which contains a specially prepared
10792 custom element at level position (x/y) == (11/9) which uses the zonk
10793 animation mentioned above. Using "game.graphics_engine_version: 4"
10794 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10795 This can also be seen from the debug output for this test element.)
10798 // when a custom element is about to change (for example by change delay),
10799 // do not reset graphic animation when the custom element is moving
10800 if (game.graphics_engine_version < 4 &&
10803 ResetGfxAnimation(x, y);
10804 ResetRandomAnimationValue(x, y);
10807 if (change->pre_change_function)
10808 change->pre_change_function(x, y);
10812 ChangeDelay[x][y]--;
10814 if (ChangeDelay[x][y] != 0) // continue element change
10816 if (change->can_change)
10818 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10820 if (IS_ANIMATED(graphic))
10821 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10823 if (change->change_function)
10824 change->change_function(x, y);
10827 else // finish element change
10829 if (ChangePage[x][y] != -1) // remember page from delayed change
10831 page = ChangePage[x][y];
10832 ChangePage[x][y] = -1;
10834 change = &ei->change_page[page];
10837 if (IS_MOVING(x, y)) // never change a running system ;-)
10839 ChangeDelay[x][y] = 1; // try change after next move step
10840 ChangePage[x][y] = page; // remember page to use for change
10845 // special case: set new level random seed before changing element
10846 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10847 handle_action_before_change = TRUE;
10849 if (change->has_action && handle_action_before_change)
10850 ExecuteCustomElementAction(x, y, element, page);
10852 if (change->can_change)
10854 if (ChangeElement(x, y, element, page))
10856 if (change->post_change_function)
10857 change->post_change_function(x, y);
10861 if (change->has_action && !handle_action_before_change)
10862 ExecuteCustomElementAction(x, y, element, page);
10866 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10867 int trigger_element,
10869 int trigger_player,
10873 boolean change_done_any = FALSE;
10874 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10877 if (!(trigger_events[trigger_element][trigger_event]))
10880 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10882 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10884 int element = EL_CUSTOM_START + i;
10885 boolean change_done = FALSE;
10888 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10889 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10892 for (p = 0; p < element_info[element].num_change_pages; p++)
10894 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10896 if (change->can_change_or_has_action &&
10897 change->has_event[trigger_event] &&
10898 change->trigger_side & trigger_side &&
10899 change->trigger_player & trigger_player &&
10900 change->trigger_page & trigger_page_bits &&
10901 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10903 change->actual_trigger_element = trigger_element;
10904 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10905 change->actual_trigger_player_bits = trigger_player;
10906 change->actual_trigger_side = trigger_side;
10907 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10908 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10910 if ((change->can_change && !change_done) || change->has_action)
10914 SCAN_PLAYFIELD(x, y)
10916 if (Tile[x][y] == element)
10918 if (change->can_change && !change_done)
10920 // if element already changed in this frame, not only prevent
10921 // another element change (checked in ChangeElement()), but
10922 // also prevent additional element actions for this element
10924 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10925 !level.use_action_after_change_bug)
10928 ChangeDelay[x][y] = 1;
10929 ChangeEvent[x][y] = trigger_event;
10931 HandleElementChange(x, y, p);
10933 else if (change->has_action)
10935 // if element already changed in this frame, not only prevent
10936 // another element change (checked in ChangeElement()), but
10937 // also prevent additional element actions for this element
10939 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10940 !level.use_action_after_change_bug)
10943 ExecuteCustomElementAction(x, y, element, p);
10944 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10949 if (change->can_change)
10951 change_done = TRUE;
10952 change_done_any = TRUE;
10959 RECURSION_LOOP_DETECTION_END();
10961 return change_done_any;
10964 static boolean CheckElementChangeExt(int x, int y,
10966 int trigger_element,
10968 int trigger_player,
10971 boolean change_done = FALSE;
10974 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10975 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10978 if (Tile[x][y] == EL_BLOCKED)
10980 Blocked2Moving(x, y, &x, &y);
10981 element = Tile[x][y];
10984 // check if element has already changed or is about to change after moving
10985 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10986 Tile[x][y] != element) ||
10988 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10989 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10990 ChangePage[x][y] != -1)))
10993 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10995 for (p = 0; p < element_info[element].num_change_pages; p++)
10997 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10999 /* check trigger element for all events where the element that is checked
11000 for changing interacts with a directly adjacent element -- this is
11001 different to element changes that affect other elements to change on the
11002 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11003 boolean check_trigger_element =
11004 (trigger_event == CE_TOUCHING_X ||
11005 trigger_event == CE_HITTING_X ||
11006 trigger_event == CE_HIT_BY_X ||
11007 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11009 if (change->can_change_or_has_action &&
11010 change->has_event[trigger_event] &&
11011 change->trigger_side & trigger_side &&
11012 change->trigger_player & trigger_player &&
11013 (!check_trigger_element ||
11014 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11016 change->actual_trigger_element = trigger_element;
11017 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11018 change->actual_trigger_player_bits = trigger_player;
11019 change->actual_trigger_side = trigger_side;
11020 change->actual_trigger_ce_value = CustomValue[x][y];
11021 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11023 // special case: trigger element not at (x,y) position for some events
11024 if (check_trigger_element)
11036 { 0, 0 }, { 0, 0 }, { 0, 0 },
11040 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11041 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11043 change->actual_trigger_ce_value = CustomValue[xx][yy];
11044 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11047 if (change->can_change && !change_done)
11049 ChangeDelay[x][y] = 1;
11050 ChangeEvent[x][y] = trigger_event;
11052 HandleElementChange(x, y, p);
11054 change_done = TRUE;
11056 else if (change->has_action)
11058 ExecuteCustomElementAction(x, y, element, p);
11059 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11064 RECURSION_LOOP_DETECTION_END();
11066 return change_done;
11069 static void PlayPlayerSound(struct PlayerInfo *player)
11071 int jx = player->jx, jy = player->jy;
11072 int sound_element = player->artwork_element;
11073 int last_action = player->last_action_waiting;
11074 int action = player->action_waiting;
11076 if (player->is_waiting)
11078 if (action != last_action)
11079 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11081 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11085 if (action != last_action)
11086 StopSound(element_info[sound_element].sound[last_action]);
11088 if (last_action == ACTION_SLEEPING)
11089 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11093 static void PlayAllPlayersSound(void)
11097 for (i = 0; i < MAX_PLAYERS; i++)
11098 if (stored_player[i].active)
11099 PlayPlayerSound(&stored_player[i]);
11102 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11104 boolean last_waiting = player->is_waiting;
11105 int move_dir = player->MovDir;
11107 player->dir_waiting = move_dir;
11108 player->last_action_waiting = player->action_waiting;
11112 if (!last_waiting) // not waiting -> waiting
11114 player->is_waiting = TRUE;
11116 player->frame_counter_bored =
11118 game.player_boring_delay_fixed +
11119 GetSimpleRandom(game.player_boring_delay_random);
11120 player->frame_counter_sleeping =
11122 game.player_sleeping_delay_fixed +
11123 GetSimpleRandom(game.player_sleeping_delay_random);
11125 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11128 if (game.player_sleeping_delay_fixed +
11129 game.player_sleeping_delay_random > 0 &&
11130 player->anim_delay_counter == 0 &&
11131 player->post_delay_counter == 0 &&
11132 FrameCounter >= player->frame_counter_sleeping)
11133 player->is_sleeping = TRUE;
11134 else if (game.player_boring_delay_fixed +
11135 game.player_boring_delay_random > 0 &&
11136 FrameCounter >= player->frame_counter_bored)
11137 player->is_bored = TRUE;
11139 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11140 player->is_bored ? ACTION_BORING :
11143 if (player->is_sleeping && player->use_murphy)
11145 // special case for sleeping Murphy when leaning against non-free tile
11147 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11148 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11149 !IS_MOVING(player->jx - 1, player->jy)))
11150 move_dir = MV_LEFT;
11151 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11152 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11153 !IS_MOVING(player->jx + 1, player->jy)))
11154 move_dir = MV_RIGHT;
11156 player->is_sleeping = FALSE;
11158 player->dir_waiting = move_dir;
11161 if (player->is_sleeping)
11163 if (player->num_special_action_sleeping > 0)
11165 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11167 int last_special_action = player->special_action_sleeping;
11168 int num_special_action = player->num_special_action_sleeping;
11169 int special_action =
11170 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11171 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11172 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11173 last_special_action + 1 : ACTION_SLEEPING);
11174 int special_graphic =
11175 el_act_dir2img(player->artwork_element, special_action, move_dir);
11177 player->anim_delay_counter =
11178 graphic_info[special_graphic].anim_delay_fixed +
11179 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11180 player->post_delay_counter =
11181 graphic_info[special_graphic].post_delay_fixed +
11182 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11184 player->special_action_sleeping = special_action;
11187 if (player->anim_delay_counter > 0)
11189 player->action_waiting = player->special_action_sleeping;
11190 player->anim_delay_counter--;
11192 else if (player->post_delay_counter > 0)
11194 player->post_delay_counter--;
11198 else if (player->is_bored)
11200 if (player->num_special_action_bored > 0)
11202 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11204 int special_action =
11205 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11206 int special_graphic =
11207 el_act_dir2img(player->artwork_element, special_action, move_dir);
11209 player->anim_delay_counter =
11210 graphic_info[special_graphic].anim_delay_fixed +
11211 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11212 player->post_delay_counter =
11213 graphic_info[special_graphic].post_delay_fixed +
11214 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11216 player->special_action_bored = special_action;
11219 if (player->anim_delay_counter > 0)
11221 player->action_waiting = player->special_action_bored;
11222 player->anim_delay_counter--;
11224 else if (player->post_delay_counter > 0)
11226 player->post_delay_counter--;
11231 else if (last_waiting) // waiting -> not waiting
11233 player->is_waiting = FALSE;
11234 player->is_bored = FALSE;
11235 player->is_sleeping = FALSE;
11237 player->frame_counter_bored = -1;
11238 player->frame_counter_sleeping = -1;
11240 player->anim_delay_counter = 0;
11241 player->post_delay_counter = 0;
11243 player->dir_waiting = player->MovDir;
11244 player->action_waiting = ACTION_DEFAULT;
11246 player->special_action_bored = ACTION_DEFAULT;
11247 player->special_action_sleeping = ACTION_DEFAULT;
11251 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11253 if ((!player->is_moving && player->was_moving) ||
11254 (player->MovPos == 0 && player->was_moving) ||
11255 (player->is_snapping && !player->was_snapping) ||
11256 (player->is_dropping && !player->was_dropping))
11258 if (!CheckSaveEngineSnapshotToList())
11261 player->was_moving = FALSE;
11262 player->was_snapping = TRUE;
11263 player->was_dropping = TRUE;
11267 if (player->is_moving)
11268 player->was_moving = TRUE;
11270 if (!player->is_snapping)
11271 player->was_snapping = FALSE;
11273 if (!player->is_dropping)
11274 player->was_dropping = FALSE;
11277 static struct MouseActionInfo mouse_action_last = { 0 };
11278 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11279 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11282 CheckSaveEngineSnapshotToList();
11284 mouse_action_last = mouse_action;
11287 static void CheckSingleStepMode(struct PlayerInfo *player)
11289 if (tape.single_step && tape.recording && !tape.pausing)
11291 /* as it is called "single step mode", just return to pause mode when the
11292 player stopped moving after one tile (or never starts moving at all) */
11293 if (!player->is_moving &&
11294 !player->is_pushing &&
11295 !player->is_dropping_pressed &&
11296 !player->effective_mouse_action.button)
11297 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11300 CheckSaveEngineSnapshot(player);
11303 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11305 int left = player_action & JOY_LEFT;
11306 int right = player_action & JOY_RIGHT;
11307 int up = player_action & JOY_UP;
11308 int down = player_action & JOY_DOWN;
11309 int button1 = player_action & JOY_BUTTON_1;
11310 int button2 = player_action & JOY_BUTTON_2;
11311 int dx = (left ? -1 : right ? 1 : 0);
11312 int dy = (up ? -1 : down ? 1 : 0);
11314 if (!player->active || tape.pausing)
11320 SnapField(player, dx, dy);
11324 DropElement(player);
11326 MovePlayer(player, dx, dy);
11329 CheckSingleStepMode(player);
11331 SetPlayerWaiting(player, FALSE);
11333 return player_action;
11337 // no actions for this player (no input at player's configured device)
11339 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11340 SnapField(player, 0, 0);
11341 CheckGravityMovementWhenNotMoving(player);
11343 if (player->MovPos == 0)
11344 SetPlayerWaiting(player, TRUE);
11346 if (player->MovPos == 0) // needed for tape.playing
11347 player->is_moving = FALSE;
11349 player->is_dropping = FALSE;
11350 player->is_dropping_pressed = FALSE;
11351 player->drop_pressed_delay = 0;
11353 CheckSingleStepMode(player);
11359 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11362 if (!tape.use_mouse_actions)
11365 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11366 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11367 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11370 static void SetTapeActionFromMouseAction(byte *tape_action,
11371 struct MouseActionInfo *mouse_action)
11373 if (!tape.use_mouse_actions)
11376 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11377 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11378 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11381 static void CheckLevelSolved(void)
11383 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11385 if (game_em.level_solved &&
11386 !game_em.game_over) // game won
11390 game_em.game_over = TRUE;
11392 game.all_players_gone = TRUE;
11395 if (game_em.game_over) // game lost
11396 game.all_players_gone = TRUE;
11398 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11400 if (game_sp.level_solved &&
11401 !game_sp.game_over) // game won
11405 game_sp.game_over = TRUE;
11407 game.all_players_gone = TRUE;
11410 if (game_sp.game_over) // game lost
11411 game.all_players_gone = TRUE;
11413 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11415 if (game_mm.level_solved &&
11416 !game_mm.game_over) // game won
11420 game_mm.game_over = TRUE;
11422 game.all_players_gone = TRUE;
11425 if (game_mm.game_over) // game lost
11426 game.all_players_gone = TRUE;
11430 static void CheckLevelTime(void)
11434 if (TimeFrames >= FRAMES_PER_SECOND)
11439 for (i = 0; i < MAX_PLAYERS; i++)
11441 struct PlayerInfo *player = &stored_player[i];
11443 if (SHIELD_ON(player))
11445 player->shield_normal_time_left--;
11447 if (player->shield_deadly_time_left > 0)
11448 player->shield_deadly_time_left--;
11452 if (!game.LevelSolved && !level.use_step_counter)
11460 if (TimeLeft <= 10 && setup.time_limit)
11461 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11463 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11464 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11466 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11468 if (!TimeLeft && setup.time_limit)
11470 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11471 game_em.lev->killed_out_of_time = TRUE;
11473 for (i = 0; i < MAX_PLAYERS; i++)
11474 KillPlayer(&stored_player[i]);
11477 else if (game.no_time_limit && !game.all_players_gone)
11479 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11482 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11485 if (tape.recording || tape.playing)
11486 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11489 if (tape.recording || tape.playing)
11490 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11492 UpdateAndDisplayGameControlValues();
11495 void AdvanceFrameAndPlayerCounters(int player_nr)
11499 // advance frame counters (global frame counter and time frame counter)
11503 // advance player counters (counters for move delay, move animation etc.)
11504 for (i = 0; i < MAX_PLAYERS; i++)
11506 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11507 int move_delay_value = stored_player[i].move_delay_value;
11508 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11510 if (!advance_player_counters) // not all players may be affected
11513 if (move_frames == 0) // less than one move per game frame
11515 int stepsize = TILEX / move_delay_value;
11516 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11517 int count = (stored_player[i].is_moving ?
11518 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11520 if (count % delay == 0)
11524 stored_player[i].Frame += move_frames;
11526 if (stored_player[i].MovPos != 0)
11527 stored_player[i].StepFrame += move_frames;
11529 if (stored_player[i].move_delay > 0)
11530 stored_player[i].move_delay--;
11532 // due to bugs in previous versions, counter must count up, not down
11533 if (stored_player[i].push_delay != -1)
11534 stored_player[i].push_delay++;
11536 if (stored_player[i].drop_delay > 0)
11537 stored_player[i].drop_delay--;
11539 if (stored_player[i].is_dropping_pressed)
11540 stored_player[i].drop_pressed_delay++;
11544 void StartGameActions(boolean init_network_game, boolean record_tape,
11547 unsigned int new_random_seed = InitRND(random_seed);
11550 TapeStartRecording(new_random_seed);
11552 if (init_network_game)
11554 SendToServer_LevelFile();
11555 SendToServer_StartPlaying();
11563 static void GameActionsExt(void)
11566 static unsigned int game_frame_delay = 0;
11568 unsigned int game_frame_delay_value;
11569 byte *recorded_player_action;
11570 byte summarized_player_action = 0;
11571 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11574 // detect endless loops, caused by custom element programming
11575 if (recursion_loop_detected && recursion_loop_depth == 0)
11577 char *message = getStringCat3("Internal Error! Element ",
11578 EL_NAME(recursion_loop_element),
11579 " caused endless loop! Quit the game?");
11581 Warn("element '%s' caused endless loop in game engine",
11582 EL_NAME(recursion_loop_element));
11584 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11586 recursion_loop_detected = FALSE; // if game should be continued
11593 if (game.restart_level)
11594 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11596 CheckLevelSolved();
11598 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11601 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11604 if (game_status != GAME_MODE_PLAYING) // status might have changed
11607 game_frame_delay_value =
11608 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11610 if (tape.playing && tape.warp_forward && !tape.pausing)
11611 game_frame_delay_value = 0;
11613 SetVideoFrameDelay(game_frame_delay_value);
11615 // (de)activate virtual buttons depending on current game status
11616 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11618 if (game.all_players_gone) // if no players there to be controlled anymore
11619 SetOverlayActive(FALSE);
11620 else if (!tape.playing) // if game continues after tape stopped playing
11621 SetOverlayActive(TRUE);
11626 // ---------- main game synchronization point ----------
11628 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11630 Debug("game:playing:skip", "skip == %d", skip);
11633 // ---------- main game synchronization point ----------
11635 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11639 if (network_playing && !network_player_action_received)
11641 // try to get network player actions in time
11643 // last chance to get network player actions without main loop delay
11644 HandleNetworking();
11646 // game was quit by network peer
11647 if (game_status != GAME_MODE_PLAYING)
11650 // check if network player actions still missing and game still running
11651 if (!network_player_action_received && !checkGameEnded())
11652 return; // failed to get network player actions in time
11654 // do not yet reset "network_player_action_received" (for tape.pausing)
11660 // at this point we know that we really continue executing the game
11662 network_player_action_received = FALSE;
11664 // when playing tape, read previously recorded player input from tape data
11665 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11667 local_player->effective_mouse_action = local_player->mouse_action;
11669 if (recorded_player_action != NULL)
11670 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11671 recorded_player_action);
11673 // TapePlayAction() may return NULL when toggling to "pause before death"
11677 if (tape.set_centered_player)
11679 game.centered_player_nr_next = tape.centered_player_nr_next;
11680 game.set_centered_player = TRUE;
11683 for (i = 0; i < MAX_PLAYERS; i++)
11685 summarized_player_action |= stored_player[i].action;
11687 if (!network_playing && (game.team_mode || tape.playing))
11688 stored_player[i].effective_action = stored_player[i].action;
11691 if (network_playing && !checkGameEnded())
11692 SendToServer_MovePlayer(summarized_player_action);
11694 // summarize all actions at local players mapped input device position
11695 // (this allows using different input devices in single player mode)
11696 if (!network.enabled && !game.team_mode)
11697 stored_player[map_player_action[local_player->index_nr]].effective_action =
11698 summarized_player_action;
11700 // summarize all actions at centered player in local team mode
11701 if (tape.recording &&
11702 setup.team_mode && !network.enabled &&
11703 setup.input_on_focus &&
11704 game.centered_player_nr != -1)
11706 for (i = 0; i < MAX_PLAYERS; i++)
11707 stored_player[map_player_action[i]].effective_action =
11708 (i == game.centered_player_nr ? summarized_player_action : 0);
11711 if (recorded_player_action != NULL)
11712 for (i = 0; i < MAX_PLAYERS; i++)
11713 stored_player[i].effective_action = recorded_player_action[i];
11715 for (i = 0; i < MAX_PLAYERS; i++)
11717 tape_action[i] = stored_player[i].effective_action;
11719 /* (this may happen in the RND game engine if a player was not present on
11720 the playfield on level start, but appeared later from a custom element */
11721 if (setup.team_mode &&
11724 !tape.player_participates[i])
11725 tape.player_participates[i] = TRUE;
11728 SetTapeActionFromMouseAction(tape_action,
11729 &local_player->effective_mouse_action);
11731 // only record actions from input devices, but not programmed actions
11732 if (tape.recording)
11733 TapeRecordAction(tape_action);
11735 // remember if game was played (especially after tape stopped playing)
11736 if (!tape.playing && summarized_player_action)
11737 game.GamePlayed = TRUE;
11739 #if USE_NEW_PLAYER_ASSIGNMENTS
11740 // !!! also map player actions in single player mode !!!
11741 // if (game.team_mode)
11744 byte mapped_action[MAX_PLAYERS];
11746 #if DEBUG_PLAYER_ACTIONS
11747 for (i = 0; i < MAX_PLAYERS; i++)
11748 DebugContinued("", "%d, ", stored_player[i].effective_action);
11751 for (i = 0; i < MAX_PLAYERS; i++)
11752 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11754 for (i = 0; i < MAX_PLAYERS; i++)
11755 stored_player[i].effective_action = mapped_action[i];
11757 #if DEBUG_PLAYER_ACTIONS
11758 DebugContinued("", "=> ");
11759 for (i = 0; i < MAX_PLAYERS; i++)
11760 DebugContinued("", "%d, ", stored_player[i].effective_action);
11761 DebugContinued("game:playing:player", "\n");
11764 #if DEBUG_PLAYER_ACTIONS
11767 for (i = 0; i < MAX_PLAYERS; i++)
11768 DebugContinued("", "%d, ", stored_player[i].effective_action);
11769 DebugContinued("game:playing:player", "\n");
11774 for (i = 0; i < MAX_PLAYERS; i++)
11776 // allow engine snapshot in case of changed movement attempt
11777 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11778 (stored_player[i].effective_action & KEY_MOTION))
11779 game.snapshot.changed_action = TRUE;
11781 // allow engine snapshot in case of snapping/dropping attempt
11782 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11783 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11784 game.snapshot.changed_action = TRUE;
11786 game.snapshot.last_action[i] = stored_player[i].effective_action;
11789 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11791 GameActions_EM_Main();
11793 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11795 GameActions_SP_Main();
11797 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11799 GameActions_MM_Main();
11803 GameActions_RND_Main();
11806 BlitScreenToBitmap(backbuffer);
11808 CheckLevelSolved();
11811 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11813 if (global.show_frames_per_second)
11815 static unsigned int fps_counter = 0;
11816 static int fps_frames = 0;
11817 unsigned int fps_delay_ms = Counter() - fps_counter;
11821 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11823 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11826 fps_counter = Counter();
11828 // always draw FPS to screen after FPS value was updated
11829 redraw_mask |= REDRAW_FPS;
11832 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11833 if (GetDrawDeactivationMask() == REDRAW_NONE)
11834 redraw_mask |= REDRAW_FPS;
11838 static void GameActions_CheckSaveEngineSnapshot(void)
11840 if (!game.snapshot.save_snapshot)
11843 // clear flag for saving snapshot _before_ saving snapshot
11844 game.snapshot.save_snapshot = FALSE;
11846 SaveEngineSnapshotToList();
11849 void GameActions(void)
11853 GameActions_CheckSaveEngineSnapshot();
11856 void GameActions_EM_Main(void)
11858 byte effective_action[MAX_PLAYERS];
11859 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11862 for (i = 0; i < MAX_PLAYERS; i++)
11863 effective_action[i] = stored_player[i].effective_action;
11865 GameActions_EM(effective_action, warp_mode);
11868 void GameActions_SP_Main(void)
11870 byte effective_action[MAX_PLAYERS];
11871 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11874 for (i = 0; i < MAX_PLAYERS; i++)
11875 effective_action[i] = stored_player[i].effective_action;
11877 GameActions_SP(effective_action, warp_mode);
11879 for (i = 0; i < MAX_PLAYERS; i++)
11881 if (stored_player[i].force_dropping)
11882 stored_player[i].action |= KEY_BUTTON_DROP;
11884 stored_player[i].force_dropping = FALSE;
11888 void GameActions_MM_Main(void)
11890 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11892 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11895 void GameActions_RND_Main(void)
11900 void GameActions_RND(void)
11902 static struct MouseActionInfo mouse_action_last = { 0 };
11903 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11904 int magic_wall_x = 0, magic_wall_y = 0;
11905 int i, x, y, element, graphic, last_gfx_frame;
11907 InitPlayfieldScanModeVars();
11909 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11911 SCAN_PLAYFIELD(x, y)
11913 ChangeCount[x][y] = 0;
11914 ChangeEvent[x][y] = -1;
11918 if (game.set_centered_player)
11920 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11922 // switching to "all players" only possible if all players fit to screen
11923 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11925 game.centered_player_nr_next = game.centered_player_nr;
11926 game.set_centered_player = FALSE;
11929 // do not switch focus to non-existing (or non-active) player
11930 if (game.centered_player_nr_next >= 0 &&
11931 !stored_player[game.centered_player_nr_next].active)
11933 game.centered_player_nr_next = game.centered_player_nr;
11934 game.set_centered_player = FALSE;
11938 if (game.set_centered_player &&
11939 ScreenMovPos == 0) // screen currently aligned at tile position
11943 if (game.centered_player_nr_next == -1)
11945 setScreenCenteredToAllPlayers(&sx, &sy);
11949 sx = stored_player[game.centered_player_nr_next].jx;
11950 sy = stored_player[game.centered_player_nr_next].jy;
11953 game.centered_player_nr = game.centered_player_nr_next;
11954 game.set_centered_player = FALSE;
11956 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11957 DrawGameDoorValues();
11960 for (i = 0; i < MAX_PLAYERS; i++)
11962 int actual_player_action = stored_player[i].effective_action;
11965 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11966 - rnd_equinox_tetrachloride 048
11967 - rnd_equinox_tetrachloride_ii 096
11968 - rnd_emanuel_schmieg 002
11969 - doctor_sloan_ww 001, 020
11971 if (stored_player[i].MovPos == 0)
11972 CheckGravityMovement(&stored_player[i]);
11975 // overwrite programmed action with tape action
11976 if (stored_player[i].programmed_action)
11977 actual_player_action = stored_player[i].programmed_action;
11979 PlayerActions(&stored_player[i], actual_player_action);
11981 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11984 ScrollScreen(NULL, SCROLL_GO_ON);
11986 /* for backwards compatibility, the following code emulates a fixed bug that
11987 occured when pushing elements (causing elements that just made their last
11988 pushing step to already (if possible) make their first falling step in the
11989 same game frame, which is bad); this code is also needed to use the famous
11990 "spring push bug" which is used in older levels and might be wanted to be
11991 used also in newer levels, but in this case the buggy pushing code is only
11992 affecting the "spring" element and no other elements */
11994 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11996 for (i = 0; i < MAX_PLAYERS; i++)
11998 struct PlayerInfo *player = &stored_player[i];
11999 int x = player->jx;
12000 int y = player->jy;
12002 if (player->active && player->is_pushing && player->is_moving &&
12004 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12005 Tile[x][y] == EL_SPRING))
12007 ContinueMoving(x, y);
12009 // continue moving after pushing (this is actually a bug)
12010 if (!IS_MOVING(x, y))
12011 Stop[x][y] = FALSE;
12016 SCAN_PLAYFIELD(x, y)
12018 Last[x][y] = Tile[x][y];
12020 ChangeCount[x][y] = 0;
12021 ChangeEvent[x][y] = -1;
12023 // this must be handled before main playfield loop
12024 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12027 if (MovDelay[x][y] <= 0)
12031 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12034 if (MovDelay[x][y] <= 0)
12037 TEST_DrawLevelField(x, y);
12039 TestIfElementTouchesCustomElement(x, y); // for empty space
12044 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12046 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12048 Debug("game:playing:GameActions_RND", "This should never happen!");
12050 ChangePage[x][y] = -1;
12054 Stop[x][y] = FALSE;
12055 if (WasJustMoving[x][y] > 0)
12056 WasJustMoving[x][y]--;
12057 if (WasJustFalling[x][y] > 0)
12058 WasJustFalling[x][y]--;
12059 if (CheckCollision[x][y] > 0)
12060 CheckCollision[x][y]--;
12061 if (CheckImpact[x][y] > 0)
12062 CheckImpact[x][y]--;
12066 /* reset finished pushing action (not done in ContinueMoving() to allow
12067 continuous pushing animation for elements with zero push delay) */
12068 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12070 ResetGfxAnimation(x, y);
12071 TEST_DrawLevelField(x, y);
12075 if (IS_BLOCKED(x, y))
12079 Blocked2Moving(x, y, &oldx, &oldy);
12080 if (!IS_MOVING(oldx, oldy))
12082 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12083 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12084 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12085 Debug("game:playing:GameActions_RND", "This should never happen!");
12091 if (mouse_action.button)
12093 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12095 x = mouse_action.lx;
12096 y = mouse_action.ly;
12097 element = Tile[x][y];
12101 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12102 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12105 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12106 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12109 SCAN_PLAYFIELD(x, y)
12111 element = Tile[x][y];
12112 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12113 last_gfx_frame = GfxFrame[x][y];
12115 ResetGfxFrame(x, y);
12117 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12118 DrawLevelGraphicAnimation(x, y, graphic);
12120 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12121 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12122 ResetRandomAnimationValue(x, y);
12124 SetRandomAnimationValue(x, y);
12126 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12128 if (IS_INACTIVE(element))
12130 if (IS_ANIMATED(graphic))
12131 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12136 // this may take place after moving, so 'element' may have changed
12137 if (IS_CHANGING(x, y) &&
12138 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12140 int page = element_info[element].event_page_nr[CE_DELAY];
12142 HandleElementChange(x, y, page);
12144 element = Tile[x][y];
12145 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12148 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12152 element = Tile[x][y];
12153 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12155 if (IS_ANIMATED(graphic) &&
12156 !IS_MOVING(x, y) &&
12158 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12160 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12161 TEST_DrawTwinkleOnField(x, y);
12163 else if (element == EL_ACID)
12166 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12168 else if ((element == EL_EXIT_OPEN ||
12169 element == EL_EM_EXIT_OPEN ||
12170 element == EL_SP_EXIT_OPEN ||
12171 element == EL_STEEL_EXIT_OPEN ||
12172 element == EL_EM_STEEL_EXIT_OPEN ||
12173 element == EL_SP_TERMINAL ||
12174 element == EL_SP_TERMINAL_ACTIVE ||
12175 element == EL_EXTRA_TIME ||
12176 element == EL_SHIELD_NORMAL ||
12177 element == EL_SHIELD_DEADLY) &&
12178 IS_ANIMATED(graphic))
12179 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12180 else if (IS_MOVING(x, y))
12181 ContinueMoving(x, y);
12182 else if (IS_ACTIVE_BOMB(element))
12183 CheckDynamite(x, y);
12184 else if (element == EL_AMOEBA_GROWING)
12185 AmoebaGrowing(x, y);
12186 else if (element == EL_AMOEBA_SHRINKING)
12187 AmoebaShrinking(x, y);
12189 #if !USE_NEW_AMOEBA_CODE
12190 else if (IS_AMOEBALIVE(element))
12191 AmoebaReproduce(x, y);
12194 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12196 else if (element == EL_EXIT_CLOSED)
12198 else if (element == EL_EM_EXIT_CLOSED)
12200 else if (element == EL_STEEL_EXIT_CLOSED)
12201 CheckExitSteel(x, y);
12202 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12203 CheckExitSteelEM(x, y);
12204 else if (element == EL_SP_EXIT_CLOSED)
12206 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12207 element == EL_EXPANDABLE_STEELWALL_GROWING)
12208 MauerWaechst(x, y);
12209 else if (element == EL_EXPANDABLE_WALL ||
12210 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12211 element == EL_EXPANDABLE_WALL_VERTICAL ||
12212 element == EL_EXPANDABLE_WALL_ANY ||
12213 element == EL_BD_EXPANDABLE_WALL)
12214 MauerAbleger(x, y);
12215 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12216 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12217 element == EL_EXPANDABLE_STEELWALL_ANY)
12218 MauerAblegerStahl(x, y);
12219 else if (element == EL_FLAMES)
12220 CheckForDragon(x, y);
12221 else if (element == EL_EXPLOSION)
12222 ; // drawing of correct explosion animation is handled separately
12223 else if (element == EL_ELEMENT_SNAPPING ||
12224 element == EL_DIAGONAL_SHRINKING ||
12225 element == EL_DIAGONAL_GROWING)
12227 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12229 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12231 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12232 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12234 if (IS_BELT_ACTIVE(element))
12235 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12237 if (game.magic_wall_active)
12239 int jx = local_player->jx, jy = local_player->jy;
12241 // play the element sound at the position nearest to the player
12242 if ((element == EL_MAGIC_WALL_FULL ||
12243 element == EL_MAGIC_WALL_ACTIVE ||
12244 element == EL_MAGIC_WALL_EMPTYING ||
12245 element == EL_BD_MAGIC_WALL_FULL ||
12246 element == EL_BD_MAGIC_WALL_ACTIVE ||
12247 element == EL_BD_MAGIC_WALL_EMPTYING ||
12248 element == EL_DC_MAGIC_WALL_FULL ||
12249 element == EL_DC_MAGIC_WALL_ACTIVE ||
12250 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12251 ABS(x - jx) + ABS(y - jy) <
12252 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12260 #if USE_NEW_AMOEBA_CODE
12261 // new experimental amoeba growth stuff
12262 if (!(FrameCounter % 8))
12264 static unsigned int random = 1684108901;
12266 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12268 x = RND(lev_fieldx);
12269 y = RND(lev_fieldy);
12270 element = Tile[x][y];
12272 if (!IS_PLAYER(x,y) &&
12273 (element == EL_EMPTY ||
12274 CAN_GROW_INTO(element) ||
12275 element == EL_QUICKSAND_EMPTY ||
12276 element == EL_QUICKSAND_FAST_EMPTY ||
12277 element == EL_ACID_SPLASH_LEFT ||
12278 element == EL_ACID_SPLASH_RIGHT))
12280 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12281 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12282 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12283 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12284 Tile[x][y] = EL_AMOEBA_DROP;
12287 random = random * 129 + 1;
12292 game.explosions_delayed = FALSE;
12294 SCAN_PLAYFIELD(x, y)
12296 element = Tile[x][y];
12298 if (ExplodeField[x][y])
12299 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12300 else if (element == EL_EXPLOSION)
12301 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12303 ExplodeField[x][y] = EX_TYPE_NONE;
12306 game.explosions_delayed = TRUE;
12308 if (game.magic_wall_active)
12310 if (!(game.magic_wall_time_left % 4))
12312 int element = Tile[magic_wall_x][magic_wall_y];
12314 if (element == EL_BD_MAGIC_WALL_FULL ||
12315 element == EL_BD_MAGIC_WALL_ACTIVE ||
12316 element == EL_BD_MAGIC_WALL_EMPTYING)
12317 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12318 else if (element == EL_DC_MAGIC_WALL_FULL ||
12319 element == EL_DC_MAGIC_WALL_ACTIVE ||
12320 element == EL_DC_MAGIC_WALL_EMPTYING)
12321 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12323 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12326 if (game.magic_wall_time_left > 0)
12328 game.magic_wall_time_left--;
12330 if (!game.magic_wall_time_left)
12332 SCAN_PLAYFIELD(x, y)
12334 element = Tile[x][y];
12336 if (element == EL_MAGIC_WALL_ACTIVE ||
12337 element == EL_MAGIC_WALL_FULL)
12339 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12340 TEST_DrawLevelField(x, y);
12342 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12343 element == EL_BD_MAGIC_WALL_FULL)
12345 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12346 TEST_DrawLevelField(x, y);
12348 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12349 element == EL_DC_MAGIC_WALL_FULL)
12351 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12352 TEST_DrawLevelField(x, y);
12356 game.magic_wall_active = FALSE;
12361 if (game.light_time_left > 0)
12363 game.light_time_left--;
12365 if (game.light_time_left == 0)
12366 RedrawAllLightSwitchesAndInvisibleElements();
12369 if (game.timegate_time_left > 0)
12371 game.timegate_time_left--;
12373 if (game.timegate_time_left == 0)
12374 CloseAllOpenTimegates();
12377 if (game.lenses_time_left > 0)
12379 game.lenses_time_left--;
12381 if (game.lenses_time_left == 0)
12382 RedrawAllInvisibleElementsForLenses();
12385 if (game.magnify_time_left > 0)
12387 game.magnify_time_left--;
12389 if (game.magnify_time_left == 0)
12390 RedrawAllInvisibleElementsForMagnifier();
12393 for (i = 0; i < MAX_PLAYERS; i++)
12395 struct PlayerInfo *player = &stored_player[i];
12397 if (SHIELD_ON(player))
12399 if (player->shield_deadly_time_left)
12400 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12401 else if (player->shield_normal_time_left)
12402 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12406 #if USE_DELAYED_GFX_REDRAW
12407 SCAN_PLAYFIELD(x, y)
12409 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12411 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12412 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12414 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12415 DrawLevelField(x, y);
12417 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12418 DrawLevelFieldCrumbled(x, y);
12420 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12421 DrawLevelFieldCrumbledNeighbours(x, y);
12423 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12424 DrawTwinkleOnField(x, y);
12427 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12432 PlayAllPlayersSound();
12434 for (i = 0; i < MAX_PLAYERS; i++)
12436 struct PlayerInfo *player = &stored_player[i];
12438 if (player->show_envelope != 0 && (!player->active ||
12439 player->MovPos == 0))
12441 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12443 player->show_envelope = 0;
12447 // use random number generator in every frame to make it less predictable
12448 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12451 mouse_action_last = mouse_action;
12454 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12456 int min_x = x, min_y = y, max_x = x, max_y = y;
12459 for (i = 0; i < MAX_PLAYERS; i++)
12461 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12463 if (!stored_player[i].active || &stored_player[i] == player)
12466 min_x = MIN(min_x, jx);
12467 min_y = MIN(min_y, jy);
12468 max_x = MAX(max_x, jx);
12469 max_y = MAX(max_y, jy);
12472 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12475 static boolean AllPlayersInVisibleScreen(void)
12479 for (i = 0; i < MAX_PLAYERS; i++)
12481 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12483 if (!stored_player[i].active)
12486 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12493 void ScrollLevel(int dx, int dy)
12495 int scroll_offset = 2 * TILEX_VAR;
12498 BlitBitmap(drawto_field, drawto_field,
12499 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12500 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12501 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12502 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12503 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12504 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12508 x = (dx == 1 ? BX1 : BX2);
12509 for (y = BY1; y <= BY2; y++)
12510 DrawScreenField(x, y);
12515 y = (dy == 1 ? BY1 : BY2);
12516 for (x = BX1; x <= BX2; x++)
12517 DrawScreenField(x, y);
12520 redraw_mask |= REDRAW_FIELD;
12523 static boolean canFallDown(struct PlayerInfo *player)
12525 int jx = player->jx, jy = player->jy;
12527 return (IN_LEV_FIELD(jx, jy + 1) &&
12528 (IS_FREE(jx, jy + 1) ||
12529 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12530 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12531 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12534 static boolean canPassField(int x, int y, int move_dir)
12536 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12537 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12538 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12539 int nextx = x + dx;
12540 int nexty = y + dy;
12541 int element = Tile[x][y];
12543 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12544 !CAN_MOVE(element) &&
12545 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12546 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12547 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12550 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12552 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12553 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12554 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12558 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12559 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12560 (IS_DIGGABLE(Tile[newx][newy]) ||
12561 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12562 canPassField(newx, newy, move_dir)));
12565 static void CheckGravityMovement(struct PlayerInfo *player)
12567 if (player->gravity && !player->programmed_action)
12569 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12570 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12571 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12572 int jx = player->jx, jy = player->jy;
12573 boolean player_is_moving_to_valid_field =
12574 (!player_is_snapping &&
12575 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12576 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12577 boolean player_can_fall_down = canFallDown(player);
12579 if (player_can_fall_down &&
12580 !player_is_moving_to_valid_field)
12581 player->programmed_action = MV_DOWN;
12585 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12587 return CheckGravityMovement(player);
12589 if (player->gravity && !player->programmed_action)
12591 int jx = player->jx, jy = player->jy;
12592 boolean field_under_player_is_free =
12593 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12594 boolean player_is_standing_on_valid_field =
12595 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12596 (IS_WALKABLE(Tile[jx][jy]) &&
12597 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12599 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12600 player->programmed_action = MV_DOWN;
12605 MovePlayerOneStep()
12606 -----------------------------------------------------------------------------
12607 dx, dy: direction (non-diagonal) to try to move the player to
12608 real_dx, real_dy: direction as read from input device (can be diagonal)
12611 boolean MovePlayerOneStep(struct PlayerInfo *player,
12612 int dx, int dy, int real_dx, int real_dy)
12614 int jx = player->jx, jy = player->jy;
12615 int new_jx = jx + dx, new_jy = jy + dy;
12617 boolean player_can_move = !player->cannot_move;
12619 if (!player->active || (!dx && !dy))
12620 return MP_NO_ACTION;
12622 player->MovDir = (dx < 0 ? MV_LEFT :
12623 dx > 0 ? MV_RIGHT :
12625 dy > 0 ? MV_DOWN : MV_NONE);
12627 if (!IN_LEV_FIELD(new_jx, new_jy))
12628 return MP_NO_ACTION;
12630 if (!player_can_move)
12632 if (player->MovPos == 0)
12634 player->is_moving = FALSE;
12635 player->is_digging = FALSE;
12636 player->is_collecting = FALSE;
12637 player->is_snapping = FALSE;
12638 player->is_pushing = FALSE;
12642 if (!network.enabled && game.centered_player_nr == -1 &&
12643 !AllPlayersInSight(player, new_jx, new_jy))
12644 return MP_NO_ACTION;
12646 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12647 if (can_move != MP_MOVING)
12650 // check if DigField() has caused relocation of the player
12651 if (player->jx != jx || player->jy != jy)
12652 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12654 StorePlayer[jx][jy] = 0;
12655 player->last_jx = jx;
12656 player->last_jy = jy;
12657 player->jx = new_jx;
12658 player->jy = new_jy;
12659 StorePlayer[new_jx][new_jy] = player->element_nr;
12661 if (player->move_delay_value_next != -1)
12663 player->move_delay_value = player->move_delay_value_next;
12664 player->move_delay_value_next = -1;
12668 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12670 player->step_counter++;
12672 PlayerVisit[jx][jy] = FrameCounter;
12674 player->is_moving = TRUE;
12677 // should better be called in MovePlayer(), but this breaks some tapes
12678 ScrollPlayer(player, SCROLL_INIT);
12684 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12686 int jx = player->jx, jy = player->jy;
12687 int old_jx = jx, old_jy = jy;
12688 int moved = MP_NO_ACTION;
12690 if (!player->active)
12695 if (player->MovPos == 0)
12697 player->is_moving = FALSE;
12698 player->is_digging = FALSE;
12699 player->is_collecting = FALSE;
12700 player->is_snapping = FALSE;
12701 player->is_pushing = FALSE;
12707 if (player->move_delay > 0)
12710 player->move_delay = -1; // set to "uninitialized" value
12712 // store if player is automatically moved to next field
12713 player->is_auto_moving = (player->programmed_action != MV_NONE);
12715 // remove the last programmed player action
12716 player->programmed_action = 0;
12718 if (player->MovPos)
12720 // should only happen if pre-1.2 tape recordings are played
12721 // this is only for backward compatibility
12723 int original_move_delay_value = player->move_delay_value;
12726 Debug("game:playing:MovePlayer",
12727 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12731 // scroll remaining steps with finest movement resolution
12732 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12734 while (player->MovPos)
12736 ScrollPlayer(player, SCROLL_GO_ON);
12737 ScrollScreen(NULL, SCROLL_GO_ON);
12739 AdvanceFrameAndPlayerCounters(player->index_nr);
12742 BackToFront_WithFrameDelay(0);
12745 player->move_delay_value = original_move_delay_value;
12748 player->is_active = FALSE;
12750 if (player->last_move_dir & MV_HORIZONTAL)
12752 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12753 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12757 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12758 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12761 if (!moved && !player->is_active)
12763 player->is_moving = FALSE;
12764 player->is_digging = FALSE;
12765 player->is_collecting = FALSE;
12766 player->is_snapping = FALSE;
12767 player->is_pushing = FALSE;
12773 if (moved & MP_MOVING && !ScreenMovPos &&
12774 (player->index_nr == game.centered_player_nr ||
12775 game.centered_player_nr == -1))
12777 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12779 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12781 // actual player has left the screen -- scroll in that direction
12782 if (jx != old_jx) // player has moved horizontally
12783 scroll_x += (jx - old_jx);
12784 else // player has moved vertically
12785 scroll_y += (jy - old_jy);
12789 int offset_raw = game.scroll_delay_value;
12791 if (jx != old_jx) // player has moved horizontally
12793 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12794 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12795 int new_scroll_x = jx - MIDPOSX + offset_x;
12797 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12798 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12799 scroll_x = new_scroll_x;
12801 // don't scroll over playfield boundaries
12802 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12804 // don't scroll more than one field at a time
12805 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12807 // don't scroll against the player's moving direction
12808 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12809 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12810 scroll_x = old_scroll_x;
12812 else // player has moved vertically
12814 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12815 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12816 int new_scroll_y = jy - MIDPOSY + offset_y;
12818 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12819 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12820 scroll_y = new_scroll_y;
12822 // don't scroll over playfield boundaries
12823 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12825 // don't scroll more than one field at a time
12826 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12828 // don't scroll against the player's moving direction
12829 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12830 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12831 scroll_y = old_scroll_y;
12835 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12837 if (!network.enabled && game.centered_player_nr == -1 &&
12838 !AllPlayersInVisibleScreen())
12840 scroll_x = old_scroll_x;
12841 scroll_y = old_scroll_y;
12845 ScrollScreen(player, SCROLL_INIT);
12846 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12851 player->StepFrame = 0;
12853 if (moved & MP_MOVING)
12855 if (old_jx != jx && old_jy == jy)
12856 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12857 else if (old_jx == jx && old_jy != jy)
12858 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12860 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12862 player->last_move_dir = player->MovDir;
12863 player->is_moving = TRUE;
12864 player->is_snapping = FALSE;
12865 player->is_switching = FALSE;
12866 player->is_dropping = FALSE;
12867 player->is_dropping_pressed = FALSE;
12868 player->drop_pressed_delay = 0;
12871 // should better be called here than above, but this breaks some tapes
12872 ScrollPlayer(player, SCROLL_INIT);
12877 CheckGravityMovementWhenNotMoving(player);
12879 player->is_moving = FALSE;
12881 /* at this point, the player is allowed to move, but cannot move right now
12882 (e.g. because of something blocking the way) -- ensure that the player
12883 is also allowed to move in the next frame (in old versions before 3.1.1,
12884 the player was forced to wait again for eight frames before next try) */
12886 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12887 player->move_delay = 0; // allow direct movement in the next frame
12890 if (player->move_delay == -1) // not yet initialized by DigField()
12891 player->move_delay = player->move_delay_value;
12893 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12895 TestIfPlayerTouchesBadThing(jx, jy);
12896 TestIfPlayerTouchesCustomElement(jx, jy);
12899 if (!player->active)
12900 RemovePlayer(player);
12905 void ScrollPlayer(struct PlayerInfo *player, int mode)
12907 int jx = player->jx, jy = player->jy;
12908 int last_jx = player->last_jx, last_jy = player->last_jy;
12909 int move_stepsize = TILEX / player->move_delay_value;
12911 if (!player->active)
12914 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12917 if (mode == SCROLL_INIT)
12919 player->actual_frame_counter = FrameCounter;
12920 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12922 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12923 Tile[last_jx][last_jy] == EL_EMPTY)
12925 int last_field_block_delay = 0; // start with no blocking at all
12926 int block_delay_adjustment = player->block_delay_adjustment;
12928 // if player blocks last field, add delay for exactly one move
12929 if (player->block_last_field)
12931 last_field_block_delay += player->move_delay_value;
12933 // when blocking enabled, prevent moving up despite gravity
12934 if (player->gravity && player->MovDir == MV_UP)
12935 block_delay_adjustment = -1;
12938 // add block delay adjustment (also possible when not blocking)
12939 last_field_block_delay += block_delay_adjustment;
12941 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12942 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12945 if (player->MovPos != 0) // player has not yet reached destination
12948 else if (!FrameReached(&player->actual_frame_counter, 1))
12951 if (player->MovPos != 0)
12953 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12954 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12956 // before DrawPlayer() to draw correct player graphic for this case
12957 if (player->MovPos == 0)
12958 CheckGravityMovement(player);
12961 if (player->MovPos == 0) // player reached destination field
12963 if (player->move_delay_reset_counter > 0)
12965 player->move_delay_reset_counter--;
12967 if (player->move_delay_reset_counter == 0)
12969 // continue with normal speed after quickly moving through gate
12970 HALVE_PLAYER_SPEED(player);
12972 // be able to make the next move without delay
12973 player->move_delay = 0;
12977 player->last_jx = jx;
12978 player->last_jy = jy;
12980 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12981 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12982 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12983 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12984 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12985 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12986 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12987 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12989 ExitPlayer(player);
12991 if (game.players_still_needed == 0 &&
12992 (game.friends_still_needed == 0 ||
12993 IS_SP_ELEMENT(Tile[jx][jy])))
12997 // this breaks one level: "machine", level 000
12999 int move_direction = player->MovDir;
13000 int enter_side = MV_DIR_OPPOSITE(move_direction);
13001 int leave_side = move_direction;
13002 int old_jx = last_jx;
13003 int old_jy = last_jy;
13004 int old_element = Tile[old_jx][old_jy];
13005 int new_element = Tile[jx][jy];
13007 if (IS_CUSTOM_ELEMENT(old_element))
13008 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13010 player->index_bit, leave_side);
13012 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13013 CE_PLAYER_LEAVES_X,
13014 player->index_bit, leave_side);
13016 if (IS_CUSTOM_ELEMENT(new_element))
13017 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13018 player->index_bit, enter_side);
13020 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13021 CE_PLAYER_ENTERS_X,
13022 player->index_bit, enter_side);
13024 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13025 CE_MOVE_OF_X, move_direction);
13028 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13030 TestIfPlayerTouchesBadThing(jx, jy);
13031 TestIfPlayerTouchesCustomElement(jx, jy);
13033 /* needed because pushed element has not yet reached its destination,
13034 so it would trigger a change event at its previous field location */
13035 if (!player->is_pushing)
13036 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13038 if (!player->active)
13039 RemovePlayer(player);
13042 if (!game.LevelSolved && level.use_step_counter)
13052 if (TimeLeft <= 10 && setup.time_limit)
13053 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13055 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13057 DisplayGameControlValues();
13059 if (!TimeLeft && setup.time_limit)
13060 for (i = 0; i < MAX_PLAYERS; i++)
13061 KillPlayer(&stored_player[i]);
13063 else if (game.no_time_limit && !game.all_players_gone)
13065 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13067 DisplayGameControlValues();
13071 if (tape.single_step && tape.recording && !tape.pausing &&
13072 !player->programmed_action)
13073 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13075 if (!player->programmed_action)
13076 CheckSaveEngineSnapshot(player);
13080 void ScrollScreen(struct PlayerInfo *player, int mode)
13082 static unsigned int screen_frame_counter = 0;
13084 if (mode == SCROLL_INIT)
13086 // set scrolling step size according to actual player's moving speed
13087 ScrollStepSize = TILEX / player->move_delay_value;
13089 screen_frame_counter = FrameCounter;
13090 ScreenMovDir = player->MovDir;
13091 ScreenMovPos = player->MovPos;
13092 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13095 else if (!FrameReached(&screen_frame_counter, 1))
13100 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13101 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13102 redraw_mask |= REDRAW_FIELD;
13105 ScreenMovDir = MV_NONE;
13108 void TestIfPlayerTouchesCustomElement(int x, int y)
13110 static int xy[4][2] =
13117 static int trigger_sides[4][2] =
13119 // center side border side
13120 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13121 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13122 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13123 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13125 static int touch_dir[4] =
13127 MV_LEFT | MV_RIGHT,
13132 int center_element = Tile[x][y]; // should always be non-moving!
13135 for (i = 0; i < NUM_DIRECTIONS; i++)
13137 int xx = x + xy[i][0];
13138 int yy = y + xy[i][1];
13139 int center_side = trigger_sides[i][0];
13140 int border_side = trigger_sides[i][1];
13141 int border_element;
13143 if (!IN_LEV_FIELD(xx, yy))
13146 if (IS_PLAYER(x, y)) // player found at center element
13148 struct PlayerInfo *player = PLAYERINFO(x, y);
13150 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13151 border_element = Tile[xx][yy]; // may be moving!
13152 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13153 border_element = Tile[xx][yy];
13154 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13155 border_element = MovingOrBlocked2Element(xx, yy);
13157 continue; // center and border element do not touch
13159 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13160 player->index_bit, border_side);
13161 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13162 CE_PLAYER_TOUCHES_X,
13163 player->index_bit, border_side);
13166 /* use player element that is initially defined in the level playfield,
13167 not the player element that corresponds to the runtime player number
13168 (example: a level that contains EL_PLAYER_3 as the only player would
13169 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13170 int player_element = PLAYERINFO(x, y)->initial_element;
13172 CheckElementChangeBySide(xx, yy, border_element, player_element,
13173 CE_TOUCHING_X, border_side);
13176 else if (IS_PLAYER(xx, yy)) // player found at border element
13178 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13180 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13182 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13183 continue; // center and border element do not touch
13186 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13187 player->index_bit, center_side);
13188 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13189 CE_PLAYER_TOUCHES_X,
13190 player->index_bit, center_side);
13193 /* use player element that is initially defined in the level playfield,
13194 not the player element that corresponds to the runtime player number
13195 (example: a level that contains EL_PLAYER_3 as the only player would
13196 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13197 int player_element = PLAYERINFO(xx, yy)->initial_element;
13199 CheckElementChangeBySide(x, y, center_element, player_element,
13200 CE_TOUCHING_X, center_side);
13208 void TestIfElementTouchesCustomElement(int x, int y)
13210 static int xy[4][2] =
13217 static int trigger_sides[4][2] =
13219 // center side border side
13220 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13221 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13222 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13223 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13225 static int touch_dir[4] =
13227 MV_LEFT | MV_RIGHT,
13232 boolean change_center_element = FALSE;
13233 int center_element = Tile[x][y]; // should always be non-moving!
13234 int border_element_old[NUM_DIRECTIONS];
13237 for (i = 0; i < NUM_DIRECTIONS; i++)
13239 int xx = x + xy[i][0];
13240 int yy = y + xy[i][1];
13241 int border_element;
13243 border_element_old[i] = -1;
13245 if (!IN_LEV_FIELD(xx, yy))
13248 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13249 border_element = Tile[xx][yy]; // may be moving!
13250 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13251 border_element = Tile[xx][yy];
13252 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13253 border_element = MovingOrBlocked2Element(xx, yy);
13255 continue; // center and border element do not touch
13257 border_element_old[i] = border_element;
13260 for (i = 0; i < NUM_DIRECTIONS; i++)
13262 int xx = x + xy[i][0];
13263 int yy = y + xy[i][1];
13264 int center_side = trigger_sides[i][0];
13265 int border_element = border_element_old[i];
13267 if (border_element == -1)
13270 // check for change of border element
13271 CheckElementChangeBySide(xx, yy, border_element, center_element,
13272 CE_TOUCHING_X, center_side);
13274 // (center element cannot be player, so we dont have to check this here)
13277 for (i = 0; i < NUM_DIRECTIONS; i++)
13279 int xx = x + xy[i][0];
13280 int yy = y + xy[i][1];
13281 int border_side = trigger_sides[i][1];
13282 int border_element = border_element_old[i];
13284 if (border_element == -1)
13287 // check for change of center element (but change it only once)
13288 if (!change_center_element)
13289 change_center_element =
13290 CheckElementChangeBySide(x, y, center_element, border_element,
13291 CE_TOUCHING_X, border_side);
13293 if (IS_PLAYER(xx, yy))
13295 /* use player element that is initially defined in the level playfield,
13296 not the player element that corresponds to the runtime player number
13297 (example: a level that contains EL_PLAYER_3 as the only player would
13298 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13299 int player_element = PLAYERINFO(xx, yy)->initial_element;
13301 CheckElementChangeBySide(x, y, center_element, player_element,
13302 CE_TOUCHING_X, border_side);
13307 void TestIfElementHitsCustomElement(int x, int y, int direction)
13309 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13310 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13311 int hitx = x + dx, hity = y + dy;
13312 int hitting_element = Tile[x][y];
13313 int touched_element;
13315 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13318 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13319 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13321 if (IN_LEV_FIELD(hitx, hity))
13323 int opposite_direction = MV_DIR_OPPOSITE(direction);
13324 int hitting_side = direction;
13325 int touched_side = opposite_direction;
13326 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13327 MovDir[hitx][hity] != direction ||
13328 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13334 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13335 CE_HITTING_X, touched_side);
13337 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13338 CE_HIT_BY_X, hitting_side);
13340 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13341 CE_HIT_BY_SOMETHING, opposite_direction);
13343 if (IS_PLAYER(hitx, hity))
13345 /* use player element that is initially defined in the level playfield,
13346 not the player element that corresponds to the runtime player number
13347 (example: a level that contains EL_PLAYER_3 as the only player would
13348 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13349 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13351 CheckElementChangeBySide(x, y, hitting_element, player_element,
13352 CE_HITTING_X, touched_side);
13357 // "hitting something" is also true when hitting the playfield border
13358 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13359 CE_HITTING_SOMETHING, direction);
13362 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13364 int i, kill_x = -1, kill_y = -1;
13366 int bad_element = -1;
13367 static int test_xy[4][2] =
13374 static int test_dir[4] =
13382 for (i = 0; i < NUM_DIRECTIONS; i++)
13384 int test_x, test_y, test_move_dir, test_element;
13386 test_x = good_x + test_xy[i][0];
13387 test_y = good_y + test_xy[i][1];
13389 if (!IN_LEV_FIELD(test_x, test_y))
13393 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13395 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13397 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13398 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13400 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13401 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13405 bad_element = test_element;
13411 if (kill_x != -1 || kill_y != -1)
13413 if (IS_PLAYER(good_x, good_y))
13415 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13417 if (player->shield_deadly_time_left > 0 &&
13418 !IS_INDESTRUCTIBLE(bad_element))
13419 Bang(kill_x, kill_y);
13420 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13421 KillPlayer(player);
13424 Bang(good_x, good_y);
13428 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13430 int i, kill_x = -1, kill_y = -1;
13431 int bad_element = Tile[bad_x][bad_y];
13432 static int test_xy[4][2] =
13439 static int touch_dir[4] =
13441 MV_LEFT | MV_RIGHT,
13446 static int test_dir[4] =
13454 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13457 for (i = 0; i < NUM_DIRECTIONS; i++)
13459 int test_x, test_y, test_move_dir, test_element;
13461 test_x = bad_x + test_xy[i][0];
13462 test_y = bad_y + test_xy[i][1];
13464 if (!IN_LEV_FIELD(test_x, test_y))
13468 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13470 test_element = Tile[test_x][test_y];
13472 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13473 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13475 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13476 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13478 // good thing is player or penguin that does not move away
13479 if (IS_PLAYER(test_x, test_y))
13481 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13483 if (bad_element == EL_ROBOT && player->is_moving)
13484 continue; // robot does not kill player if he is moving
13486 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13488 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13489 continue; // center and border element do not touch
13497 else if (test_element == EL_PENGUIN)
13507 if (kill_x != -1 || kill_y != -1)
13509 if (IS_PLAYER(kill_x, kill_y))
13511 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13513 if (player->shield_deadly_time_left > 0 &&
13514 !IS_INDESTRUCTIBLE(bad_element))
13515 Bang(bad_x, bad_y);
13516 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13517 KillPlayer(player);
13520 Bang(kill_x, kill_y);
13524 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13526 int bad_element = Tile[bad_x][bad_y];
13527 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13528 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13529 int test_x = bad_x + dx, test_y = bad_y + dy;
13530 int test_move_dir, test_element;
13531 int kill_x = -1, kill_y = -1;
13533 if (!IN_LEV_FIELD(test_x, test_y))
13537 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13539 test_element = Tile[test_x][test_y];
13541 if (test_move_dir != bad_move_dir)
13543 // good thing can be player or penguin that does not move away
13544 if (IS_PLAYER(test_x, test_y))
13546 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13548 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13549 player as being hit when he is moving towards the bad thing, because
13550 the "get hit by" condition would be lost after the player stops) */
13551 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13552 return; // player moves away from bad thing
13557 else if (test_element == EL_PENGUIN)
13564 if (kill_x != -1 || kill_y != -1)
13566 if (IS_PLAYER(kill_x, kill_y))
13568 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13570 if (player->shield_deadly_time_left > 0 &&
13571 !IS_INDESTRUCTIBLE(bad_element))
13572 Bang(bad_x, bad_y);
13573 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13574 KillPlayer(player);
13577 Bang(kill_x, kill_y);
13581 void TestIfPlayerTouchesBadThing(int x, int y)
13583 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13586 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13588 TestIfGoodThingHitsBadThing(x, y, move_dir);
13591 void TestIfBadThingTouchesPlayer(int x, int y)
13593 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13596 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13598 TestIfBadThingHitsGoodThing(x, y, move_dir);
13601 void TestIfFriendTouchesBadThing(int x, int y)
13603 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13606 void TestIfBadThingTouchesFriend(int x, int y)
13608 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13611 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13613 int i, kill_x = bad_x, kill_y = bad_y;
13614 static int xy[4][2] =
13622 for (i = 0; i < NUM_DIRECTIONS; i++)
13626 x = bad_x + xy[i][0];
13627 y = bad_y + xy[i][1];
13628 if (!IN_LEV_FIELD(x, y))
13631 element = Tile[x][y];
13632 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13633 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13641 if (kill_x != bad_x || kill_y != bad_y)
13642 Bang(bad_x, bad_y);
13645 void KillPlayer(struct PlayerInfo *player)
13647 int jx = player->jx, jy = player->jy;
13649 if (!player->active)
13653 Debug("game:playing:KillPlayer",
13654 "0: killed == %d, active == %d, reanimated == %d",
13655 player->killed, player->active, player->reanimated);
13658 /* the following code was introduced to prevent an infinite loop when calling
13660 -> CheckTriggeredElementChangeExt()
13661 -> ExecuteCustomElementAction()
13663 -> (infinitely repeating the above sequence of function calls)
13664 which occurs when killing the player while having a CE with the setting
13665 "kill player X when explosion of <player X>"; the solution using a new
13666 field "player->killed" was chosen for backwards compatibility, although
13667 clever use of the fields "player->active" etc. would probably also work */
13669 if (player->killed)
13673 player->killed = TRUE;
13675 // remove accessible field at the player's position
13676 Tile[jx][jy] = EL_EMPTY;
13678 // deactivate shield (else Bang()/Explode() would not work right)
13679 player->shield_normal_time_left = 0;
13680 player->shield_deadly_time_left = 0;
13683 Debug("game:playing:KillPlayer",
13684 "1: killed == %d, active == %d, reanimated == %d",
13685 player->killed, player->active, player->reanimated);
13691 Debug("game:playing:KillPlayer",
13692 "2: killed == %d, active == %d, reanimated == %d",
13693 player->killed, player->active, player->reanimated);
13696 if (player->reanimated) // killed player may have been reanimated
13697 player->killed = player->reanimated = FALSE;
13699 BuryPlayer(player);
13702 static void KillPlayerUnlessEnemyProtected(int x, int y)
13704 if (!PLAYER_ENEMY_PROTECTED(x, y))
13705 KillPlayer(PLAYERINFO(x, y));
13708 static void KillPlayerUnlessExplosionProtected(int x, int y)
13710 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13711 KillPlayer(PLAYERINFO(x, y));
13714 void BuryPlayer(struct PlayerInfo *player)
13716 int jx = player->jx, jy = player->jy;
13718 if (!player->active)
13721 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13722 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13724 RemovePlayer(player);
13726 player->buried = TRUE;
13728 if (game.all_players_gone)
13729 game.GameOver = TRUE;
13732 void RemovePlayer(struct PlayerInfo *player)
13734 int jx = player->jx, jy = player->jy;
13735 int i, found = FALSE;
13737 player->present = FALSE;
13738 player->active = FALSE;
13740 // required for some CE actions (even if the player is not active anymore)
13741 player->MovPos = 0;
13743 if (!ExplodeField[jx][jy])
13744 StorePlayer[jx][jy] = 0;
13746 if (player->is_moving)
13747 TEST_DrawLevelField(player->last_jx, player->last_jy);
13749 for (i = 0; i < MAX_PLAYERS; i++)
13750 if (stored_player[i].active)
13755 game.all_players_gone = TRUE;
13756 game.GameOver = TRUE;
13759 game.exit_x = game.robot_wheel_x = jx;
13760 game.exit_y = game.robot_wheel_y = jy;
13763 void ExitPlayer(struct PlayerInfo *player)
13765 DrawPlayer(player); // needed here only to cleanup last field
13766 RemovePlayer(player);
13768 if (game.players_still_needed > 0)
13769 game.players_still_needed--;
13772 static void setFieldForSnapping(int x, int y, int element, int direction)
13774 struct ElementInfo *ei = &element_info[element];
13775 int direction_bit = MV_DIR_TO_BIT(direction);
13776 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13777 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13778 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13780 Tile[x][y] = EL_ELEMENT_SNAPPING;
13781 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13783 ResetGfxAnimation(x, y);
13785 GfxElement[x][y] = element;
13786 GfxAction[x][y] = action;
13787 GfxDir[x][y] = direction;
13788 GfxFrame[x][y] = -1;
13792 =============================================================================
13793 checkDiagonalPushing()
13794 -----------------------------------------------------------------------------
13795 check if diagonal input device direction results in pushing of object
13796 (by checking if the alternative direction is walkable, diggable, ...)
13797 =============================================================================
13800 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13801 int x, int y, int real_dx, int real_dy)
13803 int jx, jy, dx, dy, xx, yy;
13805 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13808 // diagonal direction: check alternative direction
13813 xx = jx + (dx == 0 ? real_dx : 0);
13814 yy = jy + (dy == 0 ? real_dy : 0);
13816 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13820 =============================================================================
13822 -----------------------------------------------------------------------------
13823 x, y: field next to player (non-diagonal) to try to dig to
13824 real_dx, real_dy: direction as read from input device (can be diagonal)
13825 =============================================================================
13828 static int DigField(struct PlayerInfo *player,
13829 int oldx, int oldy, int x, int y,
13830 int real_dx, int real_dy, int mode)
13832 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13833 boolean player_was_pushing = player->is_pushing;
13834 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13835 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13836 int jx = oldx, jy = oldy;
13837 int dx = x - jx, dy = y - jy;
13838 int nextx = x + dx, nexty = y + dy;
13839 int move_direction = (dx == -1 ? MV_LEFT :
13840 dx == +1 ? MV_RIGHT :
13842 dy == +1 ? MV_DOWN : MV_NONE);
13843 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13844 int dig_side = MV_DIR_OPPOSITE(move_direction);
13845 int old_element = Tile[jx][jy];
13846 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13849 if (is_player) // function can also be called by EL_PENGUIN
13851 if (player->MovPos == 0)
13853 player->is_digging = FALSE;
13854 player->is_collecting = FALSE;
13857 if (player->MovPos == 0) // last pushing move finished
13858 player->is_pushing = FALSE;
13860 if (mode == DF_NO_PUSH) // player just stopped pushing
13862 player->is_switching = FALSE;
13863 player->push_delay = -1;
13865 return MP_NO_ACTION;
13869 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13870 old_element = Back[jx][jy];
13872 // in case of element dropped at player position, check background
13873 else if (Back[jx][jy] != EL_EMPTY &&
13874 game.engine_version >= VERSION_IDENT(2,2,0,0))
13875 old_element = Back[jx][jy];
13877 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13878 return MP_NO_ACTION; // field has no opening in this direction
13880 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13881 return MP_NO_ACTION; // field has no opening in this direction
13883 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13887 Tile[jx][jy] = player->artwork_element;
13888 InitMovingField(jx, jy, MV_DOWN);
13889 Store[jx][jy] = EL_ACID;
13890 ContinueMoving(jx, jy);
13891 BuryPlayer(player);
13893 return MP_DONT_RUN_INTO;
13896 if (player_can_move && DONT_RUN_INTO(element))
13898 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13900 return MP_DONT_RUN_INTO;
13903 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13904 return MP_NO_ACTION;
13906 collect_count = element_info[element].collect_count_initial;
13908 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13909 return MP_NO_ACTION;
13911 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13912 player_can_move = player_can_move_or_snap;
13914 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13915 game.engine_version >= VERSION_IDENT(2,2,0,0))
13917 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13918 player->index_bit, dig_side);
13919 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13920 player->index_bit, dig_side);
13922 if (element == EL_DC_LANDMINE)
13925 if (Tile[x][y] != element) // field changed by snapping
13928 return MP_NO_ACTION;
13931 if (player->gravity && is_player && !player->is_auto_moving &&
13932 canFallDown(player) && move_direction != MV_DOWN &&
13933 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13934 return MP_NO_ACTION; // player cannot walk here due to gravity
13936 if (player_can_move &&
13937 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13939 int sound_element = SND_ELEMENT(element);
13940 int sound_action = ACTION_WALKING;
13942 if (IS_RND_GATE(element))
13944 if (!player->key[RND_GATE_NR(element)])
13945 return MP_NO_ACTION;
13947 else if (IS_RND_GATE_GRAY(element))
13949 if (!player->key[RND_GATE_GRAY_NR(element)])
13950 return MP_NO_ACTION;
13952 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13954 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13955 return MP_NO_ACTION;
13957 else if (element == EL_EXIT_OPEN ||
13958 element == EL_EM_EXIT_OPEN ||
13959 element == EL_EM_EXIT_OPENING ||
13960 element == EL_STEEL_EXIT_OPEN ||
13961 element == EL_EM_STEEL_EXIT_OPEN ||
13962 element == EL_EM_STEEL_EXIT_OPENING ||
13963 element == EL_SP_EXIT_OPEN ||
13964 element == EL_SP_EXIT_OPENING)
13966 sound_action = ACTION_PASSING; // player is passing exit
13968 else if (element == EL_EMPTY)
13970 sound_action = ACTION_MOVING; // nothing to walk on
13973 // play sound from background or player, whatever is available
13974 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13975 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13977 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13979 else if (player_can_move &&
13980 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13982 if (!ACCESS_FROM(element, opposite_direction))
13983 return MP_NO_ACTION; // field not accessible from this direction
13985 if (CAN_MOVE(element)) // only fixed elements can be passed!
13986 return MP_NO_ACTION;
13988 if (IS_EM_GATE(element))
13990 if (!player->key[EM_GATE_NR(element)])
13991 return MP_NO_ACTION;
13993 else if (IS_EM_GATE_GRAY(element))
13995 if (!player->key[EM_GATE_GRAY_NR(element)])
13996 return MP_NO_ACTION;
13998 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14000 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14001 return MP_NO_ACTION;
14003 else if (IS_EMC_GATE(element))
14005 if (!player->key[EMC_GATE_NR(element)])
14006 return MP_NO_ACTION;
14008 else if (IS_EMC_GATE_GRAY(element))
14010 if (!player->key[EMC_GATE_GRAY_NR(element)])
14011 return MP_NO_ACTION;
14013 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14015 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14016 return MP_NO_ACTION;
14018 else if (element == EL_DC_GATE_WHITE ||
14019 element == EL_DC_GATE_WHITE_GRAY ||
14020 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14022 if (player->num_white_keys == 0)
14023 return MP_NO_ACTION;
14025 player->num_white_keys--;
14027 else if (IS_SP_PORT(element))
14029 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14030 element == EL_SP_GRAVITY_PORT_RIGHT ||
14031 element == EL_SP_GRAVITY_PORT_UP ||
14032 element == EL_SP_GRAVITY_PORT_DOWN)
14033 player->gravity = !player->gravity;
14034 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14035 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14036 element == EL_SP_GRAVITY_ON_PORT_UP ||
14037 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14038 player->gravity = TRUE;
14039 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14040 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14041 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14042 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14043 player->gravity = FALSE;
14046 // automatically move to the next field with double speed
14047 player->programmed_action = move_direction;
14049 if (player->move_delay_reset_counter == 0)
14051 player->move_delay_reset_counter = 2; // two double speed steps
14053 DOUBLE_PLAYER_SPEED(player);
14056 PlayLevelSoundAction(x, y, ACTION_PASSING);
14058 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14062 if (mode != DF_SNAP)
14064 GfxElement[x][y] = GFX_ELEMENT(element);
14065 player->is_digging = TRUE;
14068 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14070 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14071 player->index_bit, dig_side);
14073 // if digging triggered player relocation, finish digging tile
14074 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14075 setFieldForSnapping(x, y, element, move_direction);
14077 if (mode == DF_SNAP)
14079 if (level.block_snap_field)
14080 setFieldForSnapping(x, y, element, move_direction);
14082 TestIfElementTouchesCustomElement(x, y); // for empty space
14084 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14085 player->index_bit, dig_side);
14088 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14092 if (is_player && mode != DF_SNAP)
14094 GfxElement[x][y] = element;
14095 player->is_collecting = TRUE;
14098 if (element == EL_SPEED_PILL)
14100 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14102 else if (element == EL_EXTRA_TIME && level.time > 0)
14104 TimeLeft += level.extra_time;
14106 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14108 DisplayGameControlValues();
14110 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14112 player->shield_normal_time_left += level.shield_normal_time;
14113 if (element == EL_SHIELD_DEADLY)
14114 player->shield_deadly_time_left += level.shield_deadly_time;
14116 else if (element == EL_DYNAMITE ||
14117 element == EL_EM_DYNAMITE ||
14118 element == EL_SP_DISK_RED)
14120 if (player->inventory_size < MAX_INVENTORY_SIZE)
14121 player->inventory_element[player->inventory_size++] = element;
14123 DrawGameDoorValues();
14125 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14127 player->dynabomb_count++;
14128 player->dynabombs_left++;
14130 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14132 player->dynabomb_size++;
14134 else if (element == EL_DYNABOMB_INCREASE_POWER)
14136 player->dynabomb_xl = TRUE;
14138 else if (IS_KEY(element))
14140 player->key[KEY_NR(element)] = TRUE;
14142 DrawGameDoorValues();
14144 else if (element == EL_DC_KEY_WHITE)
14146 player->num_white_keys++;
14148 // display white keys?
14149 // DrawGameDoorValues();
14151 else if (IS_ENVELOPE(element))
14153 player->show_envelope = element;
14155 else if (element == EL_EMC_LENSES)
14157 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14159 RedrawAllInvisibleElementsForLenses();
14161 else if (element == EL_EMC_MAGNIFIER)
14163 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14165 RedrawAllInvisibleElementsForMagnifier();
14167 else if (IS_DROPPABLE(element) ||
14168 IS_THROWABLE(element)) // can be collected and dropped
14172 if (collect_count == 0)
14173 player->inventory_infinite_element = element;
14175 for (i = 0; i < collect_count; i++)
14176 if (player->inventory_size < MAX_INVENTORY_SIZE)
14177 player->inventory_element[player->inventory_size++] = element;
14179 DrawGameDoorValues();
14181 else if (collect_count > 0)
14183 game.gems_still_needed -= collect_count;
14184 if (game.gems_still_needed < 0)
14185 game.gems_still_needed = 0;
14187 game.snapshot.collected_item = TRUE;
14189 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14191 DisplayGameControlValues();
14194 RaiseScoreElement(element);
14195 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14199 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14200 player->index_bit, dig_side);
14202 // if collecting triggered player relocation, finish collecting tile
14203 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14204 setFieldForSnapping(x, y, element, move_direction);
14207 if (mode == DF_SNAP)
14209 if (level.block_snap_field)
14210 setFieldForSnapping(x, y, element, move_direction);
14212 TestIfElementTouchesCustomElement(x, y); // for empty space
14214 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14215 player->index_bit, dig_side);
14218 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14220 if (mode == DF_SNAP && element != EL_BD_ROCK)
14221 return MP_NO_ACTION;
14223 if (CAN_FALL(element) && dy)
14224 return MP_NO_ACTION;
14226 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14227 !(element == EL_SPRING && level.use_spring_bug))
14228 return MP_NO_ACTION;
14230 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14231 ((move_direction & MV_VERTICAL &&
14232 ((element_info[element].move_pattern & MV_LEFT &&
14233 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14234 (element_info[element].move_pattern & MV_RIGHT &&
14235 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14236 (move_direction & MV_HORIZONTAL &&
14237 ((element_info[element].move_pattern & MV_UP &&
14238 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14239 (element_info[element].move_pattern & MV_DOWN &&
14240 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14241 return MP_NO_ACTION;
14243 // do not push elements already moving away faster than player
14244 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14245 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14246 return MP_NO_ACTION;
14248 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14250 if (player->push_delay_value == -1 || !player_was_pushing)
14251 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14253 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14255 if (player->push_delay_value == -1)
14256 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14258 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14260 if (!player->is_pushing)
14261 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14264 player->is_pushing = TRUE;
14265 player->is_active = TRUE;
14267 if (!(IN_LEV_FIELD(nextx, nexty) &&
14268 (IS_FREE(nextx, nexty) ||
14269 (IS_SB_ELEMENT(element) &&
14270 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14271 (IS_CUSTOM_ELEMENT(element) &&
14272 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14273 return MP_NO_ACTION;
14275 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14276 return MP_NO_ACTION;
14278 if (player->push_delay == -1) // new pushing; restart delay
14279 player->push_delay = 0;
14281 if (player->push_delay < player->push_delay_value &&
14282 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14283 element != EL_SPRING && element != EL_BALLOON)
14285 // make sure that there is no move delay before next try to push
14286 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14287 player->move_delay = 0;
14289 return MP_NO_ACTION;
14292 if (IS_CUSTOM_ELEMENT(element) &&
14293 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14295 if (!DigFieldByCE(nextx, nexty, element))
14296 return MP_NO_ACTION;
14299 if (IS_SB_ELEMENT(element))
14301 boolean sokoban_task_solved = FALSE;
14303 if (element == EL_SOKOBAN_FIELD_FULL)
14305 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14307 IncrementSokobanFieldsNeeded();
14308 IncrementSokobanObjectsNeeded();
14311 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14313 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14315 DecrementSokobanFieldsNeeded();
14316 DecrementSokobanObjectsNeeded();
14318 // sokoban object was pushed from empty field to sokoban field
14319 if (Back[x][y] == EL_EMPTY)
14320 sokoban_task_solved = TRUE;
14323 Tile[x][y] = EL_SOKOBAN_OBJECT;
14325 if (Back[x][y] == Back[nextx][nexty])
14326 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14327 else if (Back[x][y] != 0)
14328 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14331 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14334 if (sokoban_task_solved &&
14335 game.sokoban_fields_still_needed == 0 &&
14336 game.sokoban_objects_still_needed == 0 &&
14337 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14339 game.players_still_needed = 0;
14343 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14347 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14349 InitMovingField(x, y, move_direction);
14350 GfxAction[x][y] = ACTION_PUSHING;
14352 if (mode == DF_SNAP)
14353 ContinueMoving(x, y);
14355 MovPos[x][y] = (dx != 0 ? dx : dy);
14357 Pushed[x][y] = TRUE;
14358 Pushed[nextx][nexty] = TRUE;
14360 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14361 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14363 player->push_delay_value = -1; // get new value later
14365 // check for element change _after_ element has been pushed
14366 if (game.use_change_when_pushing_bug)
14368 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14369 player->index_bit, dig_side);
14370 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14371 player->index_bit, dig_side);
14374 else if (IS_SWITCHABLE(element))
14376 if (PLAYER_SWITCHING(player, x, y))
14378 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14379 player->index_bit, dig_side);
14384 player->is_switching = TRUE;
14385 player->switch_x = x;
14386 player->switch_y = y;
14388 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14390 if (element == EL_ROBOT_WHEEL)
14392 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14394 game.robot_wheel_x = x;
14395 game.robot_wheel_y = y;
14396 game.robot_wheel_active = TRUE;
14398 TEST_DrawLevelField(x, y);
14400 else if (element == EL_SP_TERMINAL)
14404 SCAN_PLAYFIELD(xx, yy)
14406 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14410 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14412 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14414 ResetGfxAnimation(xx, yy);
14415 TEST_DrawLevelField(xx, yy);
14419 else if (IS_BELT_SWITCH(element))
14421 ToggleBeltSwitch(x, y);
14423 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14424 element == EL_SWITCHGATE_SWITCH_DOWN ||
14425 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14426 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14428 ToggleSwitchgateSwitch(x, y);
14430 else if (element == EL_LIGHT_SWITCH ||
14431 element == EL_LIGHT_SWITCH_ACTIVE)
14433 ToggleLightSwitch(x, y);
14435 else if (element == EL_TIMEGATE_SWITCH ||
14436 element == EL_DC_TIMEGATE_SWITCH)
14438 ActivateTimegateSwitch(x, y);
14440 else if (element == EL_BALLOON_SWITCH_LEFT ||
14441 element == EL_BALLOON_SWITCH_RIGHT ||
14442 element == EL_BALLOON_SWITCH_UP ||
14443 element == EL_BALLOON_SWITCH_DOWN ||
14444 element == EL_BALLOON_SWITCH_NONE ||
14445 element == EL_BALLOON_SWITCH_ANY)
14447 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14448 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14449 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14450 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14451 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14454 else if (element == EL_LAMP)
14456 Tile[x][y] = EL_LAMP_ACTIVE;
14457 game.lights_still_needed--;
14459 ResetGfxAnimation(x, y);
14460 TEST_DrawLevelField(x, y);
14462 else if (element == EL_TIME_ORB_FULL)
14464 Tile[x][y] = EL_TIME_ORB_EMPTY;
14466 if (level.time > 0 || level.use_time_orb_bug)
14468 TimeLeft += level.time_orb_time;
14469 game.no_time_limit = FALSE;
14471 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14473 DisplayGameControlValues();
14476 ResetGfxAnimation(x, y);
14477 TEST_DrawLevelField(x, y);
14479 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14480 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14484 game.ball_active = !game.ball_active;
14486 SCAN_PLAYFIELD(xx, yy)
14488 int e = Tile[xx][yy];
14490 if (game.ball_active)
14492 if (e == EL_EMC_MAGIC_BALL)
14493 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14494 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14495 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14499 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14500 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14501 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14502 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14507 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14508 player->index_bit, dig_side);
14510 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14511 player->index_bit, dig_side);
14513 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14514 player->index_bit, dig_side);
14520 if (!PLAYER_SWITCHING(player, x, y))
14522 player->is_switching = TRUE;
14523 player->switch_x = x;
14524 player->switch_y = y;
14526 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14527 player->index_bit, dig_side);
14528 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14529 player->index_bit, dig_side);
14531 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14532 player->index_bit, dig_side);
14533 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14534 player->index_bit, dig_side);
14537 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14538 player->index_bit, dig_side);
14539 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14540 player->index_bit, dig_side);
14542 return MP_NO_ACTION;
14545 player->push_delay = -1;
14547 if (is_player) // function can also be called by EL_PENGUIN
14549 if (Tile[x][y] != element) // really digged/collected something
14551 player->is_collecting = !player->is_digging;
14552 player->is_active = TRUE;
14559 static boolean DigFieldByCE(int x, int y, int digging_element)
14561 int element = Tile[x][y];
14563 if (!IS_FREE(x, y))
14565 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14566 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14569 // no element can dig solid indestructible elements
14570 if (IS_INDESTRUCTIBLE(element) &&
14571 !IS_DIGGABLE(element) &&
14572 !IS_COLLECTIBLE(element))
14575 if (AmoebaNr[x][y] &&
14576 (element == EL_AMOEBA_FULL ||
14577 element == EL_BD_AMOEBA ||
14578 element == EL_AMOEBA_GROWING))
14580 AmoebaCnt[AmoebaNr[x][y]]--;
14581 AmoebaCnt2[AmoebaNr[x][y]]--;
14584 if (IS_MOVING(x, y))
14585 RemoveMovingField(x, y);
14589 TEST_DrawLevelField(x, y);
14592 // if digged element was about to explode, prevent the explosion
14593 ExplodeField[x][y] = EX_TYPE_NONE;
14595 PlayLevelSoundAction(x, y, action);
14598 Store[x][y] = EL_EMPTY;
14600 // this makes it possible to leave the removed element again
14601 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14602 Store[x][y] = element;
14607 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14609 int jx = player->jx, jy = player->jy;
14610 int x = jx + dx, y = jy + dy;
14611 int snap_direction = (dx == -1 ? MV_LEFT :
14612 dx == +1 ? MV_RIGHT :
14614 dy == +1 ? MV_DOWN : MV_NONE);
14615 boolean can_continue_snapping = (level.continuous_snapping &&
14616 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14618 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14621 if (!player->active || !IN_LEV_FIELD(x, y))
14629 if (player->MovPos == 0)
14630 player->is_pushing = FALSE;
14632 player->is_snapping = FALSE;
14634 if (player->MovPos == 0)
14636 player->is_moving = FALSE;
14637 player->is_digging = FALSE;
14638 player->is_collecting = FALSE;
14644 // prevent snapping with already pressed snap key when not allowed
14645 if (player->is_snapping && !can_continue_snapping)
14648 player->MovDir = snap_direction;
14650 if (player->MovPos == 0)
14652 player->is_moving = FALSE;
14653 player->is_digging = FALSE;
14654 player->is_collecting = FALSE;
14657 player->is_dropping = FALSE;
14658 player->is_dropping_pressed = FALSE;
14659 player->drop_pressed_delay = 0;
14661 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14664 player->is_snapping = TRUE;
14665 player->is_active = TRUE;
14667 if (player->MovPos == 0)
14669 player->is_moving = FALSE;
14670 player->is_digging = FALSE;
14671 player->is_collecting = FALSE;
14674 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14675 TEST_DrawLevelField(player->last_jx, player->last_jy);
14677 TEST_DrawLevelField(x, y);
14682 static boolean DropElement(struct PlayerInfo *player)
14684 int old_element, new_element;
14685 int dropx = player->jx, dropy = player->jy;
14686 int drop_direction = player->MovDir;
14687 int drop_side = drop_direction;
14688 int drop_element = get_next_dropped_element(player);
14690 /* do not drop an element on top of another element; when holding drop key
14691 pressed without moving, dropped element must move away before the next
14692 element can be dropped (this is especially important if the next element
14693 is dynamite, which can be placed on background for historical reasons) */
14694 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14697 if (IS_THROWABLE(drop_element))
14699 dropx += GET_DX_FROM_DIR(drop_direction);
14700 dropy += GET_DY_FROM_DIR(drop_direction);
14702 if (!IN_LEV_FIELD(dropx, dropy))
14706 old_element = Tile[dropx][dropy]; // old element at dropping position
14707 new_element = drop_element; // default: no change when dropping
14709 // check if player is active, not moving and ready to drop
14710 if (!player->active || player->MovPos || player->drop_delay > 0)
14713 // check if player has anything that can be dropped
14714 if (new_element == EL_UNDEFINED)
14717 // only set if player has anything that can be dropped
14718 player->is_dropping_pressed = TRUE;
14720 // check if drop key was pressed long enough for EM style dynamite
14721 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14724 // check if anything can be dropped at the current position
14725 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14728 // collected custom elements can only be dropped on empty fields
14729 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14732 if (old_element != EL_EMPTY)
14733 Back[dropx][dropy] = old_element; // store old element on this field
14735 ResetGfxAnimation(dropx, dropy);
14736 ResetRandomAnimationValue(dropx, dropy);
14738 if (player->inventory_size > 0 ||
14739 player->inventory_infinite_element != EL_UNDEFINED)
14741 if (player->inventory_size > 0)
14743 player->inventory_size--;
14745 DrawGameDoorValues();
14747 if (new_element == EL_DYNAMITE)
14748 new_element = EL_DYNAMITE_ACTIVE;
14749 else if (new_element == EL_EM_DYNAMITE)
14750 new_element = EL_EM_DYNAMITE_ACTIVE;
14751 else if (new_element == EL_SP_DISK_RED)
14752 new_element = EL_SP_DISK_RED_ACTIVE;
14755 Tile[dropx][dropy] = new_element;
14757 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14758 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14759 el2img(Tile[dropx][dropy]), 0);
14761 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14763 // needed if previous element just changed to "empty" in the last frame
14764 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14766 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14767 player->index_bit, drop_side);
14768 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14770 player->index_bit, drop_side);
14772 TestIfElementTouchesCustomElement(dropx, dropy);
14774 else // player is dropping a dyna bomb
14776 player->dynabombs_left--;
14778 Tile[dropx][dropy] = new_element;
14780 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14781 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14782 el2img(Tile[dropx][dropy]), 0);
14784 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14787 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14788 InitField_WithBug1(dropx, dropy, FALSE);
14790 new_element = Tile[dropx][dropy]; // element might have changed
14792 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14793 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14795 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14796 MovDir[dropx][dropy] = drop_direction;
14798 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14800 // do not cause impact style collision by dropping elements that can fall
14801 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14804 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14805 player->is_dropping = TRUE;
14807 player->drop_pressed_delay = 0;
14808 player->is_dropping_pressed = FALSE;
14810 player->drop_x = dropx;
14811 player->drop_y = dropy;
14816 // ----------------------------------------------------------------------------
14817 // game sound playing functions
14818 // ----------------------------------------------------------------------------
14820 static int *loop_sound_frame = NULL;
14821 static int *loop_sound_volume = NULL;
14823 void InitPlayLevelSound(void)
14825 int num_sounds = getSoundListSize();
14827 checked_free(loop_sound_frame);
14828 checked_free(loop_sound_volume);
14830 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14831 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14834 static void PlayLevelSound(int x, int y, int nr)
14836 int sx = SCREENX(x), sy = SCREENY(y);
14837 int volume, stereo_position;
14838 int max_distance = 8;
14839 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14841 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14842 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14845 if (!IN_LEV_FIELD(x, y) ||
14846 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14847 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14850 volume = SOUND_MAX_VOLUME;
14852 if (!IN_SCR_FIELD(sx, sy))
14854 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14855 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14857 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14860 stereo_position = (SOUND_MAX_LEFT +
14861 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14862 (SCR_FIELDX + 2 * max_distance));
14864 if (IS_LOOP_SOUND(nr))
14866 /* This assures that quieter loop sounds do not overwrite louder ones,
14867 while restarting sound volume comparison with each new game frame. */
14869 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14872 loop_sound_volume[nr] = volume;
14873 loop_sound_frame[nr] = FrameCounter;
14876 PlaySoundExt(nr, volume, stereo_position, type);
14879 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14881 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14882 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14883 y < LEVELY(BY1) ? LEVELY(BY1) :
14884 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14888 static void PlayLevelSoundAction(int x, int y, int action)
14890 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14893 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14895 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14897 if (sound_effect != SND_UNDEFINED)
14898 PlayLevelSound(x, y, sound_effect);
14901 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14904 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14906 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14907 PlayLevelSound(x, y, sound_effect);
14910 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14912 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14914 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14915 PlayLevelSound(x, y, sound_effect);
14918 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14920 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14922 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14923 StopSound(sound_effect);
14926 static int getLevelMusicNr(void)
14928 if (levelset.music[level_nr] != MUS_UNDEFINED)
14929 return levelset.music[level_nr]; // from config file
14931 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14934 static void FadeLevelSounds(void)
14939 static void FadeLevelMusic(void)
14941 int music_nr = getLevelMusicNr();
14942 char *curr_music = getCurrentlyPlayingMusicFilename();
14943 char *next_music = getMusicInfoEntryFilename(music_nr);
14945 if (!strEqual(curr_music, next_music))
14949 void FadeLevelSoundsAndMusic(void)
14955 static void PlayLevelMusic(void)
14957 int music_nr = getLevelMusicNr();
14958 char *curr_music = getCurrentlyPlayingMusicFilename();
14959 char *next_music = getMusicInfoEntryFilename(music_nr);
14961 if (!strEqual(curr_music, next_music))
14962 PlayMusicLoop(music_nr);
14965 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14967 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14969 int x = xx - offset;
14970 int y = yy - offset;
14975 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14979 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14991 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14995 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14999 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15002 case SOUND_android_clone:
15003 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15006 case SOUND_android_move:
15007 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15011 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15015 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15019 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15022 case SOUND_eater_eat:
15023 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15027 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15030 case SOUND_collect:
15031 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15034 case SOUND_diamond:
15035 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15039 // !!! CHECK THIS !!!
15041 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15043 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15047 case SOUND_wonderfall:
15048 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15052 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15056 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15060 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15064 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15068 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15072 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15076 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15080 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15083 case SOUND_exit_open:
15084 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15087 case SOUND_exit_leave:
15088 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15091 case SOUND_dynamite:
15092 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15096 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15100 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15104 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15108 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15112 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15116 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15120 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15125 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15127 int element = map_element_SP_to_RND(element_sp);
15128 int action = map_action_SP_to_RND(action_sp);
15129 int offset = (setup.sp_show_border_elements ? 0 : 1);
15130 int x = xx - offset;
15131 int y = yy - offset;
15133 PlayLevelSoundElementAction(x, y, element, action);
15136 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15138 int element = map_element_MM_to_RND(element_mm);
15139 int action = map_action_MM_to_RND(action_mm);
15141 int x = xx - offset;
15142 int y = yy - offset;
15144 if (!IS_MM_ELEMENT(element))
15145 element = EL_MM_DEFAULT;
15147 PlayLevelSoundElementAction(x, y, element, action);
15150 void PlaySound_MM(int sound_mm)
15152 int sound = map_sound_MM_to_RND(sound_mm);
15154 if (sound == SND_UNDEFINED)
15160 void PlaySoundLoop_MM(int sound_mm)
15162 int sound = map_sound_MM_to_RND(sound_mm);
15164 if (sound == SND_UNDEFINED)
15167 PlaySoundLoop(sound);
15170 void StopSound_MM(int sound_mm)
15172 int sound = map_sound_MM_to_RND(sound_mm);
15174 if (sound == SND_UNDEFINED)
15180 void RaiseScore(int value)
15182 game.score += value;
15184 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15186 DisplayGameControlValues();
15189 void RaiseScoreElement(int element)
15194 case EL_BD_DIAMOND:
15195 case EL_EMERALD_YELLOW:
15196 case EL_EMERALD_RED:
15197 case EL_EMERALD_PURPLE:
15198 case EL_SP_INFOTRON:
15199 RaiseScore(level.score[SC_EMERALD]);
15202 RaiseScore(level.score[SC_DIAMOND]);
15205 RaiseScore(level.score[SC_CRYSTAL]);
15208 RaiseScore(level.score[SC_PEARL]);
15211 case EL_BD_BUTTERFLY:
15212 case EL_SP_ELECTRON:
15213 RaiseScore(level.score[SC_BUG]);
15216 case EL_BD_FIREFLY:
15217 case EL_SP_SNIKSNAK:
15218 RaiseScore(level.score[SC_SPACESHIP]);
15221 case EL_DARK_YAMYAM:
15222 RaiseScore(level.score[SC_YAMYAM]);
15225 RaiseScore(level.score[SC_ROBOT]);
15228 RaiseScore(level.score[SC_PACMAN]);
15231 RaiseScore(level.score[SC_NUT]);
15234 case EL_EM_DYNAMITE:
15235 case EL_SP_DISK_RED:
15236 case EL_DYNABOMB_INCREASE_NUMBER:
15237 case EL_DYNABOMB_INCREASE_SIZE:
15238 case EL_DYNABOMB_INCREASE_POWER:
15239 RaiseScore(level.score[SC_DYNAMITE]);
15241 case EL_SHIELD_NORMAL:
15242 case EL_SHIELD_DEADLY:
15243 RaiseScore(level.score[SC_SHIELD]);
15245 case EL_EXTRA_TIME:
15246 RaiseScore(level.extra_time_score);
15260 case EL_DC_KEY_WHITE:
15261 RaiseScore(level.score[SC_KEY]);
15264 RaiseScore(element_info[element].collect_score);
15269 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15271 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15273 // closing door required in case of envelope style request dialogs
15276 // prevent short reactivation of overlay buttons while closing door
15277 SetOverlayActive(FALSE);
15279 CloseDoor(DOOR_CLOSE_1);
15282 if (network.enabled)
15283 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15287 FadeSkipNextFadeIn();
15289 SetGameStatus(GAME_MODE_MAIN);
15294 else // continue playing the game
15296 if (tape.playing && tape.deactivate_display)
15297 TapeDeactivateDisplayOff(TRUE);
15299 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15301 if (tape.playing && tape.deactivate_display)
15302 TapeDeactivateDisplayOn();
15306 void RequestQuitGame(boolean ask_if_really_quit)
15308 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15309 boolean skip_request = game.all_players_gone || quick_quit;
15311 RequestQuitGameExt(skip_request, quick_quit,
15312 "Do you really want to quit the game?");
15315 void RequestRestartGame(char *message)
15317 game.restart_game_message = NULL;
15319 boolean has_started_game = hasStartedNetworkGame();
15320 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15322 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15324 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15328 // needed in case of envelope request to close game panel
15329 CloseDoor(DOOR_CLOSE_1);
15331 SetGameStatus(GAME_MODE_MAIN);
15337 void CheckGameOver(void)
15339 static boolean last_game_over = FALSE;
15340 static int game_over_delay = 0;
15341 int game_over_delay_value = 50;
15342 boolean game_over = checkGameFailed();
15344 // do not handle game over if request dialog is already active
15345 if (game.request_active)
15348 // do not ask to play again if game was never actually played
15349 if (!game.GamePlayed)
15354 last_game_over = FALSE;
15355 game_over_delay = game_over_delay_value;
15360 if (game_over_delay > 0)
15367 if (last_game_over != game_over)
15368 game.restart_game_message = (hasStartedNetworkGame() ?
15369 "Game over! Play it again?" :
15372 last_game_over = game_over;
15375 boolean checkGameSolved(void)
15377 // set for all game engines if level was solved
15378 return game.LevelSolved_GameEnd;
15381 boolean checkGameFailed(void)
15383 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15384 return (game_em.game_over && !game_em.level_solved);
15385 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15386 return (game_sp.game_over && !game_sp.level_solved);
15387 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15388 return (game_mm.game_over && !game_mm.level_solved);
15389 else // GAME_ENGINE_TYPE_RND
15390 return (game.GameOver && !game.LevelSolved);
15393 boolean checkGameEnded(void)
15395 return (checkGameSolved() || checkGameFailed());
15399 // ----------------------------------------------------------------------------
15400 // random generator functions
15401 // ----------------------------------------------------------------------------
15403 unsigned int InitEngineRandom_RND(int seed)
15405 game.num_random_calls = 0;
15407 return InitEngineRandom(seed);
15410 unsigned int RND(int max)
15414 game.num_random_calls++;
15416 return GetEngineRandom(max);
15423 // ----------------------------------------------------------------------------
15424 // game engine snapshot handling functions
15425 // ----------------------------------------------------------------------------
15427 struct EngineSnapshotInfo
15429 // runtime values for custom element collect score
15430 int collect_score[NUM_CUSTOM_ELEMENTS];
15432 // runtime values for group element choice position
15433 int choice_pos[NUM_GROUP_ELEMENTS];
15435 // runtime values for belt position animations
15436 int belt_graphic[4][NUM_BELT_PARTS];
15437 int belt_anim_mode[4][NUM_BELT_PARTS];
15440 static struct EngineSnapshotInfo engine_snapshot_rnd;
15441 static char *snapshot_level_identifier = NULL;
15442 static int snapshot_level_nr = -1;
15444 static void SaveEngineSnapshotValues_RND(void)
15446 static int belt_base_active_element[4] =
15448 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15449 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15450 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15451 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15455 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15457 int element = EL_CUSTOM_START + i;
15459 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15462 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15464 int element = EL_GROUP_START + i;
15466 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15469 for (i = 0; i < 4; i++)
15471 for (j = 0; j < NUM_BELT_PARTS; j++)
15473 int element = belt_base_active_element[i] + j;
15474 int graphic = el2img(element);
15475 int anim_mode = graphic_info[graphic].anim_mode;
15477 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15478 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15483 static void LoadEngineSnapshotValues_RND(void)
15485 unsigned int num_random_calls = game.num_random_calls;
15488 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15490 int element = EL_CUSTOM_START + i;
15492 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15495 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15497 int element = EL_GROUP_START + i;
15499 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15502 for (i = 0; i < 4; i++)
15504 for (j = 0; j < NUM_BELT_PARTS; j++)
15506 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15507 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15509 graphic_info[graphic].anim_mode = anim_mode;
15513 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15515 InitRND(tape.random_seed);
15516 for (i = 0; i < num_random_calls; i++)
15520 if (game.num_random_calls != num_random_calls)
15522 Error("number of random calls out of sync");
15523 Error("number of random calls should be %d", num_random_calls);
15524 Error("number of random calls is %d", game.num_random_calls);
15526 Fail("this should not happen -- please debug");
15530 void FreeEngineSnapshotSingle(void)
15532 FreeSnapshotSingle();
15534 setString(&snapshot_level_identifier, NULL);
15535 snapshot_level_nr = -1;
15538 void FreeEngineSnapshotList(void)
15540 FreeSnapshotList();
15543 static ListNode *SaveEngineSnapshotBuffers(void)
15545 ListNode *buffers = NULL;
15547 // copy some special values to a structure better suited for the snapshot
15549 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15550 SaveEngineSnapshotValues_RND();
15551 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15552 SaveEngineSnapshotValues_EM();
15553 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15554 SaveEngineSnapshotValues_SP(&buffers);
15555 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15556 SaveEngineSnapshotValues_MM(&buffers);
15558 // save values stored in special snapshot structure
15560 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15562 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15564 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15566 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15569 // save further RND engine values
15571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15583 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15602 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15605 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15609 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15612 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15621 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15622 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15623 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15625 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15626 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15629 ListNode *node = engine_snapshot_list_rnd;
15632 while (node != NULL)
15634 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15639 Debug("game:playing:SaveEngineSnapshotBuffers",
15640 "size of engine snapshot: %d bytes", num_bytes);
15646 void SaveEngineSnapshotSingle(void)
15648 ListNode *buffers = SaveEngineSnapshotBuffers();
15650 // finally save all snapshot buffers to single snapshot
15651 SaveSnapshotSingle(buffers);
15653 // save level identification information
15654 setString(&snapshot_level_identifier, leveldir_current->identifier);
15655 snapshot_level_nr = level_nr;
15658 boolean CheckSaveEngineSnapshotToList(void)
15660 boolean save_snapshot =
15661 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15662 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15663 game.snapshot.changed_action) ||
15664 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15665 game.snapshot.collected_item));
15667 game.snapshot.changed_action = FALSE;
15668 game.snapshot.collected_item = FALSE;
15669 game.snapshot.save_snapshot = save_snapshot;
15671 return save_snapshot;
15674 void SaveEngineSnapshotToList(void)
15676 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15680 ListNode *buffers = SaveEngineSnapshotBuffers();
15682 // finally save all snapshot buffers to snapshot list
15683 SaveSnapshotToList(buffers);
15686 void SaveEngineSnapshotToListInitial(void)
15688 FreeEngineSnapshotList();
15690 SaveEngineSnapshotToList();
15693 static void LoadEngineSnapshotValues(void)
15695 // restore special values from snapshot structure
15697 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15698 LoadEngineSnapshotValues_RND();
15699 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15700 LoadEngineSnapshotValues_EM();
15701 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15702 LoadEngineSnapshotValues_SP();
15703 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15704 LoadEngineSnapshotValues_MM();
15707 void LoadEngineSnapshotSingle(void)
15709 LoadSnapshotSingle();
15711 LoadEngineSnapshotValues();
15714 static void LoadEngineSnapshot_Undo(int steps)
15716 LoadSnapshotFromList_Older(steps);
15718 LoadEngineSnapshotValues();
15721 static void LoadEngineSnapshot_Redo(int steps)
15723 LoadSnapshotFromList_Newer(steps);
15725 LoadEngineSnapshotValues();
15728 boolean CheckEngineSnapshotSingle(void)
15730 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15731 snapshot_level_nr == level_nr);
15734 boolean CheckEngineSnapshotList(void)
15736 return CheckSnapshotList();
15740 // ---------- new game button stuff -------------------------------------------
15747 boolean *setup_value;
15748 boolean allowed_on_tape;
15749 boolean is_touch_button;
15751 } gamebutton_info[NUM_GAME_BUTTONS] =
15754 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15755 GAME_CTRL_ID_STOP, NULL,
15756 TRUE, FALSE, "stop game"
15759 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15760 GAME_CTRL_ID_PAUSE, NULL,
15761 TRUE, FALSE, "pause game"
15764 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15765 GAME_CTRL_ID_PLAY, NULL,
15766 TRUE, FALSE, "play game"
15769 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15770 GAME_CTRL_ID_UNDO, NULL,
15771 TRUE, FALSE, "undo step"
15774 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15775 GAME_CTRL_ID_REDO, NULL,
15776 TRUE, FALSE, "redo step"
15779 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15780 GAME_CTRL_ID_SAVE, NULL,
15781 TRUE, FALSE, "save game"
15784 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15785 GAME_CTRL_ID_PAUSE2, NULL,
15786 TRUE, FALSE, "pause game"
15789 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15790 GAME_CTRL_ID_LOAD, NULL,
15791 TRUE, FALSE, "load game"
15794 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15795 GAME_CTRL_ID_PANEL_STOP, NULL,
15796 FALSE, FALSE, "stop game"
15799 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15800 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15801 FALSE, FALSE, "pause game"
15804 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15805 GAME_CTRL_ID_PANEL_PLAY, NULL,
15806 FALSE, FALSE, "play game"
15809 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15810 GAME_CTRL_ID_TOUCH_STOP, NULL,
15811 FALSE, TRUE, "stop game"
15814 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15815 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15816 FALSE, TRUE, "pause game"
15819 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15820 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15821 TRUE, FALSE, "background music on/off"
15824 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15825 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15826 TRUE, FALSE, "sound loops on/off"
15829 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15830 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15831 TRUE, FALSE, "normal sounds on/off"
15834 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15835 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15836 FALSE, FALSE, "background music on/off"
15839 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15840 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15841 FALSE, FALSE, "sound loops on/off"
15844 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15845 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15846 FALSE, FALSE, "normal sounds on/off"
15850 void CreateGameButtons(void)
15854 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15856 int graphic = gamebutton_info[i].graphic;
15857 struct GraphicInfo *gfx = &graphic_info[graphic];
15858 struct XY *pos = gamebutton_info[i].pos;
15859 struct GadgetInfo *gi;
15862 unsigned int event_mask;
15863 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15864 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15865 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15866 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15867 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15868 int gd_x = gfx->src_x;
15869 int gd_y = gfx->src_y;
15870 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15871 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15872 int gd_xa = gfx->src_x + gfx->active_xoffset;
15873 int gd_ya = gfx->src_y + gfx->active_yoffset;
15874 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15875 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15876 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15877 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15880 if (gfx->bitmap == NULL)
15882 game_gadget[id] = NULL;
15887 if (id == GAME_CTRL_ID_STOP ||
15888 id == GAME_CTRL_ID_PANEL_STOP ||
15889 id == GAME_CTRL_ID_TOUCH_STOP ||
15890 id == GAME_CTRL_ID_PLAY ||
15891 id == GAME_CTRL_ID_PANEL_PLAY ||
15892 id == GAME_CTRL_ID_SAVE ||
15893 id == GAME_CTRL_ID_LOAD)
15895 button_type = GD_TYPE_NORMAL_BUTTON;
15897 event_mask = GD_EVENT_RELEASED;
15899 else if (id == GAME_CTRL_ID_UNDO ||
15900 id == GAME_CTRL_ID_REDO)
15902 button_type = GD_TYPE_NORMAL_BUTTON;
15904 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15908 button_type = GD_TYPE_CHECK_BUTTON;
15909 checked = (gamebutton_info[i].setup_value != NULL ?
15910 *gamebutton_info[i].setup_value : FALSE);
15911 event_mask = GD_EVENT_PRESSED;
15914 gi = CreateGadget(GDI_CUSTOM_ID, id,
15915 GDI_IMAGE_ID, graphic,
15916 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15919 GDI_WIDTH, gfx->width,
15920 GDI_HEIGHT, gfx->height,
15921 GDI_TYPE, button_type,
15922 GDI_STATE, GD_BUTTON_UNPRESSED,
15923 GDI_CHECKED, checked,
15924 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15925 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15926 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15927 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15928 GDI_DIRECT_DRAW, FALSE,
15929 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15930 GDI_EVENT_MASK, event_mask,
15931 GDI_CALLBACK_ACTION, HandleGameButtons,
15935 Fail("cannot create gadget");
15937 game_gadget[id] = gi;
15941 void FreeGameButtons(void)
15945 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15946 FreeGadget(game_gadget[i]);
15949 static void UnmapGameButtonsAtSamePosition(int id)
15953 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15955 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15956 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15957 UnmapGadget(game_gadget[i]);
15960 static void UnmapGameButtonsAtSamePosition_All(void)
15962 if (setup.show_snapshot_buttons)
15964 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15965 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15966 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15970 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15971 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15972 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15974 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15975 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15976 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15980 static void MapGameButtonsAtSamePosition(int id)
15984 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15986 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15987 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15988 MapGadget(game_gadget[i]);
15990 UnmapGameButtonsAtSamePosition_All();
15993 void MapUndoRedoButtons(void)
15995 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15996 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15998 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15999 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16002 void UnmapUndoRedoButtons(void)
16004 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16005 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16007 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16008 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16011 void ModifyPauseButtons(void)
16015 GAME_CTRL_ID_PAUSE,
16016 GAME_CTRL_ID_PAUSE2,
16017 GAME_CTRL_ID_PANEL_PAUSE,
16018 GAME_CTRL_ID_TOUCH_PAUSE,
16023 for (i = 0; ids[i] > -1; i++)
16024 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16027 static void MapGameButtonsExt(boolean on_tape)
16031 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16032 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16033 i != GAME_CTRL_ID_UNDO &&
16034 i != GAME_CTRL_ID_REDO)
16035 MapGadget(game_gadget[i]);
16037 UnmapGameButtonsAtSamePosition_All();
16039 RedrawGameButtons();
16042 static void UnmapGameButtonsExt(boolean on_tape)
16046 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16047 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16048 UnmapGadget(game_gadget[i]);
16051 static void RedrawGameButtonsExt(boolean on_tape)
16055 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16056 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16057 RedrawGadget(game_gadget[i]);
16060 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16065 gi->checked = state;
16068 static void RedrawSoundButtonGadget(int id)
16070 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16071 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16072 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16073 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16074 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16075 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16078 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16079 RedrawGadget(game_gadget[id2]);
16082 void MapGameButtons(void)
16084 MapGameButtonsExt(FALSE);
16087 void UnmapGameButtons(void)
16089 UnmapGameButtonsExt(FALSE);
16092 void RedrawGameButtons(void)
16094 RedrawGameButtonsExt(FALSE);
16097 void MapGameButtonsOnTape(void)
16099 MapGameButtonsExt(TRUE);
16102 void UnmapGameButtonsOnTape(void)
16104 UnmapGameButtonsExt(TRUE);
16107 void RedrawGameButtonsOnTape(void)
16109 RedrawGameButtonsExt(TRUE);
16112 static void GameUndoRedoExt(void)
16114 ClearPlayerAction();
16116 tape.pausing = TRUE;
16119 UpdateAndDisplayGameControlValues();
16121 DrawCompleteVideoDisplay();
16122 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16123 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16124 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16129 static void GameUndo(int steps)
16131 if (!CheckEngineSnapshotList())
16134 LoadEngineSnapshot_Undo(steps);
16139 static void GameRedo(int steps)
16141 if (!CheckEngineSnapshotList())
16144 LoadEngineSnapshot_Redo(steps);
16149 static void HandleGameButtonsExt(int id, int button)
16151 static boolean game_undo_executed = FALSE;
16152 int steps = BUTTON_STEPSIZE(button);
16153 boolean handle_game_buttons =
16154 (game_status == GAME_MODE_PLAYING ||
16155 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16157 if (!handle_game_buttons)
16162 case GAME_CTRL_ID_STOP:
16163 case GAME_CTRL_ID_PANEL_STOP:
16164 case GAME_CTRL_ID_TOUCH_STOP:
16165 if (game_status == GAME_MODE_MAIN)
16171 RequestQuitGame(TRUE);
16175 case GAME_CTRL_ID_PAUSE:
16176 case GAME_CTRL_ID_PAUSE2:
16177 case GAME_CTRL_ID_PANEL_PAUSE:
16178 case GAME_CTRL_ID_TOUCH_PAUSE:
16179 if (network.enabled && game_status == GAME_MODE_PLAYING)
16182 SendToServer_ContinuePlaying();
16184 SendToServer_PausePlaying();
16187 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16189 game_undo_executed = FALSE;
16193 case GAME_CTRL_ID_PLAY:
16194 case GAME_CTRL_ID_PANEL_PLAY:
16195 if (game_status == GAME_MODE_MAIN)
16197 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16199 else if (tape.pausing)
16201 if (network.enabled)
16202 SendToServer_ContinuePlaying();
16204 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16208 case GAME_CTRL_ID_UNDO:
16209 // Important: When using "save snapshot when collecting an item" mode,
16210 // load last (current) snapshot for first "undo" after pressing "pause"
16211 // (else the last-but-one snapshot would be loaded, because the snapshot
16212 // pointer already points to the last snapshot when pressing "pause",
16213 // which is fine for "every step/move" mode, but not for "every collect")
16214 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16215 !game_undo_executed)
16218 game_undo_executed = TRUE;
16223 case GAME_CTRL_ID_REDO:
16227 case GAME_CTRL_ID_SAVE:
16231 case GAME_CTRL_ID_LOAD:
16235 case SOUND_CTRL_ID_MUSIC:
16236 case SOUND_CTRL_ID_PANEL_MUSIC:
16237 if (setup.sound_music)
16239 setup.sound_music = FALSE;
16243 else if (audio.music_available)
16245 setup.sound = setup.sound_music = TRUE;
16247 SetAudioMode(setup.sound);
16249 if (game_status == GAME_MODE_PLAYING)
16253 RedrawSoundButtonGadget(id);
16257 case SOUND_CTRL_ID_LOOPS:
16258 case SOUND_CTRL_ID_PANEL_LOOPS:
16259 if (setup.sound_loops)
16260 setup.sound_loops = FALSE;
16261 else if (audio.loops_available)
16263 setup.sound = setup.sound_loops = TRUE;
16265 SetAudioMode(setup.sound);
16268 RedrawSoundButtonGadget(id);
16272 case SOUND_CTRL_ID_SIMPLE:
16273 case SOUND_CTRL_ID_PANEL_SIMPLE:
16274 if (setup.sound_simple)
16275 setup.sound_simple = FALSE;
16276 else if (audio.sound_available)
16278 setup.sound = setup.sound_simple = TRUE;
16280 SetAudioMode(setup.sound);
16283 RedrawSoundButtonGadget(id);
16292 static void HandleGameButtons(struct GadgetInfo *gi)
16294 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16297 void HandleSoundButtonKeys(Key key)
16299 if (key == setup.shortcut.sound_simple)
16300 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16301 else if (key == setup.shortcut.sound_loops)
16302 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16303 else if (key == setup.shortcut.sound_music)
16304 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);