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 // try to display as many collected keys as possible in the default game panel
2330 for (i = STD_NUM_KEYS; i < MAX_NUM_KEYS + 1; i++) // EMC keys + white key
2332 int nr = GAME_PANEL_KEY_1 + i;
2333 int emc_key = get_key_element_from_nr(i);
2334 int element = (i < MAX_NUM_KEYS ? emc_key : EL_DC_KEY_WHITE);
2335 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2336 struct TextPosInfo *pos = gpc->pos;
2338 // check if panel position is undefined for a certain EMC key or white key
2339 if (gpc->value != EL_EMPTY && pos->x == -1 && pos->y == -1)
2341 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2343 // 1st try: display key at the same position as normal or EM keys
2344 if (game_panel_controls[nr_new].value == EL_EMPTY)
2346 game_panel_controls[nr_new].value = element;
2350 // 2nd try: display key at the next free position in the key panel
2351 for (k = 0; k < STD_NUM_KEYS; k++)
2353 nr_new = GAME_PANEL_KEY_1 + k;
2355 if (game_panel_controls[nr_new].value == EL_EMPTY)
2357 game_panel_controls[nr_new].value = element;
2366 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2368 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2369 get_inventory_element_from_pos(local_player, i);
2370 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2371 get_inventory_element_from_pos(local_player, -i - 1);
2374 game_panel_controls[GAME_PANEL_SCORE].value = score;
2375 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2377 game_panel_controls[GAME_PANEL_TIME].value = time;
2379 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2380 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2381 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2383 if (level.time == 0)
2384 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2386 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2388 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2389 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2391 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2393 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2394 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2396 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2397 local_player->shield_normal_time_left;
2398 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2399 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2401 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2402 local_player->shield_deadly_time_left;
2404 game_panel_controls[GAME_PANEL_EXIT].value =
2405 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2407 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2408 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2409 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2410 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2411 EL_EMC_MAGIC_BALL_SWITCH);
2413 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2414 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2415 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2416 game.light_time_left;
2418 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2419 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2420 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2421 game.timegate_time_left;
2423 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2424 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2426 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2427 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2428 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2429 game.lenses_time_left;
2431 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2432 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2433 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2434 game.magnify_time_left;
2436 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2437 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2438 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2439 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2440 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2441 EL_BALLOON_SWITCH_NONE);
2443 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2444 local_player->dynabomb_count;
2445 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2446 local_player->dynabomb_size;
2447 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2448 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2450 game_panel_controls[GAME_PANEL_PENGUINS].value =
2451 game.friends_still_needed;
2453 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2454 game.sokoban_objects_still_needed;
2455 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2456 game.sokoban_fields_still_needed;
2458 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2459 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2461 for (i = 0; i < NUM_BELTS; i++)
2463 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2464 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2465 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2466 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2467 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2470 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2471 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2472 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2473 game.magic_wall_time_left;
2475 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2476 local_player->gravity;
2478 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2479 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2481 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2482 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2483 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2484 game.panel.element[i].id : EL_UNDEFINED);
2486 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2487 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2488 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2489 element_info[game.panel.element_count[i].id].element_count : 0);
2491 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2492 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2493 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2494 element_info[game.panel.ce_score[i].id].collect_score : 0);
2496 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2497 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2498 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2499 element_info[game.panel.ce_score_element[i].id].collect_score :
2502 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2503 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2504 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2506 // update game panel control frames
2508 for (i = 0; game_panel_controls[i].nr != -1; i++)
2510 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2512 if (gpc->type == TYPE_ELEMENT)
2514 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2516 int last_anim_random_frame = gfx.anim_random_frame;
2517 int element = gpc->value;
2518 int graphic = el2panelimg(element);
2519 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2520 sync_random_frame : INIT_GFX_RANDOM());
2522 if (gpc->value != gpc->last_value)
2525 gpc->gfx_random = init_gfx_random;
2531 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2532 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2533 gpc->gfx_random = init_gfx_random;
2536 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2537 gfx.anim_random_frame = gpc->gfx_random;
2539 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2540 gpc->gfx_frame = element_info[element].collect_score;
2542 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2544 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2545 gfx.anim_random_frame = last_anim_random_frame;
2548 else if (gpc->type == TYPE_GRAPHIC)
2550 if (gpc->graphic != IMG_UNDEFINED)
2552 int last_anim_random_frame = gfx.anim_random_frame;
2553 int graphic = gpc->graphic;
2554 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2555 sync_random_frame : INIT_GFX_RANDOM());
2557 if (gpc->value != gpc->last_value)
2560 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2567 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2568 gpc->gfx_random = init_gfx_random;
2571 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2572 gfx.anim_random_frame = gpc->gfx_random;
2574 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2576 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2577 gfx.anim_random_frame = last_anim_random_frame;
2583 static void DisplayGameControlValues(void)
2585 boolean redraw_panel = FALSE;
2588 for (i = 0; game_panel_controls[i].nr != -1; i++)
2590 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2592 if (PANEL_DEACTIVATED(gpc->pos))
2595 if (gpc->value == gpc->last_value &&
2596 gpc->frame == gpc->last_frame)
2599 redraw_panel = TRUE;
2605 // copy default game door content to main double buffer
2607 // !!! CHECK AGAIN !!!
2608 SetPanelBackground();
2609 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2610 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2612 // redraw game control buttons
2613 RedrawGameButtons();
2615 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2617 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2619 int nr = game_panel_order[i].nr;
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2621 struct TextPosInfo *pos = gpc->pos;
2622 int type = gpc->type;
2623 int value = gpc->value;
2624 int frame = gpc->frame;
2625 int size = pos->size;
2626 int font = pos->font;
2627 boolean draw_masked = pos->draw_masked;
2628 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2630 if (PANEL_DEACTIVATED(pos))
2633 if (pos->class == get_hash_from_key("extra_panel_items") &&
2634 !setup.prefer_extra_panel_items)
2637 gpc->last_value = value;
2638 gpc->last_frame = frame;
2640 if (type == TYPE_INTEGER)
2642 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2643 nr == GAME_PANEL_TIME)
2645 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2647 if (use_dynamic_size) // use dynamic number of digits
2649 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2650 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2651 int size2 = size1 + 1;
2652 int font1 = pos->font;
2653 int font2 = pos->font_alt;
2655 size = (value < value_change ? size1 : size2);
2656 font = (value < value_change ? font1 : font2);
2660 // correct text size if "digits" is zero or less
2662 size = strlen(int2str(value, size));
2664 // dynamically correct text alignment
2665 pos->width = size * getFontWidth(font);
2667 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2668 int2str(value, size), font, mask_mode);
2670 else if (type == TYPE_ELEMENT)
2672 int element, graphic;
2676 int dst_x = PANEL_XPOS(pos);
2677 int dst_y = PANEL_YPOS(pos);
2679 if (value != EL_UNDEFINED && value != EL_EMPTY)
2682 graphic = el2panelimg(value);
2685 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2686 element, EL_NAME(element), size);
2689 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2692 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2695 width = graphic_info[graphic].width * size / TILESIZE;
2696 height = graphic_info[graphic].height * size / TILESIZE;
2699 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2702 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2706 else if (type == TYPE_GRAPHIC)
2708 int graphic = gpc->graphic;
2709 int graphic_active = gpc->graphic_active;
2713 int dst_x = PANEL_XPOS(pos);
2714 int dst_y = PANEL_YPOS(pos);
2715 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2716 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2718 if (graphic != IMG_UNDEFINED && !skip)
2720 if (pos->style == STYLE_REVERSE)
2721 value = 100 - value;
2723 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2725 if (pos->direction & MV_HORIZONTAL)
2727 width = graphic_info[graphic_active].width * value / 100;
2728 height = graphic_info[graphic_active].height;
2730 if (pos->direction == MV_LEFT)
2732 src_x += graphic_info[graphic_active].width - width;
2733 dst_x += graphic_info[graphic_active].width - width;
2738 width = graphic_info[graphic_active].width;
2739 height = graphic_info[graphic_active].height * value / 100;
2741 if (pos->direction == MV_UP)
2743 src_y += graphic_info[graphic_active].height - height;
2744 dst_y += graphic_info[graphic_active].height - height;
2749 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2752 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2755 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2757 if (pos->direction & MV_HORIZONTAL)
2759 if (pos->direction == MV_RIGHT)
2766 dst_x = PANEL_XPOS(pos);
2769 width = graphic_info[graphic].width - width;
2773 if (pos->direction == MV_DOWN)
2780 dst_y = PANEL_YPOS(pos);
2783 height = graphic_info[graphic].height - height;
2787 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2790 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2794 else if (type == TYPE_STRING)
2796 boolean active = (value != 0);
2797 char *state_normal = "off";
2798 char *state_active = "on";
2799 char *state = (active ? state_active : state_normal);
2800 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2801 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2802 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2803 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2805 if (nr == GAME_PANEL_GRAVITY_STATE)
2807 int font1 = pos->font; // (used for normal state)
2808 int font2 = pos->font_alt; // (used for active state)
2810 font = (active ? font2 : font1);
2819 // don't truncate output if "chars" is zero or less
2822 // dynamically correct text alignment
2823 pos->width = size * getFontWidth(font);
2826 s_cut = getStringCopyN(s, size);
2828 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2829 s_cut, font, mask_mode);
2835 redraw_mask |= REDRAW_DOOR_1;
2838 SetGameStatus(GAME_MODE_PLAYING);
2841 void UpdateAndDisplayGameControlValues(void)
2843 if (tape.deactivate_display)
2846 UpdateGameControlValues();
2847 DisplayGameControlValues();
2851 static void UpdateGameDoorValues(void)
2853 UpdateGameControlValues();
2857 void DrawGameDoorValues(void)
2859 DisplayGameControlValues();
2863 // ============================================================================
2865 // ----------------------------------------------------------------------------
2866 // initialize game engine due to level / tape version number
2867 // ============================================================================
2869 static void InitGameEngine(void)
2871 int i, j, k, l, x, y;
2873 // set game engine from tape file when re-playing, else from level file
2874 game.engine_version = (tape.playing ? tape.engine_version :
2875 level.game_version);
2877 // set single or multi-player game mode (needed for re-playing tapes)
2878 game.team_mode = setup.team_mode;
2882 int num_players = 0;
2884 for (i = 0; i < MAX_PLAYERS; i++)
2885 if (tape.player_participates[i])
2888 // multi-player tapes contain input data for more than one player
2889 game.team_mode = (num_players > 1);
2893 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2894 level.game_version);
2895 Debug("game:init:level", " tape.file_version == %06d",
2897 Debug("game:init:level", " tape.game_version == %06d",
2899 Debug("game:init:level", " tape.engine_version == %06d",
2900 tape.engine_version);
2901 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2902 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2905 // --------------------------------------------------------------------------
2906 // set flags for bugs and changes according to active game engine version
2907 // --------------------------------------------------------------------------
2911 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2913 Bug was introduced in version:
2916 Bug was fixed in version:
2920 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2921 but the property "can fall" was missing, which caused some levels to be
2922 unsolvable. This was fixed in version 4.2.0.0.
2924 Affected levels/tapes:
2925 An example for a tape that was fixed by this bugfix is tape 029 from the
2926 level set "rnd_sam_bateman".
2927 The wrong behaviour will still be used for all levels or tapes that were
2928 created/recorded with it. An example for this is tape 023 from the level
2929 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2932 boolean use_amoeba_dropping_cannot_fall_bug =
2933 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2934 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2936 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2937 tape.game_version < VERSION_IDENT(4,2,0,0)));
2940 Summary of bugfix/change:
2941 Fixed move speed of elements entering or leaving magic wall.
2943 Fixed/changed in version:
2947 Before 2.0.1, move speed of elements entering or leaving magic wall was
2948 twice as fast as it is now.
2949 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2951 Affected levels/tapes:
2952 The first condition is generally needed for all levels/tapes before version
2953 2.0.1, which might use the old behaviour before it was changed; known tapes
2954 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2955 The second condition is an exception from the above case and is needed for
2956 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2957 above, but before it was known that this change would break tapes like the
2958 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2959 although the engine version while recording maybe was before 2.0.1. There
2960 are a lot of tapes that are affected by this exception, like tape 006 from
2961 the level set "rnd_conor_mancone".
2964 boolean use_old_move_stepsize_for_magic_wall =
2965 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2967 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2968 tape.game_version < VERSION_IDENT(4,2,0,0)));
2971 Summary of bugfix/change:
2972 Fixed handling for custom elements that change when pushed by the player.
2974 Fixed/changed in version:
2978 Before 3.1.0, custom elements that "change when pushing" changed directly
2979 after the player started pushing them (until then handled in "DigField()").
2980 Since 3.1.0, these custom elements are not changed until the "pushing"
2981 move of the element is finished (now handled in "ContinueMoving()").
2983 Affected levels/tapes:
2984 The first condition is generally needed for all levels/tapes before version
2985 3.1.0, which might use the old behaviour before it was changed; known tapes
2986 that are affected are some tapes from the level set "Walpurgis Gardens" by
2988 The second condition is an exception from the above case and is needed for
2989 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2990 above (including some development versions of 3.1.0), but before it was
2991 known that this change would break tapes like the above and was fixed in
2992 3.1.1, so that the changed behaviour was active although the engine version
2993 while recording maybe was before 3.1.0. There is at least one tape that is
2994 affected by this exception, which is the tape for the one-level set "Bug
2995 Machine" by Juergen Bonhagen.
2998 game.use_change_when_pushing_bug =
2999 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3001 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3002 tape.game_version < VERSION_IDENT(3,1,1,0)));
3005 Summary of bugfix/change:
3006 Fixed handling for blocking the field the player leaves when moving.
3008 Fixed/changed in version:
3012 Before 3.1.1, when "block last field when moving" was enabled, the field
3013 the player is leaving when moving was blocked for the time of the move,
3014 and was directly unblocked afterwards. This resulted in the last field
3015 being blocked for exactly one less than the number of frames of one player
3016 move. Additionally, even when blocking was disabled, the last field was
3017 blocked for exactly one frame.
3018 Since 3.1.1, due to changes in player movement handling, the last field
3019 is not blocked at all when blocking is disabled. When blocking is enabled,
3020 the last field is blocked for exactly the number of frames of one player
3021 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3022 last field is blocked for exactly one more than the number of frames of
3025 Affected levels/tapes:
3026 (!!! yet to be determined -- probably many !!!)
3029 game.use_block_last_field_bug =
3030 (game.engine_version < VERSION_IDENT(3,1,1,0));
3032 /* various special flags and settings for native Emerald Mine game engine */
3034 game_em.use_single_button =
3035 (game.engine_version > VERSION_IDENT(4,0,0,2));
3037 game_em.use_snap_key_bug =
3038 (game.engine_version < VERSION_IDENT(4,0,1,0));
3040 game_em.use_random_bug =
3041 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3043 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3045 game_em.use_old_explosions = use_old_em_engine;
3046 game_em.use_old_android = use_old_em_engine;
3047 game_em.use_old_push_elements = use_old_em_engine;
3048 game_em.use_old_push_into_acid = use_old_em_engine;
3050 game_em.use_wrap_around = !use_old_em_engine;
3052 // --------------------------------------------------------------------------
3054 // set maximal allowed number of custom element changes per game frame
3055 game.max_num_changes_per_frame = 1;
3057 // default scan direction: scan playfield from top/left to bottom/right
3058 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3060 // dynamically adjust element properties according to game engine version
3061 InitElementPropertiesEngine(game.engine_version);
3063 // ---------- initialize special element properties -------------------------
3065 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3066 if (use_amoeba_dropping_cannot_fall_bug)
3067 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3069 // ---------- initialize player's initial move delay ------------------------
3071 // dynamically adjust player properties according to level information
3072 for (i = 0; i < MAX_PLAYERS; i++)
3073 game.initial_move_delay_value[i] =
3074 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3076 // dynamically adjust player properties according to game engine version
3077 for (i = 0; i < MAX_PLAYERS; i++)
3078 game.initial_move_delay[i] =
3079 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3080 game.initial_move_delay_value[i] : 0);
3082 // ---------- initialize player's initial push delay ------------------------
3084 // dynamically adjust player properties according to game engine version
3085 game.initial_push_delay_value =
3086 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3088 // ---------- initialize changing elements ----------------------------------
3090 // initialize changing elements information
3091 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3093 struct ElementInfo *ei = &element_info[i];
3095 // this pointer might have been changed in the level editor
3096 ei->change = &ei->change_page[0];
3098 if (!IS_CUSTOM_ELEMENT(i))
3100 ei->change->target_element = EL_EMPTY_SPACE;
3101 ei->change->delay_fixed = 0;
3102 ei->change->delay_random = 0;
3103 ei->change->delay_frames = 1;
3106 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3108 ei->has_change_event[j] = FALSE;
3110 ei->event_page_nr[j] = 0;
3111 ei->event_page[j] = &ei->change_page[0];
3115 // add changing elements from pre-defined list
3116 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3118 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3119 struct ElementInfo *ei = &element_info[ch_delay->element];
3121 ei->change->target_element = ch_delay->target_element;
3122 ei->change->delay_fixed = ch_delay->change_delay;
3124 ei->change->pre_change_function = ch_delay->pre_change_function;
3125 ei->change->change_function = ch_delay->change_function;
3126 ei->change->post_change_function = ch_delay->post_change_function;
3128 ei->change->can_change = TRUE;
3129 ei->change->can_change_or_has_action = TRUE;
3131 ei->has_change_event[CE_DELAY] = TRUE;
3133 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3134 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3137 // ---------- initialize internal run-time variables ------------------------
3139 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3141 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3143 for (j = 0; j < ei->num_change_pages; j++)
3145 ei->change_page[j].can_change_or_has_action =
3146 (ei->change_page[j].can_change |
3147 ei->change_page[j].has_action);
3151 // add change events from custom element configuration
3152 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3154 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3156 for (j = 0; j < ei->num_change_pages; j++)
3158 if (!ei->change_page[j].can_change_or_has_action)
3161 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3163 // only add event page for the first page found with this event
3164 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3166 ei->has_change_event[k] = TRUE;
3168 ei->event_page_nr[k] = j;
3169 ei->event_page[k] = &ei->change_page[j];
3175 // ---------- initialize reference elements in change conditions ------------
3177 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3179 int element = EL_CUSTOM_START + i;
3180 struct ElementInfo *ei = &element_info[element];
3182 for (j = 0; j < ei->num_change_pages; j++)
3184 int trigger_element = ei->change_page[j].initial_trigger_element;
3186 if (trigger_element >= EL_PREV_CE_8 &&
3187 trigger_element <= EL_NEXT_CE_8)
3188 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3190 ei->change_page[j].trigger_element = trigger_element;
3194 // ---------- initialize run-time trigger player and element ----------------
3196 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3198 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3200 for (j = 0; j < ei->num_change_pages; j++)
3202 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3203 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3204 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3205 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3206 ei->change_page[j].actual_trigger_ce_value = 0;
3207 ei->change_page[j].actual_trigger_ce_score = 0;
3211 // ---------- initialize trigger events -------------------------------------
3213 // initialize trigger events information
3214 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3215 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3216 trigger_events[i][j] = FALSE;
3218 // add trigger events from element change event properties
3219 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3221 struct ElementInfo *ei = &element_info[i];
3223 for (j = 0; j < ei->num_change_pages; j++)
3225 if (!ei->change_page[j].can_change_or_has_action)
3228 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3230 int trigger_element = ei->change_page[j].trigger_element;
3232 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3234 if (ei->change_page[j].has_event[k])
3236 if (IS_GROUP_ELEMENT(trigger_element))
3238 struct ElementGroupInfo *group =
3239 element_info[trigger_element].group;
3241 for (l = 0; l < group->num_elements_resolved; l++)
3242 trigger_events[group->element_resolved[l]][k] = TRUE;
3244 else if (trigger_element == EL_ANY_ELEMENT)
3245 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3246 trigger_events[l][k] = TRUE;
3248 trigger_events[trigger_element][k] = TRUE;
3255 // ---------- initialize push delay -----------------------------------------
3257 // initialize push delay values to default
3258 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 if (!IS_CUSTOM_ELEMENT(i))
3262 // set default push delay values (corrected since version 3.0.7-1)
3263 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3265 element_info[i].push_delay_fixed = 2;
3266 element_info[i].push_delay_random = 8;
3270 element_info[i].push_delay_fixed = 8;
3271 element_info[i].push_delay_random = 8;
3276 // set push delay value for certain elements from pre-defined list
3277 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3279 int e = push_delay_list[i].element;
3281 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3282 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3285 // set push delay value for Supaplex elements for newer engine versions
3286 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3288 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3290 if (IS_SP_ELEMENT(i))
3292 // set SP push delay to just enough to push under a falling zonk
3293 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3295 element_info[i].push_delay_fixed = delay;
3296 element_info[i].push_delay_random = 0;
3301 // ---------- initialize move stepsize --------------------------------------
3303 // initialize move stepsize values to default
3304 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3305 if (!IS_CUSTOM_ELEMENT(i))
3306 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3308 // set move stepsize value for certain elements from pre-defined list
3309 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3311 int e = move_stepsize_list[i].element;
3313 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3315 // set move stepsize value for certain elements for older engine versions
3316 if (use_old_move_stepsize_for_magic_wall)
3318 if (e == EL_MAGIC_WALL_FILLING ||
3319 e == EL_MAGIC_WALL_EMPTYING ||
3320 e == EL_BD_MAGIC_WALL_FILLING ||
3321 e == EL_BD_MAGIC_WALL_EMPTYING)
3322 element_info[e].move_stepsize *= 2;
3326 // ---------- initialize collect score --------------------------------------
3328 // initialize collect score values for custom elements from initial value
3329 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3330 if (IS_CUSTOM_ELEMENT(i))
3331 element_info[i].collect_score = element_info[i].collect_score_initial;
3333 // ---------- initialize collect count --------------------------------------
3335 // initialize collect count values for non-custom elements
3336 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3337 if (!IS_CUSTOM_ELEMENT(i))
3338 element_info[i].collect_count_initial = 0;
3340 // add collect count values for all elements from pre-defined list
3341 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3342 element_info[collect_count_list[i].element].collect_count_initial =
3343 collect_count_list[i].count;
3345 // ---------- initialize access direction -----------------------------------
3347 // initialize access direction values to default (access from every side)
3348 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3349 if (!IS_CUSTOM_ELEMENT(i))
3350 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3352 // set access direction value for certain elements from pre-defined list
3353 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3354 element_info[access_direction_list[i].element].access_direction =
3355 access_direction_list[i].direction;
3357 // ---------- initialize explosion content ----------------------------------
3358 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3360 if (IS_CUSTOM_ELEMENT(i))
3363 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3365 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3367 element_info[i].content.e[x][y] =
3368 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3369 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3370 i == EL_PLAYER_3 ? EL_EMERALD :
3371 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3372 i == EL_MOLE ? EL_EMERALD_RED :
3373 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3374 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3375 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3376 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3377 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3378 i == EL_WALL_EMERALD ? EL_EMERALD :
3379 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3380 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3381 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3382 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3383 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3384 i == EL_WALL_PEARL ? EL_PEARL :
3385 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3390 // ---------- initialize recursion detection --------------------------------
3391 recursion_loop_depth = 0;
3392 recursion_loop_detected = FALSE;
3393 recursion_loop_element = EL_UNDEFINED;
3395 // ---------- initialize graphics engine ------------------------------------
3396 game.scroll_delay_value =
3397 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3398 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3399 !setup.forced_scroll_delay ? 0 :
3400 setup.scroll_delay ? setup.scroll_delay_value : 0);
3401 game.scroll_delay_value =
3402 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3404 // ---------- initialize game engine snapshots ------------------------------
3405 for (i = 0; i < MAX_PLAYERS; i++)
3406 game.snapshot.last_action[i] = 0;
3407 game.snapshot.changed_action = FALSE;
3408 game.snapshot.collected_item = FALSE;
3409 game.snapshot.mode =
3410 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3411 SNAPSHOT_MODE_EVERY_STEP :
3412 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3413 SNAPSHOT_MODE_EVERY_MOVE :
3414 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3415 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3416 game.snapshot.save_snapshot = FALSE;
3418 // ---------- initialize level time for Supaplex engine ---------------------
3419 // Supaplex levels with time limit currently unsupported -- should be added
3420 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3423 // ---------- initialize flags for handling game actions --------------------
3425 // set flags for game actions to default values
3426 game.use_key_actions = TRUE;
3427 game.use_mouse_actions = FALSE;
3429 // when using Mirror Magic game engine, handle mouse events only
3430 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3432 game.use_key_actions = FALSE;
3433 game.use_mouse_actions = TRUE;
3436 // check for custom elements with mouse click events
3437 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3439 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3441 int element = EL_CUSTOM_START + i;
3443 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3444 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3445 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3446 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3447 game.use_mouse_actions = TRUE;
3452 static int get_num_special_action(int element, int action_first,
3455 int num_special_action = 0;
3458 for (i = action_first; i <= action_last; i++)
3460 boolean found = FALSE;
3462 for (j = 0; j < NUM_DIRECTIONS; j++)
3463 if (el_act_dir2img(element, i, j) !=
3464 el_act_dir2img(element, ACTION_DEFAULT, j))
3468 num_special_action++;
3473 return num_special_action;
3477 // ============================================================================
3479 // ----------------------------------------------------------------------------
3480 // initialize and start new game
3481 // ============================================================================
3483 #if DEBUG_INIT_PLAYER
3484 static void DebugPrintPlayerStatus(char *message)
3491 Debug("game:init:player", "%s:", message);
3493 for (i = 0; i < MAX_PLAYERS; i++)
3495 struct PlayerInfo *player = &stored_player[i];
3497 Debug("game:init:player",
3498 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3502 player->connected_locally,
3503 player->connected_network,
3505 (local_player == player ? " (local player)" : ""));
3512 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3513 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3514 int fade_mask = REDRAW_FIELD;
3516 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3517 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3518 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3519 int initial_move_dir = MV_DOWN;
3522 // required here to update video display before fading (FIX THIS)
3523 DrawMaskedBorder(REDRAW_DOOR_2);
3525 if (!game.restart_level)
3526 CloseDoor(DOOR_CLOSE_1);
3528 SetGameStatus(GAME_MODE_PLAYING);
3530 if (level_editor_test_game)
3531 FadeSkipNextFadeOut();
3533 FadeSetEnterScreen();
3536 fade_mask = REDRAW_ALL;
3538 FadeLevelSoundsAndMusic();
3540 ExpireSoundLoops(TRUE);
3544 if (level_editor_test_game)
3545 FadeSkipNextFadeIn();
3547 // needed if different viewport properties defined for playing
3548 ChangeViewportPropertiesIfNeeded();
3552 DrawCompleteVideoDisplay();
3554 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3557 InitGameControlValues();
3559 // initialize tape actions from game when recording tape
3562 tape.use_key_actions = game.use_key_actions;
3563 tape.use_mouse_actions = game.use_mouse_actions;
3566 // don't play tapes over network
3567 network_playing = (network.enabled && !tape.playing);
3569 for (i = 0; i < MAX_PLAYERS; i++)
3571 struct PlayerInfo *player = &stored_player[i];
3573 player->index_nr = i;
3574 player->index_bit = (1 << i);
3575 player->element_nr = EL_PLAYER_1 + i;
3577 player->present = FALSE;
3578 player->active = FALSE;
3579 player->mapped = FALSE;
3581 player->killed = FALSE;
3582 player->reanimated = FALSE;
3583 player->buried = FALSE;
3586 player->effective_action = 0;
3587 player->programmed_action = 0;
3588 player->snap_action = 0;
3590 player->mouse_action.lx = 0;
3591 player->mouse_action.ly = 0;
3592 player->mouse_action.button = 0;
3593 player->mouse_action.button_hint = 0;
3595 player->effective_mouse_action.lx = 0;
3596 player->effective_mouse_action.ly = 0;
3597 player->effective_mouse_action.button = 0;
3598 player->effective_mouse_action.button_hint = 0;
3600 for (j = 0; j < MAX_NUM_KEYS; j++)
3601 player->key[j] = FALSE;
3603 player->num_white_keys = 0;
3605 player->dynabomb_count = 0;
3606 player->dynabomb_size = 1;
3607 player->dynabombs_left = 0;
3608 player->dynabomb_xl = FALSE;
3610 player->MovDir = initial_move_dir;
3613 player->GfxDir = initial_move_dir;
3614 player->GfxAction = ACTION_DEFAULT;
3616 player->StepFrame = 0;
3618 player->initial_element = player->element_nr;
3619 player->artwork_element =
3620 (level.use_artwork_element[i] ? level.artwork_element[i] :
3621 player->element_nr);
3622 player->use_murphy = FALSE;
3624 player->block_last_field = FALSE; // initialized in InitPlayerField()
3625 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3627 player->gravity = level.initial_player_gravity[i];
3629 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3631 player->actual_frame_counter = 0;
3633 player->step_counter = 0;
3635 player->last_move_dir = initial_move_dir;
3637 player->is_active = FALSE;
3639 player->is_waiting = FALSE;
3640 player->is_moving = FALSE;
3641 player->is_auto_moving = FALSE;
3642 player->is_digging = FALSE;
3643 player->is_snapping = FALSE;
3644 player->is_collecting = FALSE;
3645 player->is_pushing = FALSE;
3646 player->is_switching = FALSE;
3647 player->is_dropping = FALSE;
3648 player->is_dropping_pressed = FALSE;
3650 player->is_bored = FALSE;
3651 player->is_sleeping = FALSE;
3653 player->was_waiting = TRUE;
3654 player->was_moving = FALSE;
3655 player->was_snapping = FALSE;
3656 player->was_dropping = FALSE;
3658 player->force_dropping = FALSE;
3660 player->frame_counter_bored = -1;
3661 player->frame_counter_sleeping = -1;
3663 player->anim_delay_counter = 0;
3664 player->post_delay_counter = 0;
3666 player->dir_waiting = initial_move_dir;
3667 player->action_waiting = ACTION_DEFAULT;
3668 player->last_action_waiting = ACTION_DEFAULT;
3669 player->special_action_bored = ACTION_DEFAULT;
3670 player->special_action_sleeping = ACTION_DEFAULT;
3672 player->switch_x = -1;
3673 player->switch_y = -1;
3675 player->drop_x = -1;
3676 player->drop_y = -1;
3678 player->show_envelope = 0;
3680 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3682 player->push_delay = -1; // initialized when pushing starts
3683 player->push_delay_value = game.initial_push_delay_value;
3685 player->drop_delay = 0;
3686 player->drop_pressed_delay = 0;
3688 player->last_jx = -1;
3689 player->last_jy = -1;
3693 player->shield_normal_time_left = 0;
3694 player->shield_deadly_time_left = 0;
3696 player->inventory_infinite_element = EL_UNDEFINED;
3697 player->inventory_size = 0;
3699 if (level.use_initial_inventory[i])
3701 for (j = 0; j < level.initial_inventory_size[i]; j++)
3703 int element = level.initial_inventory_content[i][j];
3704 int collect_count = element_info[element].collect_count_initial;
3707 if (!IS_CUSTOM_ELEMENT(element))
3710 if (collect_count == 0)
3711 player->inventory_infinite_element = element;
3713 for (k = 0; k < collect_count; k++)
3714 if (player->inventory_size < MAX_INVENTORY_SIZE)
3715 player->inventory_element[player->inventory_size++] = element;
3719 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3720 SnapField(player, 0, 0);
3722 map_player_action[i] = i;
3725 network_player_action_received = FALSE;
3727 // initial null action
3728 if (network_playing)
3729 SendToServer_MovePlayer(MV_NONE);
3734 TimeLeft = level.time;
3737 ScreenMovDir = MV_NONE;
3741 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3743 game.robot_wheel_x = -1;
3744 game.robot_wheel_y = -1;
3749 game.all_players_gone = FALSE;
3751 game.LevelSolved = FALSE;
3752 game.GameOver = FALSE;
3754 game.GamePlayed = !tape.playing;
3756 game.LevelSolved_GameWon = FALSE;
3757 game.LevelSolved_GameEnd = FALSE;
3758 game.LevelSolved_SaveTape = FALSE;
3759 game.LevelSolved_SaveScore = FALSE;
3761 game.LevelSolved_CountingTime = 0;
3762 game.LevelSolved_CountingScore = 0;
3763 game.LevelSolved_CountingHealth = 0;
3765 game.panel.active = TRUE;
3767 game.no_time_limit = (level.time == 0);
3769 game.yamyam_content_nr = 0;
3770 game.robot_wheel_active = FALSE;
3771 game.magic_wall_active = FALSE;
3772 game.magic_wall_time_left = 0;
3773 game.light_time_left = 0;
3774 game.timegate_time_left = 0;
3775 game.switchgate_pos = 0;
3776 game.wind_direction = level.wind_direction_initial;
3779 game.score_final = 0;
3781 game.health = MAX_HEALTH;
3782 game.health_final = MAX_HEALTH;
3784 game.gems_still_needed = level.gems_needed;
3785 game.sokoban_fields_still_needed = 0;
3786 game.sokoban_objects_still_needed = 0;
3787 game.lights_still_needed = 0;
3788 game.players_still_needed = 0;
3789 game.friends_still_needed = 0;
3791 game.lenses_time_left = 0;
3792 game.magnify_time_left = 0;
3794 game.ball_active = level.ball_active_initial;
3795 game.ball_content_nr = 0;
3797 game.explosions_delayed = TRUE;
3799 game.envelope_active = FALSE;
3801 for (i = 0; i < NUM_BELTS; i++)
3803 game.belt_dir[i] = MV_NONE;
3804 game.belt_dir_nr[i] = 3; // not moving, next moving left
3807 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3808 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3810 #if DEBUG_INIT_PLAYER
3811 DebugPrintPlayerStatus("Player status at level initialization");
3814 SCAN_PLAYFIELD(x, y)
3816 Tile[x][y] = Last[x][y] = level.field[x][y];
3817 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3818 ChangeDelay[x][y] = 0;
3819 ChangePage[x][y] = -1;
3820 CustomValue[x][y] = 0; // initialized in InitField()
3821 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3823 WasJustMoving[x][y] = 0;
3824 WasJustFalling[x][y] = 0;
3825 CheckCollision[x][y] = 0;
3826 CheckImpact[x][y] = 0;
3828 Pushed[x][y] = FALSE;
3830 ChangeCount[x][y] = 0;
3831 ChangeEvent[x][y] = -1;
3833 ExplodePhase[x][y] = 0;
3834 ExplodeDelay[x][y] = 0;
3835 ExplodeField[x][y] = EX_TYPE_NONE;
3837 RunnerVisit[x][y] = 0;
3838 PlayerVisit[x][y] = 0;
3841 GfxRandom[x][y] = INIT_GFX_RANDOM();
3842 GfxElement[x][y] = EL_UNDEFINED;
3843 GfxAction[x][y] = ACTION_DEFAULT;
3844 GfxDir[x][y] = MV_NONE;
3845 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3848 SCAN_PLAYFIELD(x, y)
3850 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3852 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3854 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3857 InitField(x, y, TRUE);
3859 ResetGfxAnimation(x, y);
3864 for (i = 0; i < MAX_PLAYERS; i++)
3866 struct PlayerInfo *player = &stored_player[i];
3868 // set number of special actions for bored and sleeping animation
3869 player->num_special_action_bored =
3870 get_num_special_action(player->artwork_element,
3871 ACTION_BORING_1, ACTION_BORING_LAST);
3872 player->num_special_action_sleeping =
3873 get_num_special_action(player->artwork_element,
3874 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3877 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3878 emulate_sb ? EMU_SOKOBAN :
3879 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3881 // initialize type of slippery elements
3882 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3884 if (!IS_CUSTOM_ELEMENT(i))
3886 // default: elements slip down either to the left or right randomly
3887 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3889 // SP style elements prefer to slip down on the left side
3890 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3891 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3893 // BD style elements prefer to slip down on the left side
3894 if (game.emulation == EMU_BOULDERDASH)
3895 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3899 // initialize explosion and ignition delay
3900 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3902 if (!IS_CUSTOM_ELEMENT(i))
3905 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3906 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3907 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3908 int last_phase = (num_phase + 1) * delay;
3909 int half_phase = (num_phase / 2) * delay;
3911 element_info[i].explosion_delay = last_phase - 1;
3912 element_info[i].ignition_delay = half_phase;
3914 if (i == EL_BLACK_ORB)
3915 element_info[i].ignition_delay = 1;
3919 // correct non-moving belts to start moving left
3920 for (i = 0; i < NUM_BELTS; i++)
3921 if (game.belt_dir[i] == MV_NONE)
3922 game.belt_dir_nr[i] = 3; // not moving, next moving left
3924 #if USE_NEW_PLAYER_ASSIGNMENTS
3925 // use preferred player also in local single-player mode
3926 if (!network.enabled && !game.team_mode)
3928 int new_index_nr = setup.network_player_nr;
3930 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3932 for (i = 0; i < MAX_PLAYERS; i++)
3933 stored_player[i].connected_locally = FALSE;
3935 stored_player[new_index_nr].connected_locally = TRUE;
3939 for (i = 0; i < MAX_PLAYERS; i++)
3941 stored_player[i].connected = FALSE;
3943 // in network game mode, the local player might not be the first player
3944 if (stored_player[i].connected_locally)
3945 local_player = &stored_player[i];
3948 if (!network.enabled)
3949 local_player->connected = TRUE;
3953 for (i = 0; i < MAX_PLAYERS; i++)
3954 stored_player[i].connected = tape.player_participates[i];
3956 else if (network.enabled)
3958 // add team mode players connected over the network (needed for correct
3959 // assignment of player figures from level to locally playing players)
3961 for (i = 0; i < MAX_PLAYERS; i++)
3962 if (stored_player[i].connected_network)
3963 stored_player[i].connected = TRUE;
3965 else if (game.team_mode)
3967 // try to guess locally connected team mode players (needed for correct
3968 // assignment of player figures from level to locally playing players)
3970 for (i = 0; i < MAX_PLAYERS; i++)
3971 if (setup.input[i].use_joystick ||
3972 setup.input[i].key.left != KSYM_UNDEFINED)
3973 stored_player[i].connected = TRUE;
3976 #if DEBUG_INIT_PLAYER
3977 DebugPrintPlayerStatus("Player status after level initialization");
3980 #if DEBUG_INIT_PLAYER
3981 Debug("game:init:player", "Reassigning players ...");
3984 // check if any connected player was not found in playfield
3985 for (i = 0; i < MAX_PLAYERS; i++)
3987 struct PlayerInfo *player = &stored_player[i];
3989 if (player->connected && !player->present)
3991 struct PlayerInfo *field_player = NULL;
3993 #if DEBUG_INIT_PLAYER
3994 Debug("game:init:player",
3995 "- looking for field player for player %d ...", i + 1);
3998 // assign first free player found that is present in the playfield
4000 // first try: look for unmapped playfield player that is not connected
4001 for (j = 0; j < MAX_PLAYERS; j++)
4002 if (field_player == NULL &&
4003 stored_player[j].present &&
4004 !stored_player[j].mapped &&
4005 !stored_player[j].connected)
4006 field_player = &stored_player[j];
4008 // second try: look for *any* unmapped playfield player
4009 for (j = 0; j < MAX_PLAYERS; j++)
4010 if (field_player == NULL &&
4011 stored_player[j].present &&
4012 !stored_player[j].mapped)
4013 field_player = &stored_player[j];
4015 if (field_player != NULL)
4017 int jx = field_player->jx, jy = field_player->jy;
4019 #if DEBUG_INIT_PLAYER
4020 Debug("game:init:player", "- found player %d",
4021 field_player->index_nr + 1);
4024 player->present = FALSE;
4025 player->active = FALSE;
4027 field_player->present = TRUE;
4028 field_player->active = TRUE;
4031 player->initial_element = field_player->initial_element;
4032 player->artwork_element = field_player->artwork_element;
4034 player->block_last_field = field_player->block_last_field;
4035 player->block_delay_adjustment = field_player->block_delay_adjustment;
4038 StorePlayer[jx][jy] = field_player->element_nr;
4040 field_player->jx = field_player->last_jx = jx;
4041 field_player->jy = field_player->last_jy = jy;
4043 if (local_player == player)
4044 local_player = field_player;
4046 map_player_action[field_player->index_nr] = i;
4048 field_player->mapped = TRUE;
4050 #if DEBUG_INIT_PLAYER
4051 Debug("game:init:player", "- map_player_action[%d] == %d",
4052 field_player->index_nr + 1, i + 1);
4057 if (player->connected && player->present)
4058 player->mapped = TRUE;
4061 #if DEBUG_INIT_PLAYER
4062 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4067 // check if any connected player was not found in playfield
4068 for (i = 0; i < MAX_PLAYERS; i++)
4070 struct PlayerInfo *player = &stored_player[i];
4072 if (player->connected && !player->present)
4074 for (j = 0; j < MAX_PLAYERS; j++)
4076 struct PlayerInfo *field_player = &stored_player[j];
4077 int jx = field_player->jx, jy = field_player->jy;
4079 // assign first free player found that is present in the playfield
4080 if (field_player->present && !field_player->connected)
4082 player->present = TRUE;
4083 player->active = TRUE;
4085 field_player->present = FALSE;
4086 field_player->active = FALSE;
4088 player->initial_element = field_player->initial_element;
4089 player->artwork_element = field_player->artwork_element;
4091 player->block_last_field = field_player->block_last_field;
4092 player->block_delay_adjustment = field_player->block_delay_adjustment;
4094 StorePlayer[jx][jy] = player->element_nr;
4096 player->jx = player->last_jx = jx;
4097 player->jy = player->last_jy = jy;
4107 Debug("game:init:player", "local_player->present == %d",
4108 local_player->present);
4111 // set focus to local player for network games, else to all players
4112 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4113 game.centered_player_nr_next = game.centered_player_nr;
4114 game.set_centered_player = FALSE;
4115 game.set_centered_player_wrap = FALSE;
4117 if (network_playing && tape.recording)
4119 // store client dependent player focus when recording network games
4120 tape.centered_player_nr_next = game.centered_player_nr_next;
4121 tape.set_centered_player = TRUE;
4126 // when playing a tape, eliminate all players who do not participate
4128 #if USE_NEW_PLAYER_ASSIGNMENTS
4130 if (!game.team_mode)
4132 for (i = 0; i < MAX_PLAYERS; i++)
4134 if (stored_player[i].active &&
4135 !tape.player_participates[map_player_action[i]])
4137 struct PlayerInfo *player = &stored_player[i];
4138 int jx = player->jx, jy = player->jy;
4140 #if DEBUG_INIT_PLAYER
4141 Debug("game:init:player", "Removing player %d at (%d, %d)",
4145 player->active = FALSE;
4146 StorePlayer[jx][jy] = 0;
4147 Tile[jx][jy] = EL_EMPTY;
4154 for (i = 0; i < MAX_PLAYERS; i++)
4156 if (stored_player[i].active &&
4157 !tape.player_participates[i])
4159 struct PlayerInfo *player = &stored_player[i];
4160 int jx = player->jx, jy = player->jy;
4162 player->active = FALSE;
4163 StorePlayer[jx][jy] = 0;
4164 Tile[jx][jy] = EL_EMPTY;
4169 else if (!network.enabled && !game.team_mode) // && !tape.playing
4171 // when in single player mode, eliminate all but the local player
4173 for (i = 0; i < MAX_PLAYERS; i++)
4175 struct PlayerInfo *player = &stored_player[i];
4177 if (player->active && player != local_player)
4179 int jx = player->jx, jy = player->jy;
4181 player->active = FALSE;
4182 player->present = FALSE;
4184 StorePlayer[jx][jy] = 0;
4185 Tile[jx][jy] = EL_EMPTY;
4190 for (i = 0; i < MAX_PLAYERS; i++)
4191 if (stored_player[i].active)
4192 game.players_still_needed++;
4194 if (level.solved_by_one_player)
4195 game.players_still_needed = 1;
4197 // when recording the game, store which players take part in the game
4200 #if USE_NEW_PLAYER_ASSIGNMENTS
4201 for (i = 0; i < MAX_PLAYERS; i++)
4202 if (stored_player[i].connected)
4203 tape.player_participates[i] = TRUE;
4205 for (i = 0; i < MAX_PLAYERS; i++)
4206 if (stored_player[i].active)
4207 tape.player_participates[i] = TRUE;
4211 #if DEBUG_INIT_PLAYER
4212 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4215 if (BorderElement == EL_EMPTY)
4218 SBX_Right = lev_fieldx - SCR_FIELDX;
4220 SBY_Lower = lev_fieldy - SCR_FIELDY;
4225 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4227 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4230 if (full_lev_fieldx <= SCR_FIELDX)
4231 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4232 if (full_lev_fieldy <= SCR_FIELDY)
4233 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4235 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4237 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4240 // if local player not found, look for custom element that might create
4241 // the player (make some assumptions about the right custom element)
4242 if (!local_player->present)
4244 int start_x = 0, start_y = 0;
4245 int found_rating = 0;
4246 int found_element = EL_UNDEFINED;
4247 int player_nr = local_player->index_nr;
4249 SCAN_PLAYFIELD(x, y)
4251 int element = Tile[x][y];
4256 if (level.use_start_element[player_nr] &&
4257 level.start_element[player_nr] == element &&
4264 found_element = element;
4267 if (!IS_CUSTOM_ELEMENT(element))
4270 if (CAN_CHANGE(element))
4272 for (i = 0; i < element_info[element].num_change_pages; i++)
4274 // check for player created from custom element as single target
4275 content = element_info[element].change_page[i].target_element;
4276 is_player = ELEM_IS_PLAYER(content);
4278 if (is_player && (found_rating < 3 ||
4279 (found_rating == 3 && element < found_element)))
4285 found_element = element;
4290 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4292 // check for player created from custom element as explosion content
4293 content = element_info[element].content.e[xx][yy];
4294 is_player = ELEM_IS_PLAYER(content);
4296 if (is_player && (found_rating < 2 ||
4297 (found_rating == 2 && element < found_element)))
4299 start_x = x + xx - 1;
4300 start_y = y + yy - 1;
4303 found_element = element;
4306 if (!CAN_CHANGE(element))
4309 for (i = 0; i < element_info[element].num_change_pages; i++)
4311 // check for player created from custom element as extended target
4313 element_info[element].change_page[i].target_content.e[xx][yy];
4315 is_player = ELEM_IS_PLAYER(content);
4317 if (is_player && (found_rating < 1 ||
4318 (found_rating == 1 && element < found_element)))
4320 start_x = x + xx - 1;
4321 start_y = y + yy - 1;
4324 found_element = element;
4330 scroll_x = SCROLL_POSITION_X(start_x);
4331 scroll_y = SCROLL_POSITION_Y(start_y);
4335 scroll_x = SCROLL_POSITION_X(local_player->jx);
4336 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4339 // !!! FIX THIS (START) !!!
4340 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4342 InitGameEngine_EM();
4344 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4346 InitGameEngine_SP();
4348 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4350 InitGameEngine_MM();
4354 DrawLevel(REDRAW_FIELD);
4357 // after drawing the level, correct some elements
4358 if (game.timegate_time_left == 0)
4359 CloseAllOpenTimegates();
4362 // blit playfield from scroll buffer to normal back buffer for fading in
4363 BlitScreenToBitmap(backbuffer);
4364 // !!! FIX THIS (END) !!!
4366 DrawMaskedBorder(fade_mask);
4371 // full screen redraw is required at this point in the following cases:
4372 // - special editor door undrawn when game was started from level editor
4373 // - drawing area (playfield) was changed and has to be removed completely
4374 redraw_mask = REDRAW_ALL;
4378 if (!game.restart_level)
4380 // copy default game door content to main double buffer
4382 // !!! CHECK AGAIN !!!
4383 SetPanelBackground();
4384 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4385 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4388 SetPanelBackground();
4389 SetDrawBackgroundMask(REDRAW_DOOR_1);
4391 UpdateAndDisplayGameControlValues();
4393 if (!game.restart_level)
4399 CreateGameButtons();
4404 // copy actual game door content to door double buffer for OpenDoor()
4405 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4407 OpenDoor(DOOR_OPEN_ALL);
4409 KeyboardAutoRepeatOffUnlessAutoplay();
4411 #if DEBUG_INIT_PLAYER
4412 DebugPrintPlayerStatus("Player status (final)");
4421 if (!game.restart_level && !tape.playing)
4423 LevelStats_incPlayed(level_nr);
4425 SaveLevelSetup_SeriesInfo();
4428 game.restart_level = FALSE;
4429 game.restart_game_message = NULL;
4430 game.request_active = FALSE;
4432 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4433 InitGameActions_MM();
4435 SaveEngineSnapshotToListInitial();
4437 if (!game.restart_level)
4439 PlaySound(SND_GAME_STARTING);
4441 if (setup.sound_music)
4446 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4447 int actual_player_x, int actual_player_y)
4449 // this is used for non-R'n'D game engines to update certain engine values
4451 // needed to determine if sounds are played within the visible screen area
4452 scroll_x = actual_scroll_x;
4453 scroll_y = actual_scroll_y;
4455 // needed to get player position for "follow finger" playing input method
4456 local_player->jx = actual_player_x;
4457 local_player->jy = actual_player_y;
4460 void InitMovDir(int x, int y)
4462 int i, element = Tile[x][y];
4463 static int xy[4][2] =
4470 static int direction[3][4] =
4472 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4473 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4474 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4483 Tile[x][y] = EL_BUG;
4484 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4487 case EL_SPACESHIP_RIGHT:
4488 case EL_SPACESHIP_UP:
4489 case EL_SPACESHIP_LEFT:
4490 case EL_SPACESHIP_DOWN:
4491 Tile[x][y] = EL_SPACESHIP;
4492 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4495 case EL_BD_BUTTERFLY_RIGHT:
4496 case EL_BD_BUTTERFLY_UP:
4497 case EL_BD_BUTTERFLY_LEFT:
4498 case EL_BD_BUTTERFLY_DOWN:
4499 Tile[x][y] = EL_BD_BUTTERFLY;
4500 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4503 case EL_BD_FIREFLY_RIGHT:
4504 case EL_BD_FIREFLY_UP:
4505 case EL_BD_FIREFLY_LEFT:
4506 case EL_BD_FIREFLY_DOWN:
4507 Tile[x][y] = EL_BD_FIREFLY;
4508 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4511 case EL_PACMAN_RIGHT:
4513 case EL_PACMAN_LEFT:
4514 case EL_PACMAN_DOWN:
4515 Tile[x][y] = EL_PACMAN;
4516 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4519 case EL_YAMYAM_LEFT:
4520 case EL_YAMYAM_RIGHT:
4522 case EL_YAMYAM_DOWN:
4523 Tile[x][y] = EL_YAMYAM;
4524 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4527 case EL_SP_SNIKSNAK:
4528 MovDir[x][y] = MV_UP;
4531 case EL_SP_ELECTRON:
4532 MovDir[x][y] = MV_LEFT;
4539 Tile[x][y] = EL_MOLE;
4540 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4543 case EL_SPRING_LEFT:
4544 case EL_SPRING_RIGHT:
4545 Tile[x][y] = EL_SPRING;
4546 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4550 if (IS_CUSTOM_ELEMENT(element))
4552 struct ElementInfo *ei = &element_info[element];
4553 int move_direction_initial = ei->move_direction_initial;
4554 int move_pattern = ei->move_pattern;
4556 if (move_direction_initial == MV_START_PREVIOUS)
4558 if (MovDir[x][y] != MV_NONE)
4561 move_direction_initial = MV_START_AUTOMATIC;
4564 if (move_direction_initial == MV_START_RANDOM)
4565 MovDir[x][y] = 1 << RND(4);
4566 else if (move_direction_initial & MV_ANY_DIRECTION)
4567 MovDir[x][y] = move_direction_initial;
4568 else if (move_pattern == MV_ALL_DIRECTIONS ||
4569 move_pattern == MV_TURNING_LEFT ||
4570 move_pattern == MV_TURNING_RIGHT ||
4571 move_pattern == MV_TURNING_LEFT_RIGHT ||
4572 move_pattern == MV_TURNING_RIGHT_LEFT ||
4573 move_pattern == MV_TURNING_RANDOM)
4574 MovDir[x][y] = 1 << RND(4);
4575 else if (move_pattern == MV_HORIZONTAL)
4576 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4577 else if (move_pattern == MV_VERTICAL)
4578 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4579 else if (move_pattern & MV_ANY_DIRECTION)
4580 MovDir[x][y] = element_info[element].move_pattern;
4581 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4582 move_pattern == MV_ALONG_RIGHT_SIDE)
4584 // use random direction as default start direction
4585 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4586 MovDir[x][y] = 1 << RND(4);
4588 for (i = 0; i < NUM_DIRECTIONS; i++)
4590 int x1 = x + xy[i][0];
4591 int y1 = y + xy[i][1];
4593 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4595 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4596 MovDir[x][y] = direction[0][i];
4598 MovDir[x][y] = direction[1][i];
4607 MovDir[x][y] = 1 << RND(4);
4609 if (element != EL_BUG &&
4610 element != EL_SPACESHIP &&
4611 element != EL_BD_BUTTERFLY &&
4612 element != EL_BD_FIREFLY)
4615 for (i = 0; i < NUM_DIRECTIONS; i++)
4617 int x1 = x + xy[i][0];
4618 int y1 = y + xy[i][1];
4620 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4622 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4624 MovDir[x][y] = direction[0][i];
4627 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4628 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4630 MovDir[x][y] = direction[1][i];
4639 GfxDir[x][y] = MovDir[x][y];
4642 void InitAmoebaNr(int x, int y)
4645 int group_nr = AmoebaNeighbourNr(x, y);
4649 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4651 if (AmoebaCnt[i] == 0)
4659 AmoebaNr[x][y] = group_nr;
4660 AmoebaCnt[group_nr]++;
4661 AmoebaCnt2[group_nr]++;
4664 static void LevelSolved(void)
4666 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4667 game.players_still_needed > 0)
4670 game.LevelSolved = TRUE;
4671 game.GameOver = TRUE;
4673 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4674 game_em.lev->score :
4675 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4678 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4679 MM_HEALTH(game_mm.laser_overload_value) :
4682 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4683 game.LevelSolved_CountingScore = game.score_final;
4684 game.LevelSolved_CountingHealth = game.health_final;
4689 static int time_count_steps;
4690 static int time, time_final;
4691 static int score, score_final;
4692 static int health, health_final;
4693 static int game_over_delay_1 = 0;
4694 static int game_over_delay_2 = 0;
4695 static int game_over_delay_3 = 0;
4696 int game_over_delay_value_1 = 50;
4697 int game_over_delay_value_2 = 25;
4698 int game_over_delay_value_3 = 50;
4700 if (!game.LevelSolved_GameWon)
4704 // do not start end game actions before the player stops moving (to exit)
4705 if (local_player->active && local_player->MovPos)
4708 game.LevelSolved_GameWon = TRUE;
4709 game.LevelSolved_SaveTape = tape.recording;
4710 game.LevelSolved_SaveScore = !tape.playing;
4714 LevelStats_incSolved(level_nr);
4716 SaveLevelSetup_SeriesInfo();
4719 if (tape.auto_play) // tape might already be stopped here
4720 tape.auto_play_level_solved = TRUE;
4724 game_over_delay_1 = 0;
4725 game_over_delay_2 = 0;
4726 game_over_delay_3 = game_over_delay_value_3;
4728 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4729 score = score_final = game.score_final;
4730 health = health_final = game.health_final;
4732 if (level.score[SC_TIME_BONUS] > 0)
4737 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4739 else if (game.no_time_limit && TimePlayed < 999)
4742 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4745 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4747 game_over_delay_1 = game_over_delay_value_1;
4749 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4752 score_final += health * level.score[SC_TIME_BONUS];
4754 game_over_delay_2 = game_over_delay_value_2;
4757 game.score_final = score_final;
4758 game.health_final = health_final;
4761 if (level_editor_test_game)
4764 score = score_final;
4766 game.LevelSolved_CountingTime = time;
4767 game.LevelSolved_CountingScore = score;
4769 game_panel_controls[GAME_PANEL_TIME].value = time;
4770 game_panel_controls[GAME_PANEL_SCORE].value = score;
4772 DisplayGameControlValues();
4775 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4777 // check if last player has left the level
4778 if (game.exit_x >= 0 &&
4781 int x = game.exit_x;
4782 int y = game.exit_y;
4783 int element = Tile[x][y];
4785 // close exit door after last player
4786 if ((game.all_players_gone &&
4787 (element == EL_EXIT_OPEN ||
4788 element == EL_SP_EXIT_OPEN ||
4789 element == EL_STEEL_EXIT_OPEN)) ||
4790 element == EL_EM_EXIT_OPEN ||
4791 element == EL_EM_STEEL_EXIT_OPEN)
4795 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4796 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4797 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4798 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4799 EL_EM_STEEL_EXIT_CLOSING);
4801 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4804 // player disappears
4805 DrawLevelField(x, y);
4808 for (i = 0; i < MAX_PLAYERS; i++)
4810 struct PlayerInfo *player = &stored_player[i];
4812 if (player->present)
4814 RemovePlayer(player);
4816 // player disappears
4817 DrawLevelField(player->jx, player->jy);
4822 PlaySound(SND_GAME_WINNING);
4825 if (game_over_delay_1 > 0)
4827 game_over_delay_1--;
4832 if (time != time_final)
4834 int time_to_go = ABS(time_final - time);
4835 int time_count_dir = (time < time_final ? +1 : -1);
4837 if (time_to_go < time_count_steps)
4838 time_count_steps = 1;
4840 time += time_count_steps * time_count_dir;
4841 score += time_count_steps * level.score[SC_TIME_BONUS];
4843 game.LevelSolved_CountingTime = time;
4844 game.LevelSolved_CountingScore = score;
4846 game_panel_controls[GAME_PANEL_TIME].value = time;
4847 game_panel_controls[GAME_PANEL_SCORE].value = score;
4849 DisplayGameControlValues();
4851 if (time == time_final)
4852 StopSound(SND_GAME_LEVELTIME_BONUS);
4853 else if (setup.sound_loops)
4854 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4856 PlaySound(SND_GAME_LEVELTIME_BONUS);
4861 if (game_over_delay_2 > 0)
4863 game_over_delay_2--;
4868 if (health != health_final)
4870 int health_count_dir = (health < health_final ? +1 : -1);
4872 health += health_count_dir;
4873 score += level.score[SC_TIME_BONUS];
4875 game.LevelSolved_CountingHealth = health;
4876 game.LevelSolved_CountingScore = score;
4878 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4879 game_panel_controls[GAME_PANEL_SCORE].value = score;
4881 DisplayGameControlValues();
4883 if (health == health_final)
4884 StopSound(SND_GAME_LEVELTIME_BONUS);
4885 else if (setup.sound_loops)
4886 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4888 PlaySound(SND_GAME_LEVELTIME_BONUS);
4893 game.panel.active = FALSE;
4895 if (game_over_delay_3 > 0)
4897 game_over_delay_3--;
4907 // used instead of "level_nr" (needed for network games)
4908 int last_level_nr = levelset.level_nr;
4911 game.LevelSolved_GameEnd = TRUE;
4913 if (game.LevelSolved_SaveTape)
4915 // make sure that request dialog to save tape does not open door again
4916 if (!global.use_envelope_request)
4917 CloseDoor(DOOR_CLOSE_1);
4919 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4922 // if no tape is to be saved, close both doors simultaneously
4923 CloseDoor(DOOR_CLOSE_ALL);
4925 if (level_editor_test_game)
4927 SetGameStatus(GAME_MODE_MAIN);
4934 if (!game.LevelSolved_SaveScore)
4936 SetGameStatus(GAME_MODE_MAIN);
4943 if (level_nr == leveldir_current->handicap_level)
4945 leveldir_current->handicap_level++;
4947 SaveLevelSetup_SeriesInfo();
4950 if (setup.increment_levels &&
4951 level_nr < leveldir_current->last_level &&
4954 level_nr++; // advance to next level
4955 TapeErase(); // start with empty tape
4957 if (setup.auto_play_next_level)
4959 LoadLevel(level_nr);
4961 SaveLevelSetup_SeriesInfo();
4965 hi_pos = NewHiScore(last_level_nr);
4967 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4969 SetGameStatus(GAME_MODE_SCORES);
4971 DrawHallOfFame(last_level_nr, hi_pos);
4973 else if (setup.auto_play_next_level && setup.increment_levels &&
4974 last_level_nr < leveldir_current->last_level &&
4977 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4981 SetGameStatus(GAME_MODE_MAIN);
4987 int NewHiScore(int level_nr)
4991 boolean one_score_entry_per_name = !program.many_scores_per_name;
4993 LoadScore(level_nr);
4995 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4996 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4999 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5001 if (game.score_final > highscore[k].Score)
5003 // player has made it to the hall of fame
5005 if (k < MAX_SCORE_ENTRIES - 1)
5007 int m = MAX_SCORE_ENTRIES - 1;
5009 if (one_score_entry_per_name)
5011 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5012 if (strEqual(setup.player_name, highscore[l].Name))
5015 if (m == k) // player's new highscore overwrites his old one
5019 for (l = m; l > k; l--)
5021 strcpy(highscore[l].Name, highscore[l - 1].Name);
5022 highscore[l].Score = highscore[l - 1].Score;
5028 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5029 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5030 highscore[k].Score = game.score_final;
5035 else if (one_score_entry_per_name &&
5036 !strncmp(setup.player_name, highscore[k].Name,
5037 MAX_PLAYER_NAME_LEN))
5038 break; // player already there with a higher score
5042 SaveScore(level_nr);
5047 static int getElementMoveStepsizeExt(int x, int y, int direction)
5049 int element = Tile[x][y];
5050 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5051 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5052 int horiz_move = (dx != 0);
5053 int sign = (horiz_move ? dx : dy);
5054 int step = sign * element_info[element].move_stepsize;
5056 // special values for move stepsize for spring and things on conveyor belt
5059 if (CAN_FALL(element) &&
5060 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5061 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5062 else if (element == EL_SPRING)
5063 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5069 static int getElementMoveStepsize(int x, int y)
5071 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5074 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5076 if (player->GfxAction != action || player->GfxDir != dir)
5078 player->GfxAction = action;
5079 player->GfxDir = dir;
5081 player->StepFrame = 0;
5085 static void ResetGfxFrame(int x, int y)
5087 // profiling showed that "autotest" spends 10~20% of its time in this function
5088 if (DrawingDeactivatedField())
5091 int element = Tile[x][y];
5092 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5094 if (graphic_info[graphic].anim_global_sync)
5095 GfxFrame[x][y] = FrameCounter;
5096 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5097 GfxFrame[x][y] = CustomValue[x][y];
5098 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5099 GfxFrame[x][y] = element_info[element].collect_score;
5100 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5101 GfxFrame[x][y] = ChangeDelay[x][y];
5104 static void ResetGfxAnimation(int x, int y)
5106 GfxAction[x][y] = ACTION_DEFAULT;
5107 GfxDir[x][y] = MovDir[x][y];
5110 ResetGfxFrame(x, y);
5113 static void ResetRandomAnimationValue(int x, int y)
5115 GfxRandom[x][y] = INIT_GFX_RANDOM();
5118 static void InitMovingField(int x, int y, int direction)
5120 int element = Tile[x][y];
5121 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5122 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5125 boolean is_moving_before, is_moving_after;
5127 // check if element was/is moving or being moved before/after mode change
5128 is_moving_before = (WasJustMoving[x][y] != 0);
5129 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5131 // reset animation only for moving elements which change direction of moving
5132 // or which just started or stopped moving
5133 // (else CEs with property "can move" / "not moving" are reset each frame)
5134 if (is_moving_before != is_moving_after ||
5135 direction != MovDir[x][y])
5136 ResetGfxAnimation(x, y);
5138 MovDir[x][y] = direction;
5139 GfxDir[x][y] = direction;
5141 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5142 direction == MV_DOWN && CAN_FALL(element) ?
5143 ACTION_FALLING : ACTION_MOVING);
5145 // this is needed for CEs with property "can move" / "not moving"
5147 if (is_moving_after)
5149 if (Tile[newx][newy] == EL_EMPTY)
5150 Tile[newx][newy] = EL_BLOCKED;
5152 MovDir[newx][newy] = MovDir[x][y];
5154 CustomValue[newx][newy] = CustomValue[x][y];
5156 GfxFrame[newx][newy] = GfxFrame[x][y];
5157 GfxRandom[newx][newy] = GfxRandom[x][y];
5158 GfxAction[newx][newy] = GfxAction[x][y];
5159 GfxDir[newx][newy] = GfxDir[x][y];
5163 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5165 int direction = MovDir[x][y];
5166 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5167 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5173 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5175 int oldx = x, oldy = y;
5176 int direction = MovDir[x][y];
5178 if (direction == MV_LEFT)
5180 else if (direction == MV_RIGHT)
5182 else if (direction == MV_UP)
5184 else if (direction == MV_DOWN)
5187 *comes_from_x = oldx;
5188 *comes_from_y = oldy;
5191 static int MovingOrBlocked2Element(int x, int y)
5193 int element = Tile[x][y];
5195 if (element == EL_BLOCKED)
5199 Blocked2Moving(x, y, &oldx, &oldy);
5200 return Tile[oldx][oldy];
5206 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5208 // like MovingOrBlocked2Element(), but if element is moving
5209 // and (x,y) is the field the moving element is just leaving,
5210 // return EL_BLOCKED instead of the element value
5211 int element = Tile[x][y];
5213 if (IS_MOVING(x, y))
5215 if (element == EL_BLOCKED)
5219 Blocked2Moving(x, y, &oldx, &oldy);
5220 return Tile[oldx][oldy];
5229 static void RemoveField(int x, int y)
5231 Tile[x][y] = EL_EMPTY;
5237 CustomValue[x][y] = 0;
5240 ChangeDelay[x][y] = 0;
5241 ChangePage[x][y] = -1;
5242 Pushed[x][y] = FALSE;
5244 GfxElement[x][y] = EL_UNDEFINED;
5245 GfxAction[x][y] = ACTION_DEFAULT;
5246 GfxDir[x][y] = MV_NONE;
5249 static void RemoveMovingField(int x, int y)
5251 int oldx = x, oldy = y, newx = x, newy = y;
5252 int element = Tile[x][y];
5253 int next_element = EL_UNDEFINED;
5255 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5258 if (IS_MOVING(x, y))
5260 Moving2Blocked(x, y, &newx, &newy);
5262 if (Tile[newx][newy] != EL_BLOCKED)
5264 // element is moving, but target field is not free (blocked), but
5265 // already occupied by something different (example: acid pool);
5266 // in this case, only remove the moving field, but not the target
5268 RemoveField(oldx, oldy);
5270 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5272 TEST_DrawLevelField(oldx, oldy);
5277 else if (element == EL_BLOCKED)
5279 Blocked2Moving(x, y, &oldx, &oldy);
5280 if (!IS_MOVING(oldx, oldy))
5284 if (element == EL_BLOCKED &&
5285 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5286 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5287 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5288 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5289 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5290 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5291 next_element = get_next_element(Tile[oldx][oldy]);
5293 RemoveField(oldx, oldy);
5294 RemoveField(newx, newy);
5296 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5298 if (next_element != EL_UNDEFINED)
5299 Tile[oldx][oldy] = next_element;
5301 TEST_DrawLevelField(oldx, oldy);
5302 TEST_DrawLevelField(newx, newy);
5305 void DrawDynamite(int x, int y)
5307 int sx = SCREENX(x), sy = SCREENY(y);
5308 int graphic = el2img(Tile[x][y]);
5311 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5314 if (IS_WALKABLE_INSIDE(Back[x][y]))
5318 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5319 else if (Store[x][y])
5320 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5322 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5324 if (Back[x][y] || Store[x][y])
5325 DrawGraphicThruMask(sx, sy, graphic, frame);
5327 DrawGraphic(sx, sy, graphic, frame);
5330 static void CheckDynamite(int x, int y)
5332 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5336 if (MovDelay[x][y] != 0)
5339 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5345 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5350 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5352 boolean num_checked_players = 0;
5355 for (i = 0; i < MAX_PLAYERS; i++)
5357 if (stored_player[i].active)
5359 int sx = stored_player[i].jx;
5360 int sy = stored_player[i].jy;
5362 if (num_checked_players == 0)
5369 *sx1 = MIN(*sx1, sx);
5370 *sy1 = MIN(*sy1, sy);
5371 *sx2 = MAX(*sx2, sx);
5372 *sy2 = MAX(*sy2, sy);
5375 num_checked_players++;
5380 static boolean checkIfAllPlayersFitToScreen_RND(void)
5382 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5384 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5386 return (sx2 - sx1 < SCR_FIELDX &&
5387 sy2 - sy1 < SCR_FIELDY);
5390 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5392 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5394 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5396 *sx = (sx1 + sx2) / 2;
5397 *sy = (sy1 + sy2) / 2;
5400 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5401 boolean center_screen, boolean quick_relocation)
5403 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5404 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5405 boolean no_delay = (tape.warp_forward);
5406 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5407 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5408 int new_scroll_x, new_scroll_y;
5410 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5412 // case 1: quick relocation inside visible screen (without scrolling)
5419 if (!level.shifted_relocation || center_screen)
5421 // relocation _with_ centering of screen
5423 new_scroll_x = SCROLL_POSITION_X(x);
5424 new_scroll_y = SCROLL_POSITION_Y(y);
5428 // relocation _without_ centering of screen
5430 int center_scroll_x = SCROLL_POSITION_X(old_x);
5431 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5432 int offset_x = x + (scroll_x - center_scroll_x);
5433 int offset_y = y + (scroll_y - center_scroll_y);
5435 // for new screen position, apply previous offset to center position
5436 new_scroll_x = SCROLL_POSITION_X(offset_x);
5437 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5440 if (quick_relocation)
5442 // case 2: quick relocation (redraw without visible scrolling)
5444 scroll_x = new_scroll_x;
5445 scroll_y = new_scroll_y;
5452 // case 3: visible relocation (with scrolling to new position)
5454 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5456 SetVideoFrameDelay(wait_delay_value);
5458 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5460 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5461 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5463 if (dx == 0 && dy == 0) // no scrolling needed at all
5469 // set values for horizontal/vertical screen scrolling (half tile size)
5470 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5471 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5472 int pos_x = dx * TILEX / 2;
5473 int pos_y = dy * TILEY / 2;
5474 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5475 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5477 ScrollLevel(dx, dy);
5480 // scroll in two steps of half tile size to make things smoother
5481 BlitScreenToBitmapExt_RND(window, fx, fy);
5483 // scroll second step to align at full tile size
5484 BlitScreenToBitmap(window);
5490 SetVideoFrameDelay(frame_delay_value_old);
5493 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5495 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5496 int player_nr = GET_PLAYER_NR(el_player);
5497 struct PlayerInfo *player = &stored_player[player_nr];
5498 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5499 boolean no_delay = (tape.warp_forward);
5500 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5501 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5502 int old_jx = player->jx;
5503 int old_jy = player->jy;
5504 int old_element = Tile[old_jx][old_jy];
5505 int element = Tile[jx][jy];
5506 boolean player_relocated = (old_jx != jx || old_jy != jy);
5508 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5509 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5510 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5511 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5512 int leave_side_horiz = move_dir_horiz;
5513 int leave_side_vert = move_dir_vert;
5514 int enter_side = enter_side_horiz | enter_side_vert;
5515 int leave_side = leave_side_horiz | leave_side_vert;
5517 if (player->buried) // do not reanimate dead player
5520 if (!player_relocated) // no need to relocate the player
5523 if (IS_PLAYER(jx, jy)) // player already placed at new position
5525 RemoveField(jx, jy); // temporarily remove newly placed player
5526 DrawLevelField(jx, jy);
5529 if (player->present)
5531 while (player->MovPos)
5533 ScrollPlayer(player, SCROLL_GO_ON);
5534 ScrollScreen(NULL, SCROLL_GO_ON);
5536 AdvanceFrameAndPlayerCounters(player->index_nr);
5540 BackToFront_WithFrameDelay(wait_delay_value);
5543 DrawPlayer(player); // needed here only to cleanup last field
5544 DrawLevelField(player->jx, player->jy); // remove player graphic
5546 player->is_moving = FALSE;
5549 if (IS_CUSTOM_ELEMENT(old_element))
5550 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5552 player->index_bit, leave_side);
5554 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5556 player->index_bit, leave_side);
5558 Tile[jx][jy] = el_player;
5559 InitPlayerField(jx, jy, el_player, TRUE);
5561 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5562 possible that the relocation target field did not contain a player element,
5563 but a walkable element, to which the new player was relocated -- in this
5564 case, restore that (already initialized!) element on the player field */
5565 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5567 Tile[jx][jy] = element; // restore previously existing element
5570 // only visually relocate centered player
5571 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5572 FALSE, level.instant_relocation);
5574 TestIfPlayerTouchesBadThing(jx, jy);
5575 TestIfPlayerTouchesCustomElement(jx, jy);
5577 if (IS_CUSTOM_ELEMENT(element))
5578 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5579 player->index_bit, enter_side);
5581 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5582 player->index_bit, enter_side);
5584 if (player->is_switching)
5586 /* ensure that relocation while still switching an element does not cause
5587 a new element to be treated as also switched directly after relocation
5588 (this is important for teleporter switches that teleport the player to
5589 a place where another teleporter switch is in the same direction, which
5590 would then incorrectly be treated as immediately switched before the
5591 direction key that caused the switch was released) */
5593 player->switch_x += jx - old_jx;
5594 player->switch_y += jy - old_jy;
5598 static void Explode(int ex, int ey, int phase, int mode)
5604 // !!! eliminate this variable !!!
5605 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5607 if (game.explosions_delayed)
5609 ExplodeField[ex][ey] = mode;
5613 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5615 int center_element = Tile[ex][ey];
5616 int artwork_element, explosion_element; // set these values later
5618 // remove things displayed in background while burning dynamite
5619 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5622 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5624 // put moving element to center field (and let it explode there)
5625 center_element = MovingOrBlocked2Element(ex, ey);
5626 RemoveMovingField(ex, ey);
5627 Tile[ex][ey] = center_element;
5630 // now "center_element" is finally determined -- set related values now
5631 artwork_element = center_element; // for custom player artwork
5632 explosion_element = center_element; // for custom player artwork
5634 if (IS_PLAYER(ex, ey))
5636 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5638 artwork_element = stored_player[player_nr].artwork_element;
5640 if (level.use_explosion_element[player_nr])
5642 explosion_element = level.explosion_element[player_nr];
5643 artwork_element = explosion_element;
5647 if (mode == EX_TYPE_NORMAL ||
5648 mode == EX_TYPE_CENTER ||
5649 mode == EX_TYPE_CROSS)
5650 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5652 last_phase = element_info[explosion_element].explosion_delay + 1;
5654 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5656 int xx = x - ex + 1;
5657 int yy = y - ey + 1;
5660 if (!IN_LEV_FIELD(x, y) ||
5661 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5662 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5665 element = Tile[x][y];
5667 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5669 element = MovingOrBlocked2Element(x, y);
5671 if (!IS_EXPLOSION_PROOF(element))
5672 RemoveMovingField(x, y);
5675 // indestructible elements can only explode in center (but not flames)
5676 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5677 mode == EX_TYPE_BORDER)) ||
5678 element == EL_FLAMES)
5681 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5682 behaviour, for example when touching a yamyam that explodes to rocks
5683 with active deadly shield, a rock is created under the player !!! */
5684 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5686 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5687 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5688 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5690 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5693 if (IS_ACTIVE_BOMB(element))
5695 // re-activate things under the bomb like gate or penguin
5696 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5703 // save walkable background elements while explosion on same tile
5704 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5705 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5706 Back[x][y] = element;
5708 // ignite explodable elements reached by other explosion
5709 if (element == EL_EXPLOSION)
5710 element = Store2[x][y];
5712 if (AmoebaNr[x][y] &&
5713 (element == EL_AMOEBA_FULL ||
5714 element == EL_BD_AMOEBA ||
5715 element == EL_AMOEBA_GROWING))
5717 AmoebaCnt[AmoebaNr[x][y]]--;
5718 AmoebaCnt2[AmoebaNr[x][y]]--;
5723 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5725 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5727 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5729 if (PLAYERINFO(ex, ey)->use_murphy)
5730 Store[x][y] = EL_EMPTY;
5733 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5734 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5735 else if (ELEM_IS_PLAYER(center_element))
5736 Store[x][y] = EL_EMPTY;
5737 else if (center_element == EL_YAMYAM)
5738 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5739 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5740 Store[x][y] = element_info[center_element].content.e[xx][yy];
5742 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5743 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5744 // otherwise) -- FIX THIS !!!
5745 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5746 Store[x][y] = element_info[element].content.e[1][1];
5748 else if (!CAN_EXPLODE(element))
5749 Store[x][y] = element_info[element].content.e[1][1];
5752 Store[x][y] = EL_EMPTY;
5754 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5755 center_element == EL_AMOEBA_TO_DIAMOND)
5756 Store2[x][y] = element;
5758 Tile[x][y] = EL_EXPLOSION;
5759 GfxElement[x][y] = artwork_element;
5761 ExplodePhase[x][y] = 1;
5762 ExplodeDelay[x][y] = last_phase;
5767 if (center_element == EL_YAMYAM)
5768 game.yamyam_content_nr =
5769 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5781 GfxFrame[x][y] = 0; // restart explosion animation
5783 last_phase = ExplodeDelay[x][y];
5785 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5787 // this can happen if the player leaves an explosion just in time
5788 if (GfxElement[x][y] == EL_UNDEFINED)
5789 GfxElement[x][y] = EL_EMPTY;
5791 border_element = Store2[x][y];
5792 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5793 border_element = StorePlayer[x][y];
5795 if (phase == element_info[border_element].ignition_delay ||
5796 phase == last_phase)
5798 boolean border_explosion = FALSE;
5800 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5801 !PLAYER_EXPLOSION_PROTECTED(x, y))
5803 KillPlayerUnlessExplosionProtected(x, y);
5804 border_explosion = TRUE;
5806 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5808 Tile[x][y] = Store2[x][y];
5811 border_explosion = TRUE;
5813 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5815 AmoebaToDiamond(x, y);
5817 border_explosion = TRUE;
5820 // if an element just explodes due to another explosion (chain-reaction),
5821 // do not immediately end the new explosion when it was the last frame of
5822 // the explosion (as it would be done in the following "if"-statement!)
5823 if (border_explosion && phase == last_phase)
5827 if (phase == last_phase)
5831 element = Tile[x][y] = Store[x][y];
5832 Store[x][y] = Store2[x][y] = 0;
5833 GfxElement[x][y] = EL_UNDEFINED;
5835 // player can escape from explosions and might therefore be still alive
5836 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5837 element <= EL_PLAYER_IS_EXPLODING_4)
5839 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5840 int explosion_element = EL_PLAYER_1 + player_nr;
5841 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5842 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5844 if (level.use_explosion_element[player_nr])
5845 explosion_element = level.explosion_element[player_nr];
5847 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5848 element_info[explosion_element].content.e[xx][yy]);
5851 // restore probably existing indestructible background element
5852 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5853 element = Tile[x][y] = Back[x][y];
5856 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5857 GfxDir[x][y] = MV_NONE;
5858 ChangeDelay[x][y] = 0;
5859 ChangePage[x][y] = -1;
5861 CustomValue[x][y] = 0;
5863 InitField_WithBug2(x, y, FALSE);
5865 TEST_DrawLevelField(x, y);
5867 TestIfElementTouchesCustomElement(x, y);
5869 if (GFX_CRUMBLED(element))
5870 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5872 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5873 StorePlayer[x][y] = 0;
5875 if (ELEM_IS_PLAYER(element))
5876 RelocatePlayer(x, y, element);
5878 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5880 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5881 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5884 TEST_DrawLevelFieldCrumbled(x, y);
5886 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5888 DrawLevelElement(x, y, Back[x][y]);
5889 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5891 else if (IS_WALKABLE_UNDER(Back[x][y]))
5893 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5894 DrawLevelElementThruMask(x, y, Back[x][y]);
5896 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5897 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5901 static void DynaExplode(int ex, int ey)
5904 int dynabomb_element = Tile[ex][ey];
5905 int dynabomb_size = 1;
5906 boolean dynabomb_xl = FALSE;
5907 struct PlayerInfo *player;
5908 static int xy[4][2] =
5916 if (IS_ACTIVE_BOMB(dynabomb_element))
5918 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5919 dynabomb_size = player->dynabomb_size;
5920 dynabomb_xl = player->dynabomb_xl;
5921 player->dynabombs_left++;
5924 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5926 for (i = 0; i < NUM_DIRECTIONS; i++)
5928 for (j = 1; j <= dynabomb_size; j++)
5930 int x = ex + j * xy[i][0];
5931 int y = ey + j * xy[i][1];
5934 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5937 element = Tile[x][y];
5939 // do not restart explosions of fields with active bombs
5940 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5943 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5945 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5946 !IS_DIGGABLE(element) && !dynabomb_xl)
5952 void Bang(int x, int y)
5954 int element = MovingOrBlocked2Element(x, y);
5955 int explosion_type = EX_TYPE_NORMAL;
5957 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5959 struct PlayerInfo *player = PLAYERINFO(x, y);
5961 element = Tile[x][y] = player->initial_element;
5963 if (level.use_explosion_element[player->index_nr])
5965 int explosion_element = level.explosion_element[player->index_nr];
5967 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5968 explosion_type = EX_TYPE_CROSS;
5969 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5970 explosion_type = EX_TYPE_CENTER;
5978 case EL_BD_BUTTERFLY:
5981 case EL_DARK_YAMYAM:
5985 RaiseScoreElement(element);
5988 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5989 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5990 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5991 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5992 case EL_DYNABOMB_INCREASE_NUMBER:
5993 case EL_DYNABOMB_INCREASE_SIZE:
5994 case EL_DYNABOMB_INCREASE_POWER:
5995 explosion_type = EX_TYPE_DYNA;
5998 case EL_DC_LANDMINE:
5999 explosion_type = EX_TYPE_CENTER;
6004 case EL_LAMP_ACTIVE:
6005 case EL_AMOEBA_TO_DIAMOND:
6006 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6007 explosion_type = EX_TYPE_CENTER;
6011 if (element_info[element].explosion_type == EXPLODES_CROSS)
6012 explosion_type = EX_TYPE_CROSS;
6013 else if (element_info[element].explosion_type == EXPLODES_1X1)
6014 explosion_type = EX_TYPE_CENTER;
6018 if (explosion_type == EX_TYPE_DYNA)
6021 Explode(x, y, EX_PHASE_START, explosion_type);
6023 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6026 static void SplashAcid(int x, int y)
6028 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6029 (!IN_LEV_FIELD(x - 1, y - 2) ||
6030 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6031 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6033 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6034 (!IN_LEV_FIELD(x + 1, y - 2) ||
6035 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6036 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6038 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6041 static void InitBeltMovement(void)
6043 static int belt_base_element[4] =
6045 EL_CONVEYOR_BELT_1_LEFT,
6046 EL_CONVEYOR_BELT_2_LEFT,
6047 EL_CONVEYOR_BELT_3_LEFT,
6048 EL_CONVEYOR_BELT_4_LEFT
6050 static int belt_base_active_element[4] =
6052 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6053 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6054 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6055 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6060 // set frame order for belt animation graphic according to belt direction
6061 for (i = 0; i < NUM_BELTS; i++)
6065 for (j = 0; j < NUM_BELT_PARTS; j++)
6067 int element = belt_base_active_element[belt_nr] + j;
6068 int graphic_1 = el2img(element);
6069 int graphic_2 = el2panelimg(element);
6071 if (game.belt_dir[i] == MV_LEFT)
6073 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6074 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6078 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6079 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6084 SCAN_PLAYFIELD(x, y)
6086 int element = Tile[x][y];
6088 for (i = 0; i < NUM_BELTS; i++)
6090 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6092 int e_belt_nr = getBeltNrFromBeltElement(element);
6095 if (e_belt_nr == belt_nr)
6097 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6099 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6106 static void ToggleBeltSwitch(int x, int y)
6108 static int belt_base_element[4] =
6110 EL_CONVEYOR_BELT_1_LEFT,
6111 EL_CONVEYOR_BELT_2_LEFT,
6112 EL_CONVEYOR_BELT_3_LEFT,
6113 EL_CONVEYOR_BELT_4_LEFT
6115 static int belt_base_active_element[4] =
6117 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6118 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6119 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6120 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6122 static int belt_base_switch_element[4] =
6124 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6125 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6126 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6127 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6129 static int belt_move_dir[4] =
6137 int element = Tile[x][y];
6138 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6139 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6140 int belt_dir = belt_move_dir[belt_dir_nr];
6143 if (!IS_BELT_SWITCH(element))
6146 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6147 game.belt_dir[belt_nr] = belt_dir;
6149 if (belt_dir_nr == 3)
6152 // set frame order for belt animation graphic according to belt direction
6153 for (i = 0; i < NUM_BELT_PARTS; i++)
6155 int element = belt_base_active_element[belt_nr] + i;
6156 int graphic_1 = el2img(element);
6157 int graphic_2 = el2panelimg(element);
6159 if (belt_dir == MV_LEFT)
6161 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6162 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6166 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6167 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6171 SCAN_PLAYFIELD(xx, yy)
6173 int element = Tile[xx][yy];
6175 if (IS_BELT_SWITCH(element))
6177 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6179 if (e_belt_nr == belt_nr)
6181 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6182 TEST_DrawLevelField(xx, yy);
6185 else if (IS_BELT(element) && belt_dir != MV_NONE)
6187 int e_belt_nr = getBeltNrFromBeltElement(element);
6189 if (e_belt_nr == belt_nr)
6191 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6193 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6194 TEST_DrawLevelField(xx, yy);
6197 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6199 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6201 if (e_belt_nr == belt_nr)
6203 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6205 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6206 TEST_DrawLevelField(xx, yy);
6212 static void ToggleSwitchgateSwitch(int x, int y)
6216 game.switchgate_pos = !game.switchgate_pos;
6218 SCAN_PLAYFIELD(xx, yy)
6220 int element = Tile[xx][yy];
6222 if (element == EL_SWITCHGATE_SWITCH_UP)
6224 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6225 TEST_DrawLevelField(xx, yy);
6227 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6229 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6230 TEST_DrawLevelField(xx, yy);
6232 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6234 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6235 TEST_DrawLevelField(xx, yy);
6237 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6239 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6240 TEST_DrawLevelField(xx, yy);
6242 else if (element == EL_SWITCHGATE_OPEN ||
6243 element == EL_SWITCHGATE_OPENING)
6245 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6247 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6249 else if (element == EL_SWITCHGATE_CLOSED ||
6250 element == EL_SWITCHGATE_CLOSING)
6252 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6254 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6259 static int getInvisibleActiveFromInvisibleElement(int element)
6261 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6262 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6263 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6267 static int getInvisibleFromInvisibleActiveElement(int element)
6269 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6270 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6271 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6275 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6279 SCAN_PLAYFIELD(x, y)
6281 int element = Tile[x][y];
6283 if (element == EL_LIGHT_SWITCH &&
6284 game.light_time_left > 0)
6286 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6287 TEST_DrawLevelField(x, y);
6289 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6290 game.light_time_left == 0)
6292 Tile[x][y] = EL_LIGHT_SWITCH;
6293 TEST_DrawLevelField(x, y);
6295 else if (element == EL_EMC_DRIPPER &&
6296 game.light_time_left > 0)
6298 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6299 TEST_DrawLevelField(x, y);
6301 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6302 game.light_time_left == 0)
6304 Tile[x][y] = EL_EMC_DRIPPER;
6305 TEST_DrawLevelField(x, y);
6307 else if (element == EL_INVISIBLE_STEELWALL ||
6308 element == EL_INVISIBLE_WALL ||
6309 element == EL_INVISIBLE_SAND)
6311 if (game.light_time_left > 0)
6312 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6314 TEST_DrawLevelField(x, y);
6316 // uncrumble neighbour fields, if needed
6317 if (element == EL_INVISIBLE_SAND)
6318 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6320 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6321 element == EL_INVISIBLE_WALL_ACTIVE ||
6322 element == EL_INVISIBLE_SAND_ACTIVE)
6324 if (game.light_time_left == 0)
6325 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6327 TEST_DrawLevelField(x, y);
6329 // re-crumble neighbour fields, if needed
6330 if (element == EL_INVISIBLE_SAND)
6331 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6336 static void RedrawAllInvisibleElementsForLenses(void)
6340 SCAN_PLAYFIELD(x, y)
6342 int element = Tile[x][y];
6344 if (element == EL_EMC_DRIPPER &&
6345 game.lenses_time_left > 0)
6347 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6348 TEST_DrawLevelField(x, y);
6350 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6351 game.lenses_time_left == 0)
6353 Tile[x][y] = EL_EMC_DRIPPER;
6354 TEST_DrawLevelField(x, y);
6356 else if (element == EL_INVISIBLE_STEELWALL ||
6357 element == EL_INVISIBLE_WALL ||
6358 element == EL_INVISIBLE_SAND)
6360 if (game.lenses_time_left > 0)
6361 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6363 TEST_DrawLevelField(x, y);
6365 // uncrumble neighbour fields, if needed
6366 if (element == EL_INVISIBLE_SAND)
6367 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6369 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6370 element == EL_INVISIBLE_WALL_ACTIVE ||
6371 element == EL_INVISIBLE_SAND_ACTIVE)
6373 if (game.lenses_time_left == 0)
6374 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6376 TEST_DrawLevelField(x, y);
6378 // re-crumble neighbour fields, if needed
6379 if (element == EL_INVISIBLE_SAND)
6380 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6385 static void RedrawAllInvisibleElementsForMagnifier(void)
6389 SCAN_PLAYFIELD(x, y)
6391 int element = Tile[x][y];
6393 if (element == EL_EMC_FAKE_GRASS &&
6394 game.magnify_time_left > 0)
6396 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6397 TEST_DrawLevelField(x, y);
6399 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6400 game.magnify_time_left == 0)
6402 Tile[x][y] = EL_EMC_FAKE_GRASS;
6403 TEST_DrawLevelField(x, y);
6405 else if (IS_GATE_GRAY(element) &&
6406 game.magnify_time_left > 0)
6408 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6409 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6410 IS_EM_GATE_GRAY(element) ?
6411 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6412 IS_EMC_GATE_GRAY(element) ?
6413 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6414 IS_DC_GATE_GRAY(element) ?
6415 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6417 TEST_DrawLevelField(x, y);
6419 else if (IS_GATE_GRAY_ACTIVE(element) &&
6420 game.magnify_time_left == 0)
6422 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6423 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6424 IS_EM_GATE_GRAY_ACTIVE(element) ?
6425 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6426 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6427 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6428 IS_DC_GATE_GRAY_ACTIVE(element) ?
6429 EL_DC_GATE_WHITE_GRAY :
6431 TEST_DrawLevelField(x, y);
6436 static void ToggleLightSwitch(int x, int y)
6438 int element = Tile[x][y];
6440 game.light_time_left =
6441 (element == EL_LIGHT_SWITCH ?
6442 level.time_light * FRAMES_PER_SECOND : 0);
6444 RedrawAllLightSwitchesAndInvisibleElements();
6447 static void ActivateTimegateSwitch(int x, int y)
6451 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6453 SCAN_PLAYFIELD(xx, yy)
6455 int element = Tile[xx][yy];
6457 if (element == EL_TIMEGATE_CLOSED ||
6458 element == EL_TIMEGATE_CLOSING)
6460 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6461 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6465 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6467 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6468 TEST_DrawLevelField(xx, yy);
6474 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6475 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6478 static void Impact(int x, int y)
6480 boolean last_line = (y == lev_fieldy - 1);
6481 boolean object_hit = FALSE;
6482 boolean impact = (last_line || object_hit);
6483 int element = Tile[x][y];
6484 int smashed = EL_STEELWALL;
6486 if (!last_line) // check if element below was hit
6488 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6491 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6492 MovDir[x][y + 1] != MV_DOWN ||
6493 MovPos[x][y + 1] <= TILEY / 2));
6495 // do not smash moving elements that left the smashed field in time
6496 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6497 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6500 #if USE_QUICKSAND_IMPACT_BUGFIX
6501 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6503 RemoveMovingField(x, y + 1);
6504 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6505 Tile[x][y + 2] = EL_ROCK;
6506 TEST_DrawLevelField(x, y + 2);
6511 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6513 RemoveMovingField(x, y + 1);
6514 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6515 Tile[x][y + 2] = EL_ROCK;
6516 TEST_DrawLevelField(x, y + 2);
6523 smashed = MovingOrBlocked2Element(x, y + 1);
6525 impact = (last_line || object_hit);
6528 if (!last_line && smashed == EL_ACID) // element falls into acid
6530 SplashAcid(x, y + 1);
6534 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6535 // only reset graphic animation if graphic really changes after impact
6537 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6539 ResetGfxAnimation(x, y);
6540 TEST_DrawLevelField(x, y);
6543 if (impact && CAN_EXPLODE_IMPACT(element))
6548 else if (impact && element == EL_PEARL &&
6549 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6551 ResetGfxAnimation(x, y);
6553 Tile[x][y] = EL_PEARL_BREAKING;
6554 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6557 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6559 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6564 if (impact && element == EL_AMOEBA_DROP)
6566 if (object_hit && IS_PLAYER(x, y + 1))
6567 KillPlayerUnlessEnemyProtected(x, y + 1);
6568 else if (object_hit && smashed == EL_PENGUIN)
6572 Tile[x][y] = EL_AMOEBA_GROWING;
6573 Store[x][y] = EL_AMOEBA_WET;
6575 ResetRandomAnimationValue(x, y);
6580 if (object_hit) // check which object was hit
6582 if ((CAN_PASS_MAGIC_WALL(element) &&
6583 (smashed == EL_MAGIC_WALL ||
6584 smashed == EL_BD_MAGIC_WALL)) ||
6585 (CAN_PASS_DC_MAGIC_WALL(element) &&
6586 smashed == EL_DC_MAGIC_WALL))
6589 int activated_magic_wall =
6590 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6591 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6592 EL_DC_MAGIC_WALL_ACTIVE);
6594 // activate magic wall / mill
6595 SCAN_PLAYFIELD(xx, yy)
6597 if (Tile[xx][yy] == smashed)
6598 Tile[xx][yy] = activated_magic_wall;
6601 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6602 game.magic_wall_active = TRUE;
6604 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6605 SND_MAGIC_WALL_ACTIVATING :
6606 smashed == EL_BD_MAGIC_WALL ?
6607 SND_BD_MAGIC_WALL_ACTIVATING :
6608 SND_DC_MAGIC_WALL_ACTIVATING));
6611 if (IS_PLAYER(x, y + 1))
6613 if (CAN_SMASH_PLAYER(element))
6615 KillPlayerUnlessEnemyProtected(x, y + 1);
6619 else if (smashed == EL_PENGUIN)
6621 if (CAN_SMASH_PLAYER(element))
6627 else if (element == EL_BD_DIAMOND)
6629 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6635 else if (((element == EL_SP_INFOTRON ||
6636 element == EL_SP_ZONK) &&
6637 (smashed == EL_SP_SNIKSNAK ||
6638 smashed == EL_SP_ELECTRON ||
6639 smashed == EL_SP_DISK_ORANGE)) ||
6640 (element == EL_SP_INFOTRON &&
6641 smashed == EL_SP_DISK_YELLOW))
6646 else if (CAN_SMASH_EVERYTHING(element))
6648 if (IS_CLASSIC_ENEMY(smashed) ||
6649 CAN_EXPLODE_SMASHED(smashed))
6654 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6656 if (smashed == EL_LAMP ||
6657 smashed == EL_LAMP_ACTIVE)
6662 else if (smashed == EL_NUT)
6664 Tile[x][y + 1] = EL_NUT_BREAKING;
6665 PlayLevelSound(x, y, SND_NUT_BREAKING);
6666 RaiseScoreElement(EL_NUT);
6669 else if (smashed == EL_PEARL)
6671 ResetGfxAnimation(x, y);
6673 Tile[x][y + 1] = EL_PEARL_BREAKING;
6674 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6677 else if (smashed == EL_DIAMOND)
6679 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6680 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6683 else if (IS_BELT_SWITCH(smashed))
6685 ToggleBeltSwitch(x, y + 1);
6687 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6688 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6689 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6690 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6692 ToggleSwitchgateSwitch(x, y + 1);
6694 else if (smashed == EL_LIGHT_SWITCH ||
6695 smashed == EL_LIGHT_SWITCH_ACTIVE)
6697 ToggleLightSwitch(x, y + 1);
6701 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6703 CheckElementChangeBySide(x, y + 1, smashed, element,
6704 CE_SWITCHED, CH_SIDE_TOP);
6705 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6711 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6716 // play sound of magic wall / mill
6718 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6719 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6720 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6722 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6723 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6724 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6725 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6726 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6727 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6732 // play sound of object that hits the ground
6733 if (last_line || object_hit)
6734 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6737 static void TurnRoundExt(int x, int y)
6749 { 0, 0 }, { 0, 0 }, { 0, 0 },
6754 int left, right, back;
6758 { MV_DOWN, MV_UP, MV_RIGHT },
6759 { MV_UP, MV_DOWN, MV_LEFT },
6761 { MV_LEFT, MV_RIGHT, MV_DOWN },
6765 { MV_RIGHT, MV_LEFT, MV_UP }
6768 int element = Tile[x][y];
6769 int move_pattern = element_info[element].move_pattern;
6771 int old_move_dir = MovDir[x][y];
6772 int left_dir = turn[old_move_dir].left;
6773 int right_dir = turn[old_move_dir].right;
6774 int back_dir = turn[old_move_dir].back;
6776 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6777 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6778 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6779 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6781 int left_x = x + left_dx, left_y = y + left_dy;
6782 int right_x = x + right_dx, right_y = y + right_dy;
6783 int move_x = x + move_dx, move_y = y + move_dy;
6787 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6789 TestIfBadThingTouchesOtherBadThing(x, y);
6791 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6792 MovDir[x][y] = right_dir;
6793 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6794 MovDir[x][y] = left_dir;
6796 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6798 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6801 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6803 TestIfBadThingTouchesOtherBadThing(x, y);
6805 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6806 MovDir[x][y] = left_dir;
6807 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6808 MovDir[x][y] = right_dir;
6810 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6812 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6815 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6817 TestIfBadThingTouchesOtherBadThing(x, y);
6819 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6820 MovDir[x][y] = left_dir;
6821 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6822 MovDir[x][y] = right_dir;
6824 if (MovDir[x][y] != old_move_dir)
6827 else if (element == EL_YAMYAM)
6829 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6830 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6832 if (can_turn_left && can_turn_right)
6833 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6834 else if (can_turn_left)
6835 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6836 else if (can_turn_right)
6837 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6839 MovDir[x][y] = back_dir;
6841 MovDelay[x][y] = 16 + 16 * RND(3);
6843 else if (element == EL_DARK_YAMYAM)
6845 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6847 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6850 if (can_turn_left && can_turn_right)
6851 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6852 else if (can_turn_left)
6853 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6854 else if (can_turn_right)
6855 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6857 MovDir[x][y] = back_dir;
6859 MovDelay[x][y] = 16 + 16 * RND(3);
6861 else if (element == EL_PACMAN)
6863 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6864 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6866 if (can_turn_left && can_turn_right)
6867 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6868 else if (can_turn_left)
6869 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6870 else if (can_turn_right)
6871 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6873 MovDir[x][y] = back_dir;
6875 MovDelay[x][y] = 6 + RND(40);
6877 else if (element == EL_PIG)
6879 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6880 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6881 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6882 boolean should_turn_left, should_turn_right, should_move_on;
6884 int rnd = RND(rnd_value);
6886 should_turn_left = (can_turn_left &&
6888 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6889 y + back_dy + left_dy)));
6890 should_turn_right = (can_turn_right &&
6892 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6893 y + back_dy + right_dy)));
6894 should_move_on = (can_move_on &&
6897 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6898 y + move_dy + left_dy) ||
6899 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6900 y + move_dy + right_dy)));
6902 if (should_turn_left || should_turn_right || should_move_on)
6904 if (should_turn_left && should_turn_right && should_move_on)
6905 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6906 rnd < 2 * rnd_value / 3 ? right_dir :
6908 else if (should_turn_left && should_turn_right)
6909 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6910 else if (should_turn_left && should_move_on)
6911 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6912 else if (should_turn_right && should_move_on)
6913 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6914 else if (should_turn_left)
6915 MovDir[x][y] = left_dir;
6916 else if (should_turn_right)
6917 MovDir[x][y] = right_dir;
6918 else if (should_move_on)
6919 MovDir[x][y] = old_move_dir;
6921 else if (can_move_on && rnd > rnd_value / 8)
6922 MovDir[x][y] = old_move_dir;
6923 else if (can_turn_left && can_turn_right)
6924 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6925 else if (can_turn_left && rnd > rnd_value / 8)
6926 MovDir[x][y] = left_dir;
6927 else if (can_turn_right && rnd > rnd_value/8)
6928 MovDir[x][y] = right_dir;
6930 MovDir[x][y] = back_dir;
6932 xx = x + move_xy[MovDir[x][y]].dx;
6933 yy = y + move_xy[MovDir[x][y]].dy;
6935 if (!IN_LEV_FIELD(xx, yy) ||
6936 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6937 MovDir[x][y] = old_move_dir;
6941 else if (element == EL_DRAGON)
6943 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6944 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6945 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6947 int rnd = RND(rnd_value);
6949 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_AND_IS_FREE(xx, yy))
6964 MovDir[x][y] = old_move_dir;
6968 else if (element == EL_MOLE)
6970 boolean can_move_on =
6971 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6972 IS_AMOEBOID(Tile[move_x][move_y]) ||
6973 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6976 boolean can_turn_left =
6977 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6978 IS_AMOEBOID(Tile[left_x][left_y])));
6980 boolean can_turn_right =
6981 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6982 IS_AMOEBOID(Tile[right_x][right_y])));
6984 if (can_turn_left && can_turn_right)
6985 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6986 else if (can_turn_left)
6987 MovDir[x][y] = left_dir;
6989 MovDir[x][y] = right_dir;
6992 if (MovDir[x][y] != old_move_dir)
6995 else if (element == EL_BALLOON)
6997 MovDir[x][y] = game.wind_direction;
7000 else if (element == EL_SPRING)
7002 if (MovDir[x][y] & MV_HORIZONTAL)
7004 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7005 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7007 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7008 ResetGfxAnimation(move_x, move_y);
7009 TEST_DrawLevelField(move_x, move_y);
7011 MovDir[x][y] = back_dir;
7013 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7014 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7015 MovDir[x][y] = MV_NONE;
7020 else if (element == EL_ROBOT ||
7021 element == EL_SATELLITE ||
7022 element == EL_PENGUIN ||
7023 element == EL_EMC_ANDROID)
7025 int attr_x = -1, attr_y = -1;
7027 if (game.all_players_gone)
7029 attr_x = game.exit_x;
7030 attr_y = game.exit_y;
7036 for (i = 0; i < MAX_PLAYERS; i++)
7038 struct PlayerInfo *player = &stored_player[i];
7039 int jx = player->jx, jy = player->jy;
7041 if (!player->active)
7045 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7053 if (element == EL_ROBOT &&
7054 game.robot_wheel_x >= 0 &&
7055 game.robot_wheel_y >= 0 &&
7056 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7057 game.engine_version < VERSION_IDENT(3,1,0,0)))
7059 attr_x = game.robot_wheel_x;
7060 attr_y = game.robot_wheel_y;
7063 if (element == EL_PENGUIN)
7066 static int xy[4][2] =
7074 for (i = 0; i < NUM_DIRECTIONS; i++)
7076 int ex = x + xy[i][0];
7077 int ey = y + xy[i][1];
7079 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7080 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7081 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7082 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7091 MovDir[x][y] = MV_NONE;
7093 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7094 else if (attr_x > x)
7095 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7097 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7098 else if (attr_y > y)
7099 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7101 if (element == EL_ROBOT)
7105 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7106 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7107 Moving2Blocked(x, y, &newx, &newy);
7109 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7110 MovDelay[x][y] = 8 + 8 * !RND(3);
7112 MovDelay[x][y] = 16;
7114 else if (element == EL_PENGUIN)
7120 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7122 boolean first_horiz = RND(2);
7123 int new_move_dir = MovDir[x][y];
7126 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7127 Moving2Blocked(x, y, &newx, &newy);
7129 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7133 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7134 Moving2Blocked(x, y, &newx, &newy);
7136 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7139 MovDir[x][y] = old_move_dir;
7143 else if (element == EL_SATELLITE)
7149 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7151 boolean first_horiz = RND(2);
7152 int new_move_dir = MovDir[x][y];
7155 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7156 Moving2Blocked(x, y, &newx, &newy);
7158 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7162 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7163 Moving2Blocked(x, y, &newx, &newy);
7165 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7168 MovDir[x][y] = old_move_dir;
7172 else if (element == EL_EMC_ANDROID)
7174 static int check_pos[16] =
7176 -1, // 0 => (invalid)
7179 -1, // 3 => (invalid)
7181 0, // 5 => MV_LEFT | MV_UP
7182 2, // 6 => MV_RIGHT | MV_UP
7183 -1, // 7 => (invalid)
7185 6, // 9 => MV_LEFT | MV_DOWN
7186 4, // 10 => MV_RIGHT | MV_DOWN
7187 -1, // 11 => (invalid)
7188 -1, // 12 => (invalid)
7189 -1, // 13 => (invalid)
7190 -1, // 14 => (invalid)
7191 -1, // 15 => (invalid)
7199 { -1, -1, MV_LEFT | MV_UP },
7201 { +1, -1, MV_RIGHT | MV_UP },
7202 { +1, 0, MV_RIGHT },
7203 { +1, +1, MV_RIGHT | MV_DOWN },
7205 { -1, +1, MV_LEFT | MV_DOWN },
7208 int start_pos, check_order;
7209 boolean can_clone = FALSE;
7212 // check if there is any free field around current position
7213 for (i = 0; i < 8; i++)
7215 int newx = x + check_xy[i].dx;
7216 int newy = y + check_xy[i].dy;
7218 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7226 if (can_clone) // randomly find an element to clone
7230 start_pos = check_pos[RND(8)];
7231 check_order = (RND(2) ? -1 : +1);
7233 for (i = 0; i < 8; i++)
7235 int pos_raw = start_pos + i * check_order;
7236 int pos = (pos_raw + 8) % 8;
7237 int newx = x + check_xy[pos].dx;
7238 int newy = y + check_xy[pos].dy;
7240 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7242 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7243 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7245 Store[x][y] = Tile[newx][newy];
7254 if (can_clone) // randomly find a direction to move
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;
7267 int new_move_dir = check_xy[pos].dir;
7269 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7271 MovDir[x][y] = new_move_dir;
7272 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7281 if (can_clone) // cloning and moving successful
7284 // cannot clone -- try to move towards player
7286 start_pos = check_pos[MovDir[x][y] & 0x0f];
7287 check_order = (RND(2) ? -1 : +1);
7289 for (i = 0; i < 3; i++)
7291 // first check start_pos, then previous/next or (next/previous) pos
7292 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7293 int pos = (pos_raw + 8) % 8;
7294 int newx = x + check_xy[pos].dx;
7295 int newy = y + check_xy[pos].dy;
7296 int new_move_dir = check_xy[pos].dir;
7298 if (IS_PLAYER(newx, newy))
7301 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7303 MovDir[x][y] = new_move_dir;
7304 MovDelay[x][y] = level.android_move_time * 8 + 1;
7311 else if (move_pattern == MV_TURNING_LEFT ||
7312 move_pattern == MV_TURNING_RIGHT ||
7313 move_pattern == MV_TURNING_LEFT_RIGHT ||
7314 move_pattern == MV_TURNING_RIGHT_LEFT ||
7315 move_pattern == MV_TURNING_RANDOM ||
7316 move_pattern == MV_ALL_DIRECTIONS)
7318 boolean can_turn_left =
7319 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7320 boolean can_turn_right =
7321 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7323 if (element_info[element].move_stepsize == 0) // "not moving"
7326 if (move_pattern == MV_TURNING_LEFT)
7327 MovDir[x][y] = left_dir;
7328 else if (move_pattern == MV_TURNING_RIGHT)
7329 MovDir[x][y] = right_dir;
7330 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7331 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7332 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7333 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7334 else if (move_pattern == MV_TURNING_RANDOM)
7335 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7336 can_turn_right && !can_turn_left ? right_dir :
7337 RND(2) ? left_dir : right_dir);
7338 else if (can_turn_left && can_turn_right)
7339 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7340 else if (can_turn_left)
7341 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7342 else if (can_turn_right)
7343 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7345 MovDir[x][y] = back_dir;
7347 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7349 else if (move_pattern == MV_HORIZONTAL ||
7350 move_pattern == MV_VERTICAL)
7352 if (move_pattern & old_move_dir)
7353 MovDir[x][y] = back_dir;
7354 else if (move_pattern == MV_HORIZONTAL)
7355 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7356 else if (move_pattern == MV_VERTICAL)
7357 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7359 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7361 else if (move_pattern & MV_ANY_DIRECTION)
7363 MovDir[x][y] = move_pattern;
7364 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7366 else if (move_pattern & MV_WIND_DIRECTION)
7368 MovDir[x][y] = game.wind_direction;
7369 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7371 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7373 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7374 MovDir[x][y] = left_dir;
7375 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7376 MovDir[x][y] = right_dir;
7378 if (MovDir[x][y] != old_move_dir)
7379 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7381 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7383 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7384 MovDir[x][y] = right_dir;
7385 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7386 MovDir[x][y] = left_dir;
7388 if (MovDir[x][y] != old_move_dir)
7389 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7391 else if (move_pattern == MV_TOWARDS_PLAYER ||
7392 move_pattern == MV_AWAY_FROM_PLAYER)
7394 int attr_x = -1, attr_y = -1;
7396 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7398 if (game.all_players_gone)
7400 attr_x = game.exit_x;
7401 attr_y = game.exit_y;
7407 for (i = 0; i < MAX_PLAYERS; i++)
7409 struct PlayerInfo *player = &stored_player[i];
7410 int jx = player->jx, jy = player->jy;
7412 if (!player->active)
7416 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7424 MovDir[x][y] = MV_NONE;
7426 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7427 else if (attr_x > x)
7428 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7430 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7431 else if (attr_y > y)
7432 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7434 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7436 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7438 boolean first_horiz = RND(2);
7439 int new_move_dir = MovDir[x][y];
7441 if (element_info[element].move_stepsize == 0) // "not moving"
7443 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7444 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7450 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7451 Moving2Blocked(x, y, &newx, &newy);
7453 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7457 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7458 Moving2Blocked(x, y, &newx, &newy);
7460 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7463 MovDir[x][y] = old_move_dir;
7466 else if (move_pattern == MV_WHEN_PUSHED ||
7467 move_pattern == MV_WHEN_DROPPED)
7469 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7470 MovDir[x][y] = MV_NONE;
7474 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7476 static int test_xy[7][2] =
7486 static int test_dir[7] =
7496 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7497 int move_preference = -1000000; // start with very low preference
7498 int new_move_dir = MV_NONE;
7499 int start_test = RND(4);
7502 for (i = 0; i < NUM_DIRECTIONS; i++)
7504 int move_dir = test_dir[start_test + i];
7505 int move_dir_preference;
7507 xx = x + test_xy[start_test + i][0];
7508 yy = y + test_xy[start_test + i][1];
7510 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7511 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7513 new_move_dir = move_dir;
7518 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7521 move_dir_preference = -1 * RunnerVisit[xx][yy];
7522 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7523 move_dir_preference = PlayerVisit[xx][yy];
7525 if (move_dir_preference > move_preference)
7527 // prefer field that has not been visited for the longest time
7528 move_preference = move_dir_preference;
7529 new_move_dir = move_dir;
7531 else if (move_dir_preference == move_preference &&
7532 move_dir == old_move_dir)
7534 // prefer last direction when all directions are preferred equally
7535 move_preference = move_dir_preference;
7536 new_move_dir = move_dir;
7540 MovDir[x][y] = new_move_dir;
7541 if (old_move_dir != new_move_dir)
7542 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7546 static void TurnRound(int x, int y)
7548 int direction = MovDir[x][y];
7552 GfxDir[x][y] = MovDir[x][y];
7554 if (direction != MovDir[x][y])
7558 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7560 ResetGfxFrame(x, y);
7563 static boolean JustBeingPushed(int x, int y)
7567 for (i = 0; i < MAX_PLAYERS; i++)
7569 struct PlayerInfo *player = &stored_player[i];
7571 if (player->active && player->is_pushing && player->MovPos)
7573 int next_jx = player->jx + (player->jx - player->last_jx);
7574 int next_jy = player->jy + (player->jy - player->last_jy);
7576 if (x == next_jx && y == next_jy)
7584 static void StartMoving(int x, int y)
7586 boolean started_moving = FALSE; // some elements can fall _and_ move
7587 int element = Tile[x][y];
7592 if (MovDelay[x][y] == 0)
7593 GfxAction[x][y] = ACTION_DEFAULT;
7595 if (CAN_FALL(element) && y < lev_fieldy - 1)
7597 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7598 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7599 if (JustBeingPushed(x, y))
7602 if (element == EL_QUICKSAND_FULL)
7604 if (IS_FREE(x, y + 1))
7606 InitMovingField(x, y, MV_DOWN);
7607 started_moving = TRUE;
7609 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7610 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7611 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7612 Store[x][y] = EL_ROCK;
7614 Store[x][y] = EL_ROCK;
7617 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7619 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7621 if (!MovDelay[x][y])
7623 MovDelay[x][y] = TILEY + 1;
7625 ResetGfxAnimation(x, y);
7626 ResetGfxAnimation(x, y + 1);
7631 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7632 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7639 Tile[x][y] = EL_QUICKSAND_EMPTY;
7640 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7641 Store[x][y + 1] = Store[x][y];
7644 PlayLevelSoundAction(x, y, ACTION_FILLING);
7646 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7648 if (!MovDelay[x][y])
7650 MovDelay[x][y] = TILEY + 1;
7652 ResetGfxAnimation(x, y);
7653 ResetGfxAnimation(x, y + 1);
7658 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7659 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7666 Tile[x][y] = EL_QUICKSAND_EMPTY;
7667 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7668 Store[x][y + 1] = Store[x][y];
7671 PlayLevelSoundAction(x, y, ACTION_FILLING);
7674 else if (element == EL_QUICKSAND_FAST_FULL)
7676 if (IS_FREE(x, y + 1))
7678 InitMovingField(x, y, MV_DOWN);
7679 started_moving = TRUE;
7681 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7682 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7683 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7684 Store[x][y] = EL_ROCK;
7686 Store[x][y] = EL_ROCK;
7689 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7691 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7693 if (!MovDelay[x][y])
7695 MovDelay[x][y] = TILEY + 1;
7697 ResetGfxAnimation(x, y);
7698 ResetGfxAnimation(x, y + 1);
7703 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7704 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7711 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7712 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7713 Store[x][y + 1] = Store[x][y];
7716 PlayLevelSoundAction(x, y, ACTION_FILLING);
7718 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7720 if (!MovDelay[x][y])
7722 MovDelay[x][y] = TILEY + 1;
7724 ResetGfxAnimation(x, y);
7725 ResetGfxAnimation(x, y + 1);
7730 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7731 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7738 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7739 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7740 Store[x][y + 1] = Store[x][y];
7743 PlayLevelSoundAction(x, y, ACTION_FILLING);
7746 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7747 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7749 InitMovingField(x, y, MV_DOWN);
7750 started_moving = TRUE;
7752 Tile[x][y] = EL_QUICKSAND_FILLING;
7753 Store[x][y] = element;
7755 PlayLevelSoundAction(x, y, ACTION_FILLING);
7757 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7758 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7760 InitMovingField(x, y, MV_DOWN);
7761 started_moving = TRUE;
7763 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7764 Store[x][y] = element;
7766 PlayLevelSoundAction(x, y, ACTION_FILLING);
7768 else if (element == EL_MAGIC_WALL_FULL)
7770 if (IS_FREE(x, y + 1))
7772 InitMovingField(x, y, MV_DOWN);
7773 started_moving = TRUE;
7775 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7776 Store[x][y] = EL_CHANGED(Store[x][y]);
7778 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7780 if (!MovDelay[x][y])
7781 MovDelay[x][y] = TILEY / 4 + 1;
7790 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7791 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7792 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7796 else if (element == EL_BD_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_BD_MAGIC_WALL_EMPTYING;
7804 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7806 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7808 if (!MovDelay[x][y])
7809 MovDelay[x][y] = TILEY / 4 + 1;
7818 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7819 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7820 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7824 else if (element == EL_DC_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_DC_MAGIC_WALL_EMPTYING;
7832 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7834 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7836 if (!MovDelay[x][y])
7837 MovDelay[x][y] = TILEY / 4 + 1;
7846 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7847 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7848 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7852 else if ((CAN_PASS_MAGIC_WALL(element) &&
7853 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7854 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7855 (CAN_PASS_DC_MAGIC_WALL(element) &&
7856 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7859 InitMovingField(x, y, MV_DOWN);
7860 started_moving = TRUE;
7863 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7864 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7865 EL_DC_MAGIC_WALL_FILLING);
7866 Store[x][y] = element;
7868 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7870 SplashAcid(x, y + 1);
7872 InitMovingField(x, y, MV_DOWN);
7873 started_moving = TRUE;
7875 Store[x][y] = EL_ACID;
7878 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7879 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7880 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7881 CAN_FALL(element) && WasJustFalling[x][y] &&
7882 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7884 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7885 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7886 (Tile[x][y + 1] == EL_BLOCKED)))
7888 /* this is needed for a special case not covered by calling "Impact()"
7889 from "ContinueMoving()": if an element moves to a tile directly below
7890 another element which was just falling on that tile (which was empty
7891 in the previous frame), the falling element above would just stop
7892 instead of smashing the element below (in previous version, the above
7893 element was just checked for "moving" instead of "falling", resulting
7894 in incorrect smashes caused by horizontal movement of the above
7895 element; also, the case of the player being the element to smash was
7896 simply not covered here... :-/ ) */
7898 CheckCollision[x][y] = 0;
7899 CheckImpact[x][y] = 0;
7903 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7905 if (MovDir[x][y] == MV_NONE)
7907 InitMovingField(x, y, MV_DOWN);
7908 started_moving = TRUE;
7911 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7913 if (WasJustFalling[x][y]) // prevent animation from being restarted
7914 MovDir[x][y] = MV_DOWN;
7916 InitMovingField(x, y, MV_DOWN);
7917 started_moving = TRUE;
7919 else if (element == EL_AMOEBA_DROP)
7921 Tile[x][y] = EL_AMOEBA_GROWING;
7922 Store[x][y] = EL_AMOEBA_WET;
7924 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7925 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7926 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7927 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7929 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7930 (IS_FREE(x - 1, y + 1) ||
7931 Tile[x - 1][y + 1] == EL_ACID));
7932 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7933 (IS_FREE(x + 1, y + 1) ||
7934 Tile[x + 1][y + 1] == EL_ACID));
7935 boolean can_fall_any = (can_fall_left || can_fall_right);
7936 boolean can_fall_both = (can_fall_left && can_fall_right);
7937 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7939 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7941 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7942 can_fall_right = FALSE;
7943 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7944 can_fall_left = FALSE;
7945 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7946 can_fall_right = FALSE;
7947 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7948 can_fall_left = FALSE;
7950 can_fall_any = (can_fall_left || can_fall_right);
7951 can_fall_both = FALSE;
7956 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7957 can_fall_right = FALSE; // slip down on left side
7959 can_fall_left = !(can_fall_right = RND(2));
7961 can_fall_both = FALSE;
7966 // if not determined otherwise, prefer left side for slipping down
7967 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7968 started_moving = TRUE;
7971 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7973 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7974 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7975 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7976 int belt_dir = game.belt_dir[belt_nr];
7978 if ((belt_dir == MV_LEFT && left_is_free) ||
7979 (belt_dir == MV_RIGHT && right_is_free))
7981 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7983 InitMovingField(x, y, belt_dir);
7984 started_moving = TRUE;
7986 Pushed[x][y] = TRUE;
7987 Pushed[nextx][y] = TRUE;
7989 GfxAction[x][y] = ACTION_DEFAULT;
7993 MovDir[x][y] = 0; // if element was moving, stop it
7998 // not "else if" because of elements that can fall and move (EL_SPRING)
7999 if (CAN_MOVE(element) && !started_moving)
8001 int move_pattern = element_info[element].move_pattern;
8004 Moving2Blocked(x, y, &newx, &newy);
8006 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8009 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8010 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8012 WasJustMoving[x][y] = 0;
8013 CheckCollision[x][y] = 0;
8015 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8017 if (Tile[x][y] != element) // element has changed
8021 if (!MovDelay[x][y]) // start new movement phase
8023 // all objects that can change their move direction after each step
8024 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8026 if (element != EL_YAMYAM &&
8027 element != EL_DARK_YAMYAM &&
8028 element != EL_PACMAN &&
8029 !(move_pattern & MV_ANY_DIRECTION) &&
8030 move_pattern != MV_TURNING_LEFT &&
8031 move_pattern != MV_TURNING_RIGHT &&
8032 move_pattern != MV_TURNING_LEFT_RIGHT &&
8033 move_pattern != MV_TURNING_RIGHT_LEFT &&
8034 move_pattern != MV_TURNING_RANDOM)
8038 if (MovDelay[x][y] && (element == EL_BUG ||
8039 element == EL_SPACESHIP ||
8040 element == EL_SP_SNIKSNAK ||
8041 element == EL_SP_ELECTRON ||
8042 element == EL_MOLE))
8043 TEST_DrawLevelField(x, y);
8047 if (MovDelay[x][y]) // wait some time before next movement
8051 if (element == EL_ROBOT ||
8052 element == EL_YAMYAM ||
8053 element == EL_DARK_YAMYAM)
8055 DrawLevelElementAnimationIfNeeded(x, y, element);
8056 PlayLevelSoundAction(x, y, ACTION_WAITING);
8058 else if (element == EL_SP_ELECTRON)
8059 DrawLevelElementAnimationIfNeeded(x, y, element);
8060 else if (element == EL_DRAGON)
8063 int dir = MovDir[x][y];
8064 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8065 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8066 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8067 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8068 dir == MV_UP ? IMG_FLAMES_1_UP :
8069 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8070 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8072 GfxAction[x][y] = ACTION_ATTACKING;
8074 if (IS_PLAYER(x, y))
8075 DrawPlayerField(x, y);
8077 TEST_DrawLevelField(x, y);
8079 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8081 for (i = 1; i <= 3; i++)
8083 int xx = x + i * dx;
8084 int yy = y + i * dy;
8085 int sx = SCREENX(xx);
8086 int sy = SCREENY(yy);
8087 int flame_graphic = graphic + (i - 1);
8089 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8094 int flamed = MovingOrBlocked2Element(xx, yy);
8096 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8099 RemoveMovingField(xx, yy);
8101 ChangeDelay[xx][yy] = 0;
8103 Tile[xx][yy] = EL_FLAMES;
8105 if (IN_SCR_FIELD(sx, sy))
8107 TEST_DrawLevelFieldCrumbled(xx, yy);
8108 DrawGraphic(sx, sy, flame_graphic, frame);
8113 if (Tile[xx][yy] == EL_FLAMES)
8114 Tile[xx][yy] = EL_EMPTY;
8115 TEST_DrawLevelField(xx, yy);
8120 if (MovDelay[x][y]) // element still has to wait some time
8122 PlayLevelSoundAction(x, y, ACTION_WAITING);
8128 // now make next step
8130 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8132 if (DONT_COLLIDE_WITH(element) &&
8133 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8134 !PLAYER_ENEMY_PROTECTED(newx, newy))
8136 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8141 else if (CAN_MOVE_INTO_ACID(element) &&
8142 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8143 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8144 (MovDir[x][y] == MV_DOWN ||
8145 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8147 SplashAcid(newx, newy);
8148 Store[x][y] = EL_ACID;
8150 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8152 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8153 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8154 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8155 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8158 TEST_DrawLevelField(x, y);
8160 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8161 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8162 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8164 game.friends_still_needed--;
8165 if (!game.friends_still_needed &&
8167 game.all_players_gone)
8172 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8174 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8175 TEST_DrawLevelField(newx, newy);
8177 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8179 else if (!IS_FREE(newx, newy))
8181 GfxAction[x][y] = ACTION_WAITING;
8183 if (IS_PLAYER(x, y))
8184 DrawPlayerField(x, y);
8186 TEST_DrawLevelField(x, y);
8191 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8193 if (IS_FOOD_PIG(Tile[newx][newy]))
8195 if (IS_MOVING(newx, newy))
8196 RemoveMovingField(newx, newy);
8199 Tile[newx][newy] = EL_EMPTY;
8200 TEST_DrawLevelField(newx, newy);
8203 PlayLevelSound(x, y, SND_PIG_DIGGING);
8205 else if (!IS_FREE(newx, newy))
8207 if (IS_PLAYER(x, y))
8208 DrawPlayerField(x, y);
8210 TEST_DrawLevelField(x, y);
8215 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8217 if (Store[x][y] != EL_EMPTY)
8219 boolean can_clone = FALSE;
8222 // check if element to clone is still there
8223 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8225 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8233 // cannot clone or target field not free anymore -- do not clone
8234 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8235 Store[x][y] = EL_EMPTY;
8238 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8240 if (IS_MV_DIAGONAL(MovDir[x][y]))
8242 int diagonal_move_dir = MovDir[x][y];
8243 int stored = Store[x][y];
8244 int change_delay = 8;
8247 // android is moving diagonally
8249 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8251 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8252 GfxElement[x][y] = EL_EMC_ANDROID;
8253 GfxAction[x][y] = ACTION_SHRINKING;
8254 GfxDir[x][y] = diagonal_move_dir;
8255 ChangeDelay[x][y] = change_delay;
8257 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8260 DrawLevelGraphicAnimation(x, y, graphic);
8261 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8263 if (Tile[newx][newy] == EL_ACID)
8265 SplashAcid(newx, newy);
8270 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8272 Store[newx][newy] = EL_EMC_ANDROID;
8273 GfxElement[newx][newy] = EL_EMC_ANDROID;
8274 GfxAction[newx][newy] = ACTION_GROWING;
8275 GfxDir[newx][newy] = diagonal_move_dir;
8276 ChangeDelay[newx][newy] = change_delay;
8278 graphic = el_act_dir2img(GfxElement[newx][newy],
8279 GfxAction[newx][newy], GfxDir[newx][newy]);
8281 DrawLevelGraphicAnimation(newx, newy, graphic);
8282 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8288 Tile[newx][newy] = EL_EMPTY;
8289 TEST_DrawLevelField(newx, newy);
8291 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8294 else if (!IS_FREE(newx, newy))
8299 else if (IS_CUSTOM_ELEMENT(element) &&
8300 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8302 if (!DigFieldByCE(newx, newy, element))
8305 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8307 RunnerVisit[x][y] = FrameCounter;
8308 PlayerVisit[x][y] /= 8; // expire player visit path
8311 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8313 if (!IS_FREE(newx, newy))
8315 if (IS_PLAYER(x, y))
8316 DrawPlayerField(x, y);
8318 TEST_DrawLevelField(x, y);
8324 boolean wanna_flame = !RND(10);
8325 int dx = newx - x, dy = newy - y;
8326 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8327 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8328 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8329 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8330 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8331 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8334 IS_CLASSIC_ENEMY(element1) ||
8335 IS_CLASSIC_ENEMY(element2)) &&
8336 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8337 element1 != EL_FLAMES && element2 != EL_FLAMES)
8339 ResetGfxAnimation(x, y);
8340 GfxAction[x][y] = ACTION_ATTACKING;
8342 if (IS_PLAYER(x, y))
8343 DrawPlayerField(x, y);
8345 TEST_DrawLevelField(x, y);
8347 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8349 MovDelay[x][y] = 50;
8351 Tile[newx][newy] = EL_FLAMES;
8352 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8353 Tile[newx1][newy1] = EL_FLAMES;
8354 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8355 Tile[newx2][newy2] = EL_FLAMES;
8361 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8362 Tile[newx][newy] == EL_DIAMOND)
8364 if (IS_MOVING(newx, newy))
8365 RemoveMovingField(newx, newy);
8368 Tile[newx][newy] = EL_EMPTY;
8369 TEST_DrawLevelField(newx, newy);
8372 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8374 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8375 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8377 if (AmoebaNr[newx][newy])
8379 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8380 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8381 Tile[newx][newy] == EL_BD_AMOEBA)
8382 AmoebaCnt[AmoebaNr[newx][newy]]--;
8385 if (IS_MOVING(newx, newy))
8387 RemoveMovingField(newx, newy);
8391 Tile[newx][newy] = EL_EMPTY;
8392 TEST_DrawLevelField(newx, newy);
8395 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8397 else if ((element == EL_PACMAN || element == EL_MOLE)
8398 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8400 if (AmoebaNr[newx][newy])
8402 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8403 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8404 Tile[newx][newy] == EL_BD_AMOEBA)
8405 AmoebaCnt[AmoebaNr[newx][newy]]--;
8408 if (element == EL_MOLE)
8410 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8411 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8413 ResetGfxAnimation(x, y);
8414 GfxAction[x][y] = ACTION_DIGGING;
8415 TEST_DrawLevelField(x, y);
8417 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8419 return; // wait for shrinking amoeba
8421 else // element == EL_PACMAN
8423 Tile[newx][newy] = EL_EMPTY;
8424 TEST_DrawLevelField(newx, newy);
8425 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8428 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8429 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8430 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8432 // wait for shrinking amoeba to completely disappear
8435 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8437 // object was running against a wall
8441 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8442 DrawLevelElementAnimation(x, y, element);
8444 if (DONT_TOUCH(element))
8445 TestIfBadThingTouchesPlayer(x, y);
8450 InitMovingField(x, y, MovDir[x][y]);
8452 PlayLevelSoundAction(x, y, ACTION_MOVING);
8456 ContinueMoving(x, y);
8459 void ContinueMoving(int x, int y)
8461 int element = Tile[x][y];
8462 struct ElementInfo *ei = &element_info[element];
8463 int direction = MovDir[x][y];
8464 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8465 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8466 int newx = x + dx, newy = y + dy;
8467 int stored = Store[x][y];
8468 int stored_new = Store[newx][newy];
8469 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8470 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8471 boolean last_line = (newy == lev_fieldy - 1);
8473 MovPos[x][y] += getElementMoveStepsize(x, y);
8475 if (pushed_by_player) // special case: moving object pushed by player
8476 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8478 if (ABS(MovPos[x][y]) < TILEX)
8480 TEST_DrawLevelField(x, y);
8482 return; // element is still moving
8485 // element reached destination field
8487 Tile[x][y] = EL_EMPTY;
8488 Tile[newx][newy] = element;
8489 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8491 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8493 element = Tile[newx][newy] = EL_ACID;
8495 else if (element == EL_MOLE)
8497 Tile[x][y] = EL_SAND;
8499 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8501 else if (element == EL_QUICKSAND_FILLING)
8503 element = Tile[newx][newy] = get_next_element(element);
8504 Store[newx][newy] = Store[x][y];
8506 else if (element == EL_QUICKSAND_EMPTYING)
8508 Tile[x][y] = get_next_element(element);
8509 element = Tile[newx][newy] = Store[x][y];
8511 else if (element == EL_QUICKSAND_FAST_FILLING)
8513 element = Tile[newx][newy] = get_next_element(element);
8514 Store[newx][newy] = Store[x][y];
8516 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8518 Tile[x][y] = get_next_element(element);
8519 element = Tile[newx][newy] = Store[x][y];
8521 else if (element == EL_MAGIC_WALL_FILLING)
8523 element = Tile[newx][newy] = get_next_element(element);
8524 if (!game.magic_wall_active)
8525 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8526 Store[newx][newy] = Store[x][y];
8528 else if (element == EL_MAGIC_WALL_EMPTYING)
8530 Tile[x][y] = get_next_element(element);
8531 if (!game.magic_wall_active)
8532 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8533 element = Tile[newx][newy] = Store[x][y];
8535 InitField(newx, newy, FALSE);
8537 else if (element == EL_BD_MAGIC_WALL_FILLING)
8539 element = Tile[newx][newy] = get_next_element(element);
8540 if (!game.magic_wall_active)
8541 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8542 Store[newx][newy] = Store[x][y];
8544 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8546 Tile[x][y] = get_next_element(element);
8547 if (!game.magic_wall_active)
8548 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8549 element = Tile[newx][newy] = Store[x][y];
8551 InitField(newx, newy, FALSE);
8553 else if (element == EL_DC_MAGIC_WALL_FILLING)
8555 element = Tile[newx][newy] = get_next_element(element);
8556 if (!game.magic_wall_active)
8557 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8558 Store[newx][newy] = Store[x][y];
8560 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8562 Tile[x][y] = get_next_element(element);
8563 if (!game.magic_wall_active)
8564 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8565 element = Tile[newx][newy] = Store[x][y];
8567 InitField(newx, newy, FALSE);
8569 else if (element == EL_AMOEBA_DROPPING)
8571 Tile[x][y] = get_next_element(element);
8572 element = Tile[newx][newy] = Store[x][y];
8574 else if (element == EL_SOKOBAN_OBJECT)
8577 Tile[x][y] = Back[x][y];
8579 if (Back[newx][newy])
8580 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8582 Back[x][y] = Back[newx][newy] = 0;
8585 Store[x][y] = EL_EMPTY;
8590 MovDelay[newx][newy] = 0;
8592 if (CAN_CHANGE_OR_HAS_ACTION(element))
8594 // copy element change control values to new field
8595 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8596 ChangePage[newx][newy] = ChangePage[x][y];
8597 ChangeCount[newx][newy] = ChangeCount[x][y];
8598 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8601 CustomValue[newx][newy] = CustomValue[x][y];
8603 ChangeDelay[x][y] = 0;
8604 ChangePage[x][y] = -1;
8605 ChangeCount[x][y] = 0;
8606 ChangeEvent[x][y] = -1;
8608 CustomValue[x][y] = 0;
8610 // copy animation control values to new field
8611 GfxFrame[newx][newy] = GfxFrame[x][y];
8612 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8613 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8614 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8616 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8618 // some elements can leave other elements behind after moving
8619 if (ei->move_leave_element != EL_EMPTY &&
8620 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8621 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8623 int move_leave_element = ei->move_leave_element;
8625 // this makes it possible to leave the removed element again
8626 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8627 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8629 Tile[x][y] = move_leave_element;
8631 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8632 MovDir[x][y] = direction;
8634 InitField(x, y, FALSE);
8636 if (GFX_CRUMBLED(Tile[x][y]))
8637 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8639 if (ELEM_IS_PLAYER(move_leave_element))
8640 RelocatePlayer(x, y, move_leave_element);
8643 // do this after checking for left-behind element
8644 ResetGfxAnimation(x, y); // reset animation values for old field
8646 if (!CAN_MOVE(element) ||
8647 (CAN_FALL(element) && direction == MV_DOWN &&
8648 (element == EL_SPRING ||
8649 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8650 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8651 GfxDir[x][y] = MovDir[newx][newy] = 0;
8653 TEST_DrawLevelField(x, y);
8654 TEST_DrawLevelField(newx, newy);
8656 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8658 // prevent pushed element from moving on in pushed direction
8659 if (pushed_by_player && CAN_MOVE(element) &&
8660 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8661 !(element_info[element].move_pattern & direction))
8662 TurnRound(newx, newy);
8664 // prevent elements on conveyor belt from moving on in last direction
8665 if (pushed_by_conveyor && CAN_FALL(element) &&
8666 direction & MV_HORIZONTAL)
8667 MovDir[newx][newy] = 0;
8669 if (!pushed_by_player)
8671 int nextx = newx + dx, nexty = newy + dy;
8672 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8674 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8676 if (CAN_FALL(element) && direction == MV_DOWN)
8677 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8679 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8680 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8682 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8683 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8686 if (DONT_TOUCH(element)) // object may be nasty to player or others
8688 TestIfBadThingTouchesPlayer(newx, newy);
8689 TestIfBadThingTouchesFriend(newx, newy);
8691 if (!IS_CUSTOM_ELEMENT(element))
8692 TestIfBadThingTouchesOtherBadThing(newx, newy);
8694 else if (element == EL_PENGUIN)
8695 TestIfFriendTouchesBadThing(newx, newy);
8697 if (DONT_GET_HIT_BY(element))
8699 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8702 // give the player one last chance (one more frame) to move away
8703 if (CAN_FALL(element) && direction == MV_DOWN &&
8704 (last_line || (!IS_FREE(x, newy + 1) &&
8705 (!IS_PLAYER(x, newy + 1) ||
8706 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8709 if (pushed_by_player && !game.use_change_when_pushing_bug)
8711 int push_side = MV_DIR_OPPOSITE(direction);
8712 struct PlayerInfo *player = PLAYERINFO(x, y);
8714 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8715 player->index_bit, push_side);
8716 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8717 player->index_bit, push_side);
8720 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8721 MovDelay[newx][newy] = 1;
8723 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8725 TestIfElementTouchesCustomElement(x, y); // empty or new element
8726 TestIfElementHitsCustomElement(newx, newy, direction);
8727 TestIfPlayerTouchesCustomElement(newx, newy);
8728 TestIfElementTouchesCustomElement(newx, newy);
8730 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8731 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8732 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8733 MV_DIR_OPPOSITE(direction));
8736 int AmoebaNeighbourNr(int ax, int ay)
8739 int element = Tile[ax][ay];
8741 static int xy[4][2] =
8749 for (i = 0; i < NUM_DIRECTIONS; i++)
8751 int x = ax + xy[i][0];
8752 int y = ay + xy[i][1];
8754 if (!IN_LEV_FIELD(x, y))
8757 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8758 group_nr = AmoebaNr[x][y];
8764 static void AmoebaMerge(int ax, int ay)
8766 int i, x, y, xx, yy;
8767 int new_group_nr = AmoebaNr[ax][ay];
8768 static int xy[4][2] =
8776 if (new_group_nr == 0)
8779 for (i = 0; i < NUM_DIRECTIONS; i++)
8784 if (!IN_LEV_FIELD(x, y))
8787 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8788 Tile[x][y] == EL_BD_AMOEBA ||
8789 Tile[x][y] == EL_AMOEBA_DEAD) &&
8790 AmoebaNr[x][y] != new_group_nr)
8792 int old_group_nr = AmoebaNr[x][y];
8794 if (old_group_nr == 0)
8797 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8798 AmoebaCnt[old_group_nr] = 0;
8799 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8800 AmoebaCnt2[old_group_nr] = 0;
8802 SCAN_PLAYFIELD(xx, yy)
8804 if (AmoebaNr[xx][yy] == old_group_nr)
8805 AmoebaNr[xx][yy] = new_group_nr;
8811 void AmoebaToDiamond(int ax, int ay)
8815 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8817 int group_nr = AmoebaNr[ax][ay];
8822 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8823 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8829 SCAN_PLAYFIELD(x, y)
8831 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8834 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8838 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8839 SND_AMOEBA_TURNING_TO_GEM :
8840 SND_AMOEBA_TURNING_TO_ROCK));
8845 static int xy[4][2] =
8853 for (i = 0; i < NUM_DIRECTIONS; i++)
8858 if (!IN_LEV_FIELD(x, y))
8861 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8863 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8864 SND_AMOEBA_TURNING_TO_GEM :
8865 SND_AMOEBA_TURNING_TO_ROCK));
8872 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8875 int group_nr = AmoebaNr[ax][ay];
8876 boolean done = FALSE;
8881 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8882 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8888 SCAN_PLAYFIELD(x, y)
8890 if (AmoebaNr[x][y] == group_nr &&
8891 (Tile[x][y] == EL_AMOEBA_DEAD ||
8892 Tile[x][y] == EL_BD_AMOEBA ||
8893 Tile[x][y] == EL_AMOEBA_GROWING))
8896 Tile[x][y] = new_element;
8897 InitField(x, y, FALSE);
8898 TEST_DrawLevelField(x, y);
8904 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8905 SND_BD_AMOEBA_TURNING_TO_ROCK :
8906 SND_BD_AMOEBA_TURNING_TO_GEM));
8909 static void AmoebaGrowing(int x, int y)
8911 static unsigned int sound_delay = 0;
8912 static unsigned int sound_delay_value = 0;
8914 if (!MovDelay[x][y]) // start new growing cycle
8918 if (DelayReached(&sound_delay, sound_delay_value))
8920 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8921 sound_delay_value = 30;
8925 if (MovDelay[x][y]) // wait some time before growing bigger
8928 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8930 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8931 6 - MovDelay[x][y]);
8933 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8936 if (!MovDelay[x][y])
8938 Tile[x][y] = Store[x][y];
8940 TEST_DrawLevelField(x, y);
8945 static void AmoebaShrinking(int x, int y)
8947 static unsigned int sound_delay = 0;
8948 static unsigned int sound_delay_value = 0;
8950 if (!MovDelay[x][y]) // start new shrinking cycle
8954 if (DelayReached(&sound_delay, sound_delay_value))
8955 sound_delay_value = 30;
8958 if (MovDelay[x][y]) // wait some time before shrinking
8961 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8963 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8964 6 - MovDelay[x][y]);
8966 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8969 if (!MovDelay[x][y])
8971 Tile[x][y] = EL_EMPTY;
8972 TEST_DrawLevelField(x, y);
8974 // don't let mole enter this field in this cycle;
8975 // (give priority to objects falling to this field from above)
8981 static void AmoebaReproduce(int ax, int ay)
8984 int element = Tile[ax][ay];
8985 int graphic = el2img(element);
8986 int newax = ax, neway = ay;
8987 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8988 static int xy[4][2] =
8996 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8998 Tile[ax][ay] = EL_AMOEBA_DEAD;
8999 TEST_DrawLevelField(ax, ay);
9003 if (IS_ANIMATED(graphic))
9004 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9006 if (!MovDelay[ax][ay]) // start making new amoeba field
9007 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9009 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9012 if (MovDelay[ax][ay])
9016 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9019 int x = ax + xy[start][0];
9020 int y = ay + xy[start][1];
9022 if (!IN_LEV_FIELD(x, y))
9025 if (IS_FREE(x, y) ||
9026 CAN_GROW_INTO(Tile[x][y]) ||
9027 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9028 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9034 if (newax == ax && neway == ay)
9037 else // normal or "filled" (BD style) amoeba
9040 boolean waiting_for_player = FALSE;
9042 for (i = 0; i < NUM_DIRECTIONS; i++)
9044 int j = (start + i) % 4;
9045 int x = ax + xy[j][0];
9046 int y = ay + xy[j][1];
9048 if (!IN_LEV_FIELD(x, y))
9051 if (IS_FREE(x, y) ||
9052 CAN_GROW_INTO(Tile[x][y]) ||
9053 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9054 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9060 else if (IS_PLAYER(x, y))
9061 waiting_for_player = TRUE;
9064 if (newax == ax && neway == ay) // amoeba cannot grow
9066 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9068 Tile[ax][ay] = EL_AMOEBA_DEAD;
9069 TEST_DrawLevelField(ax, ay);
9070 AmoebaCnt[AmoebaNr[ax][ay]]--;
9072 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9074 if (element == EL_AMOEBA_FULL)
9075 AmoebaToDiamond(ax, ay);
9076 else if (element == EL_BD_AMOEBA)
9077 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9082 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9084 // amoeba gets larger by growing in some direction
9086 int new_group_nr = AmoebaNr[ax][ay];
9089 if (new_group_nr == 0)
9091 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9093 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9099 AmoebaNr[newax][neway] = new_group_nr;
9100 AmoebaCnt[new_group_nr]++;
9101 AmoebaCnt2[new_group_nr]++;
9103 // if amoeba touches other amoeba(s) after growing, unify them
9104 AmoebaMerge(newax, neway);
9106 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9108 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9114 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9115 (neway == lev_fieldy - 1 && newax != ax))
9117 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9118 Store[newax][neway] = element;
9120 else if (neway == ay || element == EL_EMC_DRIPPER)
9122 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9124 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9128 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9129 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9130 Store[ax][ay] = EL_AMOEBA_DROP;
9131 ContinueMoving(ax, ay);
9135 TEST_DrawLevelField(newax, neway);
9138 static void Life(int ax, int ay)
9142 int element = Tile[ax][ay];
9143 int graphic = el2img(element);
9144 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9146 boolean changed = FALSE;
9148 if (IS_ANIMATED(graphic))
9149 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9154 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9155 MovDelay[ax][ay] = life_time;
9157 if (MovDelay[ax][ay]) // wait some time before next cycle
9160 if (MovDelay[ax][ay])
9164 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9166 int xx = ax+x1, yy = ay+y1;
9167 int old_element = Tile[xx][yy];
9168 int num_neighbours = 0;
9170 if (!IN_LEV_FIELD(xx, yy))
9173 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9175 int x = xx+x2, y = yy+y2;
9177 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9180 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9181 boolean is_neighbour = FALSE;
9183 if (level.use_life_bugs)
9185 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9186 (IS_FREE(x, y) && Stop[x][y]));
9189 (Last[x][y] == element || is_player_cell);
9195 boolean is_free = FALSE;
9197 if (level.use_life_bugs)
9198 is_free = (IS_FREE(xx, yy));
9200 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9202 if (xx == ax && yy == ay) // field in the middle
9204 if (num_neighbours < life_parameter[0] ||
9205 num_neighbours > life_parameter[1])
9207 Tile[xx][yy] = EL_EMPTY;
9208 if (Tile[xx][yy] != old_element)
9209 TEST_DrawLevelField(xx, yy);
9210 Stop[xx][yy] = TRUE;
9214 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9215 { // free border field
9216 if (num_neighbours >= life_parameter[2] &&
9217 num_neighbours <= life_parameter[3])
9219 Tile[xx][yy] = element;
9220 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9221 if (Tile[xx][yy] != old_element)
9222 TEST_DrawLevelField(xx, yy);
9223 Stop[xx][yy] = TRUE;
9230 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9231 SND_GAME_OF_LIFE_GROWING);
9234 static void InitRobotWheel(int x, int y)
9236 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9239 static void RunRobotWheel(int x, int y)
9241 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9244 static void StopRobotWheel(int x, int y)
9246 if (game.robot_wheel_x == x &&
9247 game.robot_wheel_y == y)
9249 game.robot_wheel_x = -1;
9250 game.robot_wheel_y = -1;
9251 game.robot_wheel_active = FALSE;
9255 static void InitTimegateWheel(int x, int y)
9257 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9260 static void RunTimegateWheel(int x, int y)
9262 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9265 static void InitMagicBallDelay(int x, int y)
9267 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9270 static void ActivateMagicBall(int bx, int by)
9274 if (level.ball_random)
9276 int pos_border = RND(8); // select one of the eight border elements
9277 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9278 int xx = pos_content % 3;
9279 int yy = pos_content / 3;
9284 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9285 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9289 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9291 int xx = x - bx + 1;
9292 int yy = y - by + 1;
9294 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9295 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9299 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9302 static void CheckExit(int x, int y)
9304 if (game.gems_still_needed > 0 ||
9305 game.sokoban_fields_still_needed > 0 ||
9306 game.sokoban_objects_still_needed > 0 ||
9307 game.lights_still_needed > 0)
9309 int element = Tile[x][y];
9310 int graphic = el2img(element);
9312 if (IS_ANIMATED(graphic))
9313 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9318 // do not re-open exit door closed after last player
9319 if (game.all_players_gone)
9322 Tile[x][y] = EL_EXIT_OPENING;
9324 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9327 static void CheckExitEM(int x, int y)
9329 if (game.gems_still_needed > 0 ||
9330 game.sokoban_fields_still_needed > 0 ||
9331 game.sokoban_objects_still_needed > 0 ||
9332 game.lights_still_needed > 0)
9334 int element = Tile[x][y];
9335 int graphic = el2img(element);
9337 if (IS_ANIMATED(graphic))
9338 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9343 // do not re-open exit door closed after last player
9344 if (game.all_players_gone)
9347 Tile[x][y] = EL_EM_EXIT_OPENING;
9349 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9352 static void CheckExitSteel(int x, int y)
9354 if (game.gems_still_needed > 0 ||
9355 game.sokoban_fields_still_needed > 0 ||
9356 game.sokoban_objects_still_needed > 0 ||
9357 game.lights_still_needed > 0)
9359 int element = Tile[x][y];
9360 int graphic = el2img(element);
9362 if (IS_ANIMATED(graphic))
9363 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9368 // do not re-open exit door closed after last player
9369 if (game.all_players_gone)
9372 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9374 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9377 static void CheckExitSteelEM(int x, int y)
9379 if (game.gems_still_needed > 0 ||
9380 game.sokoban_fields_still_needed > 0 ||
9381 game.sokoban_objects_still_needed > 0 ||
9382 game.lights_still_needed > 0)
9384 int element = Tile[x][y];
9385 int graphic = el2img(element);
9387 if (IS_ANIMATED(graphic))
9388 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9393 // do not re-open exit door closed after last player
9394 if (game.all_players_gone)
9397 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9399 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9402 static void CheckExitSP(int x, int y)
9404 if (game.gems_still_needed > 0)
9406 int element = Tile[x][y];
9407 int graphic = el2img(element);
9409 if (IS_ANIMATED(graphic))
9410 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9415 // do not re-open exit door closed after last player
9416 if (game.all_players_gone)
9419 Tile[x][y] = EL_SP_EXIT_OPENING;
9421 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9424 static void CloseAllOpenTimegates(void)
9428 SCAN_PLAYFIELD(x, y)
9430 int element = Tile[x][y];
9432 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9434 Tile[x][y] = EL_TIMEGATE_CLOSING;
9436 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9441 static void DrawTwinkleOnField(int x, int y)
9443 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9446 if (Tile[x][y] == EL_BD_DIAMOND)
9449 if (MovDelay[x][y] == 0) // next animation frame
9450 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9452 if (MovDelay[x][y] != 0) // wait some time before next frame
9456 DrawLevelElementAnimation(x, y, Tile[x][y]);
9458 if (MovDelay[x][y] != 0)
9460 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9461 10 - MovDelay[x][y]);
9463 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9468 static void MauerWaechst(int x, int y)
9472 if (!MovDelay[x][y]) // next animation frame
9473 MovDelay[x][y] = 3 * delay;
9475 if (MovDelay[x][y]) // wait some time before next frame
9479 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9481 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9482 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9484 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9487 if (!MovDelay[x][y])
9489 if (MovDir[x][y] == MV_LEFT)
9491 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9492 TEST_DrawLevelField(x - 1, y);
9494 else if (MovDir[x][y] == MV_RIGHT)
9496 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9497 TEST_DrawLevelField(x + 1, y);
9499 else if (MovDir[x][y] == MV_UP)
9501 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9502 TEST_DrawLevelField(x, y - 1);
9506 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9507 TEST_DrawLevelField(x, y + 1);
9510 Tile[x][y] = Store[x][y];
9512 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9513 TEST_DrawLevelField(x, y);
9518 static void MauerAbleger(int ax, int ay)
9520 int element = Tile[ax][ay];
9521 int graphic = el2img(element);
9522 boolean oben_frei = FALSE, unten_frei = FALSE;
9523 boolean links_frei = FALSE, rechts_frei = FALSE;
9524 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9525 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9526 boolean new_wall = FALSE;
9528 if (IS_ANIMATED(graphic))
9529 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9531 if (!MovDelay[ax][ay]) // start building new wall
9532 MovDelay[ax][ay] = 6;
9534 if (MovDelay[ax][ay]) // wait some time before building new wall
9537 if (MovDelay[ax][ay])
9541 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9543 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9545 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9547 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9550 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9551 element == EL_EXPANDABLE_WALL_ANY)
9555 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9556 Store[ax][ay-1] = element;
9557 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9558 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9559 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9560 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9565 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9566 Store[ax][ay+1] = element;
9567 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9568 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9569 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9570 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9575 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9576 element == EL_EXPANDABLE_WALL_ANY ||
9577 element == EL_EXPANDABLE_WALL ||
9578 element == EL_BD_EXPANDABLE_WALL)
9582 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9583 Store[ax-1][ay] = element;
9584 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9585 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9586 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9587 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9593 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9594 Store[ax+1][ay] = element;
9595 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9596 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9597 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9598 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9603 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9604 TEST_DrawLevelField(ax, ay);
9606 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9608 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9609 unten_massiv = TRUE;
9610 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9611 links_massiv = TRUE;
9612 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9613 rechts_massiv = TRUE;
9615 if (((oben_massiv && unten_massiv) ||
9616 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9617 element == EL_EXPANDABLE_WALL) &&
9618 ((links_massiv && rechts_massiv) ||
9619 element == EL_EXPANDABLE_WALL_VERTICAL))
9620 Tile[ax][ay] = EL_WALL;
9623 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9626 static void MauerAblegerStahl(int ax, int ay)
9628 int element = Tile[ax][ay];
9629 int graphic = el2img(element);
9630 boolean oben_frei = FALSE, unten_frei = FALSE;
9631 boolean links_frei = FALSE, rechts_frei = FALSE;
9632 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9633 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9634 boolean new_wall = FALSE;
9636 if (IS_ANIMATED(graphic))
9637 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9639 if (!MovDelay[ax][ay]) // start building new wall
9640 MovDelay[ax][ay] = 6;
9642 if (MovDelay[ax][ay]) // wait some time before building new wall
9645 if (MovDelay[ax][ay])
9649 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9651 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9653 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9655 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9658 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9659 element == EL_EXPANDABLE_STEELWALL_ANY)
9663 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9664 Store[ax][ay-1] = element;
9665 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9666 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9667 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9668 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9673 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9674 Store[ax][ay+1] = element;
9675 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9676 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9677 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9678 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9683 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9684 element == EL_EXPANDABLE_STEELWALL_ANY)
9688 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9689 Store[ax-1][ay] = element;
9690 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9691 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9692 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9693 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9699 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9700 Store[ax+1][ay] = element;
9701 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9702 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9703 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9704 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9709 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9711 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9712 unten_massiv = TRUE;
9713 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9714 links_massiv = TRUE;
9715 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9716 rechts_massiv = TRUE;
9718 if (((oben_massiv && unten_massiv) ||
9719 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9720 ((links_massiv && rechts_massiv) ||
9721 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9722 Tile[ax][ay] = EL_STEELWALL;
9725 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9728 static void CheckForDragon(int x, int y)
9731 boolean dragon_found = FALSE;
9732 static int xy[4][2] =
9740 for (i = 0; i < NUM_DIRECTIONS; i++)
9742 for (j = 0; j < 4; j++)
9744 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9746 if (IN_LEV_FIELD(xx, yy) &&
9747 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9749 if (Tile[xx][yy] == EL_DRAGON)
9750 dragon_found = TRUE;
9759 for (i = 0; i < NUM_DIRECTIONS; i++)
9761 for (j = 0; j < 3; j++)
9763 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9765 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9767 Tile[xx][yy] = EL_EMPTY;
9768 TEST_DrawLevelField(xx, yy);
9777 static void InitBuggyBase(int x, int y)
9779 int element = Tile[x][y];
9780 int activating_delay = FRAMES_PER_SECOND / 4;
9783 (element == EL_SP_BUGGY_BASE ?
9784 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9785 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9787 element == EL_SP_BUGGY_BASE_ACTIVE ?
9788 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9791 static void WarnBuggyBase(int x, int y)
9794 static int xy[4][2] =
9802 for (i = 0; i < NUM_DIRECTIONS; i++)
9804 int xx = x + xy[i][0];
9805 int yy = y + xy[i][1];
9807 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9809 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9816 static void InitTrap(int x, int y)
9818 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9821 static void ActivateTrap(int x, int y)
9823 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9826 static void ChangeActiveTrap(int x, int y)
9828 int graphic = IMG_TRAP_ACTIVE;
9830 // if new animation frame was drawn, correct crumbled sand border
9831 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9832 TEST_DrawLevelFieldCrumbled(x, y);
9835 static int getSpecialActionElement(int element, int number, int base_element)
9837 return (element != EL_EMPTY ? element :
9838 number != -1 ? base_element + number - 1 :
9842 static int getModifiedActionNumber(int value_old, int operator, int operand,
9843 int value_min, int value_max)
9845 int value_new = (operator == CA_MODE_SET ? operand :
9846 operator == CA_MODE_ADD ? value_old + operand :
9847 operator == CA_MODE_SUBTRACT ? value_old - operand :
9848 operator == CA_MODE_MULTIPLY ? value_old * operand :
9849 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9850 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9853 return (value_new < value_min ? value_min :
9854 value_new > value_max ? value_max :
9858 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9860 struct ElementInfo *ei = &element_info[element];
9861 struct ElementChangeInfo *change = &ei->change_page[page];
9862 int target_element = change->target_element;
9863 int action_type = change->action_type;
9864 int action_mode = change->action_mode;
9865 int action_arg = change->action_arg;
9866 int action_element = change->action_element;
9869 if (!change->has_action)
9872 // ---------- determine action paramater values -----------------------------
9874 int level_time_value =
9875 (level.time > 0 ? TimeLeft :
9878 int action_arg_element_raw =
9879 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9880 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9881 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9882 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9883 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9884 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9885 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9887 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9889 int action_arg_direction =
9890 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9891 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9892 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9893 change->actual_trigger_side :
9894 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9895 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9898 int action_arg_number_min =
9899 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9902 int action_arg_number_max =
9903 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9904 action_type == CA_SET_LEVEL_GEMS ? 999 :
9905 action_type == CA_SET_LEVEL_TIME ? 9999 :
9906 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9907 action_type == CA_SET_CE_VALUE ? 9999 :
9908 action_type == CA_SET_CE_SCORE ? 9999 :
9911 int action_arg_number_reset =
9912 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9913 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9914 action_type == CA_SET_LEVEL_TIME ? level.time :
9915 action_type == CA_SET_LEVEL_SCORE ? 0 :
9916 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9917 action_type == CA_SET_CE_SCORE ? 0 :
9920 int action_arg_number =
9921 (action_arg <= CA_ARG_MAX ? action_arg :
9922 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9923 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9924 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9925 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9926 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9927 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9928 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9929 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9930 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9931 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9932 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9933 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9934 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9935 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9936 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9937 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9938 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9939 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9940 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9941 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9942 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9945 int action_arg_number_old =
9946 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9947 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9948 action_type == CA_SET_LEVEL_SCORE ? game.score :
9949 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9950 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9953 int action_arg_number_new =
9954 getModifiedActionNumber(action_arg_number_old,
9955 action_mode, action_arg_number,
9956 action_arg_number_min, action_arg_number_max);
9958 int trigger_player_bits =
9959 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9960 change->actual_trigger_player_bits : change->trigger_player);
9962 int action_arg_player_bits =
9963 (action_arg >= CA_ARG_PLAYER_1 &&
9964 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9965 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9966 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9969 // ---------- execute action -----------------------------------------------
9971 switch (action_type)
9978 // ---------- level actions ----------------------------------------------
9980 case CA_RESTART_LEVEL:
9982 game.restart_level = TRUE;
9987 case CA_SHOW_ENVELOPE:
9989 int element = getSpecialActionElement(action_arg_element,
9990 action_arg_number, EL_ENVELOPE_1);
9992 if (IS_ENVELOPE(element))
9993 local_player->show_envelope = element;
9998 case CA_SET_LEVEL_TIME:
10000 if (level.time > 0) // only modify limited time value
10002 TimeLeft = action_arg_number_new;
10004 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10006 DisplayGameControlValues();
10008 if (!TimeLeft && setup.time_limit)
10009 for (i = 0; i < MAX_PLAYERS; i++)
10010 KillPlayer(&stored_player[i]);
10016 case CA_SET_LEVEL_SCORE:
10018 game.score = action_arg_number_new;
10020 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10022 DisplayGameControlValues();
10027 case CA_SET_LEVEL_GEMS:
10029 game.gems_still_needed = action_arg_number_new;
10031 game.snapshot.collected_item = TRUE;
10033 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10035 DisplayGameControlValues();
10040 case CA_SET_LEVEL_WIND:
10042 game.wind_direction = action_arg_direction;
10047 case CA_SET_LEVEL_RANDOM_SEED:
10049 // ensure that setting a new random seed while playing is predictable
10050 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10055 // ---------- player actions ---------------------------------------------
10057 case CA_MOVE_PLAYER:
10058 case CA_MOVE_PLAYER_NEW:
10060 // automatically move to the next field in specified direction
10061 for (i = 0; i < MAX_PLAYERS; i++)
10062 if (trigger_player_bits & (1 << i))
10063 if (action_type == CA_MOVE_PLAYER ||
10064 stored_player[i].MovPos == 0)
10065 stored_player[i].programmed_action = action_arg_direction;
10070 case CA_EXIT_PLAYER:
10072 for (i = 0; i < MAX_PLAYERS; i++)
10073 if (action_arg_player_bits & (1 << i))
10074 ExitPlayer(&stored_player[i]);
10076 if (game.players_still_needed == 0)
10082 case CA_KILL_PLAYER:
10084 for (i = 0; i < MAX_PLAYERS; i++)
10085 if (action_arg_player_bits & (1 << i))
10086 KillPlayer(&stored_player[i]);
10091 case CA_SET_PLAYER_KEYS:
10093 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10094 int element = getSpecialActionElement(action_arg_element,
10095 action_arg_number, EL_KEY_1);
10097 if (IS_KEY(element))
10099 for (i = 0; i < MAX_PLAYERS; i++)
10101 if (trigger_player_bits & (1 << i))
10103 stored_player[i].key[KEY_NR(element)] = key_state;
10105 DrawGameDoorValues();
10113 case CA_SET_PLAYER_SPEED:
10115 for (i = 0; i < MAX_PLAYERS; i++)
10117 if (trigger_player_bits & (1 << i))
10119 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10121 if (action_arg == CA_ARG_SPEED_FASTER &&
10122 stored_player[i].cannot_move)
10124 action_arg_number = STEPSIZE_VERY_SLOW;
10126 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10127 action_arg == CA_ARG_SPEED_FASTER)
10129 action_arg_number = 2;
10130 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10133 else if (action_arg == CA_ARG_NUMBER_RESET)
10135 action_arg_number = level.initial_player_stepsize[i];
10139 getModifiedActionNumber(move_stepsize,
10142 action_arg_number_min,
10143 action_arg_number_max);
10145 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10152 case CA_SET_PLAYER_SHIELD:
10154 for (i = 0; i < MAX_PLAYERS; i++)
10156 if (trigger_player_bits & (1 << i))
10158 if (action_arg == CA_ARG_SHIELD_OFF)
10160 stored_player[i].shield_normal_time_left = 0;
10161 stored_player[i].shield_deadly_time_left = 0;
10163 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10165 stored_player[i].shield_normal_time_left = 999999;
10167 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10169 stored_player[i].shield_normal_time_left = 999999;
10170 stored_player[i].shield_deadly_time_left = 999999;
10178 case CA_SET_PLAYER_GRAVITY:
10180 for (i = 0; i < MAX_PLAYERS; i++)
10182 if (trigger_player_bits & (1 << i))
10184 stored_player[i].gravity =
10185 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10186 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10187 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10188 stored_player[i].gravity);
10195 case CA_SET_PLAYER_ARTWORK:
10197 for (i = 0; i < MAX_PLAYERS; i++)
10199 if (trigger_player_bits & (1 << i))
10201 int artwork_element = action_arg_element;
10203 if (action_arg == CA_ARG_ELEMENT_RESET)
10205 (level.use_artwork_element[i] ? level.artwork_element[i] :
10206 stored_player[i].element_nr);
10208 if (stored_player[i].artwork_element != artwork_element)
10209 stored_player[i].Frame = 0;
10211 stored_player[i].artwork_element = artwork_element;
10213 SetPlayerWaiting(&stored_player[i], FALSE);
10215 // set number of special actions for bored and sleeping animation
10216 stored_player[i].num_special_action_bored =
10217 get_num_special_action(artwork_element,
10218 ACTION_BORING_1, ACTION_BORING_LAST);
10219 stored_player[i].num_special_action_sleeping =
10220 get_num_special_action(artwork_element,
10221 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10228 case CA_SET_PLAYER_INVENTORY:
10230 for (i = 0; i < MAX_PLAYERS; i++)
10232 struct PlayerInfo *player = &stored_player[i];
10235 if (trigger_player_bits & (1 << i))
10237 int inventory_element = action_arg_element;
10239 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10240 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10241 action_arg == CA_ARG_ELEMENT_ACTION)
10243 int element = inventory_element;
10244 int collect_count = element_info[element].collect_count_initial;
10246 if (!IS_CUSTOM_ELEMENT(element))
10249 if (collect_count == 0)
10250 player->inventory_infinite_element = element;
10252 for (k = 0; k < collect_count; k++)
10253 if (player->inventory_size < MAX_INVENTORY_SIZE)
10254 player->inventory_element[player->inventory_size++] =
10257 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10258 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10259 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10261 if (player->inventory_infinite_element != EL_UNDEFINED &&
10262 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10263 action_arg_element_raw))
10264 player->inventory_infinite_element = EL_UNDEFINED;
10266 for (k = 0, j = 0; j < player->inventory_size; j++)
10268 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10269 action_arg_element_raw))
10270 player->inventory_element[k++] = player->inventory_element[j];
10273 player->inventory_size = k;
10275 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10277 if (player->inventory_size > 0)
10279 for (j = 0; j < player->inventory_size - 1; j++)
10280 player->inventory_element[j] = player->inventory_element[j + 1];
10282 player->inventory_size--;
10285 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10287 if (player->inventory_size > 0)
10288 player->inventory_size--;
10290 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10292 player->inventory_infinite_element = EL_UNDEFINED;
10293 player->inventory_size = 0;
10295 else if (action_arg == CA_ARG_INVENTORY_RESET)
10297 player->inventory_infinite_element = EL_UNDEFINED;
10298 player->inventory_size = 0;
10300 if (level.use_initial_inventory[i])
10302 for (j = 0; j < level.initial_inventory_size[i]; j++)
10304 int element = level.initial_inventory_content[i][j];
10305 int collect_count = element_info[element].collect_count_initial;
10307 if (!IS_CUSTOM_ELEMENT(element))
10310 if (collect_count == 0)
10311 player->inventory_infinite_element = element;
10313 for (k = 0; k < collect_count; k++)
10314 if (player->inventory_size < MAX_INVENTORY_SIZE)
10315 player->inventory_element[player->inventory_size++] =
10326 // ---------- CE actions -------------------------------------------------
10328 case CA_SET_CE_VALUE:
10330 int last_ce_value = CustomValue[x][y];
10332 CustomValue[x][y] = action_arg_number_new;
10334 if (CustomValue[x][y] != last_ce_value)
10336 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10337 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10339 if (CustomValue[x][y] == 0)
10341 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10342 ChangeCount[x][y] = 0; // allow at least one more change
10344 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10345 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10352 case CA_SET_CE_SCORE:
10354 int last_ce_score = ei->collect_score;
10356 ei->collect_score = action_arg_number_new;
10358 if (ei->collect_score != last_ce_score)
10360 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10361 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10363 if (ei->collect_score == 0)
10367 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10368 ChangeCount[x][y] = 0; // allow at least one more change
10370 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10371 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10374 This is a very special case that seems to be a mixture between
10375 CheckElementChange() and CheckTriggeredElementChange(): while
10376 the first one only affects single elements that are triggered
10377 directly, the second one affects multiple elements in the playfield
10378 that are triggered indirectly by another element. This is a third
10379 case: Changing the CE score always affects multiple identical CEs,
10380 so every affected CE must be checked, not only the single CE for
10381 which the CE score was changed in the first place (as every instance
10382 of that CE shares the same CE score, and therefore also can change)!
10384 SCAN_PLAYFIELD(xx, yy)
10386 if (Tile[xx][yy] == element)
10387 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10388 CE_SCORE_GETS_ZERO);
10396 case CA_SET_CE_ARTWORK:
10398 int artwork_element = action_arg_element;
10399 boolean reset_frame = FALSE;
10402 if (action_arg == CA_ARG_ELEMENT_RESET)
10403 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10406 if (ei->gfx_element != artwork_element)
10407 reset_frame = TRUE;
10409 ei->gfx_element = artwork_element;
10411 SCAN_PLAYFIELD(xx, yy)
10413 if (Tile[xx][yy] == element)
10417 ResetGfxAnimation(xx, yy);
10418 ResetRandomAnimationValue(xx, yy);
10421 TEST_DrawLevelField(xx, yy);
10428 // ---------- engine actions ---------------------------------------------
10430 case CA_SET_ENGINE_SCAN_MODE:
10432 InitPlayfieldScanMode(action_arg);
10442 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10444 int old_element = Tile[x][y];
10445 int new_element = GetElementFromGroupElement(element);
10446 int previous_move_direction = MovDir[x][y];
10447 int last_ce_value = CustomValue[x][y];
10448 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10449 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10450 boolean add_player_onto_element = (new_element_is_player &&
10451 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10452 IS_WALKABLE(old_element));
10454 if (!add_player_onto_element)
10456 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10457 RemoveMovingField(x, y);
10461 Tile[x][y] = new_element;
10463 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10464 MovDir[x][y] = previous_move_direction;
10466 if (element_info[new_element].use_last_ce_value)
10467 CustomValue[x][y] = last_ce_value;
10469 InitField_WithBug1(x, y, FALSE);
10471 new_element = Tile[x][y]; // element may have changed
10473 ResetGfxAnimation(x, y);
10474 ResetRandomAnimationValue(x, y);
10476 TEST_DrawLevelField(x, y);
10478 if (GFX_CRUMBLED(new_element))
10479 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10482 // check if element under the player changes from accessible to unaccessible
10483 // (needed for special case of dropping element which then changes)
10484 // (must be checked after creating new element for walkable group elements)
10485 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10486 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10493 // "ChangeCount" not set yet to allow "entered by player" change one time
10494 if (new_element_is_player)
10495 RelocatePlayer(x, y, new_element);
10498 ChangeCount[x][y]++; // count number of changes in the same frame
10500 TestIfBadThingTouchesPlayer(x, y);
10501 TestIfPlayerTouchesCustomElement(x, y);
10502 TestIfElementTouchesCustomElement(x, y);
10505 static void CreateField(int x, int y, int element)
10507 CreateFieldExt(x, y, element, FALSE);
10510 static void CreateElementFromChange(int x, int y, int element)
10512 element = GET_VALID_RUNTIME_ELEMENT(element);
10514 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10516 int old_element = Tile[x][y];
10518 // prevent changed element from moving in same engine frame
10519 // unless both old and new element can either fall or move
10520 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10521 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10525 CreateFieldExt(x, y, element, TRUE);
10528 static boolean ChangeElement(int x, int y, int element, int page)
10530 struct ElementInfo *ei = &element_info[element];
10531 struct ElementChangeInfo *change = &ei->change_page[page];
10532 int ce_value = CustomValue[x][y];
10533 int ce_score = ei->collect_score;
10534 int target_element;
10535 int old_element = Tile[x][y];
10537 // always use default change event to prevent running into a loop
10538 if (ChangeEvent[x][y] == -1)
10539 ChangeEvent[x][y] = CE_DELAY;
10541 if (ChangeEvent[x][y] == CE_DELAY)
10543 // reset actual trigger element, trigger player and action element
10544 change->actual_trigger_element = EL_EMPTY;
10545 change->actual_trigger_player = EL_EMPTY;
10546 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10547 change->actual_trigger_side = CH_SIDE_NONE;
10548 change->actual_trigger_ce_value = 0;
10549 change->actual_trigger_ce_score = 0;
10552 // do not change elements more than a specified maximum number of changes
10553 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10556 ChangeCount[x][y]++; // count number of changes in the same frame
10558 if (change->explode)
10565 if (change->use_target_content)
10567 boolean complete_replace = TRUE;
10568 boolean can_replace[3][3];
10571 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10574 boolean is_walkable;
10575 boolean is_diggable;
10576 boolean is_collectible;
10577 boolean is_removable;
10578 boolean is_destructible;
10579 int ex = x + xx - 1;
10580 int ey = y + yy - 1;
10581 int content_element = change->target_content.e[xx][yy];
10584 can_replace[xx][yy] = TRUE;
10586 if (ex == x && ey == y) // do not check changing element itself
10589 if (content_element == EL_EMPTY_SPACE)
10591 can_replace[xx][yy] = FALSE; // do not replace border with space
10596 if (!IN_LEV_FIELD(ex, ey))
10598 can_replace[xx][yy] = FALSE;
10599 complete_replace = FALSE;
10606 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10607 e = MovingOrBlocked2Element(ex, ey);
10609 is_empty = (IS_FREE(ex, ey) ||
10610 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10612 is_walkable = (is_empty || IS_WALKABLE(e));
10613 is_diggable = (is_empty || IS_DIGGABLE(e));
10614 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10615 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10616 is_removable = (is_diggable || is_collectible);
10618 can_replace[xx][yy] =
10619 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10620 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10621 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10622 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10623 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10624 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10625 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10627 if (!can_replace[xx][yy])
10628 complete_replace = FALSE;
10631 if (!change->only_if_complete || complete_replace)
10633 boolean something_has_changed = FALSE;
10635 if (change->only_if_complete && change->use_random_replace &&
10636 RND(100) < change->random_percentage)
10639 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10641 int ex = x + xx - 1;
10642 int ey = y + yy - 1;
10643 int content_element;
10645 if (can_replace[xx][yy] && (!change->use_random_replace ||
10646 RND(100) < change->random_percentage))
10648 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10649 RemoveMovingField(ex, ey);
10651 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10653 content_element = change->target_content.e[xx][yy];
10654 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10655 ce_value, ce_score);
10657 CreateElementFromChange(ex, ey, target_element);
10659 something_has_changed = TRUE;
10661 // for symmetry reasons, freeze newly created border elements
10662 if (ex != x || ey != y)
10663 Stop[ex][ey] = TRUE; // no more moving in this frame
10667 if (something_has_changed)
10669 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10670 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10676 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10677 ce_value, ce_score);
10679 if (element == EL_DIAGONAL_GROWING ||
10680 element == EL_DIAGONAL_SHRINKING)
10682 target_element = Store[x][y];
10684 Store[x][y] = EL_EMPTY;
10687 CreateElementFromChange(x, y, target_element);
10689 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10690 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10693 // this uses direct change before indirect change
10694 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10699 static void HandleElementChange(int x, int y, int page)
10701 int element = MovingOrBlocked2Element(x, y);
10702 struct ElementInfo *ei = &element_info[element];
10703 struct ElementChangeInfo *change = &ei->change_page[page];
10704 boolean handle_action_before_change = FALSE;
10707 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10708 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10710 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10711 x, y, element, element_info[element].token_name);
10712 Debug("game:playing:HandleElementChange", "This should never happen!");
10716 // this can happen with classic bombs on walkable, changing elements
10717 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10722 if (ChangeDelay[x][y] == 0) // initialize element change
10724 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10726 if (change->can_change)
10728 // !!! not clear why graphic animation should be reset at all here !!!
10729 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10730 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10733 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10735 When using an animation frame delay of 1 (this only happens with
10736 "sp_zonk.moving.left/right" in the classic graphics), the default
10737 (non-moving) animation shows wrong animation frames (while the
10738 moving animation, like "sp_zonk.moving.left/right", is correct,
10739 so this graphical bug never shows up with the classic graphics).
10740 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10741 be drawn instead of the correct frames 0,1,2,3. This is caused by
10742 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10743 an element change: First when the change delay ("ChangeDelay[][]")
10744 counter has reached zero after decrementing, then a second time in
10745 the next frame (after "GfxFrame[][]" was already incremented) when
10746 "ChangeDelay[][]" is reset to the initial delay value again.
10748 This causes frame 0 to be drawn twice, while the last frame won't
10749 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10751 As some animations may already be cleverly designed around this bug
10752 (at least the "Snake Bite" snake tail animation does this), it cannot
10753 simply be fixed here without breaking such existing animations.
10754 Unfortunately, it cannot easily be detected if a graphics set was
10755 designed "before" or "after" the bug was fixed. As a workaround,
10756 a new graphics set option "game.graphics_engine_version" was added
10757 to be able to specify the game's major release version for which the
10758 graphics set was designed, which can then be used to decide if the
10759 bugfix should be used (version 4 and above) or not (version 3 or
10760 below, or if no version was specified at all, as with old sets).
10762 (The wrong/fixed animation frames can be tested with the test level set
10763 "test_gfxframe" and level "000", which contains a specially prepared
10764 custom element at level position (x/y) == (11/9) which uses the zonk
10765 animation mentioned above. Using "game.graphics_engine_version: 4"
10766 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10767 This can also be seen from the debug output for this test element.)
10770 // when a custom element is about to change (for example by change delay),
10771 // do not reset graphic animation when the custom element is moving
10772 if (game.graphics_engine_version < 4 &&
10775 ResetGfxAnimation(x, y);
10776 ResetRandomAnimationValue(x, y);
10779 if (change->pre_change_function)
10780 change->pre_change_function(x, y);
10784 ChangeDelay[x][y]--;
10786 if (ChangeDelay[x][y] != 0) // continue element change
10788 if (change->can_change)
10790 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10792 if (IS_ANIMATED(graphic))
10793 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10795 if (change->change_function)
10796 change->change_function(x, y);
10799 else // finish element change
10801 if (ChangePage[x][y] != -1) // remember page from delayed change
10803 page = ChangePage[x][y];
10804 ChangePage[x][y] = -1;
10806 change = &ei->change_page[page];
10809 if (IS_MOVING(x, y)) // never change a running system ;-)
10811 ChangeDelay[x][y] = 1; // try change after next move step
10812 ChangePage[x][y] = page; // remember page to use for change
10817 // special case: set new level random seed before changing element
10818 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10819 handle_action_before_change = TRUE;
10821 if (change->has_action && handle_action_before_change)
10822 ExecuteCustomElementAction(x, y, element, page);
10824 if (change->can_change)
10826 if (ChangeElement(x, y, element, page))
10828 if (change->post_change_function)
10829 change->post_change_function(x, y);
10833 if (change->has_action && !handle_action_before_change)
10834 ExecuteCustomElementAction(x, y, element, page);
10838 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10839 int trigger_element,
10841 int trigger_player,
10845 boolean change_done_any = FALSE;
10846 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10849 if (!(trigger_events[trigger_element][trigger_event]))
10852 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10854 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10856 int element = EL_CUSTOM_START + i;
10857 boolean change_done = FALSE;
10860 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10861 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10864 for (p = 0; p < element_info[element].num_change_pages; p++)
10866 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10868 if (change->can_change_or_has_action &&
10869 change->has_event[trigger_event] &&
10870 change->trigger_side & trigger_side &&
10871 change->trigger_player & trigger_player &&
10872 change->trigger_page & trigger_page_bits &&
10873 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10875 change->actual_trigger_element = trigger_element;
10876 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10877 change->actual_trigger_player_bits = trigger_player;
10878 change->actual_trigger_side = trigger_side;
10879 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10880 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10882 if ((change->can_change && !change_done) || change->has_action)
10886 SCAN_PLAYFIELD(x, y)
10888 if (Tile[x][y] == element)
10890 if (change->can_change && !change_done)
10892 // if element already changed in this frame, not only prevent
10893 // another element change (checked in ChangeElement()), but
10894 // also prevent additional element actions for this element
10896 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10897 !level.use_action_after_change_bug)
10900 ChangeDelay[x][y] = 1;
10901 ChangeEvent[x][y] = trigger_event;
10903 HandleElementChange(x, y, p);
10905 else if (change->has_action)
10907 // if element already changed in this frame, not only prevent
10908 // another element change (checked in ChangeElement()), but
10909 // also prevent additional element actions for this element
10911 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10912 !level.use_action_after_change_bug)
10915 ExecuteCustomElementAction(x, y, element, p);
10916 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10921 if (change->can_change)
10923 change_done = TRUE;
10924 change_done_any = TRUE;
10931 RECURSION_LOOP_DETECTION_END();
10933 return change_done_any;
10936 static boolean CheckElementChangeExt(int x, int y,
10938 int trigger_element,
10940 int trigger_player,
10943 boolean change_done = FALSE;
10946 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10947 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10950 if (Tile[x][y] == EL_BLOCKED)
10952 Blocked2Moving(x, y, &x, &y);
10953 element = Tile[x][y];
10956 // check if element has already changed or is about to change after moving
10957 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10958 Tile[x][y] != element) ||
10960 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10961 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10962 ChangePage[x][y] != -1)))
10965 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10967 for (p = 0; p < element_info[element].num_change_pages; p++)
10969 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10971 /* check trigger element for all events where the element that is checked
10972 for changing interacts with a directly adjacent element -- this is
10973 different to element changes that affect other elements to change on the
10974 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10975 boolean check_trigger_element =
10976 (trigger_event == CE_TOUCHING_X ||
10977 trigger_event == CE_HITTING_X ||
10978 trigger_event == CE_HIT_BY_X ||
10979 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10981 if (change->can_change_or_has_action &&
10982 change->has_event[trigger_event] &&
10983 change->trigger_side & trigger_side &&
10984 change->trigger_player & trigger_player &&
10985 (!check_trigger_element ||
10986 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10988 change->actual_trigger_element = trigger_element;
10989 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10990 change->actual_trigger_player_bits = trigger_player;
10991 change->actual_trigger_side = trigger_side;
10992 change->actual_trigger_ce_value = CustomValue[x][y];
10993 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10995 // special case: trigger element not at (x,y) position for some events
10996 if (check_trigger_element)
11008 { 0, 0 }, { 0, 0 }, { 0, 0 },
11012 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11013 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11015 change->actual_trigger_ce_value = CustomValue[xx][yy];
11016 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11019 if (change->can_change && !change_done)
11021 ChangeDelay[x][y] = 1;
11022 ChangeEvent[x][y] = trigger_event;
11024 HandleElementChange(x, y, p);
11026 change_done = TRUE;
11028 else if (change->has_action)
11030 ExecuteCustomElementAction(x, y, element, p);
11031 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11036 RECURSION_LOOP_DETECTION_END();
11038 return change_done;
11041 static void PlayPlayerSound(struct PlayerInfo *player)
11043 int jx = player->jx, jy = player->jy;
11044 int sound_element = player->artwork_element;
11045 int last_action = player->last_action_waiting;
11046 int action = player->action_waiting;
11048 if (player->is_waiting)
11050 if (action != last_action)
11051 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11053 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11057 if (action != last_action)
11058 StopSound(element_info[sound_element].sound[last_action]);
11060 if (last_action == ACTION_SLEEPING)
11061 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11065 static void PlayAllPlayersSound(void)
11069 for (i = 0; i < MAX_PLAYERS; i++)
11070 if (stored_player[i].active)
11071 PlayPlayerSound(&stored_player[i]);
11074 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11076 boolean last_waiting = player->is_waiting;
11077 int move_dir = player->MovDir;
11079 player->dir_waiting = move_dir;
11080 player->last_action_waiting = player->action_waiting;
11084 if (!last_waiting) // not waiting -> waiting
11086 player->is_waiting = TRUE;
11088 player->frame_counter_bored =
11090 game.player_boring_delay_fixed +
11091 GetSimpleRandom(game.player_boring_delay_random);
11092 player->frame_counter_sleeping =
11094 game.player_sleeping_delay_fixed +
11095 GetSimpleRandom(game.player_sleeping_delay_random);
11097 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11100 if (game.player_sleeping_delay_fixed +
11101 game.player_sleeping_delay_random > 0 &&
11102 player->anim_delay_counter == 0 &&
11103 player->post_delay_counter == 0 &&
11104 FrameCounter >= player->frame_counter_sleeping)
11105 player->is_sleeping = TRUE;
11106 else if (game.player_boring_delay_fixed +
11107 game.player_boring_delay_random > 0 &&
11108 FrameCounter >= player->frame_counter_bored)
11109 player->is_bored = TRUE;
11111 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11112 player->is_bored ? ACTION_BORING :
11115 if (player->is_sleeping && player->use_murphy)
11117 // special case for sleeping Murphy when leaning against non-free tile
11119 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11120 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11121 !IS_MOVING(player->jx - 1, player->jy)))
11122 move_dir = MV_LEFT;
11123 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11124 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11125 !IS_MOVING(player->jx + 1, player->jy)))
11126 move_dir = MV_RIGHT;
11128 player->is_sleeping = FALSE;
11130 player->dir_waiting = move_dir;
11133 if (player->is_sleeping)
11135 if (player->num_special_action_sleeping > 0)
11137 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11139 int last_special_action = player->special_action_sleeping;
11140 int num_special_action = player->num_special_action_sleeping;
11141 int special_action =
11142 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11143 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11144 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11145 last_special_action + 1 : ACTION_SLEEPING);
11146 int special_graphic =
11147 el_act_dir2img(player->artwork_element, special_action, move_dir);
11149 player->anim_delay_counter =
11150 graphic_info[special_graphic].anim_delay_fixed +
11151 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11152 player->post_delay_counter =
11153 graphic_info[special_graphic].post_delay_fixed +
11154 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11156 player->special_action_sleeping = special_action;
11159 if (player->anim_delay_counter > 0)
11161 player->action_waiting = player->special_action_sleeping;
11162 player->anim_delay_counter--;
11164 else if (player->post_delay_counter > 0)
11166 player->post_delay_counter--;
11170 else if (player->is_bored)
11172 if (player->num_special_action_bored > 0)
11174 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11176 int special_action =
11177 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11178 int special_graphic =
11179 el_act_dir2img(player->artwork_element, special_action, move_dir);
11181 player->anim_delay_counter =
11182 graphic_info[special_graphic].anim_delay_fixed +
11183 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11184 player->post_delay_counter =
11185 graphic_info[special_graphic].post_delay_fixed +
11186 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11188 player->special_action_bored = special_action;
11191 if (player->anim_delay_counter > 0)
11193 player->action_waiting = player->special_action_bored;
11194 player->anim_delay_counter--;
11196 else if (player->post_delay_counter > 0)
11198 player->post_delay_counter--;
11203 else if (last_waiting) // waiting -> not waiting
11205 player->is_waiting = FALSE;
11206 player->is_bored = FALSE;
11207 player->is_sleeping = FALSE;
11209 player->frame_counter_bored = -1;
11210 player->frame_counter_sleeping = -1;
11212 player->anim_delay_counter = 0;
11213 player->post_delay_counter = 0;
11215 player->dir_waiting = player->MovDir;
11216 player->action_waiting = ACTION_DEFAULT;
11218 player->special_action_bored = ACTION_DEFAULT;
11219 player->special_action_sleeping = ACTION_DEFAULT;
11223 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11225 if ((!player->is_moving && player->was_moving) ||
11226 (player->MovPos == 0 && player->was_moving) ||
11227 (player->is_snapping && !player->was_snapping) ||
11228 (player->is_dropping && !player->was_dropping))
11230 if (!CheckSaveEngineSnapshotToList())
11233 player->was_moving = FALSE;
11234 player->was_snapping = TRUE;
11235 player->was_dropping = TRUE;
11239 if (player->is_moving)
11240 player->was_moving = TRUE;
11242 if (!player->is_snapping)
11243 player->was_snapping = FALSE;
11245 if (!player->is_dropping)
11246 player->was_dropping = FALSE;
11249 static struct MouseActionInfo mouse_action_last = { 0 };
11250 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11251 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11254 CheckSaveEngineSnapshotToList();
11256 mouse_action_last = mouse_action;
11259 static void CheckSingleStepMode(struct PlayerInfo *player)
11261 if (tape.single_step && tape.recording && !tape.pausing)
11263 /* as it is called "single step mode", just return to pause mode when the
11264 player stopped moving after one tile (or never starts moving at all) */
11265 if (!player->is_moving &&
11266 !player->is_pushing &&
11267 !player->is_dropping_pressed &&
11268 !player->effective_mouse_action.button)
11269 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11272 CheckSaveEngineSnapshot(player);
11275 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11277 int left = player_action & JOY_LEFT;
11278 int right = player_action & JOY_RIGHT;
11279 int up = player_action & JOY_UP;
11280 int down = player_action & JOY_DOWN;
11281 int button1 = player_action & JOY_BUTTON_1;
11282 int button2 = player_action & JOY_BUTTON_2;
11283 int dx = (left ? -1 : right ? 1 : 0);
11284 int dy = (up ? -1 : down ? 1 : 0);
11286 if (!player->active || tape.pausing)
11292 SnapField(player, dx, dy);
11296 DropElement(player);
11298 MovePlayer(player, dx, dy);
11301 CheckSingleStepMode(player);
11303 SetPlayerWaiting(player, FALSE);
11305 return player_action;
11309 // no actions for this player (no input at player's configured device)
11311 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11312 SnapField(player, 0, 0);
11313 CheckGravityMovementWhenNotMoving(player);
11315 if (player->MovPos == 0)
11316 SetPlayerWaiting(player, TRUE);
11318 if (player->MovPos == 0) // needed for tape.playing
11319 player->is_moving = FALSE;
11321 player->is_dropping = FALSE;
11322 player->is_dropping_pressed = FALSE;
11323 player->drop_pressed_delay = 0;
11325 CheckSingleStepMode(player);
11331 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11334 if (!tape.use_mouse_actions)
11337 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11338 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11339 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11342 static void SetTapeActionFromMouseAction(byte *tape_action,
11343 struct MouseActionInfo *mouse_action)
11345 if (!tape.use_mouse_actions)
11348 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11349 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11350 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11353 static void CheckLevelSolved(void)
11355 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11357 if (game_em.level_solved &&
11358 !game_em.game_over) // game won
11362 game_em.game_over = TRUE;
11364 game.all_players_gone = TRUE;
11367 if (game_em.game_over) // game lost
11368 game.all_players_gone = TRUE;
11370 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11372 if (game_sp.level_solved &&
11373 !game_sp.game_over) // game won
11377 game_sp.game_over = TRUE;
11379 game.all_players_gone = TRUE;
11382 if (game_sp.game_over) // game lost
11383 game.all_players_gone = TRUE;
11385 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11387 if (game_mm.level_solved &&
11388 !game_mm.game_over) // game won
11392 game_mm.game_over = TRUE;
11394 game.all_players_gone = TRUE;
11397 if (game_mm.game_over) // game lost
11398 game.all_players_gone = TRUE;
11402 static void CheckLevelTime(void)
11406 if (TimeFrames >= FRAMES_PER_SECOND)
11411 for (i = 0; i < MAX_PLAYERS; i++)
11413 struct PlayerInfo *player = &stored_player[i];
11415 if (SHIELD_ON(player))
11417 player->shield_normal_time_left--;
11419 if (player->shield_deadly_time_left > 0)
11420 player->shield_deadly_time_left--;
11424 if (!game.LevelSolved && !level.use_step_counter)
11432 if (TimeLeft <= 10 && setup.time_limit)
11433 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11435 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11436 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11438 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11440 if (!TimeLeft && setup.time_limit)
11442 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11443 game_em.lev->killed_out_of_time = TRUE;
11445 for (i = 0; i < MAX_PLAYERS; i++)
11446 KillPlayer(&stored_player[i]);
11449 else if (game.no_time_limit && !game.all_players_gone)
11451 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11454 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11457 if (tape.recording || tape.playing)
11458 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11461 if (tape.recording || tape.playing)
11462 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11464 UpdateAndDisplayGameControlValues();
11467 void AdvanceFrameAndPlayerCounters(int player_nr)
11471 // advance frame counters (global frame counter and time frame counter)
11475 // advance player counters (counters for move delay, move animation etc.)
11476 for (i = 0; i < MAX_PLAYERS; i++)
11478 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11479 int move_delay_value = stored_player[i].move_delay_value;
11480 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11482 if (!advance_player_counters) // not all players may be affected
11485 if (move_frames == 0) // less than one move per game frame
11487 int stepsize = TILEX / move_delay_value;
11488 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11489 int count = (stored_player[i].is_moving ?
11490 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11492 if (count % delay == 0)
11496 stored_player[i].Frame += move_frames;
11498 if (stored_player[i].MovPos != 0)
11499 stored_player[i].StepFrame += move_frames;
11501 if (stored_player[i].move_delay > 0)
11502 stored_player[i].move_delay--;
11504 // due to bugs in previous versions, counter must count up, not down
11505 if (stored_player[i].push_delay != -1)
11506 stored_player[i].push_delay++;
11508 if (stored_player[i].drop_delay > 0)
11509 stored_player[i].drop_delay--;
11511 if (stored_player[i].is_dropping_pressed)
11512 stored_player[i].drop_pressed_delay++;
11516 void StartGameActions(boolean init_network_game, boolean record_tape,
11519 unsigned int new_random_seed = InitRND(random_seed);
11522 TapeStartRecording(new_random_seed);
11524 if (init_network_game)
11526 SendToServer_LevelFile();
11527 SendToServer_StartPlaying();
11535 static void GameActionsExt(void)
11538 static unsigned int game_frame_delay = 0;
11540 unsigned int game_frame_delay_value;
11541 byte *recorded_player_action;
11542 byte summarized_player_action = 0;
11543 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11546 // detect endless loops, caused by custom element programming
11547 if (recursion_loop_detected && recursion_loop_depth == 0)
11549 char *message = getStringCat3("Internal Error! Element ",
11550 EL_NAME(recursion_loop_element),
11551 " caused endless loop! Quit the game?");
11553 Warn("element '%s' caused endless loop in game engine",
11554 EL_NAME(recursion_loop_element));
11556 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11558 recursion_loop_detected = FALSE; // if game should be continued
11565 if (game.restart_level)
11566 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11568 CheckLevelSolved();
11570 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11573 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11576 if (game_status != GAME_MODE_PLAYING) // status might have changed
11579 game_frame_delay_value =
11580 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11582 if (tape.playing && tape.warp_forward && !tape.pausing)
11583 game_frame_delay_value = 0;
11585 SetVideoFrameDelay(game_frame_delay_value);
11587 // (de)activate virtual buttons depending on current game status
11588 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11590 if (game.all_players_gone) // if no players there to be controlled anymore
11591 SetOverlayActive(FALSE);
11592 else if (!tape.playing) // if game continues after tape stopped playing
11593 SetOverlayActive(TRUE);
11598 // ---------- main game synchronization point ----------
11600 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11602 Debug("game:playing:skip", "skip == %d", skip);
11605 // ---------- main game synchronization point ----------
11607 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11611 if (network_playing && !network_player_action_received)
11613 // try to get network player actions in time
11615 // last chance to get network player actions without main loop delay
11616 HandleNetworking();
11618 // game was quit by network peer
11619 if (game_status != GAME_MODE_PLAYING)
11622 // check if network player actions still missing and game still running
11623 if (!network_player_action_received && !checkGameEnded())
11624 return; // failed to get network player actions in time
11626 // do not yet reset "network_player_action_received" (for tape.pausing)
11632 // at this point we know that we really continue executing the game
11634 network_player_action_received = FALSE;
11636 // when playing tape, read previously recorded player input from tape data
11637 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11639 local_player->effective_mouse_action = local_player->mouse_action;
11641 if (recorded_player_action != NULL)
11642 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11643 recorded_player_action);
11645 // TapePlayAction() may return NULL when toggling to "pause before death"
11649 if (tape.set_centered_player)
11651 game.centered_player_nr_next = tape.centered_player_nr_next;
11652 game.set_centered_player = TRUE;
11655 for (i = 0; i < MAX_PLAYERS; i++)
11657 summarized_player_action |= stored_player[i].action;
11659 if (!network_playing && (game.team_mode || tape.playing))
11660 stored_player[i].effective_action = stored_player[i].action;
11663 if (network_playing && !checkGameEnded())
11664 SendToServer_MovePlayer(summarized_player_action);
11666 // summarize all actions at local players mapped input device position
11667 // (this allows using different input devices in single player mode)
11668 if (!network.enabled && !game.team_mode)
11669 stored_player[map_player_action[local_player->index_nr]].effective_action =
11670 summarized_player_action;
11672 // summarize all actions at centered player in local team mode
11673 if (tape.recording &&
11674 setup.team_mode && !network.enabled &&
11675 setup.input_on_focus &&
11676 game.centered_player_nr != -1)
11678 for (i = 0; i < MAX_PLAYERS; i++)
11679 stored_player[map_player_action[i]].effective_action =
11680 (i == game.centered_player_nr ? summarized_player_action : 0);
11683 if (recorded_player_action != NULL)
11684 for (i = 0; i < MAX_PLAYERS; i++)
11685 stored_player[i].effective_action = recorded_player_action[i];
11687 for (i = 0; i < MAX_PLAYERS; i++)
11689 tape_action[i] = stored_player[i].effective_action;
11691 /* (this may happen in the RND game engine if a player was not present on
11692 the playfield on level start, but appeared later from a custom element */
11693 if (setup.team_mode &&
11696 !tape.player_participates[i])
11697 tape.player_participates[i] = TRUE;
11700 SetTapeActionFromMouseAction(tape_action,
11701 &local_player->effective_mouse_action);
11703 // only record actions from input devices, but not programmed actions
11704 if (tape.recording)
11705 TapeRecordAction(tape_action);
11707 // remember if game was played (especially after tape stopped playing)
11708 if (!tape.playing && summarized_player_action)
11709 game.GamePlayed = TRUE;
11711 #if USE_NEW_PLAYER_ASSIGNMENTS
11712 // !!! also map player actions in single player mode !!!
11713 // if (game.team_mode)
11716 byte mapped_action[MAX_PLAYERS];
11718 #if DEBUG_PLAYER_ACTIONS
11719 for (i = 0; i < MAX_PLAYERS; i++)
11720 DebugContinued("", "%d, ", stored_player[i].effective_action);
11723 for (i = 0; i < MAX_PLAYERS; i++)
11724 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11726 for (i = 0; i < MAX_PLAYERS; i++)
11727 stored_player[i].effective_action = mapped_action[i];
11729 #if DEBUG_PLAYER_ACTIONS
11730 DebugContinued("", "=> ");
11731 for (i = 0; i < MAX_PLAYERS; i++)
11732 DebugContinued("", "%d, ", stored_player[i].effective_action);
11733 DebugContinued("game:playing:player", "\n");
11736 #if DEBUG_PLAYER_ACTIONS
11739 for (i = 0; i < MAX_PLAYERS; i++)
11740 DebugContinued("", "%d, ", stored_player[i].effective_action);
11741 DebugContinued("game:playing:player", "\n");
11746 for (i = 0; i < MAX_PLAYERS; i++)
11748 // allow engine snapshot in case of changed movement attempt
11749 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11750 (stored_player[i].effective_action & KEY_MOTION))
11751 game.snapshot.changed_action = TRUE;
11753 // allow engine snapshot in case of snapping/dropping attempt
11754 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11755 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11756 game.snapshot.changed_action = TRUE;
11758 game.snapshot.last_action[i] = stored_player[i].effective_action;
11761 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11763 GameActions_EM_Main();
11765 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11767 GameActions_SP_Main();
11769 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11771 GameActions_MM_Main();
11775 GameActions_RND_Main();
11778 BlitScreenToBitmap(backbuffer);
11780 CheckLevelSolved();
11783 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11785 if (global.show_frames_per_second)
11787 static unsigned int fps_counter = 0;
11788 static int fps_frames = 0;
11789 unsigned int fps_delay_ms = Counter() - fps_counter;
11793 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11795 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11798 fps_counter = Counter();
11800 // always draw FPS to screen after FPS value was updated
11801 redraw_mask |= REDRAW_FPS;
11804 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11805 if (GetDrawDeactivationMask() == REDRAW_NONE)
11806 redraw_mask |= REDRAW_FPS;
11810 static void GameActions_CheckSaveEngineSnapshot(void)
11812 if (!game.snapshot.save_snapshot)
11815 // clear flag for saving snapshot _before_ saving snapshot
11816 game.snapshot.save_snapshot = FALSE;
11818 SaveEngineSnapshotToList();
11821 void GameActions(void)
11825 GameActions_CheckSaveEngineSnapshot();
11828 void GameActions_EM_Main(void)
11830 byte effective_action[MAX_PLAYERS];
11831 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11834 for (i = 0; i < MAX_PLAYERS; i++)
11835 effective_action[i] = stored_player[i].effective_action;
11837 GameActions_EM(effective_action, warp_mode);
11840 void GameActions_SP_Main(void)
11842 byte effective_action[MAX_PLAYERS];
11843 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11846 for (i = 0; i < MAX_PLAYERS; i++)
11847 effective_action[i] = stored_player[i].effective_action;
11849 GameActions_SP(effective_action, warp_mode);
11851 for (i = 0; i < MAX_PLAYERS; i++)
11853 if (stored_player[i].force_dropping)
11854 stored_player[i].action |= KEY_BUTTON_DROP;
11856 stored_player[i].force_dropping = FALSE;
11860 void GameActions_MM_Main(void)
11862 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11864 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11867 void GameActions_RND_Main(void)
11872 void GameActions_RND(void)
11874 static struct MouseActionInfo mouse_action_last = { 0 };
11875 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11876 int magic_wall_x = 0, magic_wall_y = 0;
11877 int i, x, y, element, graphic, last_gfx_frame;
11879 InitPlayfieldScanModeVars();
11881 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11883 SCAN_PLAYFIELD(x, y)
11885 ChangeCount[x][y] = 0;
11886 ChangeEvent[x][y] = -1;
11890 if (game.set_centered_player)
11892 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11894 // switching to "all players" only possible if all players fit to screen
11895 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11897 game.centered_player_nr_next = game.centered_player_nr;
11898 game.set_centered_player = FALSE;
11901 // do not switch focus to non-existing (or non-active) player
11902 if (game.centered_player_nr_next >= 0 &&
11903 !stored_player[game.centered_player_nr_next].active)
11905 game.centered_player_nr_next = game.centered_player_nr;
11906 game.set_centered_player = FALSE;
11910 if (game.set_centered_player &&
11911 ScreenMovPos == 0) // screen currently aligned at tile position
11915 if (game.centered_player_nr_next == -1)
11917 setScreenCenteredToAllPlayers(&sx, &sy);
11921 sx = stored_player[game.centered_player_nr_next].jx;
11922 sy = stored_player[game.centered_player_nr_next].jy;
11925 game.centered_player_nr = game.centered_player_nr_next;
11926 game.set_centered_player = FALSE;
11928 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11929 DrawGameDoorValues();
11932 for (i = 0; i < MAX_PLAYERS; i++)
11934 int actual_player_action = stored_player[i].effective_action;
11937 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11938 - rnd_equinox_tetrachloride 048
11939 - rnd_equinox_tetrachloride_ii 096
11940 - rnd_emanuel_schmieg 002
11941 - doctor_sloan_ww 001, 020
11943 if (stored_player[i].MovPos == 0)
11944 CheckGravityMovement(&stored_player[i]);
11947 // overwrite programmed action with tape action
11948 if (stored_player[i].programmed_action)
11949 actual_player_action = stored_player[i].programmed_action;
11951 PlayerActions(&stored_player[i], actual_player_action);
11953 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11956 ScrollScreen(NULL, SCROLL_GO_ON);
11958 /* for backwards compatibility, the following code emulates a fixed bug that
11959 occured when pushing elements (causing elements that just made their last
11960 pushing step to already (if possible) make their first falling step in the
11961 same game frame, which is bad); this code is also needed to use the famous
11962 "spring push bug" which is used in older levels and might be wanted to be
11963 used also in newer levels, but in this case the buggy pushing code is only
11964 affecting the "spring" element and no other elements */
11966 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11968 for (i = 0; i < MAX_PLAYERS; i++)
11970 struct PlayerInfo *player = &stored_player[i];
11971 int x = player->jx;
11972 int y = player->jy;
11974 if (player->active && player->is_pushing && player->is_moving &&
11976 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11977 Tile[x][y] == EL_SPRING))
11979 ContinueMoving(x, y);
11981 // continue moving after pushing (this is actually a bug)
11982 if (!IS_MOVING(x, y))
11983 Stop[x][y] = FALSE;
11988 SCAN_PLAYFIELD(x, y)
11990 Last[x][y] = Tile[x][y];
11992 ChangeCount[x][y] = 0;
11993 ChangeEvent[x][y] = -1;
11995 // this must be handled before main playfield loop
11996 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11999 if (MovDelay[x][y] <= 0)
12003 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12006 if (MovDelay[x][y] <= 0)
12009 TEST_DrawLevelField(x, y);
12011 TestIfElementTouchesCustomElement(x, y); // for empty space
12016 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12018 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12020 Debug("game:playing:GameActions_RND", "This should never happen!");
12022 ChangePage[x][y] = -1;
12026 Stop[x][y] = FALSE;
12027 if (WasJustMoving[x][y] > 0)
12028 WasJustMoving[x][y]--;
12029 if (WasJustFalling[x][y] > 0)
12030 WasJustFalling[x][y]--;
12031 if (CheckCollision[x][y] > 0)
12032 CheckCollision[x][y]--;
12033 if (CheckImpact[x][y] > 0)
12034 CheckImpact[x][y]--;
12038 /* reset finished pushing action (not done in ContinueMoving() to allow
12039 continuous pushing animation for elements with zero push delay) */
12040 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12042 ResetGfxAnimation(x, y);
12043 TEST_DrawLevelField(x, y);
12047 if (IS_BLOCKED(x, y))
12051 Blocked2Moving(x, y, &oldx, &oldy);
12052 if (!IS_MOVING(oldx, oldy))
12054 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12055 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12056 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12057 Debug("game:playing:GameActions_RND", "This should never happen!");
12063 if (mouse_action.button)
12065 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12067 x = mouse_action.lx;
12068 y = mouse_action.ly;
12069 element = Tile[x][y];
12073 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12074 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12077 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12078 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12081 SCAN_PLAYFIELD(x, y)
12083 element = Tile[x][y];
12084 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12085 last_gfx_frame = GfxFrame[x][y];
12087 ResetGfxFrame(x, y);
12089 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12090 DrawLevelGraphicAnimation(x, y, graphic);
12092 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12093 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12094 ResetRandomAnimationValue(x, y);
12096 SetRandomAnimationValue(x, y);
12098 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12100 if (IS_INACTIVE(element))
12102 if (IS_ANIMATED(graphic))
12103 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12108 // this may take place after moving, so 'element' may have changed
12109 if (IS_CHANGING(x, y) &&
12110 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12112 int page = element_info[element].event_page_nr[CE_DELAY];
12114 HandleElementChange(x, y, page);
12116 element = Tile[x][y];
12117 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12120 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12124 element = Tile[x][y];
12125 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12127 if (IS_ANIMATED(graphic) &&
12128 !IS_MOVING(x, y) &&
12130 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12132 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12133 TEST_DrawTwinkleOnField(x, y);
12135 else if (element == EL_ACID)
12138 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12140 else if ((element == EL_EXIT_OPEN ||
12141 element == EL_EM_EXIT_OPEN ||
12142 element == EL_SP_EXIT_OPEN ||
12143 element == EL_STEEL_EXIT_OPEN ||
12144 element == EL_EM_STEEL_EXIT_OPEN ||
12145 element == EL_SP_TERMINAL ||
12146 element == EL_SP_TERMINAL_ACTIVE ||
12147 element == EL_EXTRA_TIME ||
12148 element == EL_SHIELD_NORMAL ||
12149 element == EL_SHIELD_DEADLY) &&
12150 IS_ANIMATED(graphic))
12151 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12152 else if (IS_MOVING(x, y))
12153 ContinueMoving(x, y);
12154 else if (IS_ACTIVE_BOMB(element))
12155 CheckDynamite(x, y);
12156 else if (element == EL_AMOEBA_GROWING)
12157 AmoebaGrowing(x, y);
12158 else if (element == EL_AMOEBA_SHRINKING)
12159 AmoebaShrinking(x, y);
12161 #if !USE_NEW_AMOEBA_CODE
12162 else if (IS_AMOEBALIVE(element))
12163 AmoebaReproduce(x, y);
12166 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12168 else if (element == EL_EXIT_CLOSED)
12170 else if (element == EL_EM_EXIT_CLOSED)
12172 else if (element == EL_STEEL_EXIT_CLOSED)
12173 CheckExitSteel(x, y);
12174 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12175 CheckExitSteelEM(x, y);
12176 else if (element == EL_SP_EXIT_CLOSED)
12178 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12179 element == EL_EXPANDABLE_STEELWALL_GROWING)
12180 MauerWaechst(x, y);
12181 else if (element == EL_EXPANDABLE_WALL ||
12182 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12183 element == EL_EXPANDABLE_WALL_VERTICAL ||
12184 element == EL_EXPANDABLE_WALL_ANY ||
12185 element == EL_BD_EXPANDABLE_WALL)
12186 MauerAbleger(x, y);
12187 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12188 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12189 element == EL_EXPANDABLE_STEELWALL_ANY)
12190 MauerAblegerStahl(x, y);
12191 else if (element == EL_FLAMES)
12192 CheckForDragon(x, y);
12193 else if (element == EL_EXPLOSION)
12194 ; // drawing of correct explosion animation is handled separately
12195 else if (element == EL_ELEMENT_SNAPPING ||
12196 element == EL_DIAGONAL_SHRINKING ||
12197 element == EL_DIAGONAL_GROWING)
12199 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12201 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12203 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12204 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12206 if (IS_BELT_ACTIVE(element))
12207 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12209 if (game.magic_wall_active)
12211 int jx = local_player->jx, jy = local_player->jy;
12213 // play the element sound at the position nearest to the player
12214 if ((element == EL_MAGIC_WALL_FULL ||
12215 element == EL_MAGIC_WALL_ACTIVE ||
12216 element == EL_MAGIC_WALL_EMPTYING ||
12217 element == EL_BD_MAGIC_WALL_FULL ||
12218 element == EL_BD_MAGIC_WALL_ACTIVE ||
12219 element == EL_BD_MAGIC_WALL_EMPTYING ||
12220 element == EL_DC_MAGIC_WALL_FULL ||
12221 element == EL_DC_MAGIC_WALL_ACTIVE ||
12222 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12223 ABS(x - jx) + ABS(y - jy) <
12224 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12232 #if USE_NEW_AMOEBA_CODE
12233 // new experimental amoeba growth stuff
12234 if (!(FrameCounter % 8))
12236 static unsigned int random = 1684108901;
12238 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12240 x = RND(lev_fieldx);
12241 y = RND(lev_fieldy);
12242 element = Tile[x][y];
12244 if (!IS_PLAYER(x,y) &&
12245 (element == EL_EMPTY ||
12246 CAN_GROW_INTO(element) ||
12247 element == EL_QUICKSAND_EMPTY ||
12248 element == EL_QUICKSAND_FAST_EMPTY ||
12249 element == EL_ACID_SPLASH_LEFT ||
12250 element == EL_ACID_SPLASH_RIGHT))
12252 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12253 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12254 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12255 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12256 Tile[x][y] = EL_AMOEBA_DROP;
12259 random = random * 129 + 1;
12264 game.explosions_delayed = FALSE;
12266 SCAN_PLAYFIELD(x, y)
12268 element = Tile[x][y];
12270 if (ExplodeField[x][y])
12271 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12272 else if (element == EL_EXPLOSION)
12273 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12275 ExplodeField[x][y] = EX_TYPE_NONE;
12278 game.explosions_delayed = TRUE;
12280 if (game.magic_wall_active)
12282 if (!(game.magic_wall_time_left % 4))
12284 int element = Tile[magic_wall_x][magic_wall_y];
12286 if (element == EL_BD_MAGIC_WALL_FULL ||
12287 element == EL_BD_MAGIC_WALL_ACTIVE ||
12288 element == EL_BD_MAGIC_WALL_EMPTYING)
12289 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12290 else if (element == EL_DC_MAGIC_WALL_FULL ||
12291 element == EL_DC_MAGIC_WALL_ACTIVE ||
12292 element == EL_DC_MAGIC_WALL_EMPTYING)
12293 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12295 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12298 if (game.magic_wall_time_left > 0)
12300 game.magic_wall_time_left--;
12302 if (!game.magic_wall_time_left)
12304 SCAN_PLAYFIELD(x, y)
12306 element = Tile[x][y];
12308 if (element == EL_MAGIC_WALL_ACTIVE ||
12309 element == EL_MAGIC_WALL_FULL)
12311 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12312 TEST_DrawLevelField(x, y);
12314 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12315 element == EL_BD_MAGIC_WALL_FULL)
12317 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12318 TEST_DrawLevelField(x, y);
12320 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12321 element == EL_DC_MAGIC_WALL_FULL)
12323 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12324 TEST_DrawLevelField(x, y);
12328 game.magic_wall_active = FALSE;
12333 if (game.light_time_left > 0)
12335 game.light_time_left--;
12337 if (game.light_time_left == 0)
12338 RedrawAllLightSwitchesAndInvisibleElements();
12341 if (game.timegate_time_left > 0)
12343 game.timegate_time_left--;
12345 if (game.timegate_time_left == 0)
12346 CloseAllOpenTimegates();
12349 if (game.lenses_time_left > 0)
12351 game.lenses_time_left--;
12353 if (game.lenses_time_left == 0)
12354 RedrawAllInvisibleElementsForLenses();
12357 if (game.magnify_time_left > 0)
12359 game.magnify_time_left--;
12361 if (game.magnify_time_left == 0)
12362 RedrawAllInvisibleElementsForMagnifier();
12365 for (i = 0; i < MAX_PLAYERS; i++)
12367 struct PlayerInfo *player = &stored_player[i];
12369 if (SHIELD_ON(player))
12371 if (player->shield_deadly_time_left)
12372 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12373 else if (player->shield_normal_time_left)
12374 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12378 #if USE_DELAYED_GFX_REDRAW
12379 SCAN_PLAYFIELD(x, y)
12381 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12383 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12384 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12386 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12387 DrawLevelField(x, y);
12389 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12390 DrawLevelFieldCrumbled(x, y);
12392 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12393 DrawLevelFieldCrumbledNeighbours(x, y);
12395 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12396 DrawTwinkleOnField(x, y);
12399 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12404 PlayAllPlayersSound();
12406 for (i = 0; i < MAX_PLAYERS; i++)
12408 struct PlayerInfo *player = &stored_player[i];
12410 if (player->show_envelope != 0 && (!player->active ||
12411 player->MovPos == 0))
12413 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12415 player->show_envelope = 0;
12419 // use random number generator in every frame to make it less predictable
12420 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12423 mouse_action_last = mouse_action;
12426 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12428 int min_x = x, min_y = y, max_x = x, max_y = y;
12431 for (i = 0; i < MAX_PLAYERS; i++)
12433 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12435 if (!stored_player[i].active || &stored_player[i] == player)
12438 min_x = MIN(min_x, jx);
12439 min_y = MIN(min_y, jy);
12440 max_x = MAX(max_x, jx);
12441 max_y = MAX(max_y, jy);
12444 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12447 static boolean AllPlayersInVisibleScreen(void)
12451 for (i = 0; i < MAX_PLAYERS; i++)
12453 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12455 if (!stored_player[i].active)
12458 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12465 void ScrollLevel(int dx, int dy)
12467 int scroll_offset = 2 * TILEX_VAR;
12470 BlitBitmap(drawto_field, drawto_field,
12471 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12472 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12473 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12474 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12475 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12476 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12480 x = (dx == 1 ? BX1 : BX2);
12481 for (y = BY1; y <= BY2; y++)
12482 DrawScreenField(x, y);
12487 y = (dy == 1 ? BY1 : BY2);
12488 for (x = BX1; x <= BX2; x++)
12489 DrawScreenField(x, y);
12492 redraw_mask |= REDRAW_FIELD;
12495 static boolean canFallDown(struct PlayerInfo *player)
12497 int jx = player->jx, jy = player->jy;
12499 return (IN_LEV_FIELD(jx, jy + 1) &&
12500 (IS_FREE(jx, jy + 1) ||
12501 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12502 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12503 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12506 static boolean canPassField(int x, int y, int move_dir)
12508 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12509 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12510 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12511 int nextx = x + dx;
12512 int nexty = y + dy;
12513 int element = Tile[x][y];
12515 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12516 !CAN_MOVE(element) &&
12517 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12518 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12519 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12522 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12524 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12525 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12526 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12530 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12531 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12532 (IS_DIGGABLE(Tile[newx][newy]) ||
12533 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12534 canPassField(newx, newy, move_dir)));
12537 static void CheckGravityMovement(struct PlayerInfo *player)
12539 if (player->gravity && !player->programmed_action)
12541 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12542 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12543 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12544 int jx = player->jx, jy = player->jy;
12545 boolean player_is_moving_to_valid_field =
12546 (!player_is_snapping &&
12547 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12548 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12549 boolean player_can_fall_down = canFallDown(player);
12551 if (player_can_fall_down &&
12552 !player_is_moving_to_valid_field)
12553 player->programmed_action = MV_DOWN;
12557 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12559 return CheckGravityMovement(player);
12561 if (player->gravity && !player->programmed_action)
12563 int jx = player->jx, jy = player->jy;
12564 boolean field_under_player_is_free =
12565 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12566 boolean player_is_standing_on_valid_field =
12567 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12568 (IS_WALKABLE(Tile[jx][jy]) &&
12569 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12571 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12572 player->programmed_action = MV_DOWN;
12577 MovePlayerOneStep()
12578 -----------------------------------------------------------------------------
12579 dx, dy: direction (non-diagonal) to try to move the player to
12580 real_dx, real_dy: direction as read from input device (can be diagonal)
12583 boolean MovePlayerOneStep(struct PlayerInfo *player,
12584 int dx, int dy, int real_dx, int real_dy)
12586 int jx = player->jx, jy = player->jy;
12587 int new_jx = jx + dx, new_jy = jy + dy;
12589 boolean player_can_move = !player->cannot_move;
12591 if (!player->active || (!dx && !dy))
12592 return MP_NO_ACTION;
12594 player->MovDir = (dx < 0 ? MV_LEFT :
12595 dx > 0 ? MV_RIGHT :
12597 dy > 0 ? MV_DOWN : MV_NONE);
12599 if (!IN_LEV_FIELD(new_jx, new_jy))
12600 return MP_NO_ACTION;
12602 if (!player_can_move)
12604 if (player->MovPos == 0)
12606 player->is_moving = FALSE;
12607 player->is_digging = FALSE;
12608 player->is_collecting = FALSE;
12609 player->is_snapping = FALSE;
12610 player->is_pushing = FALSE;
12614 if (!network.enabled && game.centered_player_nr == -1 &&
12615 !AllPlayersInSight(player, new_jx, new_jy))
12616 return MP_NO_ACTION;
12618 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12619 if (can_move != MP_MOVING)
12622 // check if DigField() has caused relocation of the player
12623 if (player->jx != jx || player->jy != jy)
12624 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12626 StorePlayer[jx][jy] = 0;
12627 player->last_jx = jx;
12628 player->last_jy = jy;
12629 player->jx = new_jx;
12630 player->jy = new_jy;
12631 StorePlayer[new_jx][new_jy] = player->element_nr;
12633 if (player->move_delay_value_next != -1)
12635 player->move_delay_value = player->move_delay_value_next;
12636 player->move_delay_value_next = -1;
12640 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12642 player->step_counter++;
12644 PlayerVisit[jx][jy] = FrameCounter;
12646 player->is_moving = TRUE;
12649 // should better be called in MovePlayer(), but this breaks some tapes
12650 ScrollPlayer(player, SCROLL_INIT);
12656 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12658 int jx = player->jx, jy = player->jy;
12659 int old_jx = jx, old_jy = jy;
12660 int moved = MP_NO_ACTION;
12662 if (!player->active)
12667 if (player->MovPos == 0)
12669 player->is_moving = FALSE;
12670 player->is_digging = FALSE;
12671 player->is_collecting = FALSE;
12672 player->is_snapping = FALSE;
12673 player->is_pushing = FALSE;
12679 if (player->move_delay > 0)
12682 player->move_delay = -1; // set to "uninitialized" value
12684 // store if player is automatically moved to next field
12685 player->is_auto_moving = (player->programmed_action != MV_NONE);
12687 // remove the last programmed player action
12688 player->programmed_action = 0;
12690 if (player->MovPos)
12692 // should only happen if pre-1.2 tape recordings are played
12693 // this is only for backward compatibility
12695 int original_move_delay_value = player->move_delay_value;
12698 Debug("game:playing:MovePlayer",
12699 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12703 // scroll remaining steps with finest movement resolution
12704 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12706 while (player->MovPos)
12708 ScrollPlayer(player, SCROLL_GO_ON);
12709 ScrollScreen(NULL, SCROLL_GO_ON);
12711 AdvanceFrameAndPlayerCounters(player->index_nr);
12714 BackToFront_WithFrameDelay(0);
12717 player->move_delay_value = original_move_delay_value;
12720 player->is_active = FALSE;
12722 if (player->last_move_dir & MV_HORIZONTAL)
12724 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12725 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12729 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12730 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12733 if (!moved && !player->is_active)
12735 player->is_moving = FALSE;
12736 player->is_digging = FALSE;
12737 player->is_collecting = FALSE;
12738 player->is_snapping = FALSE;
12739 player->is_pushing = FALSE;
12745 if (moved & MP_MOVING && !ScreenMovPos &&
12746 (player->index_nr == game.centered_player_nr ||
12747 game.centered_player_nr == -1))
12749 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12751 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12753 // actual player has left the screen -- scroll in that direction
12754 if (jx != old_jx) // player has moved horizontally
12755 scroll_x += (jx - old_jx);
12756 else // player has moved vertically
12757 scroll_y += (jy - old_jy);
12761 int offset_raw = game.scroll_delay_value;
12763 if (jx != old_jx) // player has moved horizontally
12765 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12766 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12767 int new_scroll_x = jx - MIDPOSX + offset_x;
12769 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12770 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12771 scroll_x = new_scroll_x;
12773 // don't scroll over playfield boundaries
12774 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12776 // don't scroll more than one field at a time
12777 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12779 // don't scroll against the player's moving direction
12780 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12781 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12782 scroll_x = old_scroll_x;
12784 else // player has moved vertically
12786 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12787 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12788 int new_scroll_y = jy - MIDPOSY + offset_y;
12790 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12791 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12792 scroll_y = new_scroll_y;
12794 // don't scroll over playfield boundaries
12795 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12797 // don't scroll more than one field at a time
12798 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12800 // don't scroll against the player's moving direction
12801 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12802 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12803 scroll_y = old_scroll_y;
12807 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12809 if (!network.enabled && game.centered_player_nr == -1 &&
12810 !AllPlayersInVisibleScreen())
12812 scroll_x = old_scroll_x;
12813 scroll_y = old_scroll_y;
12817 ScrollScreen(player, SCROLL_INIT);
12818 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12823 player->StepFrame = 0;
12825 if (moved & MP_MOVING)
12827 if (old_jx != jx && old_jy == jy)
12828 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12829 else if (old_jx == jx && old_jy != jy)
12830 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12832 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12834 player->last_move_dir = player->MovDir;
12835 player->is_moving = TRUE;
12836 player->is_snapping = FALSE;
12837 player->is_switching = FALSE;
12838 player->is_dropping = FALSE;
12839 player->is_dropping_pressed = FALSE;
12840 player->drop_pressed_delay = 0;
12843 // should better be called here than above, but this breaks some tapes
12844 ScrollPlayer(player, SCROLL_INIT);
12849 CheckGravityMovementWhenNotMoving(player);
12851 player->is_moving = FALSE;
12853 /* at this point, the player is allowed to move, but cannot move right now
12854 (e.g. because of something blocking the way) -- ensure that the player
12855 is also allowed to move in the next frame (in old versions before 3.1.1,
12856 the player was forced to wait again for eight frames before next try) */
12858 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12859 player->move_delay = 0; // allow direct movement in the next frame
12862 if (player->move_delay == -1) // not yet initialized by DigField()
12863 player->move_delay = player->move_delay_value;
12865 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12867 TestIfPlayerTouchesBadThing(jx, jy);
12868 TestIfPlayerTouchesCustomElement(jx, jy);
12871 if (!player->active)
12872 RemovePlayer(player);
12877 void ScrollPlayer(struct PlayerInfo *player, int mode)
12879 int jx = player->jx, jy = player->jy;
12880 int last_jx = player->last_jx, last_jy = player->last_jy;
12881 int move_stepsize = TILEX / player->move_delay_value;
12883 if (!player->active)
12886 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12889 if (mode == SCROLL_INIT)
12891 player->actual_frame_counter = FrameCounter;
12892 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12894 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12895 Tile[last_jx][last_jy] == EL_EMPTY)
12897 int last_field_block_delay = 0; // start with no blocking at all
12898 int block_delay_adjustment = player->block_delay_adjustment;
12900 // if player blocks last field, add delay for exactly one move
12901 if (player->block_last_field)
12903 last_field_block_delay += player->move_delay_value;
12905 // when blocking enabled, prevent moving up despite gravity
12906 if (player->gravity && player->MovDir == MV_UP)
12907 block_delay_adjustment = -1;
12910 // add block delay adjustment (also possible when not blocking)
12911 last_field_block_delay += block_delay_adjustment;
12913 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12914 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12917 if (player->MovPos != 0) // player has not yet reached destination
12920 else if (!FrameReached(&player->actual_frame_counter, 1))
12923 if (player->MovPos != 0)
12925 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12926 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12928 // before DrawPlayer() to draw correct player graphic for this case
12929 if (player->MovPos == 0)
12930 CheckGravityMovement(player);
12933 if (player->MovPos == 0) // player reached destination field
12935 if (player->move_delay_reset_counter > 0)
12937 player->move_delay_reset_counter--;
12939 if (player->move_delay_reset_counter == 0)
12941 // continue with normal speed after quickly moving through gate
12942 HALVE_PLAYER_SPEED(player);
12944 // be able to make the next move without delay
12945 player->move_delay = 0;
12949 player->last_jx = jx;
12950 player->last_jy = jy;
12952 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12953 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12954 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12955 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12956 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12957 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12958 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12959 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12961 ExitPlayer(player);
12963 if (game.players_still_needed == 0 &&
12964 (game.friends_still_needed == 0 ||
12965 IS_SP_ELEMENT(Tile[jx][jy])))
12969 // this breaks one level: "machine", level 000
12971 int move_direction = player->MovDir;
12972 int enter_side = MV_DIR_OPPOSITE(move_direction);
12973 int leave_side = move_direction;
12974 int old_jx = last_jx;
12975 int old_jy = last_jy;
12976 int old_element = Tile[old_jx][old_jy];
12977 int new_element = Tile[jx][jy];
12979 if (IS_CUSTOM_ELEMENT(old_element))
12980 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12982 player->index_bit, leave_side);
12984 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12985 CE_PLAYER_LEAVES_X,
12986 player->index_bit, leave_side);
12988 if (IS_CUSTOM_ELEMENT(new_element))
12989 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12990 player->index_bit, enter_side);
12992 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12993 CE_PLAYER_ENTERS_X,
12994 player->index_bit, enter_side);
12996 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12997 CE_MOVE_OF_X, move_direction);
13000 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13002 TestIfPlayerTouchesBadThing(jx, jy);
13003 TestIfPlayerTouchesCustomElement(jx, jy);
13005 /* needed because pushed element has not yet reached its destination,
13006 so it would trigger a change event at its previous field location */
13007 if (!player->is_pushing)
13008 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13010 if (!player->active)
13011 RemovePlayer(player);
13014 if (!game.LevelSolved && level.use_step_counter)
13024 if (TimeLeft <= 10 && setup.time_limit)
13025 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13027 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13029 DisplayGameControlValues();
13031 if (!TimeLeft && setup.time_limit)
13032 for (i = 0; i < MAX_PLAYERS; i++)
13033 KillPlayer(&stored_player[i]);
13035 else if (game.no_time_limit && !game.all_players_gone)
13037 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13039 DisplayGameControlValues();
13043 if (tape.single_step && tape.recording && !tape.pausing &&
13044 !player->programmed_action)
13045 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13047 if (!player->programmed_action)
13048 CheckSaveEngineSnapshot(player);
13052 void ScrollScreen(struct PlayerInfo *player, int mode)
13054 static unsigned int screen_frame_counter = 0;
13056 if (mode == SCROLL_INIT)
13058 // set scrolling step size according to actual player's moving speed
13059 ScrollStepSize = TILEX / player->move_delay_value;
13061 screen_frame_counter = FrameCounter;
13062 ScreenMovDir = player->MovDir;
13063 ScreenMovPos = player->MovPos;
13064 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13067 else if (!FrameReached(&screen_frame_counter, 1))
13072 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13073 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13074 redraw_mask |= REDRAW_FIELD;
13077 ScreenMovDir = MV_NONE;
13080 void TestIfPlayerTouchesCustomElement(int x, int y)
13082 static int xy[4][2] =
13089 static int trigger_sides[4][2] =
13091 // center side border side
13092 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13093 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13094 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13095 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13097 static int touch_dir[4] =
13099 MV_LEFT | MV_RIGHT,
13104 int center_element = Tile[x][y]; // should always be non-moving!
13107 for (i = 0; i < NUM_DIRECTIONS; i++)
13109 int xx = x + xy[i][0];
13110 int yy = y + xy[i][1];
13111 int center_side = trigger_sides[i][0];
13112 int border_side = trigger_sides[i][1];
13113 int border_element;
13115 if (!IN_LEV_FIELD(xx, yy))
13118 if (IS_PLAYER(x, y)) // player found at center element
13120 struct PlayerInfo *player = PLAYERINFO(x, y);
13122 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13123 border_element = Tile[xx][yy]; // may be moving!
13124 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13125 border_element = Tile[xx][yy];
13126 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13127 border_element = MovingOrBlocked2Element(xx, yy);
13129 continue; // center and border element do not touch
13131 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13132 player->index_bit, border_side);
13133 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13134 CE_PLAYER_TOUCHES_X,
13135 player->index_bit, border_side);
13138 /* use player element that is initially defined in the level playfield,
13139 not the player element that corresponds to the runtime player number
13140 (example: a level that contains EL_PLAYER_3 as the only player would
13141 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13142 int player_element = PLAYERINFO(x, y)->initial_element;
13144 CheckElementChangeBySide(xx, yy, border_element, player_element,
13145 CE_TOUCHING_X, border_side);
13148 else if (IS_PLAYER(xx, yy)) // player found at border element
13150 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13152 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13154 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13155 continue; // center and border element do not touch
13158 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13159 player->index_bit, center_side);
13160 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13161 CE_PLAYER_TOUCHES_X,
13162 player->index_bit, center_side);
13165 /* use player element that is initially defined in the level playfield,
13166 not the player element that corresponds to the runtime player number
13167 (example: a level that contains EL_PLAYER_3 as the only player would
13168 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13169 int player_element = PLAYERINFO(xx, yy)->initial_element;
13171 CheckElementChangeBySide(x, y, center_element, player_element,
13172 CE_TOUCHING_X, center_side);
13180 void TestIfElementTouchesCustomElement(int x, int y)
13182 static int xy[4][2] =
13189 static int trigger_sides[4][2] =
13191 // center side border side
13192 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13193 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13194 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13195 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13197 static int touch_dir[4] =
13199 MV_LEFT | MV_RIGHT,
13204 boolean change_center_element = FALSE;
13205 int center_element = Tile[x][y]; // should always be non-moving!
13206 int border_element_old[NUM_DIRECTIONS];
13209 for (i = 0; i < NUM_DIRECTIONS; i++)
13211 int xx = x + xy[i][0];
13212 int yy = y + xy[i][1];
13213 int border_element;
13215 border_element_old[i] = -1;
13217 if (!IN_LEV_FIELD(xx, yy))
13220 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13221 border_element = Tile[xx][yy]; // may be moving!
13222 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13223 border_element = Tile[xx][yy];
13224 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13225 border_element = MovingOrBlocked2Element(xx, yy);
13227 continue; // center and border element do not touch
13229 border_element_old[i] = border_element;
13232 for (i = 0; i < NUM_DIRECTIONS; i++)
13234 int xx = x + xy[i][0];
13235 int yy = y + xy[i][1];
13236 int center_side = trigger_sides[i][0];
13237 int border_element = border_element_old[i];
13239 if (border_element == -1)
13242 // check for change of border element
13243 CheckElementChangeBySide(xx, yy, border_element, center_element,
13244 CE_TOUCHING_X, center_side);
13246 // (center element cannot be player, so we dont have to check this here)
13249 for (i = 0; i < NUM_DIRECTIONS; i++)
13251 int xx = x + xy[i][0];
13252 int yy = y + xy[i][1];
13253 int border_side = trigger_sides[i][1];
13254 int border_element = border_element_old[i];
13256 if (border_element == -1)
13259 // check for change of center element (but change it only once)
13260 if (!change_center_element)
13261 change_center_element =
13262 CheckElementChangeBySide(x, y, center_element, border_element,
13263 CE_TOUCHING_X, border_side);
13265 if (IS_PLAYER(xx, yy))
13267 /* use player element that is initially defined in the level playfield,
13268 not the player element that corresponds to the runtime player number
13269 (example: a level that contains EL_PLAYER_3 as the only player would
13270 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13271 int player_element = PLAYERINFO(xx, yy)->initial_element;
13273 CheckElementChangeBySide(x, y, center_element, player_element,
13274 CE_TOUCHING_X, border_side);
13279 void TestIfElementHitsCustomElement(int x, int y, int direction)
13281 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13282 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13283 int hitx = x + dx, hity = y + dy;
13284 int hitting_element = Tile[x][y];
13285 int touched_element;
13287 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13290 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13291 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13293 if (IN_LEV_FIELD(hitx, hity))
13295 int opposite_direction = MV_DIR_OPPOSITE(direction);
13296 int hitting_side = direction;
13297 int touched_side = opposite_direction;
13298 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13299 MovDir[hitx][hity] != direction ||
13300 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13306 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13307 CE_HITTING_X, touched_side);
13309 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13310 CE_HIT_BY_X, hitting_side);
13312 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13313 CE_HIT_BY_SOMETHING, opposite_direction);
13315 if (IS_PLAYER(hitx, hity))
13317 /* use player element that is initially defined in the level playfield,
13318 not the player element that corresponds to the runtime player number
13319 (example: a level that contains EL_PLAYER_3 as the only player would
13320 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13321 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13323 CheckElementChangeBySide(x, y, hitting_element, player_element,
13324 CE_HITTING_X, touched_side);
13329 // "hitting something" is also true when hitting the playfield border
13330 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13331 CE_HITTING_SOMETHING, direction);
13334 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13336 int i, kill_x = -1, kill_y = -1;
13338 int bad_element = -1;
13339 static int test_xy[4][2] =
13346 static int test_dir[4] =
13354 for (i = 0; i < NUM_DIRECTIONS; i++)
13356 int test_x, test_y, test_move_dir, test_element;
13358 test_x = good_x + test_xy[i][0];
13359 test_y = good_y + test_xy[i][1];
13361 if (!IN_LEV_FIELD(test_x, test_y))
13365 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13367 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13369 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13370 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13372 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13373 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13377 bad_element = test_element;
13383 if (kill_x != -1 || kill_y != -1)
13385 if (IS_PLAYER(good_x, good_y))
13387 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13389 if (player->shield_deadly_time_left > 0 &&
13390 !IS_INDESTRUCTIBLE(bad_element))
13391 Bang(kill_x, kill_y);
13392 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13393 KillPlayer(player);
13396 Bang(good_x, good_y);
13400 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13402 int i, kill_x = -1, kill_y = -1;
13403 int bad_element = Tile[bad_x][bad_y];
13404 static int test_xy[4][2] =
13411 static int touch_dir[4] =
13413 MV_LEFT | MV_RIGHT,
13418 static int test_dir[4] =
13426 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13429 for (i = 0; i < NUM_DIRECTIONS; i++)
13431 int test_x, test_y, test_move_dir, test_element;
13433 test_x = bad_x + test_xy[i][0];
13434 test_y = bad_y + test_xy[i][1];
13436 if (!IN_LEV_FIELD(test_x, test_y))
13440 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13442 test_element = Tile[test_x][test_y];
13444 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13445 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13447 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13448 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13450 // good thing is player or penguin that does not move away
13451 if (IS_PLAYER(test_x, test_y))
13453 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13455 if (bad_element == EL_ROBOT && player->is_moving)
13456 continue; // robot does not kill player if he is moving
13458 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13460 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13461 continue; // center and border element do not touch
13469 else if (test_element == EL_PENGUIN)
13479 if (kill_x != -1 || kill_y != -1)
13481 if (IS_PLAYER(kill_x, kill_y))
13483 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13485 if (player->shield_deadly_time_left > 0 &&
13486 !IS_INDESTRUCTIBLE(bad_element))
13487 Bang(bad_x, bad_y);
13488 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13489 KillPlayer(player);
13492 Bang(kill_x, kill_y);
13496 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13498 int bad_element = Tile[bad_x][bad_y];
13499 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13500 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13501 int test_x = bad_x + dx, test_y = bad_y + dy;
13502 int test_move_dir, test_element;
13503 int kill_x = -1, kill_y = -1;
13505 if (!IN_LEV_FIELD(test_x, test_y))
13509 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13511 test_element = Tile[test_x][test_y];
13513 if (test_move_dir != bad_move_dir)
13515 // good thing can be player or penguin that does not move away
13516 if (IS_PLAYER(test_x, test_y))
13518 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13520 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13521 player as being hit when he is moving towards the bad thing, because
13522 the "get hit by" condition would be lost after the player stops) */
13523 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13524 return; // player moves away from bad thing
13529 else if (test_element == EL_PENGUIN)
13536 if (kill_x != -1 || kill_y != -1)
13538 if (IS_PLAYER(kill_x, kill_y))
13540 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13542 if (player->shield_deadly_time_left > 0 &&
13543 !IS_INDESTRUCTIBLE(bad_element))
13544 Bang(bad_x, bad_y);
13545 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13546 KillPlayer(player);
13549 Bang(kill_x, kill_y);
13553 void TestIfPlayerTouchesBadThing(int x, int y)
13555 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13558 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13560 TestIfGoodThingHitsBadThing(x, y, move_dir);
13563 void TestIfBadThingTouchesPlayer(int x, int y)
13565 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13568 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13570 TestIfBadThingHitsGoodThing(x, y, move_dir);
13573 void TestIfFriendTouchesBadThing(int x, int y)
13575 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13578 void TestIfBadThingTouchesFriend(int x, int y)
13580 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13583 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13585 int i, kill_x = bad_x, kill_y = bad_y;
13586 static int xy[4][2] =
13594 for (i = 0; i < NUM_DIRECTIONS; i++)
13598 x = bad_x + xy[i][0];
13599 y = bad_y + xy[i][1];
13600 if (!IN_LEV_FIELD(x, y))
13603 element = Tile[x][y];
13604 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13605 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13613 if (kill_x != bad_x || kill_y != bad_y)
13614 Bang(bad_x, bad_y);
13617 void KillPlayer(struct PlayerInfo *player)
13619 int jx = player->jx, jy = player->jy;
13621 if (!player->active)
13625 Debug("game:playing:KillPlayer",
13626 "0: killed == %d, active == %d, reanimated == %d",
13627 player->killed, player->active, player->reanimated);
13630 /* the following code was introduced to prevent an infinite loop when calling
13632 -> CheckTriggeredElementChangeExt()
13633 -> ExecuteCustomElementAction()
13635 -> (infinitely repeating the above sequence of function calls)
13636 which occurs when killing the player while having a CE with the setting
13637 "kill player X when explosion of <player X>"; the solution using a new
13638 field "player->killed" was chosen for backwards compatibility, although
13639 clever use of the fields "player->active" etc. would probably also work */
13641 if (player->killed)
13645 player->killed = TRUE;
13647 // remove accessible field at the player's position
13648 Tile[jx][jy] = EL_EMPTY;
13650 // deactivate shield (else Bang()/Explode() would not work right)
13651 player->shield_normal_time_left = 0;
13652 player->shield_deadly_time_left = 0;
13655 Debug("game:playing:KillPlayer",
13656 "1: killed == %d, active == %d, reanimated == %d",
13657 player->killed, player->active, player->reanimated);
13663 Debug("game:playing:KillPlayer",
13664 "2: killed == %d, active == %d, reanimated == %d",
13665 player->killed, player->active, player->reanimated);
13668 if (player->reanimated) // killed player may have been reanimated
13669 player->killed = player->reanimated = FALSE;
13671 BuryPlayer(player);
13674 static void KillPlayerUnlessEnemyProtected(int x, int y)
13676 if (!PLAYER_ENEMY_PROTECTED(x, y))
13677 KillPlayer(PLAYERINFO(x, y));
13680 static void KillPlayerUnlessExplosionProtected(int x, int y)
13682 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13683 KillPlayer(PLAYERINFO(x, y));
13686 void BuryPlayer(struct PlayerInfo *player)
13688 int jx = player->jx, jy = player->jy;
13690 if (!player->active)
13693 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13694 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13696 RemovePlayer(player);
13698 player->buried = TRUE;
13700 if (game.all_players_gone)
13701 game.GameOver = TRUE;
13704 void RemovePlayer(struct PlayerInfo *player)
13706 int jx = player->jx, jy = player->jy;
13707 int i, found = FALSE;
13709 player->present = FALSE;
13710 player->active = FALSE;
13712 // required for some CE actions (even if the player is not active anymore)
13713 player->MovPos = 0;
13715 if (!ExplodeField[jx][jy])
13716 StorePlayer[jx][jy] = 0;
13718 if (player->is_moving)
13719 TEST_DrawLevelField(player->last_jx, player->last_jy);
13721 for (i = 0; i < MAX_PLAYERS; i++)
13722 if (stored_player[i].active)
13727 game.all_players_gone = TRUE;
13728 game.GameOver = TRUE;
13731 game.exit_x = game.robot_wheel_x = jx;
13732 game.exit_y = game.robot_wheel_y = jy;
13735 void ExitPlayer(struct PlayerInfo *player)
13737 DrawPlayer(player); // needed here only to cleanup last field
13738 RemovePlayer(player);
13740 if (game.players_still_needed > 0)
13741 game.players_still_needed--;
13744 static void setFieldForSnapping(int x, int y, int element, int direction)
13746 struct ElementInfo *ei = &element_info[element];
13747 int direction_bit = MV_DIR_TO_BIT(direction);
13748 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13749 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13750 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13752 Tile[x][y] = EL_ELEMENT_SNAPPING;
13753 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13755 ResetGfxAnimation(x, y);
13757 GfxElement[x][y] = element;
13758 GfxAction[x][y] = action;
13759 GfxDir[x][y] = direction;
13760 GfxFrame[x][y] = -1;
13764 =============================================================================
13765 checkDiagonalPushing()
13766 -----------------------------------------------------------------------------
13767 check if diagonal input device direction results in pushing of object
13768 (by checking if the alternative direction is walkable, diggable, ...)
13769 =============================================================================
13772 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13773 int x, int y, int real_dx, int real_dy)
13775 int jx, jy, dx, dy, xx, yy;
13777 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13780 // diagonal direction: check alternative direction
13785 xx = jx + (dx == 0 ? real_dx : 0);
13786 yy = jy + (dy == 0 ? real_dy : 0);
13788 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13792 =============================================================================
13794 -----------------------------------------------------------------------------
13795 x, y: field next to player (non-diagonal) to try to dig to
13796 real_dx, real_dy: direction as read from input device (can be diagonal)
13797 =============================================================================
13800 static int DigField(struct PlayerInfo *player,
13801 int oldx, int oldy, int x, int y,
13802 int real_dx, int real_dy, int mode)
13804 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13805 boolean player_was_pushing = player->is_pushing;
13806 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13807 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13808 int jx = oldx, jy = oldy;
13809 int dx = x - jx, dy = y - jy;
13810 int nextx = x + dx, nexty = y + dy;
13811 int move_direction = (dx == -1 ? MV_LEFT :
13812 dx == +1 ? MV_RIGHT :
13814 dy == +1 ? MV_DOWN : MV_NONE);
13815 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13816 int dig_side = MV_DIR_OPPOSITE(move_direction);
13817 int old_element = Tile[jx][jy];
13818 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13821 if (is_player) // function can also be called by EL_PENGUIN
13823 if (player->MovPos == 0)
13825 player->is_digging = FALSE;
13826 player->is_collecting = FALSE;
13829 if (player->MovPos == 0) // last pushing move finished
13830 player->is_pushing = FALSE;
13832 if (mode == DF_NO_PUSH) // player just stopped pushing
13834 player->is_switching = FALSE;
13835 player->push_delay = -1;
13837 return MP_NO_ACTION;
13841 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13842 old_element = Back[jx][jy];
13844 // in case of element dropped at player position, check background
13845 else if (Back[jx][jy] != EL_EMPTY &&
13846 game.engine_version >= VERSION_IDENT(2,2,0,0))
13847 old_element = Back[jx][jy];
13849 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13850 return MP_NO_ACTION; // field has no opening in this direction
13852 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13853 return MP_NO_ACTION; // field has no opening in this direction
13855 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13859 Tile[jx][jy] = player->artwork_element;
13860 InitMovingField(jx, jy, MV_DOWN);
13861 Store[jx][jy] = EL_ACID;
13862 ContinueMoving(jx, jy);
13863 BuryPlayer(player);
13865 return MP_DONT_RUN_INTO;
13868 if (player_can_move && DONT_RUN_INTO(element))
13870 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13872 return MP_DONT_RUN_INTO;
13875 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13876 return MP_NO_ACTION;
13878 collect_count = element_info[element].collect_count_initial;
13880 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13881 return MP_NO_ACTION;
13883 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13884 player_can_move = player_can_move_or_snap;
13886 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13887 game.engine_version >= VERSION_IDENT(2,2,0,0))
13889 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13890 player->index_bit, dig_side);
13891 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13892 player->index_bit, dig_side);
13894 if (element == EL_DC_LANDMINE)
13897 if (Tile[x][y] != element) // field changed by snapping
13900 return MP_NO_ACTION;
13903 if (player->gravity && is_player && !player->is_auto_moving &&
13904 canFallDown(player) && move_direction != MV_DOWN &&
13905 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13906 return MP_NO_ACTION; // player cannot walk here due to gravity
13908 if (player_can_move &&
13909 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13911 int sound_element = SND_ELEMENT(element);
13912 int sound_action = ACTION_WALKING;
13914 if (IS_RND_GATE(element))
13916 if (!player->key[RND_GATE_NR(element)])
13917 return MP_NO_ACTION;
13919 else if (IS_RND_GATE_GRAY(element))
13921 if (!player->key[RND_GATE_GRAY_NR(element)])
13922 return MP_NO_ACTION;
13924 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13926 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13927 return MP_NO_ACTION;
13929 else if (element == EL_EXIT_OPEN ||
13930 element == EL_EM_EXIT_OPEN ||
13931 element == EL_EM_EXIT_OPENING ||
13932 element == EL_STEEL_EXIT_OPEN ||
13933 element == EL_EM_STEEL_EXIT_OPEN ||
13934 element == EL_EM_STEEL_EXIT_OPENING ||
13935 element == EL_SP_EXIT_OPEN ||
13936 element == EL_SP_EXIT_OPENING)
13938 sound_action = ACTION_PASSING; // player is passing exit
13940 else if (element == EL_EMPTY)
13942 sound_action = ACTION_MOVING; // nothing to walk on
13945 // play sound from background or player, whatever is available
13946 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13947 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13949 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13951 else if (player_can_move &&
13952 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13954 if (!ACCESS_FROM(element, opposite_direction))
13955 return MP_NO_ACTION; // field not accessible from this direction
13957 if (CAN_MOVE(element)) // only fixed elements can be passed!
13958 return MP_NO_ACTION;
13960 if (IS_EM_GATE(element))
13962 if (!player->key[EM_GATE_NR(element)])
13963 return MP_NO_ACTION;
13965 else if (IS_EM_GATE_GRAY(element))
13967 if (!player->key[EM_GATE_GRAY_NR(element)])
13968 return MP_NO_ACTION;
13970 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13972 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13973 return MP_NO_ACTION;
13975 else if (IS_EMC_GATE(element))
13977 if (!player->key[EMC_GATE_NR(element)])
13978 return MP_NO_ACTION;
13980 else if (IS_EMC_GATE_GRAY(element))
13982 if (!player->key[EMC_GATE_GRAY_NR(element)])
13983 return MP_NO_ACTION;
13985 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13987 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13988 return MP_NO_ACTION;
13990 else if (element == EL_DC_GATE_WHITE ||
13991 element == EL_DC_GATE_WHITE_GRAY ||
13992 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13994 if (player->num_white_keys == 0)
13995 return MP_NO_ACTION;
13997 player->num_white_keys--;
13999 else if (IS_SP_PORT(element))
14001 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14002 element == EL_SP_GRAVITY_PORT_RIGHT ||
14003 element == EL_SP_GRAVITY_PORT_UP ||
14004 element == EL_SP_GRAVITY_PORT_DOWN)
14005 player->gravity = !player->gravity;
14006 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14007 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14008 element == EL_SP_GRAVITY_ON_PORT_UP ||
14009 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14010 player->gravity = TRUE;
14011 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14012 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14013 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14014 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14015 player->gravity = FALSE;
14018 // automatically move to the next field with double speed
14019 player->programmed_action = move_direction;
14021 if (player->move_delay_reset_counter == 0)
14023 player->move_delay_reset_counter = 2; // two double speed steps
14025 DOUBLE_PLAYER_SPEED(player);
14028 PlayLevelSoundAction(x, y, ACTION_PASSING);
14030 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14034 if (mode != DF_SNAP)
14036 GfxElement[x][y] = GFX_ELEMENT(element);
14037 player->is_digging = TRUE;
14040 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14042 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14043 player->index_bit, dig_side);
14045 // if digging triggered player relocation, finish digging tile
14046 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14047 setFieldForSnapping(x, y, element, move_direction);
14049 if (mode == DF_SNAP)
14051 if (level.block_snap_field)
14052 setFieldForSnapping(x, y, element, move_direction);
14054 TestIfElementTouchesCustomElement(x, y); // for empty space
14056 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14057 player->index_bit, dig_side);
14060 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14064 if (is_player && mode != DF_SNAP)
14066 GfxElement[x][y] = element;
14067 player->is_collecting = TRUE;
14070 if (element == EL_SPEED_PILL)
14072 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14074 else if (element == EL_EXTRA_TIME && level.time > 0)
14076 TimeLeft += level.extra_time;
14078 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14080 DisplayGameControlValues();
14082 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14084 player->shield_normal_time_left += level.shield_normal_time;
14085 if (element == EL_SHIELD_DEADLY)
14086 player->shield_deadly_time_left += level.shield_deadly_time;
14088 else if (element == EL_DYNAMITE ||
14089 element == EL_EM_DYNAMITE ||
14090 element == EL_SP_DISK_RED)
14092 if (player->inventory_size < MAX_INVENTORY_SIZE)
14093 player->inventory_element[player->inventory_size++] = element;
14095 DrawGameDoorValues();
14097 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14099 player->dynabomb_count++;
14100 player->dynabombs_left++;
14102 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14104 player->dynabomb_size++;
14106 else if (element == EL_DYNABOMB_INCREASE_POWER)
14108 player->dynabomb_xl = TRUE;
14110 else if (IS_KEY(element))
14112 player->key[KEY_NR(element)] = TRUE;
14114 DrawGameDoorValues();
14116 else if (element == EL_DC_KEY_WHITE)
14118 player->num_white_keys++;
14120 // display white keys?
14121 // DrawGameDoorValues();
14123 else if (IS_ENVELOPE(element))
14125 player->show_envelope = element;
14127 else if (element == EL_EMC_LENSES)
14129 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14131 RedrawAllInvisibleElementsForLenses();
14133 else if (element == EL_EMC_MAGNIFIER)
14135 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14137 RedrawAllInvisibleElementsForMagnifier();
14139 else if (IS_DROPPABLE(element) ||
14140 IS_THROWABLE(element)) // can be collected and dropped
14144 if (collect_count == 0)
14145 player->inventory_infinite_element = element;
14147 for (i = 0; i < collect_count; i++)
14148 if (player->inventory_size < MAX_INVENTORY_SIZE)
14149 player->inventory_element[player->inventory_size++] = element;
14151 DrawGameDoorValues();
14153 else if (collect_count > 0)
14155 game.gems_still_needed -= collect_count;
14156 if (game.gems_still_needed < 0)
14157 game.gems_still_needed = 0;
14159 game.snapshot.collected_item = TRUE;
14161 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14163 DisplayGameControlValues();
14166 RaiseScoreElement(element);
14167 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14171 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14172 player->index_bit, dig_side);
14174 // if collecting triggered player relocation, finish collecting tile
14175 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14176 setFieldForSnapping(x, y, element, move_direction);
14179 if (mode == DF_SNAP)
14181 if (level.block_snap_field)
14182 setFieldForSnapping(x, y, element, move_direction);
14184 TestIfElementTouchesCustomElement(x, y); // for empty space
14186 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14187 player->index_bit, dig_side);
14190 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14192 if (mode == DF_SNAP && element != EL_BD_ROCK)
14193 return MP_NO_ACTION;
14195 if (CAN_FALL(element) && dy)
14196 return MP_NO_ACTION;
14198 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14199 !(element == EL_SPRING && level.use_spring_bug))
14200 return MP_NO_ACTION;
14202 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14203 ((move_direction & MV_VERTICAL &&
14204 ((element_info[element].move_pattern & MV_LEFT &&
14205 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14206 (element_info[element].move_pattern & MV_RIGHT &&
14207 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14208 (move_direction & MV_HORIZONTAL &&
14209 ((element_info[element].move_pattern & MV_UP &&
14210 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14211 (element_info[element].move_pattern & MV_DOWN &&
14212 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14213 return MP_NO_ACTION;
14215 // do not push elements already moving away faster than player
14216 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14217 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14218 return MP_NO_ACTION;
14220 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14222 if (player->push_delay_value == -1 || !player_was_pushing)
14223 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14225 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14227 if (player->push_delay_value == -1)
14228 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14230 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14232 if (!player->is_pushing)
14233 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14236 player->is_pushing = TRUE;
14237 player->is_active = TRUE;
14239 if (!(IN_LEV_FIELD(nextx, nexty) &&
14240 (IS_FREE(nextx, nexty) ||
14241 (IS_SB_ELEMENT(element) &&
14242 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14243 (IS_CUSTOM_ELEMENT(element) &&
14244 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14245 return MP_NO_ACTION;
14247 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14248 return MP_NO_ACTION;
14250 if (player->push_delay == -1) // new pushing; restart delay
14251 player->push_delay = 0;
14253 if (player->push_delay < player->push_delay_value &&
14254 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14255 element != EL_SPRING && element != EL_BALLOON)
14257 // make sure that there is no move delay before next try to push
14258 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14259 player->move_delay = 0;
14261 return MP_NO_ACTION;
14264 if (IS_CUSTOM_ELEMENT(element) &&
14265 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14267 if (!DigFieldByCE(nextx, nexty, element))
14268 return MP_NO_ACTION;
14271 if (IS_SB_ELEMENT(element))
14273 boolean sokoban_task_solved = FALSE;
14275 if (element == EL_SOKOBAN_FIELD_FULL)
14277 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14279 IncrementSokobanFieldsNeeded();
14280 IncrementSokobanObjectsNeeded();
14283 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14285 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14287 DecrementSokobanFieldsNeeded();
14288 DecrementSokobanObjectsNeeded();
14290 // sokoban object was pushed from empty field to sokoban field
14291 if (Back[x][y] == EL_EMPTY)
14292 sokoban_task_solved = TRUE;
14295 Tile[x][y] = EL_SOKOBAN_OBJECT;
14297 if (Back[x][y] == Back[nextx][nexty])
14298 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14299 else if (Back[x][y] != 0)
14300 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14303 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14306 if (sokoban_task_solved &&
14307 game.sokoban_fields_still_needed == 0 &&
14308 game.sokoban_objects_still_needed == 0 &&
14309 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14311 game.players_still_needed = 0;
14315 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14319 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14321 InitMovingField(x, y, move_direction);
14322 GfxAction[x][y] = ACTION_PUSHING;
14324 if (mode == DF_SNAP)
14325 ContinueMoving(x, y);
14327 MovPos[x][y] = (dx != 0 ? dx : dy);
14329 Pushed[x][y] = TRUE;
14330 Pushed[nextx][nexty] = TRUE;
14332 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14333 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14335 player->push_delay_value = -1; // get new value later
14337 // check for element change _after_ element has been pushed
14338 if (game.use_change_when_pushing_bug)
14340 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14341 player->index_bit, dig_side);
14342 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14343 player->index_bit, dig_side);
14346 else if (IS_SWITCHABLE(element))
14348 if (PLAYER_SWITCHING(player, x, y))
14350 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14351 player->index_bit, dig_side);
14356 player->is_switching = TRUE;
14357 player->switch_x = x;
14358 player->switch_y = y;
14360 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14362 if (element == EL_ROBOT_WHEEL)
14364 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14366 game.robot_wheel_x = x;
14367 game.robot_wheel_y = y;
14368 game.robot_wheel_active = TRUE;
14370 TEST_DrawLevelField(x, y);
14372 else if (element == EL_SP_TERMINAL)
14376 SCAN_PLAYFIELD(xx, yy)
14378 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14382 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14384 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14386 ResetGfxAnimation(xx, yy);
14387 TEST_DrawLevelField(xx, yy);
14391 else if (IS_BELT_SWITCH(element))
14393 ToggleBeltSwitch(x, y);
14395 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14396 element == EL_SWITCHGATE_SWITCH_DOWN ||
14397 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14398 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14400 ToggleSwitchgateSwitch(x, y);
14402 else if (element == EL_LIGHT_SWITCH ||
14403 element == EL_LIGHT_SWITCH_ACTIVE)
14405 ToggleLightSwitch(x, y);
14407 else if (element == EL_TIMEGATE_SWITCH ||
14408 element == EL_DC_TIMEGATE_SWITCH)
14410 ActivateTimegateSwitch(x, y);
14412 else if (element == EL_BALLOON_SWITCH_LEFT ||
14413 element == EL_BALLOON_SWITCH_RIGHT ||
14414 element == EL_BALLOON_SWITCH_UP ||
14415 element == EL_BALLOON_SWITCH_DOWN ||
14416 element == EL_BALLOON_SWITCH_NONE ||
14417 element == EL_BALLOON_SWITCH_ANY)
14419 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14420 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14421 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14422 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14423 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14426 else if (element == EL_LAMP)
14428 Tile[x][y] = EL_LAMP_ACTIVE;
14429 game.lights_still_needed--;
14431 ResetGfxAnimation(x, y);
14432 TEST_DrawLevelField(x, y);
14434 else if (element == EL_TIME_ORB_FULL)
14436 Tile[x][y] = EL_TIME_ORB_EMPTY;
14438 if (level.time > 0 || level.use_time_orb_bug)
14440 TimeLeft += level.time_orb_time;
14441 game.no_time_limit = FALSE;
14443 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14445 DisplayGameControlValues();
14448 ResetGfxAnimation(x, y);
14449 TEST_DrawLevelField(x, y);
14451 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14452 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14456 game.ball_active = !game.ball_active;
14458 SCAN_PLAYFIELD(xx, yy)
14460 int e = Tile[xx][yy];
14462 if (game.ball_active)
14464 if (e == EL_EMC_MAGIC_BALL)
14465 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14466 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14467 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14471 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14472 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14473 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14474 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14479 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14480 player->index_bit, dig_side);
14482 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14483 player->index_bit, dig_side);
14485 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14486 player->index_bit, dig_side);
14492 if (!PLAYER_SWITCHING(player, x, y))
14494 player->is_switching = TRUE;
14495 player->switch_x = x;
14496 player->switch_y = y;
14498 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14499 player->index_bit, dig_side);
14500 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14501 player->index_bit, dig_side);
14503 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14504 player->index_bit, dig_side);
14505 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14506 player->index_bit, dig_side);
14509 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14510 player->index_bit, dig_side);
14511 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14512 player->index_bit, dig_side);
14514 return MP_NO_ACTION;
14517 player->push_delay = -1;
14519 if (is_player) // function can also be called by EL_PENGUIN
14521 if (Tile[x][y] != element) // really digged/collected something
14523 player->is_collecting = !player->is_digging;
14524 player->is_active = TRUE;
14531 static boolean DigFieldByCE(int x, int y, int digging_element)
14533 int element = Tile[x][y];
14535 if (!IS_FREE(x, y))
14537 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14538 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14541 // no element can dig solid indestructible elements
14542 if (IS_INDESTRUCTIBLE(element) &&
14543 !IS_DIGGABLE(element) &&
14544 !IS_COLLECTIBLE(element))
14547 if (AmoebaNr[x][y] &&
14548 (element == EL_AMOEBA_FULL ||
14549 element == EL_BD_AMOEBA ||
14550 element == EL_AMOEBA_GROWING))
14552 AmoebaCnt[AmoebaNr[x][y]]--;
14553 AmoebaCnt2[AmoebaNr[x][y]]--;
14556 if (IS_MOVING(x, y))
14557 RemoveMovingField(x, y);
14561 TEST_DrawLevelField(x, y);
14564 // if digged element was about to explode, prevent the explosion
14565 ExplodeField[x][y] = EX_TYPE_NONE;
14567 PlayLevelSoundAction(x, y, action);
14570 Store[x][y] = EL_EMPTY;
14572 // this makes it possible to leave the removed element again
14573 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14574 Store[x][y] = element;
14579 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14581 int jx = player->jx, jy = player->jy;
14582 int x = jx + dx, y = jy + dy;
14583 int snap_direction = (dx == -1 ? MV_LEFT :
14584 dx == +1 ? MV_RIGHT :
14586 dy == +1 ? MV_DOWN : MV_NONE);
14587 boolean can_continue_snapping = (level.continuous_snapping &&
14588 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14590 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14593 if (!player->active || !IN_LEV_FIELD(x, y))
14601 if (player->MovPos == 0)
14602 player->is_pushing = FALSE;
14604 player->is_snapping = FALSE;
14606 if (player->MovPos == 0)
14608 player->is_moving = FALSE;
14609 player->is_digging = FALSE;
14610 player->is_collecting = FALSE;
14616 // prevent snapping with already pressed snap key when not allowed
14617 if (player->is_snapping && !can_continue_snapping)
14620 player->MovDir = snap_direction;
14622 if (player->MovPos == 0)
14624 player->is_moving = FALSE;
14625 player->is_digging = FALSE;
14626 player->is_collecting = FALSE;
14629 player->is_dropping = FALSE;
14630 player->is_dropping_pressed = FALSE;
14631 player->drop_pressed_delay = 0;
14633 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14636 player->is_snapping = TRUE;
14637 player->is_active = TRUE;
14639 if (player->MovPos == 0)
14641 player->is_moving = FALSE;
14642 player->is_digging = FALSE;
14643 player->is_collecting = FALSE;
14646 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14647 TEST_DrawLevelField(player->last_jx, player->last_jy);
14649 TEST_DrawLevelField(x, y);
14654 static boolean DropElement(struct PlayerInfo *player)
14656 int old_element, new_element;
14657 int dropx = player->jx, dropy = player->jy;
14658 int drop_direction = player->MovDir;
14659 int drop_side = drop_direction;
14660 int drop_element = get_next_dropped_element(player);
14662 /* do not drop an element on top of another element; when holding drop key
14663 pressed without moving, dropped element must move away before the next
14664 element can be dropped (this is especially important if the next element
14665 is dynamite, which can be placed on background for historical reasons) */
14666 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14669 if (IS_THROWABLE(drop_element))
14671 dropx += GET_DX_FROM_DIR(drop_direction);
14672 dropy += GET_DY_FROM_DIR(drop_direction);
14674 if (!IN_LEV_FIELD(dropx, dropy))
14678 old_element = Tile[dropx][dropy]; // old element at dropping position
14679 new_element = drop_element; // default: no change when dropping
14681 // check if player is active, not moving and ready to drop
14682 if (!player->active || player->MovPos || player->drop_delay > 0)
14685 // check if player has anything that can be dropped
14686 if (new_element == EL_UNDEFINED)
14689 // only set if player has anything that can be dropped
14690 player->is_dropping_pressed = TRUE;
14692 // check if drop key was pressed long enough for EM style dynamite
14693 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14696 // check if anything can be dropped at the current position
14697 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14700 // collected custom elements can only be dropped on empty fields
14701 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14704 if (old_element != EL_EMPTY)
14705 Back[dropx][dropy] = old_element; // store old element on this field
14707 ResetGfxAnimation(dropx, dropy);
14708 ResetRandomAnimationValue(dropx, dropy);
14710 if (player->inventory_size > 0 ||
14711 player->inventory_infinite_element != EL_UNDEFINED)
14713 if (player->inventory_size > 0)
14715 player->inventory_size--;
14717 DrawGameDoorValues();
14719 if (new_element == EL_DYNAMITE)
14720 new_element = EL_DYNAMITE_ACTIVE;
14721 else if (new_element == EL_EM_DYNAMITE)
14722 new_element = EL_EM_DYNAMITE_ACTIVE;
14723 else if (new_element == EL_SP_DISK_RED)
14724 new_element = EL_SP_DISK_RED_ACTIVE;
14727 Tile[dropx][dropy] = new_element;
14729 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14730 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14731 el2img(Tile[dropx][dropy]), 0);
14733 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14735 // needed if previous element just changed to "empty" in the last frame
14736 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14738 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14739 player->index_bit, drop_side);
14740 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14742 player->index_bit, drop_side);
14744 TestIfElementTouchesCustomElement(dropx, dropy);
14746 else // player is dropping a dyna bomb
14748 player->dynabombs_left--;
14750 Tile[dropx][dropy] = new_element;
14752 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14753 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14754 el2img(Tile[dropx][dropy]), 0);
14756 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14759 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14760 InitField_WithBug1(dropx, dropy, FALSE);
14762 new_element = Tile[dropx][dropy]; // element might have changed
14764 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14765 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14767 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14768 MovDir[dropx][dropy] = drop_direction;
14770 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14772 // do not cause impact style collision by dropping elements that can fall
14773 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14776 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14777 player->is_dropping = TRUE;
14779 player->drop_pressed_delay = 0;
14780 player->is_dropping_pressed = FALSE;
14782 player->drop_x = dropx;
14783 player->drop_y = dropy;
14788 // ----------------------------------------------------------------------------
14789 // game sound playing functions
14790 // ----------------------------------------------------------------------------
14792 static int *loop_sound_frame = NULL;
14793 static int *loop_sound_volume = NULL;
14795 void InitPlayLevelSound(void)
14797 int num_sounds = getSoundListSize();
14799 checked_free(loop_sound_frame);
14800 checked_free(loop_sound_volume);
14802 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14803 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14806 static void PlayLevelSound(int x, int y, int nr)
14808 int sx = SCREENX(x), sy = SCREENY(y);
14809 int volume, stereo_position;
14810 int max_distance = 8;
14811 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14813 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14814 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14817 if (!IN_LEV_FIELD(x, y) ||
14818 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14819 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14822 volume = SOUND_MAX_VOLUME;
14824 if (!IN_SCR_FIELD(sx, sy))
14826 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14827 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14829 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14832 stereo_position = (SOUND_MAX_LEFT +
14833 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14834 (SCR_FIELDX + 2 * max_distance));
14836 if (IS_LOOP_SOUND(nr))
14838 /* This assures that quieter loop sounds do not overwrite louder ones,
14839 while restarting sound volume comparison with each new game frame. */
14841 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14844 loop_sound_volume[nr] = volume;
14845 loop_sound_frame[nr] = FrameCounter;
14848 PlaySoundExt(nr, volume, stereo_position, type);
14851 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14853 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14854 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14855 y < LEVELY(BY1) ? LEVELY(BY1) :
14856 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14860 static void PlayLevelSoundAction(int x, int y, int action)
14862 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14865 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14867 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14869 if (sound_effect != SND_UNDEFINED)
14870 PlayLevelSound(x, y, sound_effect);
14873 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14876 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14878 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14879 PlayLevelSound(x, y, sound_effect);
14882 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14884 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14886 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14887 PlayLevelSound(x, y, sound_effect);
14890 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14892 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14894 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14895 StopSound(sound_effect);
14898 static int getLevelMusicNr(void)
14900 if (levelset.music[level_nr] != MUS_UNDEFINED)
14901 return levelset.music[level_nr]; // from config file
14903 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14906 static void FadeLevelSounds(void)
14911 static void FadeLevelMusic(void)
14913 int music_nr = getLevelMusicNr();
14914 char *curr_music = getCurrentlyPlayingMusicFilename();
14915 char *next_music = getMusicInfoEntryFilename(music_nr);
14917 if (!strEqual(curr_music, next_music))
14921 void FadeLevelSoundsAndMusic(void)
14927 static void PlayLevelMusic(void)
14929 int music_nr = getLevelMusicNr();
14930 char *curr_music = getCurrentlyPlayingMusicFilename();
14931 char *next_music = getMusicInfoEntryFilename(music_nr);
14933 if (!strEqual(curr_music, next_music))
14934 PlayMusicLoop(music_nr);
14937 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14939 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14941 int x = xx - offset;
14942 int y = yy - offset;
14947 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14951 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14955 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14959 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14963 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14967 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14971 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14974 case SOUND_android_clone:
14975 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14978 case SOUND_android_move:
14979 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14983 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14987 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14991 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14994 case SOUND_eater_eat:
14995 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14999 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15002 case SOUND_collect:
15003 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15006 case SOUND_diamond:
15007 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15011 // !!! CHECK THIS !!!
15013 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15015 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15019 case SOUND_wonderfall:
15020 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15024 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15028 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15032 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15036 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15040 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15044 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15048 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15052 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15055 case SOUND_exit_open:
15056 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15059 case SOUND_exit_leave:
15060 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15063 case SOUND_dynamite:
15064 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15068 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15072 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15076 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15080 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15084 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15088 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15092 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15097 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15099 int element = map_element_SP_to_RND(element_sp);
15100 int action = map_action_SP_to_RND(action_sp);
15101 int offset = (setup.sp_show_border_elements ? 0 : 1);
15102 int x = xx - offset;
15103 int y = yy - offset;
15105 PlayLevelSoundElementAction(x, y, element, action);
15108 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15110 int element = map_element_MM_to_RND(element_mm);
15111 int action = map_action_MM_to_RND(action_mm);
15113 int x = xx - offset;
15114 int y = yy - offset;
15116 if (!IS_MM_ELEMENT(element))
15117 element = EL_MM_DEFAULT;
15119 PlayLevelSoundElementAction(x, y, element, action);
15122 void PlaySound_MM(int sound_mm)
15124 int sound = map_sound_MM_to_RND(sound_mm);
15126 if (sound == SND_UNDEFINED)
15132 void PlaySoundLoop_MM(int sound_mm)
15134 int sound = map_sound_MM_to_RND(sound_mm);
15136 if (sound == SND_UNDEFINED)
15139 PlaySoundLoop(sound);
15142 void StopSound_MM(int sound_mm)
15144 int sound = map_sound_MM_to_RND(sound_mm);
15146 if (sound == SND_UNDEFINED)
15152 void RaiseScore(int value)
15154 game.score += value;
15156 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15158 DisplayGameControlValues();
15161 void RaiseScoreElement(int element)
15166 case EL_BD_DIAMOND:
15167 case EL_EMERALD_YELLOW:
15168 case EL_EMERALD_RED:
15169 case EL_EMERALD_PURPLE:
15170 case EL_SP_INFOTRON:
15171 RaiseScore(level.score[SC_EMERALD]);
15174 RaiseScore(level.score[SC_DIAMOND]);
15177 RaiseScore(level.score[SC_CRYSTAL]);
15180 RaiseScore(level.score[SC_PEARL]);
15183 case EL_BD_BUTTERFLY:
15184 case EL_SP_ELECTRON:
15185 RaiseScore(level.score[SC_BUG]);
15188 case EL_BD_FIREFLY:
15189 case EL_SP_SNIKSNAK:
15190 RaiseScore(level.score[SC_SPACESHIP]);
15193 case EL_DARK_YAMYAM:
15194 RaiseScore(level.score[SC_YAMYAM]);
15197 RaiseScore(level.score[SC_ROBOT]);
15200 RaiseScore(level.score[SC_PACMAN]);
15203 RaiseScore(level.score[SC_NUT]);
15206 case EL_EM_DYNAMITE:
15207 case EL_SP_DISK_RED:
15208 case EL_DYNABOMB_INCREASE_NUMBER:
15209 case EL_DYNABOMB_INCREASE_SIZE:
15210 case EL_DYNABOMB_INCREASE_POWER:
15211 RaiseScore(level.score[SC_DYNAMITE]);
15213 case EL_SHIELD_NORMAL:
15214 case EL_SHIELD_DEADLY:
15215 RaiseScore(level.score[SC_SHIELD]);
15217 case EL_EXTRA_TIME:
15218 RaiseScore(level.extra_time_score);
15232 case EL_DC_KEY_WHITE:
15233 RaiseScore(level.score[SC_KEY]);
15236 RaiseScore(element_info[element].collect_score);
15241 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15243 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15245 // closing door required in case of envelope style request dialogs
15248 // prevent short reactivation of overlay buttons while closing door
15249 SetOverlayActive(FALSE);
15251 CloseDoor(DOOR_CLOSE_1);
15254 if (network.enabled)
15255 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15259 FadeSkipNextFadeIn();
15261 SetGameStatus(GAME_MODE_MAIN);
15266 else // continue playing the game
15268 if (tape.playing && tape.deactivate_display)
15269 TapeDeactivateDisplayOff(TRUE);
15271 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15273 if (tape.playing && tape.deactivate_display)
15274 TapeDeactivateDisplayOn();
15278 void RequestQuitGame(boolean ask_if_really_quit)
15280 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15281 boolean skip_request = game.all_players_gone || quick_quit;
15283 RequestQuitGameExt(skip_request, quick_quit,
15284 "Do you really want to quit the game?");
15287 void RequestRestartGame(char *message)
15289 game.restart_game_message = NULL;
15291 boolean has_started_game = hasStartedNetworkGame();
15292 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15294 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15296 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15300 // needed in case of envelope request to close game panel
15301 CloseDoor(DOOR_CLOSE_1);
15303 SetGameStatus(GAME_MODE_MAIN);
15309 void CheckGameOver(void)
15311 static boolean last_game_over = FALSE;
15312 static int game_over_delay = 0;
15313 int game_over_delay_value = 50;
15314 boolean game_over = checkGameFailed();
15316 // do not handle game over if request dialog is already active
15317 if (game.request_active)
15320 // do not ask to play again if game was never actually played
15321 if (!game.GamePlayed)
15326 last_game_over = FALSE;
15327 game_over_delay = game_over_delay_value;
15332 if (game_over_delay > 0)
15339 if (last_game_over != game_over)
15340 game.restart_game_message = (hasStartedNetworkGame() ?
15341 "Game over! Play it again?" :
15344 last_game_over = game_over;
15347 boolean checkGameSolved(void)
15349 // set for all game engines if level was solved
15350 return game.LevelSolved_GameEnd;
15353 boolean checkGameFailed(void)
15355 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15356 return (game_em.game_over && !game_em.level_solved);
15357 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15358 return (game_sp.game_over && !game_sp.level_solved);
15359 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15360 return (game_mm.game_over && !game_mm.level_solved);
15361 else // GAME_ENGINE_TYPE_RND
15362 return (game.GameOver && !game.LevelSolved);
15365 boolean checkGameEnded(void)
15367 return (checkGameSolved() || checkGameFailed());
15371 // ----------------------------------------------------------------------------
15372 // random generator functions
15373 // ----------------------------------------------------------------------------
15375 unsigned int InitEngineRandom_RND(int seed)
15377 game.num_random_calls = 0;
15379 return InitEngineRandom(seed);
15382 unsigned int RND(int max)
15386 game.num_random_calls++;
15388 return GetEngineRandom(max);
15395 // ----------------------------------------------------------------------------
15396 // game engine snapshot handling functions
15397 // ----------------------------------------------------------------------------
15399 struct EngineSnapshotInfo
15401 // runtime values for custom element collect score
15402 int collect_score[NUM_CUSTOM_ELEMENTS];
15404 // runtime values for group element choice position
15405 int choice_pos[NUM_GROUP_ELEMENTS];
15407 // runtime values for belt position animations
15408 int belt_graphic[4][NUM_BELT_PARTS];
15409 int belt_anim_mode[4][NUM_BELT_PARTS];
15412 static struct EngineSnapshotInfo engine_snapshot_rnd;
15413 static char *snapshot_level_identifier = NULL;
15414 static int snapshot_level_nr = -1;
15416 static void SaveEngineSnapshotValues_RND(void)
15418 static int belt_base_active_element[4] =
15420 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15421 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15422 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15423 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15427 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15429 int element = EL_CUSTOM_START + i;
15431 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15434 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15436 int element = EL_GROUP_START + i;
15438 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15441 for (i = 0; i < 4; i++)
15443 for (j = 0; j < NUM_BELT_PARTS; j++)
15445 int element = belt_base_active_element[i] + j;
15446 int graphic = el2img(element);
15447 int anim_mode = graphic_info[graphic].anim_mode;
15449 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15450 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15455 static void LoadEngineSnapshotValues_RND(void)
15457 unsigned int num_random_calls = game.num_random_calls;
15460 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15462 int element = EL_CUSTOM_START + i;
15464 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15467 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15469 int element = EL_GROUP_START + i;
15471 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15474 for (i = 0; i < 4; i++)
15476 for (j = 0; j < NUM_BELT_PARTS; j++)
15478 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15479 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15481 graphic_info[graphic].anim_mode = anim_mode;
15485 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15487 InitRND(tape.random_seed);
15488 for (i = 0; i < num_random_calls; i++)
15492 if (game.num_random_calls != num_random_calls)
15494 Error("number of random calls out of sync");
15495 Error("number of random calls should be %d", num_random_calls);
15496 Error("number of random calls is %d", game.num_random_calls);
15498 Fail("this should not happen -- please debug");
15502 void FreeEngineSnapshotSingle(void)
15504 FreeSnapshotSingle();
15506 setString(&snapshot_level_identifier, NULL);
15507 snapshot_level_nr = -1;
15510 void FreeEngineSnapshotList(void)
15512 FreeSnapshotList();
15515 static ListNode *SaveEngineSnapshotBuffers(void)
15517 ListNode *buffers = NULL;
15519 // copy some special values to a structure better suited for the snapshot
15521 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15522 SaveEngineSnapshotValues_RND();
15523 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15524 SaveEngineSnapshotValues_EM();
15525 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15526 SaveEngineSnapshotValues_SP(&buffers);
15527 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15528 SaveEngineSnapshotValues_MM(&buffers);
15530 // save values stored in special snapshot structure
15532 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15533 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15534 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15535 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15536 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15537 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15538 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15539 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15541 // save further RND engine values
15543 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15544 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15545 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15547 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15548 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15549 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15550 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15551 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15553 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15554 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15557 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15560 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15562 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15563 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15564 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15566 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15568 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15574 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15578 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15586 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15601 ListNode *node = engine_snapshot_list_rnd;
15604 while (node != NULL)
15606 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15611 Debug("game:playing:SaveEngineSnapshotBuffers",
15612 "size of engine snapshot: %d bytes", num_bytes);
15618 void SaveEngineSnapshotSingle(void)
15620 ListNode *buffers = SaveEngineSnapshotBuffers();
15622 // finally save all snapshot buffers to single snapshot
15623 SaveSnapshotSingle(buffers);
15625 // save level identification information
15626 setString(&snapshot_level_identifier, leveldir_current->identifier);
15627 snapshot_level_nr = level_nr;
15630 boolean CheckSaveEngineSnapshotToList(void)
15632 boolean save_snapshot =
15633 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15634 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15635 game.snapshot.changed_action) ||
15636 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15637 game.snapshot.collected_item));
15639 game.snapshot.changed_action = FALSE;
15640 game.snapshot.collected_item = FALSE;
15641 game.snapshot.save_snapshot = save_snapshot;
15643 return save_snapshot;
15646 void SaveEngineSnapshotToList(void)
15648 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15652 ListNode *buffers = SaveEngineSnapshotBuffers();
15654 // finally save all snapshot buffers to snapshot list
15655 SaveSnapshotToList(buffers);
15658 void SaveEngineSnapshotToListInitial(void)
15660 FreeEngineSnapshotList();
15662 SaveEngineSnapshotToList();
15665 static void LoadEngineSnapshotValues(void)
15667 // restore special values from snapshot structure
15669 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15670 LoadEngineSnapshotValues_RND();
15671 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15672 LoadEngineSnapshotValues_EM();
15673 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15674 LoadEngineSnapshotValues_SP();
15675 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15676 LoadEngineSnapshotValues_MM();
15679 void LoadEngineSnapshotSingle(void)
15681 LoadSnapshotSingle();
15683 LoadEngineSnapshotValues();
15686 static void LoadEngineSnapshot_Undo(int steps)
15688 LoadSnapshotFromList_Older(steps);
15690 LoadEngineSnapshotValues();
15693 static void LoadEngineSnapshot_Redo(int steps)
15695 LoadSnapshotFromList_Newer(steps);
15697 LoadEngineSnapshotValues();
15700 boolean CheckEngineSnapshotSingle(void)
15702 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15703 snapshot_level_nr == level_nr);
15706 boolean CheckEngineSnapshotList(void)
15708 return CheckSnapshotList();
15712 // ---------- new game button stuff -------------------------------------------
15719 boolean *setup_value;
15720 boolean allowed_on_tape;
15721 boolean is_touch_button;
15723 } gamebutton_info[NUM_GAME_BUTTONS] =
15726 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15727 GAME_CTRL_ID_STOP, NULL,
15728 TRUE, FALSE, "stop game"
15731 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15732 GAME_CTRL_ID_PAUSE, NULL,
15733 TRUE, FALSE, "pause game"
15736 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15737 GAME_CTRL_ID_PLAY, NULL,
15738 TRUE, FALSE, "play game"
15741 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15742 GAME_CTRL_ID_UNDO, NULL,
15743 TRUE, FALSE, "undo step"
15746 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15747 GAME_CTRL_ID_REDO, NULL,
15748 TRUE, FALSE, "redo step"
15751 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15752 GAME_CTRL_ID_SAVE, NULL,
15753 TRUE, FALSE, "save game"
15756 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15757 GAME_CTRL_ID_PAUSE2, NULL,
15758 TRUE, FALSE, "pause game"
15761 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15762 GAME_CTRL_ID_LOAD, NULL,
15763 TRUE, FALSE, "load game"
15766 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15767 GAME_CTRL_ID_PANEL_STOP, NULL,
15768 FALSE, FALSE, "stop game"
15771 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15772 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15773 FALSE, FALSE, "pause game"
15776 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15777 GAME_CTRL_ID_PANEL_PLAY, NULL,
15778 FALSE, FALSE, "play game"
15781 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15782 GAME_CTRL_ID_TOUCH_STOP, NULL,
15783 FALSE, TRUE, "stop game"
15786 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15787 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15788 FALSE, TRUE, "pause game"
15791 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15792 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15793 TRUE, FALSE, "background music on/off"
15796 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15797 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15798 TRUE, FALSE, "sound loops on/off"
15801 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15802 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15803 TRUE, FALSE, "normal sounds on/off"
15806 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15807 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15808 FALSE, FALSE, "background music on/off"
15811 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15812 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15813 FALSE, FALSE, "sound loops on/off"
15816 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15817 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15818 FALSE, FALSE, "normal sounds on/off"
15822 void CreateGameButtons(void)
15826 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15828 int graphic = gamebutton_info[i].graphic;
15829 struct GraphicInfo *gfx = &graphic_info[graphic];
15830 struct XY *pos = gamebutton_info[i].pos;
15831 struct GadgetInfo *gi;
15834 unsigned int event_mask;
15835 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15836 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15837 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15838 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15839 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15840 int gd_x = gfx->src_x;
15841 int gd_y = gfx->src_y;
15842 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15843 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15844 int gd_xa = gfx->src_x + gfx->active_xoffset;
15845 int gd_ya = gfx->src_y + gfx->active_yoffset;
15846 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15847 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15848 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15849 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15852 if (gfx->bitmap == NULL)
15854 game_gadget[id] = NULL;
15859 if (id == GAME_CTRL_ID_STOP ||
15860 id == GAME_CTRL_ID_PANEL_STOP ||
15861 id == GAME_CTRL_ID_TOUCH_STOP ||
15862 id == GAME_CTRL_ID_PLAY ||
15863 id == GAME_CTRL_ID_PANEL_PLAY ||
15864 id == GAME_CTRL_ID_SAVE ||
15865 id == GAME_CTRL_ID_LOAD)
15867 button_type = GD_TYPE_NORMAL_BUTTON;
15869 event_mask = GD_EVENT_RELEASED;
15871 else if (id == GAME_CTRL_ID_UNDO ||
15872 id == GAME_CTRL_ID_REDO)
15874 button_type = GD_TYPE_NORMAL_BUTTON;
15876 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15880 button_type = GD_TYPE_CHECK_BUTTON;
15881 checked = (gamebutton_info[i].setup_value != NULL ?
15882 *gamebutton_info[i].setup_value : FALSE);
15883 event_mask = GD_EVENT_PRESSED;
15886 gi = CreateGadget(GDI_CUSTOM_ID, id,
15887 GDI_IMAGE_ID, graphic,
15888 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15891 GDI_WIDTH, gfx->width,
15892 GDI_HEIGHT, gfx->height,
15893 GDI_TYPE, button_type,
15894 GDI_STATE, GD_BUTTON_UNPRESSED,
15895 GDI_CHECKED, checked,
15896 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15897 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15898 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15899 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15900 GDI_DIRECT_DRAW, FALSE,
15901 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15902 GDI_EVENT_MASK, event_mask,
15903 GDI_CALLBACK_ACTION, HandleGameButtons,
15907 Fail("cannot create gadget");
15909 game_gadget[id] = gi;
15913 void FreeGameButtons(void)
15917 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15918 FreeGadget(game_gadget[i]);
15921 static void UnmapGameButtonsAtSamePosition(int id)
15925 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15927 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15928 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15929 UnmapGadget(game_gadget[i]);
15932 static void UnmapGameButtonsAtSamePosition_All(void)
15934 if (setup.show_snapshot_buttons)
15936 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15937 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15938 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15942 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15943 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15944 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15946 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15947 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15948 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15952 static void MapGameButtonsAtSamePosition(int id)
15956 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15958 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15959 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15960 MapGadget(game_gadget[i]);
15962 UnmapGameButtonsAtSamePosition_All();
15965 void MapUndoRedoButtons(void)
15967 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15968 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15970 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15971 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15974 void UnmapUndoRedoButtons(void)
15976 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15977 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15979 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15980 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15983 void ModifyPauseButtons(void)
15987 GAME_CTRL_ID_PAUSE,
15988 GAME_CTRL_ID_PAUSE2,
15989 GAME_CTRL_ID_PANEL_PAUSE,
15990 GAME_CTRL_ID_TOUCH_PAUSE,
15995 for (i = 0; ids[i] > -1; i++)
15996 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15999 static void MapGameButtonsExt(boolean on_tape)
16003 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16004 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16005 i != GAME_CTRL_ID_UNDO &&
16006 i != GAME_CTRL_ID_REDO)
16007 MapGadget(game_gadget[i]);
16009 UnmapGameButtonsAtSamePosition_All();
16011 RedrawGameButtons();
16014 static void UnmapGameButtonsExt(boolean on_tape)
16018 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16019 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16020 UnmapGadget(game_gadget[i]);
16023 static void RedrawGameButtonsExt(boolean on_tape)
16027 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16028 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16029 RedrawGadget(game_gadget[i]);
16032 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16037 gi->checked = state;
16040 static void RedrawSoundButtonGadget(int id)
16042 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16043 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16044 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16045 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16046 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16047 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16050 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16051 RedrawGadget(game_gadget[id2]);
16054 void MapGameButtons(void)
16056 MapGameButtonsExt(FALSE);
16059 void UnmapGameButtons(void)
16061 UnmapGameButtonsExt(FALSE);
16064 void RedrawGameButtons(void)
16066 RedrawGameButtonsExt(FALSE);
16069 void MapGameButtonsOnTape(void)
16071 MapGameButtonsExt(TRUE);
16074 void UnmapGameButtonsOnTape(void)
16076 UnmapGameButtonsExt(TRUE);
16079 void RedrawGameButtonsOnTape(void)
16081 RedrawGameButtonsExt(TRUE);
16084 static void GameUndoRedoExt(void)
16086 ClearPlayerAction();
16088 tape.pausing = TRUE;
16091 UpdateAndDisplayGameControlValues();
16093 DrawCompleteVideoDisplay();
16094 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16095 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16096 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16101 static void GameUndo(int steps)
16103 if (!CheckEngineSnapshotList())
16106 LoadEngineSnapshot_Undo(steps);
16111 static void GameRedo(int steps)
16113 if (!CheckEngineSnapshotList())
16116 LoadEngineSnapshot_Redo(steps);
16121 static void HandleGameButtonsExt(int id, int button)
16123 static boolean game_undo_executed = FALSE;
16124 int steps = BUTTON_STEPSIZE(button);
16125 boolean handle_game_buttons =
16126 (game_status == GAME_MODE_PLAYING ||
16127 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16129 if (!handle_game_buttons)
16134 case GAME_CTRL_ID_STOP:
16135 case GAME_CTRL_ID_PANEL_STOP:
16136 case GAME_CTRL_ID_TOUCH_STOP:
16137 if (game_status == GAME_MODE_MAIN)
16143 RequestQuitGame(TRUE);
16147 case GAME_CTRL_ID_PAUSE:
16148 case GAME_CTRL_ID_PAUSE2:
16149 case GAME_CTRL_ID_PANEL_PAUSE:
16150 case GAME_CTRL_ID_TOUCH_PAUSE:
16151 if (network.enabled && game_status == GAME_MODE_PLAYING)
16154 SendToServer_ContinuePlaying();
16156 SendToServer_PausePlaying();
16159 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16161 game_undo_executed = FALSE;
16165 case GAME_CTRL_ID_PLAY:
16166 case GAME_CTRL_ID_PANEL_PLAY:
16167 if (game_status == GAME_MODE_MAIN)
16169 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16171 else if (tape.pausing)
16173 if (network.enabled)
16174 SendToServer_ContinuePlaying();
16176 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16180 case GAME_CTRL_ID_UNDO:
16181 // Important: When using "save snapshot when collecting an item" mode,
16182 // load last (current) snapshot for first "undo" after pressing "pause"
16183 // (else the last-but-one snapshot would be loaded, because the snapshot
16184 // pointer already points to the last snapshot when pressing "pause",
16185 // which is fine for "every step/move" mode, but not for "every collect")
16186 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16187 !game_undo_executed)
16190 game_undo_executed = TRUE;
16195 case GAME_CTRL_ID_REDO:
16199 case GAME_CTRL_ID_SAVE:
16203 case GAME_CTRL_ID_LOAD:
16207 case SOUND_CTRL_ID_MUSIC:
16208 case SOUND_CTRL_ID_PANEL_MUSIC:
16209 if (setup.sound_music)
16211 setup.sound_music = FALSE;
16215 else if (audio.music_available)
16217 setup.sound = setup.sound_music = TRUE;
16219 SetAudioMode(setup.sound);
16221 if (game_status == GAME_MODE_PLAYING)
16225 RedrawSoundButtonGadget(id);
16229 case SOUND_CTRL_ID_LOOPS:
16230 case SOUND_CTRL_ID_PANEL_LOOPS:
16231 if (setup.sound_loops)
16232 setup.sound_loops = FALSE;
16233 else if (audio.loops_available)
16235 setup.sound = setup.sound_loops = TRUE;
16237 SetAudioMode(setup.sound);
16240 RedrawSoundButtonGadget(id);
16244 case SOUND_CTRL_ID_SIMPLE:
16245 case SOUND_CTRL_ID_PANEL_SIMPLE:
16246 if (setup.sound_simple)
16247 setup.sound_simple = FALSE;
16248 else if (audio.sound_available)
16250 setup.sound = setup.sound_simple = TRUE;
16252 SetAudioMode(setup.sound);
16255 RedrawSoundButtonGadget(id);
16264 static void HandleGameButtons(struct GadgetInfo *gi)
16266 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16269 void HandleSoundButtonKeys(Key key)
16271 if (key == setup.shortcut.sound_simple)
16272 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16273 else if (key == setup.shortcut.sound_loops)
16274 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16275 else if (key == setup.shortcut.sound_music)
16276 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);