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 // re-arrange keys on game panel, if needed or if defined by style settings
2330 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2332 int nr = GAME_PANEL_KEY_1 + i;
2333 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2334 struct TextPosInfo *pos = gpc->pos;
2336 // skip check if key is not in the player's inventory
2337 if (gpc->value == EL_EMPTY)
2340 // check if keys should be arranged on panel from left to right
2341 if (pos->style == STYLE_LEFTMOST_POSITION)
2343 // check previous key positions (left from current key)
2344 for (k = 0; k < i; k++)
2346 int nr_new = GAME_PANEL_KEY_1 + k;
2348 if (game_panel_controls[nr_new].value == EL_EMPTY)
2350 game_panel_controls[nr_new].value = gpc->value;
2351 gpc->value = EL_EMPTY;
2358 // check if "undefined" keys can be placed at some other position
2359 if (pos->x == -1 && pos->y == -1)
2361 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2363 // 1st try: display key at the same position as normal or EM keys
2364 if (game_panel_controls[nr_new].value == EL_EMPTY)
2366 game_panel_controls[nr_new].value = gpc->value;
2370 // 2nd try: display key at the next free position in the key panel
2371 for (k = 0; k < STD_NUM_KEYS; k++)
2373 nr_new = GAME_PANEL_KEY_1 + k;
2375 if (game_panel_controls[nr_new].value == EL_EMPTY)
2377 game_panel_controls[nr_new].value = gpc->value;
2386 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2388 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2389 get_inventory_element_from_pos(local_player, i);
2390 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2391 get_inventory_element_from_pos(local_player, -i - 1);
2394 game_panel_controls[GAME_PANEL_SCORE].value = score;
2395 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2397 game_panel_controls[GAME_PANEL_TIME].value = time;
2399 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2400 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2401 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2403 if (level.time == 0)
2404 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2406 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2408 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2409 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2411 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2413 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2414 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2416 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2417 local_player->shield_normal_time_left;
2418 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2419 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2421 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2422 local_player->shield_deadly_time_left;
2424 game_panel_controls[GAME_PANEL_EXIT].value =
2425 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2427 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2428 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2429 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2430 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2431 EL_EMC_MAGIC_BALL_SWITCH);
2433 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2434 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2435 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2436 game.light_time_left;
2438 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2439 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2440 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2441 game.timegate_time_left;
2443 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2444 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2446 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2447 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2448 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2449 game.lenses_time_left;
2451 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2452 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2453 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2454 game.magnify_time_left;
2456 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2457 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2458 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2459 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2460 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2461 EL_BALLOON_SWITCH_NONE);
2463 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2464 local_player->dynabomb_count;
2465 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2466 local_player->dynabomb_size;
2467 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2468 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2470 game_panel_controls[GAME_PANEL_PENGUINS].value =
2471 game.friends_still_needed;
2473 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2474 game.sokoban_objects_still_needed;
2475 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2476 game.sokoban_fields_still_needed;
2478 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2479 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2481 for (i = 0; i < NUM_BELTS; i++)
2483 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2484 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2485 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2486 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2487 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2490 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2491 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2492 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2493 game.magic_wall_time_left;
2495 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2496 local_player->gravity;
2498 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2499 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2501 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2502 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2503 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2504 game.panel.element[i].id : EL_UNDEFINED);
2506 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2507 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2508 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2509 element_info[game.panel.element_count[i].id].element_count : 0);
2511 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2512 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2513 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2514 element_info[game.panel.ce_score[i].id].collect_score : 0);
2516 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2517 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2518 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2519 element_info[game.panel.ce_score_element[i].id].collect_score :
2522 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2523 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2524 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2526 // update game panel control frames
2528 for (i = 0; game_panel_controls[i].nr != -1; i++)
2530 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2532 if (gpc->type == TYPE_ELEMENT)
2534 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2536 int last_anim_random_frame = gfx.anim_random_frame;
2537 int element = gpc->value;
2538 int graphic = el2panelimg(element);
2539 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2540 sync_random_frame : INIT_GFX_RANDOM());
2542 if (gpc->value != gpc->last_value)
2545 gpc->gfx_random = init_gfx_random;
2551 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2552 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2553 gpc->gfx_random = init_gfx_random;
2556 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2557 gfx.anim_random_frame = gpc->gfx_random;
2559 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2560 gpc->gfx_frame = element_info[element].collect_score;
2562 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2564 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2565 gfx.anim_random_frame = last_anim_random_frame;
2568 else if (gpc->type == TYPE_GRAPHIC)
2570 if (gpc->graphic != IMG_UNDEFINED)
2572 int last_anim_random_frame = gfx.anim_random_frame;
2573 int graphic = gpc->graphic;
2574 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2575 sync_random_frame : INIT_GFX_RANDOM());
2577 if (gpc->value != gpc->last_value)
2580 gpc->gfx_random = init_gfx_random;
2586 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2587 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2588 gpc->gfx_random = init_gfx_random;
2591 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2592 gfx.anim_random_frame = gpc->gfx_random;
2594 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2597 gfx.anim_random_frame = last_anim_random_frame;
2603 static void DisplayGameControlValues(void)
2605 boolean redraw_panel = FALSE;
2608 for (i = 0; game_panel_controls[i].nr != -1; i++)
2610 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2612 if (PANEL_DEACTIVATED(gpc->pos))
2615 if (gpc->value == gpc->last_value &&
2616 gpc->frame == gpc->last_frame)
2619 redraw_panel = TRUE;
2625 // copy default game door content to main double buffer
2627 // !!! CHECK AGAIN !!!
2628 SetPanelBackground();
2629 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2630 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2632 // redraw game control buttons
2633 RedrawGameButtons();
2635 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2637 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2639 int nr = game_panel_order[i].nr;
2640 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2641 struct TextPosInfo *pos = gpc->pos;
2642 int type = gpc->type;
2643 int value = gpc->value;
2644 int frame = gpc->frame;
2645 int size = pos->size;
2646 int font = pos->font;
2647 boolean draw_masked = pos->draw_masked;
2648 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2650 if (PANEL_DEACTIVATED(pos))
2653 if (pos->class == get_hash_from_key("extra_panel_items") &&
2654 !setup.prefer_extra_panel_items)
2657 gpc->last_value = value;
2658 gpc->last_frame = frame;
2660 if (type == TYPE_INTEGER)
2662 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2663 nr == GAME_PANEL_TIME)
2665 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2667 if (use_dynamic_size) // use dynamic number of digits
2669 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2670 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2671 int size2 = size1 + 1;
2672 int font1 = pos->font;
2673 int font2 = pos->font_alt;
2675 size = (value < value_change ? size1 : size2);
2676 font = (value < value_change ? font1 : font2);
2680 // correct text size if "digits" is zero or less
2682 size = strlen(int2str(value, size));
2684 // dynamically correct text alignment
2685 pos->width = size * getFontWidth(font);
2687 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2688 int2str(value, size), font, mask_mode);
2690 else if (type == TYPE_ELEMENT)
2692 int element, graphic;
2696 int dst_x = PANEL_XPOS(pos);
2697 int dst_y = PANEL_YPOS(pos);
2699 if (value != EL_UNDEFINED && value != EL_EMPTY)
2702 graphic = el2panelimg(value);
2705 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2706 element, EL_NAME(element), size);
2709 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2712 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2715 width = graphic_info[graphic].width * size / TILESIZE;
2716 height = graphic_info[graphic].height * size / TILESIZE;
2719 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2722 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2726 else if (type == TYPE_GRAPHIC)
2728 int graphic = gpc->graphic;
2729 int graphic_active = gpc->graphic_active;
2733 int dst_x = PANEL_XPOS(pos);
2734 int dst_y = PANEL_YPOS(pos);
2735 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2736 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2738 if (graphic != IMG_UNDEFINED && !skip)
2740 if (pos->style == STYLE_REVERSE)
2741 value = 100 - value;
2743 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2745 if (pos->direction & MV_HORIZONTAL)
2747 width = graphic_info[graphic_active].width * value / 100;
2748 height = graphic_info[graphic_active].height;
2750 if (pos->direction == MV_LEFT)
2752 src_x += graphic_info[graphic_active].width - width;
2753 dst_x += graphic_info[graphic_active].width - width;
2758 width = graphic_info[graphic_active].width;
2759 height = graphic_info[graphic_active].height * value / 100;
2761 if (pos->direction == MV_UP)
2763 src_y += graphic_info[graphic_active].height - height;
2764 dst_y += graphic_info[graphic_active].height - height;
2769 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2772 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2775 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2777 if (pos->direction & MV_HORIZONTAL)
2779 if (pos->direction == MV_RIGHT)
2786 dst_x = PANEL_XPOS(pos);
2789 width = graphic_info[graphic].width - width;
2793 if (pos->direction == MV_DOWN)
2800 dst_y = PANEL_YPOS(pos);
2803 height = graphic_info[graphic].height - height;
2807 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2810 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2814 else if (type == TYPE_STRING)
2816 boolean active = (value != 0);
2817 char *state_normal = "off";
2818 char *state_active = "on";
2819 char *state = (active ? state_active : state_normal);
2820 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2821 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2822 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2823 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2825 if (nr == GAME_PANEL_GRAVITY_STATE)
2827 int font1 = pos->font; // (used for normal state)
2828 int font2 = pos->font_alt; // (used for active state)
2830 font = (active ? font2 : font1);
2839 // don't truncate output if "chars" is zero or less
2842 // dynamically correct text alignment
2843 pos->width = size * getFontWidth(font);
2846 s_cut = getStringCopyN(s, size);
2848 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2849 s_cut, font, mask_mode);
2855 redraw_mask |= REDRAW_DOOR_1;
2858 SetGameStatus(GAME_MODE_PLAYING);
2861 void UpdateAndDisplayGameControlValues(void)
2863 if (tape.deactivate_display)
2866 UpdateGameControlValues();
2867 DisplayGameControlValues();
2871 static void UpdateGameDoorValues(void)
2873 UpdateGameControlValues();
2877 void DrawGameDoorValues(void)
2879 DisplayGameControlValues();
2883 // ============================================================================
2885 // ----------------------------------------------------------------------------
2886 // initialize game engine due to level / tape version number
2887 // ============================================================================
2889 static void InitGameEngine(void)
2891 int i, j, k, l, x, y;
2893 // set game engine from tape file when re-playing, else from level file
2894 game.engine_version = (tape.playing ? tape.engine_version :
2895 level.game_version);
2897 // set single or multi-player game mode (needed for re-playing tapes)
2898 game.team_mode = setup.team_mode;
2902 int num_players = 0;
2904 for (i = 0; i < MAX_PLAYERS; i++)
2905 if (tape.player_participates[i])
2908 // multi-player tapes contain input data for more than one player
2909 game.team_mode = (num_players > 1);
2913 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2914 level.game_version);
2915 Debug("game:init:level", " tape.file_version == %06d",
2917 Debug("game:init:level", " tape.game_version == %06d",
2919 Debug("game:init:level", " tape.engine_version == %06d",
2920 tape.engine_version);
2921 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2922 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2925 // --------------------------------------------------------------------------
2926 // set flags for bugs and changes according to active game engine version
2927 // --------------------------------------------------------------------------
2931 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2933 Bug was introduced in version:
2936 Bug was fixed in version:
2940 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2941 but the property "can fall" was missing, which caused some levels to be
2942 unsolvable. This was fixed in version 4.2.0.0.
2944 Affected levels/tapes:
2945 An example for a tape that was fixed by this bugfix is tape 029 from the
2946 level set "rnd_sam_bateman".
2947 The wrong behaviour will still be used for all levels or tapes that were
2948 created/recorded with it. An example for this is tape 023 from the level
2949 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2952 boolean use_amoeba_dropping_cannot_fall_bug =
2953 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2954 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2956 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2957 tape.game_version < VERSION_IDENT(4,2,0,0)));
2960 Summary of bugfix/change:
2961 Fixed move speed of elements entering or leaving magic wall.
2963 Fixed/changed in version:
2967 Before 2.0.1, move speed of elements entering or leaving magic wall was
2968 twice as fast as it is now.
2969 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2971 Affected levels/tapes:
2972 The first condition is generally needed for all levels/tapes before version
2973 2.0.1, which might use the old behaviour before it was changed; known tapes
2974 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2975 The second condition is an exception from the above case and is needed for
2976 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2977 above, but before it was known that this change would break tapes like the
2978 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2979 although the engine version while recording maybe was before 2.0.1. There
2980 are a lot of tapes that are affected by this exception, like tape 006 from
2981 the level set "rnd_conor_mancone".
2984 boolean use_old_move_stepsize_for_magic_wall =
2985 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2987 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2988 tape.game_version < VERSION_IDENT(4,2,0,0)));
2991 Summary of bugfix/change:
2992 Fixed handling for custom elements that change when pushed by the player.
2994 Fixed/changed in version:
2998 Before 3.1.0, custom elements that "change when pushing" changed directly
2999 after the player started pushing them (until then handled in "DigField()").
3000 Since 3.1.0, these custom elements are not changed until the "pushing"
3001 move of the element is finished (now handled in "ContinueMoving()").
3003 Affected levels/tapes:
3004 The first condition is generally needed for all levels/tapes before version
3005 3.1.0, which might use the old behaviour before it was changed; known tapes
3006 that are affected are some tapes from the level set "Walpurgis Gardens" by
3008 The second condition is an exception from the above case and is needed for
3009 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3010 above (including some development versions of 3.1.0), but before it was
3011 known that this change would break tapes like the above and was fixed in
3012 3.1.1, so that the changed behaviour was active although the engine version
3013 while recording maybe was before 3.1.0. There is at least one tape that is
3014 affected by this exception, which is the tape for the one-level set "Bug
3015 Machine" by Juergen Bonhagen.
3018 game.use_change_when_pushing_bug =
3019 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3021 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3022 tape.game_version < VERSION_IDENT(3,1,1,0)));
3025 Summary of bugfix/change:
3026 Fixed handling for blocking the field the player leaves when moving.
3028 Fixed/changed in version:
3032 Before 3.1.1, when "block last field when moving" was enabled, the field
3033 the player is leaving when moving was blocked for the time of the move,
3034 and was directly unblocked afterwards. This resulted in the last field
3035 being blocked for exactly one less than the number of frames of one player
3036 move. Additionally, even when blocking was disabled, the last field was
3037 blocked for exactly one frame.
3038 Since 3.1.1, due to changes in player movement handling, the last field
3039 is not blocked at all when blocking is disabled. When blocking is enabled,
3040 the last field is blocked for exactly the number of frames of one player
3041 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3042 last field is blocked for exactly one more than the number of frames of
3045 Affected levels/tapes:
3046 (!!! yet to be determined -- probably many !!!)
3049 game.use_block_last_field_bug =
3050 (game.engine_version < VERSION_IDENT(3,1,1,0));
3052 /* various special flags and settings for native Emerald Mine game engine */
3054 game_em.use_single_button =
3055 (game.engine_version > VERSION_IDENT(4,0,0,2));
3057 game_em.use_snap_key_bug =
3058 (game.engine_version < VERSION_IDENT(4,0,1,0));
3060 game_em.use_random_bug =
3061 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3063 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3065 game_em.use_old_explosions = use_old_em_engine;
3066 game_em.use_old_android = use_old_em_engine;
3067 game_em.use_old_push_elements = use_old_em_engine;
3068 game_em.use_old_push_into_acid = use_old_em_engine;
3070 game_em.use_wrap_around = !use_old_em_engine;
3072 // --------------------------------------------------------------------------
3074 // set maximal allowed number of custom element changes per game frame
3075 game.max_num_changes_per_frame = 1;
3077 // default scan direction: scan playfield from top/left to bottom/right
3078 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3080 // dynamically adjust element properties according to game engine version
3081 InitElementPropertiesEngine(game.engine_version);
3083 // ---------- initialize special element properties -------------------------
3085 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3086 if (use_amoeba_dropping_cannot_fall_bug)
3087 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3089 // ---------- initialize player's initial move delay ------------------------
3091 // dynamically adjust player properties according to level information
3092 for (i = 0; i < MAX_PLAYERS; i++)
3093 game.initial_move_delay_value[i] =
3094 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3096 // dynamically adjust player properties according to game engine version
3097 for (i = 0; i < MAX_PLAYERS; i++)
3098 game.initial_move_delay[i] =
3099 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3100 game.initial_move_delay_value[i] : 0);
3102 // ---------- initialize player's initial push delay ------------------------
3104 // dynamically adjust player properties according to game engine version
3105 game.initial_push_delay_value =
3106 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3108 // ---------- initialize changing elements ----------------------------------
3110 // initialize changing elements information
3111 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3113 struct ElementInfo *ei = &element_info[i];
3115 // this pointer might have been changed in the level editor
3116 ei->change = &ei->change_page[0];
3118 if (!IS_CUSTOM_ELEMENT(i))
3120 ei->change->target_element = EL_EMPTY_SPACE;
3121 ei->change->delay_fixed = 0;
3122 ei->change->delay_random = 0;
3123 ei->change->delay_frames = 1;
3126 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3128 ei->has_change_event[j] = FALSE;
3130 ei->event_page_nr[j] = 0;
3131 ei->event_page[j] = &ei->change_page[0];
3135 // add changing elements from pre-defined list
3136 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3138 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3139 struct ElementInfo *ei = &element_info[ch_delay->element];
3141 ei->change->target_element = ch_delay->target_element;
3142 ei->change->delay_fixed = ch_delay->change_delay;
3144 ei->change->pre_change_function = ch_delay->pre_change_function;
3145 ei->change->change_function = ch_delay->change_function;
3146 ei->change->post_change_function = ch_delay->post_change_function;
3148 ei->change->can_change = TRUE;
3149 ei->change->can_change_or_has_action = TRUE;
3151 ei->has_change_event[CE_DELAY] = TRUE;
3153 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3154 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3157 // ---------- initialize internal run-time variables ------------------------
3159 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3161 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3163 for (j = 0; j < ei->num_change_pages; j++)
3165 ei->change_page[j].can_change_or_has_action =
3166 (ei->change_page[j].can_change |
3167 ei->change_page[j].has_action);
3171 // add change events from custom element configuration
3172 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3174 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3176 for (j = 0; j < ei->num_change_pages; j++)
3178 if (!ei->change_page[j].can_change_or_has_action)
3181 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3183 // only add event page for the first page found with this event
3184 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3186 ei->has_change_event[k] = TRUE;
3188 ei->event_page_nr[k] = j;
3189 ei->event_page[k] = &ei->change_page[j];
3195 // ---------- initialize reference elements in change conditions ------------
3197 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3199 int element = EL_CUSTOM_START + i;
3200 struct ElementInfo *ei = &element_info[element];
3202 for (j = 0; j < ei->num_change_pages; j++)
3204 int trigger_element = ei->change_page[j].initial_trigger_element;
3206 if (trigger_element >= EL_PREV_CE_8 &&
3207 trigger_element <= EL_NEXT_CE_8)
3208 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3210 ei->change_page[j].trigger_element = trigger_element;
3214 // ---------- initialize run-time trigger player and element ----------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3220 for (j = 0; j < ei->num_change_pages; j++)
3222 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3223 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3224 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3225 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3226 ei->change_page[j].actual_trigger_ce_value = 0;
3227 ei->change_page[j].actual_trigger_ce_score = 0;
3231 // ---------- initialize trigger events -------------------------------------
3233 // initialize trigger events information
3234 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3235 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3236 trigger_events[i][j] = FALSE;
3238 // add trigger events from element change event properties
3239 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3241 struct ElementInfo *ei = &element_info[i];
3243 for (j = 0; j < ei->num_change_pages; j++)
3245 if (!ei->change_page[j].can_change_or_has_action)
3248 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3250 int trigger_element = ei->change_page[j].trigger_element;
3252 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3254 if (ei->change_page[j].has_event[k])
3256 if (IS_GROUP_ELEMENT(trigger_element))
3258 struct ElementGroupInfo *group =
3259 element_info[trigger_element].group;
3261 for (l = 0; l < group->num_elements_resolved; l++)
3262 trigger_events[group->element_resolved[l]][k] = TRUE;
3264 else if (trigger_element == EL_ANY_ELEMENT)
3265 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3266 trigger_events[l][k] = TRUE;
3268 trigger_events[trigger_element][k] = TRUE;
3275 // ---------- initialize push delay -----------------------------------------
3277 // initialize push delay values to default
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3280 if (!IS_CUSTOM_ELEMENT(i))
3282 // set default push delay values (corrected since version 3.0.7-1)
3283 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3285 element_info[i].push_delay_fixed = 2;
3286 element_info[i].push_delay_random = 8;
3290 element_info[i].push_delay_fixed = 8;
3291 element_info[i].push_delay_random = 8;
3296 // set push delay value for certain elements from pre-defined list
3297 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3299 int e = push_delay_list[i].element;
3301 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3302 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3305 // set push delay value for Supaplex elements for newer engine versions
3306 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3308 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3310 if (IS_SP_ELEMENT(i))
3312 // set SP push delay to just enough to push under a falling zonk
3313 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3315 element_info[i].push_delay_fixed = delay;
3316 element_info[i].push_delay_random = 0;
3321 // ---------- initialize move stepsize --------------------------------------
3323 // initialize move stepsize values to default
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3325 if (!IS_CUSTOM_ELEMENT(i))
3326 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3328 // set move stepsize value for certain elements from pre-defined list
3329 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3331 int e = move_stepsize_list[i].element;
3333 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3335 // set move stepsize value for certain elements for older engine versions
3336 if (use_old_move_stepsize_for_magic_wall)
3338 if (e == EL_MAGIC_WALL_FILLING ||
3339 e == EL_MAGIC_WALL_EMPTYING ||
3340 e == EL_BD_MAGIC_WALL_FILLING ||
3341 e == EL_BD_MAGIC_WALL_EMPTYING)
3342 element_info[e].move_stepsize *= 2;
3346 // ---------- initialize collect score --------------------------------------
3348 // initialize collect score values for custom elements from initial value
3349 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3350 if (IS_CUSTOM_ELEMENT(i))
3351 element_info[i].collect_score = element_info[i].collect_score_initial;
3353 // ---------- initialize collect count --------------------------------------
3355 // initialize collect count values for non-custom elements
3356 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3357 if (!IS_CUSTOM_ELEMENT(i))
3358 element_info[i].collect_count_initial = 0;
3360 // add collect count values for all elements from pre-defined list
3361 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3362 element_info[collect_count_list[i].element].collect_count_initial =
3363 collect_count_list[i].count;
3365 // ---------- initialize access direction -----------------------------------
3367 // initialize access direction values to default (access from every side)
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (!IS_CUSTOM_ELEMENT(i))
3370 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3372 // set access direction value for certain elements from pre-defined list
3373 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3374 element_info[access_direction_list[i].element].access_direction =
3375 access_direction_list[i].direction;
3377 // ---------- initialize explosion content ----------------------------------
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (IS_CUSTOM_ELEMENT(i))
3383 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3385 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3387 element_info[i].content.e[x][y] =
3388 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3389 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3390 i == EL_PLAYER_3 ? EL_EMERALD :
3391 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3392 i == EL_MOLE ? EL_EMERALD_RED :
3393 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3394 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3395 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3396 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3397 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3398 i == EL_WALL_EMERALD ? EL_EMERALD :
3399 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3400 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3401 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3402 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3403 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3404 i == EL_WALL_PEARL ? EL_PEARL :
3405 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3410 // ---------- initialize recursion detection --------------------------------
3411 recursion_loop_depth = 0;
3412 recursion_loop_detected = FALSE;
3413 recursion_loop_element = EL_UNDEFINED;
3415 // ---------- initialize graphics engine ------------------------------------
3416 game.scroll_delay_value =
3417 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3418 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3419 !setup.forced_scroll_delay ? 0 :
3420 setup.scroll_delay ? setup.scroll_delay_value : 0);
3421 game.scroll_delay_value =
3422 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3424 // ---------- initialize game engine snapshots ------------------------------
3425 for (i = 0; i < MAX_PLAYERS; i++)
3426 game.snapshot.last_action[i] = 0;
3427 game.snapshot.changed_action = FALSE;
3428 game.snapshot.collected_item = FALSE;
3429 game.snapshot.mode =
3430 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3431 SNAPSHOT_MODE_EVERY_STEP :
3432 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3433 SNAPSHOT_MODE_EVERY_MOVE :
3434 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3435 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3436 game.snapshot.save_snapshot = FALSE;
3438 // ---------- initialize level time for Supaplex engine ---------------------
3439 // Supaplex levels with time limit currently unsupported -- should be added
3440 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3443 // ---------- initialize flags for handling game actions --------------------
3445 // set flags for game actions to default values
3446 game.use_key_actions = TRUE;
3447 game.use_mouse_actions = FALSE;
3449 // when using Mirror Magic game engine, handle mouse events only
3450 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3452 game.use_key_actions = FALSE;
3453 game.use_mouse_actions = TRUE;
3456 // check for custom elements with mouse click events
3457 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3459 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3461 int element = EL_CUSTOM_START + i;
3463 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3464 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3465 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3466 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3467 game.use_mouse_actions = TRUE;
3472 static int get_num_special_action(int element, int action_first,
3475 int num_special_action = 0;
3478 for (i = action_first; i <= action_last; i++)
3480 boolean found = FALSE;
3482 for (j = 0; j < NUM_DIRECTIONS; j++)
3483 if (el_act_dir2img(element, i, j) !=
3484 el_act_dir2img(element, ACTION_DEFAULT, j))
3488 num_special_action++;
3493 return num_special_action;
3497 // ============================================================================
3499 // ----------------------------------------------------------------------------
3500 // initialize and start new game
3501 // ============================================================================
3503 #if DEBUG_INIT_PLAYER
3504 static void DebugPrintPlayerStatus(char *message)
3511 Debug("game:init:player", "%s:", message);
3513 for (i = 0; i < MAX_PLAYERS; i++)
3515 struct PlayerInfo *player = &stored_player[i];
3517 Debug("game:init:player",
3518 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3522 player->connected_locally,
3523 player->connected_network,
3525 (local_player == player ? " (local player)" : ""));
3532 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3533 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3534 int fade_mask = REDRAW_FIELD;
3536 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3537 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3538 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3539 int initial_move_dir = MV_DOWN;
3542 // required here to update video display before fading (FIX THIS)
3543 DrawMaskedBorder(REDRAW_DOOR_2);
3545 if (!game.restart_level)
3546 CloseDoor(DOOR_CLOSE_1);
3548 SetGameStatus(GAME_MODE_PLAYING);
3550 if (level_editor_test_game)
3551 FadeSkipNextFadeOut();
3553 FadeSetEnterScreen();
3556 fade_mask = REDRAW_ALL;
3558 FadeLevelSoundsAndMusic();
3560 ExpireSoundLoops(TRUE);
3564 if (level_editor_test_game)
3565 FadeSkipNextFadeIn();
3567 // needed if different viewport properties defined for playing
3568 ChangeViewportPropertiesIfNeeded();
3572 DrawCompleteVideoDisplay();
3574 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3577 InitGameControlValues();
3579 // initialize tape actions from game when recording tape
3582 tape.use_key_actions = game.use_key_actions;
3583 tape.use_mouse_actions = game.use_mouse_actions;
3586 // don't play tapes over network
3587 network_playing = (network.enabled && !tape.playing);
3589 for (i = 0; i < MAX_PLAYERS; i++)
3591 struct PlayerInfo *player = &stored_player[i];
3593 player->index_nr = i;
3594 player->index_bit = (1 << i);
3595 player->element_nr = EL_PLAYER_1 + i;
3597 player->present = FALSE;
3598 player->active = FALSE;
3599 player->mapped = FALSE;
3601 player->killed = FALSE;
3602 player->reanimated = FALSE;
3603 player->buried = FALSE;
3606 player->effective_action = 0;
3607 player->programmed_action = 0;
3608 player->snap_action = 0;
3610 player->mouse_action.lx = 0;
3611 player->mouse_action.ly = 0;
3612 player->mouse_action.button = 0;
3613 player->mouse_action.button_hint = 0;
3615 player->effective_mouse_action.lx = 0;
3616 player->effective_mouse_action.ly = 0;
3617 player->effective_mouse_action.button = 0;
3618 player->effective_mouse_action.button_hint = 0;
3620 for (j = 0; j < MAX_NUM_KEYS; j++)
3621 player->key[j] = FALSE;
3623 player->num_white_keys = 0;
3625 player->dynabomb_count = 0;
3626 player->dynabomb_size = 1;
3627 player->dynabombs_left = 0;
3628 player->dynabomb_xl = FALSE;
3630 player->MovDir = initial_move_dir;
3633 player->GfxDir = initial_move_dir;
3634 player->GfxAction = ACTION_DEFAULT;
3636 player->StepFrame = 0;
3638 player->initial_element = player->element_nr;
3639 player->artwork_element =
3640 (level.use_artwork_element[i] ? level.artwork_element[i] :
3641 player->element_nr);
3642 player->use_murphy = FALSE;
3644 player->block_last_field = FALSE; // initialized in InitPlayerField()
3645 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3647 player->gravity = level.initial_player_gravity[i];
3649 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3651 player->actual_frame_counter = 0;
3653 player->step_counter = 0;
3655 player->last_move_dir = initial_move_dir;
3657 player->is_active = FALSE;
3659 player->is_waiting = FALSE;
3660 player->is_moving = FALSE;
3661 player->is_auto_moving = FALSE;
3662 player->is_digging = FALSE;
3663 player->is_snapping = FALSE;
3664 player->is_collecting = FALSE;
3665 player->is_pushing = FALSE;
3666 player->is_switching = FALSE;
3667 player->is_dropping = FALSE;
3668 player->is_dropping_pressed = FALSE;
3670 player->is_bored = FALSE;
3671 player->is_sleeping = FALSE;
3673 player->was_waiting = TRUE;
3674 player->was_moving = FALSE;
3675 player->was_snapping = FALSE;
3676 player->was_dropping = FALSE;
3678 player->force_dropping = FALSE;
3680 player->frame_counter_bored = -1;
3681 player->frame_counter_sleeping = -1;
3683 player->anim_delay_counter = 0;
3684 player->post_delay_counter = 0;
3686 player->dir_waiting = initial_move_dir;
3687 player->action_waiting = ACTION_DEFAULT;
3688 player->last_action_waiting = ACTION_DEFAULT;
3689 player->special_action_bored = ACTION_DEFAULT;
3690 player->special_action_sleeping = ACTION_DEFAULT;
3692 player->switch_x = -1;
3693 player->switch_y = -1;
3695 player->drop_x = -1;
3696 player->drop_y = -1;
3698 player->show_envelope = 0;
3700 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3702 player->push_delay = -1; // initialized when pushing starts
3703 player->push_delay_value = game.initial_push_delay_value;
3705 player->drop_delay = 0;
3706 player->drop_pressed_delay = 0;
3708 player->last_jx = -1;
3709 player->last_jy = -1;
3713 player->shield_normal_time_left = 0;
3714 player->shield_deadly_time_left = 0;
3716 player->inventory_infinite_element = EL_UNDEFINED;
3717 player->inventory_size = 0;
3719 if (level.use_initial_inventory[i])
3721 for (j = 0; j < level.initial_inventory_size[i]; j++)
3723 int element = level.initial_inventory_content[i][j];
3724 int collect_count = element_info[element].collect_count_initial;
3727 if (!IS_CUSTOM_ELEMENT(element))
3730 if (collect_count == 0)
3731 player->inventory_infinite_element = element;
3733 for (k = 0; k < collect_count; k++)
3734 if (player->inventory_size < MAX_INVENTORY_SIZE)
3735 player->inventory_element[player->inventory_size++] = element;
3739 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3740 SnapField(player, 0, 0);
3742 map_player_action[i] = i;
3745 network_player_action_received = FALSE;
3747 // initial null action
3748 if (network_playing)
3749 SendToServer_MovePlayer(MV_NONE);
3754 TimeLeft = level.time;
3757 ScreenMovDir = MV_NONE;
3761 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3763 game.robot_wheel_x = -1;
3764 game.robot_wheel_y = -1;
3769 game.all_players_gone = FALSE;
3771 game.LevelSolved = FALSE;
3772 game.GameOver = FALSE;
3774 game.GamePlayed = !tape.playing;
3776 game.LevelSolved_GameWon = FALSE;
3777 game.LevelSolved_GameEnd = FALSE;
3778 game.LevelSolved_SaveTape = FALSE;
3779 game.LevelSolved_SaveScore = FALSE;
3781 game.LevelSolved_CountingTime = 0;
3782 game.LevelSolved_CountingScore = 0;
3783 game.LevelSolved_CountingHealth = 0;
3785 game.panel.active = TRUE;
3787 game.no_time_limit = (level.time == 0);
3789 game.yamyam_content_nr = 0;
3790 game.robot_wheel_active = FALSE;
3791 game.magic_wall_active = FALSE;
3792 game.magic_wall_time_left = 0;
3793 game.light_time_left = 0;
3794 game.timegate_time_left = 0;
3795 game.switchgate_pos = 0;
3796 game.wind_direction = level.wind_direction_initial;
3799 game.score_final = 0;
3801 game.health = MAX_HEALTH;
3802 game.health_final = MAX_HEALTH;
3804 game.gems_still_needed = level.gems_needed;
3805 game.sokoban_fields_still_needed = 0;
3806 game.sokoban_objects_still_needed = 0;
3807 game.lights_still_needed = 0;
3808 game.players_still_needed = 0;
3809 game.friends_still_needed = 0;
3811 game.lenses_time_left = 0;
3812 game.magnify_time_left = 0;
3814 game.ball_active = level.ball_active_initial;
3815 game.ball_content_nr = 0;
3817 game.explosions_delayed = TRUE;
3819 game.envelope_active = FALSE;
3821 for (i = 0; i < NUM_BELTS; i++)
3823 game.belt_dir[i] = MV_NONE;
3824 game.belt_dir_nr[i] = 3; // not moving, next moving left
3827 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3828 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3830 #if DEBUG_INIT_PLAYER
3831 DebugPrintPlayerStatus("Player status at level initialization");
3834 SCAN_PLAYFIELD(x, y)
3836 Tile[x][y] = Last[x][y] = level.field[x][y];
3837 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3838 ChangeDelay[x][y] = 0;
3839 ChangePage[x][y] = -1;
3840 CustomValue[x][y] = 0; // initialized in InitField()
3841 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3843 WasJustMoving[x][y] = 0;
3844 WasJustFalling[x][y] = 0;
3845 CheckCollision[x][y] = 0;
3846 CheckImpact[x][y] = 0;
3848 Pushed[x][y] = FALSE;
3850 ChangeCount[x][y] = 0;
3851 ChangeEvent[x][y] = -1;
3853 ExplodePhase[x][y] = 0;
3854 ExplodeDelay[x][y] = 0;
3855 ExplodeField[x][y] = EX_TYPE_NONE;
3857 RunnerVisit[x][y] = 0;
3858 PlayerVisit[x][y] = 0;
3861 GfxRandom[x][y] = INIT_GFX_RANDOM();
3862 GfxElement[x][y] = EL_UNDEFINED;
3863 GfxAction[x][y] = ACTION_DEFAULT;
3864 GfxDir[x][y] = MV_NONE;
3865 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3868 SCAN_PLAYFIELD(x, y)
3870 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3872 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3874 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3877 InitField(x, y, TRUE);
3879 ResetGfxAnimation(x, y);
3884 for (i = 0; i < MAX_PLAYERS; i++)
3886 struct PlayerInfo *player = &stored_player[i];
3888 // set number of special actions for bored and sleeping animation
3889 player->num_special_action_bored =
3890 get_num_special_action(player->artwork_element,
3891 ACTION_BORING_1, ACTION_BORING_LAST);
3892 player->num_special_action_sleeping =
3893 get_num_special_action(player->artwork_element,
3894 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3897 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3898 emulate_sb ? EMU_SOKOBAN :
3899 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3901 // initialize type of slippery elements
3902 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3904 if (!IS_CUSTOM_ELEMENT(i))
3906 // default: elements slip down either to the left or right randomly
3907 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3909 // SP style elements prefer to slip down on the left side
3910 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3911 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3913 // BD style elements prefer to slip down on the left side
3914 if (game.emulation == EMU_BOULDERDASH)
3915 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3919 // initialize explosion and ignition delay
3920 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3922 if (!IS_CUSTOM_ELEMENT(i))
3925 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3926 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3927 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3928 int last_phase = (num_phase + 1) * delay;
3929 int half_phase = (num_phase / 2) * delay;
3931 element_info[i].explosion_delay = last_phase - 1;
3932 element_info[i].ignition_delay = half_phase;
3934 if (i == EL_BLACK_ORB)
3935 element_info[i].ignition_delay = 1;
3939 // correct non-moving belts to start moving left
3940 for (i = 0; i < NUM_BELTS; i++)
3941 if (game.belt_dir[i] == MV_NONE)
3942 game.belt_dir_nr[i] = 3; // not moving, next moving left
3944 #if USE_NEW_PLAYER_ASSIGNMENTS
3945 // use preferred player also in local single-player mode
3946 if (!network.enabled && !game.team_mode)
3948 int new_index_nr = setup.network_player_nr;
3950 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3952 for (i = 0; i < MAX_PLAYERS; i++)
3953 stored_player[i].connected_locally = FALSE;
3955 stored_player[new_index_nr].connected_locally = TRUE;
3959 for (i = 0; i < MAX_PLAYERS; i++)
3961 stored_player[i].connected = FALSE;
3963 // in network game mode, the local player might not be the first player
3964 if (stored_player[i].connected_locally)
3965 local_player = &stored_player[i];
3968 if (!network.enabled)
3969 local_player->connected = TRUE;
3973 for (i = 0; i < MAX_PLAYERS; i++)
3974 stored_player[i].connected = tape.player_participates[i];
3976 else if (network.enabled)
3978 // add team mode players connected over the network (needed for correct
3979 // assignment of player figures from level to locally playing players)
3981 for (i = 0; i < MAX_PLAYERS; i++)
3982 if (stored_player[i].connected_network)
3983 stored_player[i].connected = TRUE;
3985 else if (game.team_mode)
3987 // try to guess locally connected team mode players (needed for correct
3988 // assignment of player figures from level to locally playing players)
3990 for (i = 0; i < MAX_PLAYERS; i++)
3991 if (setup.input[i].use_joystick ||
3992 setup.input[i].key.left != KSYM_UNDEFINED)
3993 stored_player[i].connected = TRUE;
3996 #if DEBUG_INIT_PLAYER
3997 DebugPrintPlayerStatus("Player status after level initialization");
4000 #if DEBUG_INIT_PLAYER
4001 Debug("game:init:player", "Reassigning players ...");
4004 // check if any connected player was not found in playfield
4005 for (i = 0; i < MAX_PLAYERS; i++)
4007 struct PlayerInfo *player = &stored_player[i];
4009 if (player->connected && !player->present)
4011 struct PlayerInfo *field_player = NULL;
4013 #if DEBUG_INIT_PLAYER
4014 Debug("game:init:player",
4015 "- looking for field player for player %d ...", i + 1);
4018 // assign first free player found that is present in the playfield
4020 // first try: look for unmapped playfield player that is not connected
4021 for (j = 0; j < MAX_PLAYERS; j++)
4022 if (field_player == NULL &&
4023 stored_player[j].present &&
4024 !stored_player[j].mapped &&
4025 !stored_player[j].connected)
4026 field_player = &stored_player[j];
4028 // second try: look for *any* unmapped playfield player
4029 for (j = 0; j < MAX_PLAYERS; j++)
4030 if (field_player == NULL &&
4031 stored_player[j].present &&
4032 !stored_player[j].mapped)
4033 field_player = &stored_player[j];
4035 if (field_player != NULL)
4037 int jx = field_player->jx, jy = field_player->jy;
4039 #if DEBUG_INIT_PLAYER
4040 Debug("game:init:player", "- found player %d",
4041 field_player->index_nr + 1);
4044 player->present = FALSE;
4045 player->active = FALSE;
4047 field_player->present = TRUE;
4048 field_player->active = TRUE;
4051 player->initial_element = field_player->initial_element;
4052 player->artwork_element = field_player->artwork_element;
4054 player->block_last_field = field_player->block_last_field;
4055 player->block_delay_adjustment = field_player->block_delay_adjustment;
4058 StorePlayer[jx][jy] = field_player->element_nr;
4060 field_player->jx = field_player->last_jx = jx;
4061 field_player->jy = field_player->last_jy = jy;
4063 if (local_player == player)
4064 local_player = field_player;
4066 map_player_action[field_player->index_nr] = i;
4068 field_player->mapped = TRUE;
4070 #if DEBUG_INIT_PLAYER
4071 Debug("game:init:player", "- map_player_action[%d] == %d",
4072 field_player->index_nr + 1, i + 1);
4077 if (player->connected && player->present)
4078 player->mapped = TRUE;
4081 #if DEBUG_INIT_PLAYER
4082 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4087 // check if any connected player was not found in playfield
4088 for (i = 0; i < MAX_PLAYERS; i++)
4090 struct PlayerInfo *player = &stored_player[i];
4092 if (player->connected && !player->present)
4094 for (j = 0; j < MAX_PLAYERS; j++)
4096 struct PlayerInfo *field_player = &stored_player[j];
4097 int jx = field_player->jx, jy = field_player->jy;
4099 // assign first free player found that is present in the playfield
4100 if (field_player->present && !field_player->connected)
4102 player->present = TRUE;
4103 player->active = TRUE;
4105 field_player->present = FALSE;
4106 field_player->active = FALSE;
4108 player->initial_element = field_player->initial_element;
4109 player->artwork_element = field_player->artwork_element;
4111 player->block_last_field = field_player->block_last_field;
4112 player->block_delay_adjustment = field_player->block_delay_adjustment;
4114 StorePlayer[jx][jy] = player->element_nr;
4116 player->jx = player->last_jx = jx;
4117 player->jy = player->last_jy = jy;
4127 Debug("game:init:player", "local_player->present == %d",
4128 local_player->present);
4131 // set focus to local player for network games, else to all players
4132 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4133 game.centered_player_nr_next = game.centered_player_nr;
4134 game.set_centered_player = FALSE;
4135 game.set_centered_player_wrap = FALSE;
4137 if (network_playing && tape.recording)
4139 // store client dependent player focus when recording network games
4140 tape.centered_player_nr_next = game.centered_player_nr_next;
4141 tape.set_centered_player = TRUE;
4146 // when playing a tape, eliminate all players who do not participate
4148 #if USE_NEW_PLAYER_ASSIGNMENTS
4150 if (!game.team_mode)
4152 for (i = 0; i < MAX_PLAYERS; i++)
4154 if (stored_player[i].active &&
4155 !tape.player_participates[map_player_action[i]])
4157 struct PlayerInfo *player = &stored_player[i];
4158 int jx = player->jx, jy = player->jy;
4160 #if DEBUG_INIT_PLAYER
4161 Debug("game:init:player", "Removing player %d at (%d, %d)",
4165 player->active = FALSE;
4166 StorePlayer[jx][jy] = 0;
4167 Tile[jx][jy] = EL_EMPTY;
4174 for (i = 0; i < MAX_PLAYERS; i++)
4176 if (stored_player[i].active &&
4177 !tape.player_participates[i])
4179 struct PlayerInfo *player = &stored_player[i];
4180 int jx = player->jx, jy = player->jy;
4182 player->active = FALSE;
4183 StorePlayer[jx][jy] = 0;
4184 Tile[jx][jy] = EL_EMPTY;
4189 else if (!network.enabled && !game.team_mode) // && !tape.playing
4191 // when in single player mode, eliminate all but the local player
4193 for (i = 0; i < MAX_PLAYERS; i++)
4195 struct PlayerInfo *player = &stored_player[i];
4197 if (player->active && player != local_player)
4199 int jx = player->jx, jy = player->jy;
4201 player->active = FALSE;
4202 player->present = FALSE;
4204 StorePlayer[jx][jy] = 0;
4205 Tile[jx][jy] = EL_EMPTY;
4210 for (i = 0; i < MAX_PLAYERS; i++)
4211 if (stored_player[i].active)
4212 game.players_still_needed++;
4214 if (level.solved_by_one_player)
4215 game.players_still_needed = 1;
4217 // when recording the game, store which players take part in the game
4220 #if USE_NEW_PLAYER_ASSIGNMENTS
4221 for (i = 0; i < MAX_PLAYERS; i++)
4222 if (stored_player[i].connected)
4223 tape.player_participates[i] = TRUE;
4225 for (i = 0; i < MAX_PLAYERS; i++)
4226 if (stored_player[i].active)
4227 tape.player_participates[i] = TRUE;
4231 #if DEBUG_INIT_PLAYER
4232 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4235 if (BorderElement == EL_EMPTY)
4238 SBX_Right = lev_fieldx - SCR_FIELDX;
4240 SBY_Lower = lev_fieldy - SCR_FIELDY;
4245 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4247 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4250 if (full_lev_fieldx <= SCR_FIELDX)
4251 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4252 if (full_lev_fieldy <= SCR_FIELDY)
4253 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4255 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4257 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4260 // if local player not found, look for custom element that might create
4261 // the player (make some assumptions about the right custom element)
4262 if (!local_player->present)
4264 int start_x = 0, start_y = 0;
4265 int found_rating = 0;
4266 int found_element = EL_UNDEFINED;
4267 int player_nr = local_player->index_nr;
4269 SCAN_PLAYFIELD(x, y)
4271 int element = Tile[x][y];
4276 if (level.use_start_element[player_nr] &&
4277 level.start_element[player_nr] == element &&
4284 found_element = element;
4287 if (!IS_CUSTOM_ELEMENT(element))
4290 if (CAN_CHANGE(element))
4292 for (i = 0; i < element_info[element].num_change_pages; i++)
4294 // check for player created from custom element as single target
4295 content = element_info[element].change_page[i].target_element;
4296 is_player = ELEM_IS_PLAYER(content);
4298 if (is_player && (found_rating < 3 ||
4299 (found_rating == 3 && element < found_element)))
4305 found_element = element;
4310 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4312 // check for player created from custom element as explosion content
4313 content = element_info[element].content.e[xx][yy];
4314 is_player = ELEM_IS_PLAYER(content);
4316 if (is_player && (found_rating < 2 ||
4317 (found_rating == 2 && element < found_element)))
4319 start_x = x + xx - 1;
4320 start_y = y + yy - 1;
4323 found_element = element;
4326 if (!CAN_CHANGE(element))
4329 for (i = 0; i < element_info[element].num_change_pages; i++)
4331 // check for player created from custom element as extended target
4333 element_info[element].change_page[i].target_content.e[xx][yy];
4335 is_player = ELEM_IS_PLAYER(content);
4337 if (is_player && (found_rating < 1 ||
4338 (found_rating == 1 && element < found_element)))
4340 start_x = x + xx - 1;
4341 start_y = y + yy - 1;
4344 found_element = element;
4350 scroll_x = SCROLL_POSITION_X(start_x);
4351 scroll_y = SCROLL_POSITION_Y(start_y);
4355 scroll_x = SCROLL_POSITION_X(local_player->jx);
4356 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4359 // !!! FIX THIS (START) !!!
4360 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4362 InitGameEngine_EM();
4364 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4366 InitGameEngine_SP();
4368 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4370 InitGameEngine_MM();
4374 DrawLevel(REDRAW_FIELD);
4377 // after drawing the level, correct some elements
4378 if (game.timegate_time_left == 0)
4379 CloseAllOpenTimegates();
4382 // blit playfield from scroll buffer to normal back buffer for fading in
4383 BlitScreenToBitmap(backbuffer);
4384 // !!! FIX THIS (END) !!!
4386 DrawMaskedBorder(fade_mask);
4391 // full screen redraw is required at this point in the following cases:
4392 // - special editor door undrawn when game was started from level editor
4393 // - drawing area (playfield) was changed and has to be removed completely
4394 redraw_mask = REDRAW_ALL;
4398 if (!game.restart_level)
4400 // copy default game door content to main double buffer
4402 // !!! CHECK AGAIN !!!
4403 SetPanelBackground();
4404 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4405 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4408 SetPanelBackground();
4409 SetDrawBackgroundMask(REDRAW_DOOR_1);
4411 UpdateAndDisplayGameControlValues();
4413 if (!game.restart_level)
4419 CreateGameButtons();
4424 // copy actual game door content to door double buffer for OpenDoor()
4425 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4427 OpenDoor(DOOR_OPEN_ALL);
4429 KeyboardAutoRepeatOffUnlessAutoplay();
4431 #if DEBUG_INIT_PLAYER
4432 DebugPrintPlayerStatus("Player status (final)");
4441 if (!game.restart_level && !tape.playing)
4443 LevelStats_incPlayed(level_nr);
4445 SaveLevelSetup_SeriesInfo();
4448 game.restart_level = FALSE;
4449 game.restart_game_message = NULL;
4451 game.request_active = FALSE;
4452 game.request_active_or_moving = FALSE;
4454 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4455 InitGameActions_MM();
4457 SaveEngineSnapshotToListInitial();
4459 if (!game.restart_level)
4461 PlaySound(SND_GAME_STARTING);
4463 if (setup.sound_music)
4468 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4469 int actual_player_x, int actual_player_y)
4471 // this is used for non-R'n'D game engines to update certain engine values
4473 // needed to determine if sounds are played within the visible screen area
4474 scroll_x = actual_scroll_x;
4475 scroll_y = actual_scroll_y;
4477 // needed to get player position for "follow finger" playing input method
4478 local_player->jx = actual_player_x;
4479 local_player->jy = actual_player_y;
4482 void InitMovDir(int x, int y)
4484 int i, element = Tile[x][y];
4485 static int xy[4][2] =
4492 static int direction[3][4] =
4494 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4495 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4496 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4505 Tile[x][y] = EL_BUG;
4506 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4509 case EL_SPACESHIP_RIGHT:
4510 case EL_SPACESHIP_UP:
4511 case EL_SPACESHIP_LEFT:
4512 case EL_SPACESHIP_DOWN:
4513 Tile[x][y] = EL_SPACESHIP;
4514 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4517 case EL_BD_BUTTERFLY_RIGHT:
4518 case EL_BD_BUTTERFLY_UP:
4519 case EL_BD_BUTTERFLY_LEFT:
4520 case EL_BD_BUTTERFLY_DOWN:
4521 Tile[x][y] = EL_BD_BUTTERFLY;
4522 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4525 case EL_BD_FIREFLY_RIGHT:
4526 case EL_BD_FIREFLY_UP:
4527 case EL_BD_FIREFLY_LEFT:
4528 case EL_BD_FIREFLY_DOWN:
4529 Tile[x][y] = EL_BD_FIREFLY;
4530 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4533 case EL_PACMAN_RIGHT:
4535 case EL_PACMAN_LEFT:
4536 case EL_PACMAN_DOWN:
4537 Tile[x][y] = EL_PACMAN;
4538 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4541 case EL_YAMYAM_LEFT:
4542 case EL_YAMYAM_RIGHT:
4544 case EL_YAMYAM_DOWN:
4545 Tile[x][y] = EL_YAMYAM;
4546 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4549 case EL_SP_SNIKSNAK:
4550 MovDir[x][y] = MV_UP;
4553 case EL_SP_ELECTRON:
4554 MovDir[x][y] = MV_LEFT;
4561 Tile[x][y] = EL_MOLE;
4562 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4565 case EL_SPRING_LEFT:
4566 case EL_SPRING_RIGHT:
4567 Tile[x][y] = EL_SPRING;
4568 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4572 if (IS_CUSTOM_ELEMENT(element))
4574 struct ElementInfo *ei = &element_info[element];
4575 int move_direction_initial = ei->move_direction_initial;
4576 int move_pattern = ei->move_pattern;
4578 if (move_direction_initial == MV_START_PREVIOUS)
4580 if (MovDir[x][y] != MV_NONE)
4583 move_direction_initial = MV_START_AUTOMATIC;
4586 if (move_direction_initial == MV_START_RANDOM)
4587 MovDir[x][y] = 1 << RND(4);
4588 else if (move_direction_initial & MV_ANY_DIRECTION)
4589 MovDir[x][y] = move_direction_initial;
4590 else if (move_pattern == MV_ALL_DIRECTIONS ||
4591 move_pattern == MV_TURNING_LEFT ||
4592 move_pattern == MV_TURNING_RIGHT ||
4593 move_pattern == MV_TURNING_LEFT_RIGHT ||
4594 move_pattern == MV_TURNING_RIGHT_LEFT ||
4595 move_pattern == MV_TURNING_RANDOM)
4596 MovDir[x][y] = 1 << RND(4);
4597 else if (move_pattern == MV_HORIZONTAL)
4598 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4599 else if (move_pattern == MV_VERTICAL)
4600 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4601 else if (move_pattern & MV_ANY_DIRECTION)
4602 MovDir[x][y] = element_info[element].move_pattern;
4603 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4604 move_pattern == MV_ALONG_RIGHT_SIDE)
4606 // use random direction as default start direction
4607 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4608 MovDir[x][y] = 1 << RND(4);
4610 for (i = 0; i < NUM_DIRECTIONS; i++)
4612 int x1 = x + xy[i][0];
4613 int y1 = y + xy[i][1];
4615 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4617 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4618 MovDir[x][y] = direction[0][i];
4620 MovDir[x][y] = direction[1][i];
4629 MovDir[x][y] = 1 << RND(4);
4631 if (element != EL_BUG &&
4632 element != EL_SPACESHIP &&
4633 element != EL_BD_BUTTERFLY &&
4634 element != EL_BD_FIREFLY)
4637 for (i = 0; i < NUM_DIRECTIONS; i++)
4639 int x1 = x + xy[i][0];
4640 int y1 = y + xy[i][1];
4642 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4644 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4646 MovDir[x][y] = direction[0][i];
4649 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4650 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4652 MovDir[x][y] = direction[1][i];
4661 GfxDir[x][y] = MovDir[x][y];
4664 void InitAmoebaNr(int x, int y)
4667 int group_nr = AmoebaNeighbourNr(x, y);
4671 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4673 if (AmoebaCnt[i] == 0)
4681 AmoebaNr[x][y] = group_nr;
4682 AmoebaCnt[group_nr]++;
4683 AmoebaCnt2[group_nr]++;
4686 static void LevelSolved(void)
4688 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4689 game.players_still_needed > 0)
4692 game.LevelSolved = TRUE;
4693 game.GameOver = TRUE;
4695 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4696 game_em.lev->score :
4697 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4700 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4701 MM_HEALTH(game_mm.laser_overload_value) :
4704 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4705 game.LevelSolved_CountingScore = game.score_final;
4706 game.LevelSolved_CountingHealth = game.health_final;
4711 static int time_count_steps;
4712 static int time, time_final;
4713 static int score, score_final;
4714 static int health, health_final;
4715 static int game_over_delay_1 = 0;
4716 static int game_over_delay_2 = 0;
4717 static int game_over_delay_3 = 0;
4718 int game_over_delay_value_1 = 50;
4719 int game_over_delay_value_2 = 25;
4720 int game_over_delay_value_3 = 50;
4722 if (!game.LevelSolved_GameWon)
4726 // do not start end game actions before the player stops moving (to exit)
4727 if (local_player->active && local_player->MovPos)
4730 game.LevelSolved_GameWon = TRUE;
4731 game.LevelSolved_SaveTape = tape.recording;
4732 game.LevelSolved_SaveScore = !tape.playing;
4736 LevelStats_incSolved(level_nr);
4738 SaveLevelSetup_SeriesInfo();
4741 if (tape.auto_play) // tape might already be stopped here
4742 tape.auto_play_level_solved = TRUE;
4746 game_over_delay_1 = 0;
4747 game_over_delay_2 = 0;
4748 game_over_delay_3 = game_over_delay_value_3;
4750 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4751 score = score_final = game.score_final;
4752 health = health_final = game.health_final;
4754 if (level.score[SC_TIME_BONUS] > 0)
4759 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4761 else if (game.no_time_limit && TimePlayed < 999)
4764 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4767 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4769 game_over_delay_1 = game_over_delay_value_1;
4771 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4774 score_final += health * level.score[SC_TIME_BONUS];
4776 game_over_delay_2 = game_over_delay_value_2;
4779 game.score_final = score_final;
4780 game.health_final = health_final;
4783 if (level_editor_test_game)
4786 score = score_final;
4788 game.LevelSolved_CountingTime = time;
4789 game.LevelSolved_CountingScore = score;
4791 game_panel_controls[GAME_PANEL_TIME].value = time;
4792 game_panel_controls[GAME_PANEL_SCORE].value = score;
4794 DisplayGameControlValues();
4797 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4799 // check if last player has left the level
4800 if (game.exit_x >= 0 &&
4803 int x = game.exit_x;
4804 int y = game.exit_y;
4805 int element = Tile[x][y];
4807 // close exit door after last player
4808 if ((game.all_players_gone &&
4809 (element == EL_EXIT_OPEN ||
4810 element == EL_SP_EXIT_OPEN ||
4811 element == EL_STEEL_EXIT_OPEN)) ||
4812 element == EL_EM_EXIT_OPEN ||
4813 element == EL_EM_STEEL_EXIT_OPEN)
4817 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4818 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4819 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4820 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4821 EL_EM_STEEL_EXIT_CLOSING);
4823 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4826 // player disappears
4827 DrawLevelField(x, y);
4830 for (i = 0; i < MAX_PLAYERS; i++)
4832 struct PlayerInfo *player = &stored_player[i];
4834 if (player->present)
4836 RemovePlayer(player);
4838 // player disappears
4839 DrawLevelField(player->jx, player->jy);
4844 PlaySound(SND_GAME_WINNING);
4847 if (game_over_delay_1 > 0)
4849 game_over_delay_1--;
4854 if (time != time_final)
4856 int time_to_go = ABS(time_final - time);
4857 int time_count_dir = (time < time_final ? +1 : -1);
4859 if (time_to_go < time_count_steps)
4860 time_count_steps = 1;
4862 time += time_count_steps * time_count_dir;
4863 score += time_count_steps * level.score[SC_TIME_BONUS];
4865 game.LevelSolved_CountingTime = time;
4866 game.LevelSolved_CountingScore = score;
4868 game_panel_controls[GAME_PANEL_TIME].value = time;
4869 game_panel_controls[GAME_PANEL_SCORE].value = score;
4871 DisplayGameControlValues();
4873 if (time == time_final)
4874 StopSound(SND_GAME_LEVELTIME_BONUS);
4875 else if (setup.sound_loops)
4876 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4878 PlaySound(SND_GAME_LEVELTIME_BONUS);
4883 if (game_over_delay_2 > 0)
4885 game_over_delay_2--;
4890 if (health != health_final)
4892 int health_count_dir = (health < health_final ? +1 : -1);
4894 health += health_count_dir;
4895 score += level.score[SC_TIME_BONUS];
4897 game.LevelSolved_CountingHealth = health;
4898 game.LevelSolved_CountingScore = score;
4900 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4901 game_panel_controls[GAME_PANEL_SCORE].value = score;
4903 DisplayGameControlValues();
4905 if (health == health_final)
4906 StopSound(SND_GAME_LEVELTIME_BONUS);
4907 else if (setup.sound_loops)
4908 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4910 PlaySound(SND_GAME_LEVELTIME_BONUS);
4915 game.panel.active = FALSE;
4917 if (game_over_delay_3 > 0)
4919 game_over_delay_3--;
4929 // used instead of "level_nr" (needed for network games)
4930 int last_level_nr = levelset.level_nr;
4933 game.LevelSolved_GameEnd = TRUE;
4935 if (game.LevelSolved_SaveTape)
4937 // make sure that request dialog to save tape does not open door again
4938 if (!global.use_envelope_request)
4939 CloseDoor(DOOR_CLOSE_1);
4941 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4944 // if no tape is to be saved, close both doors simultaneously
4945 CloseDoor(DOOR_CLOSE_ALL);
4947 if (level_editor_test_game)
4949 SetGameStatus(GAME_MODE_MAIN);
4956 if (!game.LevelSolved_SaveScore)
4958 SetGameStatus(GAME_MODE_MAIN);
4965 if (level_nr == leveldir_current->handicap_level)
4967 leveldir_current->handicap_level++;
4969 SaveLevelSetup_SeriesInfo();
4972 if (setup.increment_levels &&
4973 level_nr < leveldir_current->last_level &&
4976 level_nr++; // advance to next level
4977 TapeErase(); // start with empty tape
4979 if (setup.auto_play_next_level)
4981 LoadLevel(level_nr);
4983 SaveLevelSetup_SeriesInfo();
4987 hi_pos = NewHiScore(last_level_nr);
4989 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4991 SetGameStatus(GAME_MODE_SCORES);
4993 DrawHallOfFame(last_level_nr, hi_pos);
4995 else if (setup.auto_play_next_level && setup.increment_levels &&
4996 last_level_nr < leveldir_current->last_level &&
4999 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5003 SetGameStatus(GAME_MODE_MAIN);
5009 int NewHiScore(int level_nr)
5013 boolean one_score_entry_per_name = !program.many_scores_per_name;
5015 LoadScore(level_nr);
5017 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5018 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5021 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5023 if (game.score_final > highscore[k].Score)
5025 // player has made it to the hall of fame
5027 if (k < MAX_SCORE_ENTRIES - 1)
5029 int m = MAX_SCORE_ENTRIES - 1;
5031 if (one_score_entry_per_name)
5033 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5034 if (strEqual(setup.player_name, highscore[l].Name))
5037 if (m == k) // player's new highscore overwrites his old one
5041 for (l = m; l > k; l--)
5043 strcpy(highscore[l].Name, highscore[l - 1].Name);
5044 highscore[l].Score = highscore[l - 1].Score;
5050 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5051 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5052 highscore[k].Score = game.score_final;
5057 else if (one_score_entry_per_name &&
5058 !strncmp(setup.player_name, highscore[k].Name,
5059 MAX_PLAYER_NAME_LEN))
5060 break; // player already there with a higher score
5064 SaveScore(level_nr);
5069 static int getElementMoveStepsizeExt(int x, int y, int direction)
5071 int element = Tile[x][y];
5072 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5073 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5074 int horiz_move = (dx != 0);
5075 int sign = (horiz_move ? dx : dy);
5076 int step = sign * element_info[element].move_stepsize;
5078 // special values for move stepsize for spring and things on conveyor belt
5081 if (CAN_FALL(element) &&
5082 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5083 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5084 else if (element == EL_SPRING)
5085 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5091 static int getElementMoveStepsize(int x, int y)
5093 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5096 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5098 if (player->GfxAction != action || player->GfxDir != dir)
5100 player->GfxAction = action;
5101 player->GfxDir = dir;
5103 player->StepFrame = 0;
5107 static void ResetGfxFrame(int x, int y)
5109 // profiling showed that "autotest" spends 10~20% of its time in this function
5110 if (DrawingDeactivatedField())
5113 int element = Tile[x][y];
5114 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5116 if (graphic_info[graphic].anim_global_sync)
5117 GfxFrame[x][y] = FrameCounter;
5118 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5119 GfxFrame[x][y] = CustomValue[x][y];
5120 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5121 GfxFrame[x][y] = element_info[element].collect_score;
5122 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5123 GfxFrame[x][y] = ChangeDelay[x][y];
5126 static void ResetGfxAnimation(int x, int y)
5128 GfxAction[x][y] = ACTION_DEFAULT;
5129 GfxDir[x][y] = MovDir[x][y];
5132 ResetGfxFrame(x, y);
5135 static void ResetRandomAnimationValue(int x, int y)
5137 GfxRandom[x][y] = INIT_GFX_RANDOM();
5140 static void InitMovingField(int x, int y, int direction)
5142 int element = Tile[x][y];
5143 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5144 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5147 boolean is_moving_before, is_moving_after;
5149 // check if element was/is moving or being moved before/after mode change
5150 is_moving_before = (WasJustMoving[x][y] != 0);
5151 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5153 // reset animation only for moving elements which change direction of moving
5154 // or which just started or stopped moving
5155 // (else CEs with property "can move" / "not moving" are reset each frame)
5156 if (is_moving_before != is_moving_after ||
5157 direction != MovDir[x][y])
5158 ResetGfxAnimation(x, y);
5160 MovDir[x][y] = direction;
5161 GfxDir[x][y] = direction;
5163 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5164 direction == MV_DOWN && CAN_FALL(element) ?
5165 ACTION_FALLING : ACTION_MOVING);
5167 // this is needed for CEs with property "can move" / "not moving"
5169 if (is_moving_after)
5171 if (Tile[newx][newy] == EL_EMPTY)
5172 Tile[newx][newy] = EL_BLOCKED;
5174 MovDir[newx][newy] = MovDir[x][y];
5176 CustomValue[newx][newy] = CustomValue[x][y];
5178 GfxFrame[newx][newy] = GfxFrame[x][y];
5179 GfxRandom[newx][newy] = GfxRandom[x][y];
5180 GfxAction[newx][newy] = GfxAction[x][y];
5181 GfxDir[newx][newy] = GfxDir[x][y];
5185 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5187 int direction = MovDir[x][y];
5188 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5189 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5195 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5197 int oldx = x, oldy = y;
5198 int direction = MovDir[x][y];
5200 if (direction == MV_LEFT)
5202 else if (direction == MV_RIGHT)
5204 else if (direction == MV_UP)
5206 else if (direction == MV_DOWN)
5209 *comes_from_x = oldx;
5210 *comes_from_y = oldy;
5213 static int MovingOrBlocked2Element(int x, int y)
5215 int element = Tile[x][y];
5217 if (element == EL_BLOCKED)
5221 Blocked2Moving(x, y, &oldx, &oldy);
5222 return Tile[oldx][oldy];
5228 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5230 // like MovingOrBlocked2Element(), but if element is moving
5231 // and (x,y) is the field the moving element is just leaving,
5232 // return EL_BLOCKED instead of the element value
5233 int element = Tile[x][y];
5235 if (IS_MOVING(x, y))
5237 if (element == EL_BLOCKED)
5241 Blocked2Moving(x, y, &oldx, &oldy);
5242 return Tile[oldx][oldy];
5251 static void RemoveField(int x, int y)
5253 Tile[x][y] = EL_EMPTY;
5259 CustomValue[x][y] = 0;
5262 ChangeDelay[x][y] = 0;
5263 ChangePage[x][y] = -1;
5264 Pushed[x][y] = FALSE;
5266 GfxElement[x][y] = EL_UNDEFINED;
5267 GfxAction[x][y] = ACTION_DEFAULT;
5268 GfxDir[x][y] = MV_NONE;
5271 static void RemoveMovingField(int x, int y)
5273 int oldx = x, oldy = y, newx = x, newy = y;
5274 int element = Tile[x][y];
5275 int next_element = EL_UNDEFINED;
5277 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5280 if (IS_MOVING(x, y))
5282 Moving2Blocked(x, y, &newx, &newy);
5284 if (Tile[newx][newy] != EL_BLOCKED)
5286 // element is moving, but target field is not free (blocked), but
5287 // already occupied by something different (example: acid pool);
5288 // in this case, only remove the moving field, but not the target
5290 RemoveField(oldx, oldy);
5292 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5294 TEST_DrawLevelField(oldx, oldy);
5299 else if (element == EL_BLOCKED)
5301 Blocked2Moving(x, y, &oldx, &oldy);
5302 if (!IS_MOVING(oldx, oldy))
5306 if (element == EL_BLOCKED &&
5307 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5308 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5309 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5310 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5311 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5312 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5313 next_element = get_next_element(Tile[oldx][oldy]);
5315 RemoveField(oldx, oldy);
5316 RemoveField(newx, newy);
5318 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5320 if (next_element != EL_UNDEFINED)
5321 Tile[oldx][oldy] = next_element;
5323 TEST_DrawLevelField(oldx, oldy);
5324 TEST_DrawLevelField(newx, newy);
5327 void DrawDynamite(int x, int y)
5329 int sx = SCREENX(x), sy = SCREENY(y);
5330 int graphic = el2img(Tile[x][y]);
5333 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5336 if (IS_WALKABLE_INSIDE(Back[x][y]))
5340 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5341 else if (Store[x][y])
5342 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5344 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5346 if (Back[x][y] || Store[x][y])
5347 DrawGraphicThruMask(sx, sy, graphic, frame);
5349 DrawGraphic(sx, sy, graphic, frame);
5352 static void CheckDynamite(int x, int y)
5354 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5358 if (MovDelay[x][y] != 0)
5361 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5367 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5372 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5374 boolean num_checked_players = 0;
5377 for (i = 0; i < MAX_PLAYERS; i++)
5379 if (stored_player[i].active)
5381 int sx = stored_player[i].jx;
5382 int sy = stored_player[i].jy;
5384 if (num_checked_players == 0)
5391 *sx1 = MIN(*sx1, sx);
5392 *sy1 = MIN(*sy1, sy);
5393 *sx2 = MAX(*sx2, sx);
5394 *sy2 = MAX(*sy2, sy);
5397 num_checked_players++;
5402 static boolean checkIfAllPlayersFitToScreen_RND(void)
5404 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5406 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5408 return (sx2 - sx1 < SCR_FIELDX &&
5409 sy2 - sy1 < SCR_FIELDY);
5412 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5414 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5416 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5418 *sx = (sx1 + sx2) / 2;
5419 *sy = (sy1 + sy2) / 2;
5422 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5423 boolean center_screen, boolean quick_relocation)
5425 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5426 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5427 boolean no_delay = (tape.warp_forward);
5428 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5429 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5430 int new_scroll_x, new_scroll_y;
5432 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5434 // case 1: quick relocation inside visible screen (without scrolling)
5441 if (!level.shifted_relocation || center_screen)
5443 // relocation _with_ centering of screen
5445 new_scroll_x = SCROLL_POSITION_X(x);
5446 new_scroll_y = SCROLL_POSITION_Y(y);
5450 // relocation _without_ centering of screen
5452 int center_scroll_x = SCROLL_POSITION_X(old_x);
5453 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5454 int offset_x = x + (scroll_x - center_scroll_x);
5455 int offset_y = y + (scroll_y - center_scroll_y);
5457 // for new screen position, apply previous offset to center position
5458 new_scroll_x = SCROLL_POSITION_X(offset_x);
5459 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5462 if (quick_relocation)
5464 // case 2: quick relocation (redraw without visible scrolling)
5466 scroll_x = new_scroll_x;
5467 scroll_y = new_scroll_y;
5474 // case 3: visible relocation (with scrolling to new position)
5476 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5478 SetVideoFrameDelay(wait_delay_value);
5480 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5482 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5483 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5485 if (dx == 0 && dy == 0) // no scrolling needed at all
5491 // set values for horizontal/vertical screen scrolling (half tile size)
5492 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5493 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5494 int pos_x = dx * TILEX / 2;
5495 int pos_y = dy * TILEY / 2;
5496 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5497 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5499 ScrollLevel(dx, dy);
5502 // scroll in two steps of half tile size to make things smoother
5503 BlitScreenToBitmapExt_RND(window, fx, fy);
5505 // scroll second step to align at full tile size
5506 BlitScreenToBitmap(window);
5512 SetVideoFrameDelay(frame_delay_value_old);
5515 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5517 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5518 int player_nr = GET_PLAYER_NR(el_player);
5519 struct PlayerInfo *player = &stored_player[player_nr];
5520 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5521 boolean no_delay = (tape.warp_forward);
5522 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5523 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5524 int old_jx = player->jx;
5525 int old_jy = player->jy;
5526 int old_element = Tile[old_jx][old_jy];
5527 int element = Tile[jx][jy];
5528 boolean player_relocated = (old_jx != jx || old_jy != jy);
5530 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5531 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5532 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5533 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5534 int leave_side_horiz = move_dir_horiz;
5535 int leave_side_vert = move_dir_vert;
5536 int enter_side = enter_side_horiz | enter_side_vert;
5537 int leave_side = leave_side_horiz | leave_side_vert;
5539 if (player->buried) // do not reanimate dead player
5542 if (!player_relocated) // no need to relocate the player
5545 if (IS_PLAYER(jx, jy)) // player already placed at new position
5547 RemoveField(jx, jy); // temporarily remove newly placed player
5548 DrawLevelField(jx, jy);
5551 if (player->present)
5553 while (player->MovPos)
5555 ScrollPlayer(player, SCROLL_GO_ON);
5556 ScrollScreen(NULL, SCROLL_GO_ON);
5558 AdvanceFrameAndPlayerCounters(player->index_nr);
5562 BackToFront_WithFrameDelay(wait_delay_value);
5565 DrawPlayer(player); // needed here only to cleanup last field
5566 DrawLevelField(player->jx, player->jy); // remove player graphic
5568 player->is_moving = FALSE;
5571 if (IS_CUSTOM_ELEMENT(old_element))
5572 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5574 player->index_bit, leave_side);
5576 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5578 player->index_bit, leave_side);
5580 Tile[jx][jy] = el_player;
5581 InitPlayerField(jx, jy, el_player, TRUE);
5583 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5584 possible that the relocation target field did not contain a player element,
5585 but a walkable element, to which the new player was relocated -- in this
5586 case, restore that (already initialized!) element on the player field */
5587 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5589 Tile[jx][jy] = element; // restore previously existing element
5592 // only visually relocate centered player
5593 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5594 FALSE, level.instant_relocation);
5596 TestIfPlayerTouchesBadThing(jx, jy);
5597 TestIfPlayerTouchesCustomElement(jx, jy);
5599 if (IS_CUSTOM_ELEMENT(element))
5600 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5601 player->index_bit, enter_side);
5603 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5604 player->index_bit, enter_side);
5606 if (player->is_switching)
5608 /* ensure that relocation while still switching an element does not cause
5609 a new element to be treated as also switched directly after relocation
5610 (this is important for teleporter switches that teleport the player to
5611 a place where another teleporter switch is in the same direction, which
5612 would then incorrectly be treated as immediately switched before the
5613 direction key that caused the switch was released) */
5615 player->switch_x += jx - old_jx;
5616 player->switch_y += jy - old_jy;
5620 static void Explode(int ex, int ey, int phase, int mode)
5626 // !!! eliminate this variable !!!
5627 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5629 if (game.explosions_delayed)
5631 ExplodeField[ex][ey] = mode;
5635 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5637 int center_element = Tile[ex][ey];
5638 int artwork_element, explosion_element; // set these values later
5640 // remove things displayed in background while burning dynamite
5641 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5644 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5646 // put moving element to center field (and let it explode there)
5647 center_element = MovingOrBlocked2Element(ex, ey);
5648 RemoveMovingField(ex, ey);
5649 Tile[ex][ey] = center_element;
5652 // now "center_element" is finally determined -- set related values now
5653 artwork_element = center_element; // for custom player artwork
5654 explosion_element = center_element; // for custom player artwork
5656 if (IS_PLAYER(ex, ey))
5658 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5660 artwork_element = stored_player[player_nr].artwork_element;
5662 if (level.use_explosion_element[player_nr])
5664 explosion_element = level.explosion_element[player_nr];
5665 artwork_element = explosion_element;
5669 if (mode == EX_TYPE_NORMAL ||
5670 mode == EX_TYPE_CENTER ||
5671 mode == EX_TYPE_CROSS)
5672 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5674 last_phase = element_info[explosion_element].explosion_delay + 1;
5676 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5678 int xx = x - ex + 1;
5679 int yy = y - ey + 1;
5682 if (!IN_LEV_FIELD(x, y) ||
5683 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5684 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5687 element = Tile[x][y];
5689 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5691 element = MovingOrBlocked2Element(x, y);
5693 if (!IS_EXPLOSION_PROOF(element))
5694 RemoveMovingField(x, y);
5697 // indestructible elements can only explode in center (but not flames)
5698 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5699 mode == EX_TYPE_BORDER)) ||
5700 element == EL_FLAMES)
5703 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5704 behaviour, for example when touching a yamyam that explodes to rocks
5705 with active deadly shield, a rock is created under the player !!! */
5706 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5708 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5709 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5710 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5712 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5715 if (IS_ACTIVE_BOMB(element))
5717 // re-activate things under the bomb like gate or penguin
5718 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5725 // save walkable background elements while explosion on same tile
5726 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5727 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5728 Back[x][y] = element;
5730 // ignite explodable elements reached by other explosion
5731 if (element == EL_EXPLOSION)
5732 element = Store2[x][y];
5734 if (AmoebaNr[x][y] &&
5735 (element == EL_AMOEBA_FULL ||
5736 element == EL_BD_AMOEBA ||
5737 element == EL_AMOEBA_GROWING))
5739 AmoebaCnt[AmoebaNr[x][y]]--;
5740 AmoebaCnt2[AmoebaNr[x][y]]--;
5745 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5747 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5749 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5751 if (PLAYERINFO(ex, ey)->use_murphy)
5752 Store[x][y] = EL_EMPTY;
5755 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5756 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5757 else if (ELEM_IS_PLAYER(center_element))
5758 Store[x][y] = EL_EMPTY;
5759 else if (center_element == EL_YAMYAM)
5760 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5761 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5762 Store[x][y] = element_info[center_element].content.e[xx][yy];
5764 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5765 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5766 // otherwise) -- FIX THIS !!!
5767 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5768 Store[x][y] = element_info[element].content.e[1][1];
5770 else if (!CAN_EXPLODE(element))
5771 Store[x][y] = element_info[element].content.e[1][1];
5774 Store[x][y] = EL_EMPTY;
5776 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5777 center_element == EL_AMOEBA_TO_DIAMOND)
5778 Store2[x][y] = element;
5780 Tile[x][y] = EL_EXPLOSION;
5781 GfxElement[x][y] = artwork_element;
5783 ExplodePhase[x][y] = 1;
5784 ExplodeDelay[x][y] = last_phase;
5789 if (center_element == EL_YAMYAM)
5790 game.yamyam_content_nr =
5791 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5803 GfxFrame[x][y] = 0; // restart explosion animation
5805 last_phase = ExplodeDelay[x][y];
5807 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5809 // this can happen if the player leaves an explosion just in time
5810 if (GfxElement[x][y] == EL_UNDEFINED)
5811 GfxElement[x][y] = EL_EMPTY;
5813 border_element = Store2[x][y];
5814 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5815 border_element = StorePlayer[x][y];
5817 if (phase == element_info[border_element].ignition_delay ||
5818 phase == last_phase)
5820 boolean border_explosion = FALSE;
5822 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5823 !PLAYER_EXPLOSION_PROTECTED(x, y))
5825 KillPlayerUnlessExplosionProtected(x, y);
5826 border_explosion = TRUE;
5828 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5830 Tile[x][y] = Store2[x][y];
5833 border_explosion = TRUE;
5835 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5837 AmoebaToDiamond(x, y);
5839 border_explosion = TRUE;
5842 // if an element just explodes due to another explosion (chain-reaction),
5843 // do not immediately end the new explosion when it was the last frame of
5844 // the explosion (as it would be done in the following "if"-statement!)
5845 if (border_explosion && phase == last_phase)
5849 if (phase == last_phase)
5853 element = Tile[x][y] = Store[x][y];
5854 Store[x][y] = Store2[x][y] = 0;
5855 GfxElement[x][y] = EL_UNDEFINED;
5857 // player can escape from explosions and might therefore be still alive
5858 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5859 element <= EL_PLAYER_IS_EXPLODING_4)
5861 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5862 int explosion_element = EL_PLAYER_1 + player_nr;
5863 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5864 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5866 if (level.use_explosion_element[player_nr])
5867 explosion_element = level.explosion_element[player_nr];
5869 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5870 element_info[explosion_element].content.e[xx][yy]);
5873 // restore probably existing indestructible background element
5874 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5875 element = Tile[x][y] = Back[x][y];
5878 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5879 GfxDir[x][y] = MV_NONE;
5880 ChangeDelay[x][y] = 0;
5881 ChangePage[x][y] = -1;
5883 CustomValue[x][y] = 0;
5885 InitField_WithBug2(x, y, FALSE);
5887 TEST_DrawLevelField(x, y);
5889 TestIfElementTouchesCustomElement(x, y);
5891 if (GFX_CRUMBLED(element))
5892 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5894 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5895 StorePlayer[x][y] = 0;
5897 if (ELEM_IS_PLAYER(element))
5898 RelocatePlayer(x, y, element);
5900 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5902 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5903 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5906 TEST_DrawLevelFieldCrumbled(x, y);
5908 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5910 DrawLevelElement(x, y, Back[x][y]);
5911 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5913 else if (IS_WALKABLE_UNDER(Back[x][y]))
5915 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5916 DrawLevelElementThruMask(x, y, Back[x][y]);
5918 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5919 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5923 static void DynaExplode(int ex, int ey)
5926 int dynabomb_element = Tile[ex][ey];
5927 int dynabomb_size = 1;
5928 boolean dynabomb_xl = FALSE;
5929 struct PlayerInfo *player;
5930 static int xy[4][2] =
5938 if (IS_ACTIVE_BOMB(dynabomb_element))
5940 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5941 dynabomb_size = player->dynabomb_size;
5942 dynabomb_xl = player->dynabomb_xl;
5943 player->dynabombs_left++;
5946 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5948 for (i = 0; i < NUM_DIRECTIONS; i++)
5950 for (j = 1; j <= dynabomb_size; j++)
5952 int x = ex + j * xy[i][0];
5953 int y = ey + j * xy[i][1];
5956 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5959 element = Tile[x][y];
5961 // do not restart explosions of fields with active bombs
5962 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5965 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5967 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5968 !IS_DIGGABLE(element) && !dynabomb_xl)
5974 void Bang(int x, int y)
5976 int element = MovingOrBlocked2Element(x, y);
5977 int explosion_type = EX_TYPE_NORMAL;
5979 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5981 struct PlayerInfo *player = PLAYERINFO(x, y);
5983 element = Tile[x][y] = player->initial_element;
5985 if (level.use_explosion_element[player->index_nr])
5987 int explosion_element = level.explosion_element[player->index_nr];
5989 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5990 explosion_type = EX_TYPE_CROSS;
5991 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5992 explosion_type = EX_TYPE_CENTER;
6000 case EL_BD_BUTTERFLY:
6003 case EL_DARK_YAMYAM:
6007 RaiseScoreElement(element);
6010 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6011 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6012 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6013 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6014 case EL_DYNABOMB_INCREASE_NUMBER:
6015 case EL_DYNABOMB_INCREASE_SIZE:
6016 case EL_DYNABOMB_INCREASE_POWER:
6017 explosion_type = EX_TYPE_DYNA;
6020 case EL_DC_LANDMINE:
6021 explosion_type = EX_TYPE_CENTER;
6026 case EL_LAMP_ACTIVE:
6027 case EL_AMOEBA_TO_DIAMOND:
6028 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6029 explosion_type = EX_TYPE_CENTER;
6033 if (element_info[element].explosion_type == EXPLODES_CROSS)
6034 explosion_type = EX_TYPE_CROSS;
6035 else if (element_info[element].explosion_type == EXPLODES_1X1)
6036 explosion_type = EX_TYPE_CENTER;
6040 if (explosion_type == EX_TYPE_DYNA)
6043 Explode(x, y, EX_PHASE_START, explosion_type);
6045 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6048 static void SplashAcid(int x, int y)
6050 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6051 (!IN_LEV_FIELD(x - 1, y - 2) ||
6052 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6053 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6055 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6056 (!IN_LEV_FIELD(x + 1, y - 2) ||
6057 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6058 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6060 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6063 static void InitBeltMovement(void)
6065 static int belt_base_element[4] =
6067 EL_CONVEYOR_BELT_1_LEFT,
6068 EL_CONVEYOR_BELT_2_LEFT,
6069 EL_CONVEYOR_BELT_3_LEFT,
6070 EL_CONVEYOR_BELT_4_LEFT
6072 static int belt_base_active_element[4] =
6074 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6075 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6076 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6077 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6082 // set frame order for belt animation graphic according to belt direction
6083 for (i = 0; i < NUM_BELTS; i++)
6087 for (j = 0; j < NUM_BELT_PARTS; j++)
6089 int element = belt_base_active_element[belt_nr] + j;
6090 int graphic_1 = el2img(element);
6091 int graphic_2 = el2panelimg(element);
6093 if (game.belt_dir[i] == MV_LEFT)
6095 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6096 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6100 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6101 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6106 SCAN_PLAYFIELD(x, y)
6108 int element = Tile[x][y];
6110 for (i = 0; i < NUM_BELTS; i++)
6112 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6114 int e_belt_nr = getBeltNrFromBeltElement(element);
6117 if (e_belt_nr == belt_nr)
6119 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6121 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6128 static void ToggleBeltSwitch(int x, int y)
6130 static int belt_base_element[4] =
6132 EL_CONVEYOR_BELT_1_LEFT,
6133 EL_CONVEYOR_BELT_2_LEFT,
6134 EL_CONVEYOR_BELT_3_LEFT,
6135 EL_CONVEYOR_BELT_4_LEFT
6137 static int belt_base_active_element[4] =
6139 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6140 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6141 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6142 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6144 static int belt_base_switch_element[4] =
6146 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6147 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6148 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6149 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6151 static int belt_move_dir[4] =
6159 int element = Tile[x][y];
6160 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6161 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6162 int belt_dir = belt_move_dir[belt_dir_nr];
6165 if (!IS_BELT_SWITCH(element))
6168 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6169 game.belt_dir[belt_nr] = belt_dir;
6171 if (belt_dir_nr == 3)
6174 // set frame order for belt animation graphic according to belt direction
6175 for (i = 0; i < NUM_BELT_PARTS; i++)
6177 int element = belt_base_active_element[belt_nr] + i;
6178 int graphic_1 = el2img(element);
6179 int graphic_2 = el2panelimg(element);
6181 if (belt_dir == MV_LEFT)
6183 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6184 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6188 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6189 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6193 SCAN_PLAYFIELD(xx, yy)
6195 int element = Tile[xx][yy];
6197 if (IS_BELT_SWITCH(element))
6199 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6201 if (e_belt_nr == belt_nr)
6203 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6204 TEST_DrawLevelField(xx, yy);
6207 else if (IS_BELT(element) && belt_dir != MV_NONE)
6209 int e_belt_nr = getBeltNrFromBeltElement(element);
6211 if (e_belt_nr == belt_nr)
6213 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6215 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6216 TEST_DrawLevelField(xx, yy);
6219 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6221 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6223 if (e_belt_nr == belt_nr)
6225 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6227 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6228 TEST_DrawLevelField(xx, yy);
6234 static void ToggleSwitchgateSwitch(int x, int y)
6238 game.switchgate_pos = !game.switchgate_pos;
6240 SCAN_PLAYFIELD(xx, yy)
6242 int element = Tile[xx][yy];
6244 if (element == EL_SWITCHGATE_SWITCH_UP)
6246 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6247 TEST_DrawLevelField(xx, yy);
6249 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6251 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6252 TEST_DrawLevelField(xx, yy);
6254 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6256 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6257 TEST_DrawLevelField(xx, yy);
6259 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6261 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6262 TEST_DrawLevelField(xx, yy);
6264 else if (element == EL_SWITCHGATE_OPEN ||
6265 element == EL_SWITCHGATE_OPENING)
6267 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6269 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6271 else if (element == EL_SWITCHGATE_CLOSED ||
6272 element == EL_SWITCHGATE_CLOSING)
6274 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6276 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6281 static int getInvisibleActiveFromInvisibleElement(int element)
6283 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6284 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6285 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6289 static int getInvisibleFromInvisibleActiveElement(int element)
6291 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6292 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6293 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6297 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6301 SCAN_PLAYFIELD(x, y)
6303 int element = Tile[x][y];
6305 if (element == EL_LIGHT_SWITCH &&
6306 game.light_time_left > 0)
6308 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6309 TEST_DrawLevelField(x, y);
6311 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6312 game.light_time_left == 0)
6314 Tile[x][y] = EL_LIGHT_SWITCH;
6315 TEST_DrawLevelField(x, y);
6317 else if (element == EL_EMC_DRIPPER &&
6318 game.light_time_left > 0)
6320 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6321 TEST_DrawLevelField(x, y);
6323 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6324 game.light_time_left == 0)
6326 Tile[x][y] = EL_EMC_DRIPPER;
6327 TEST_DrawLevelField(x, y);
6329 else if (element == EL_INVISIBLE_STEELWALL ||
6330 element == EL_INVISIBLE_WALL ||
6331 element == EL_INVISIBLE_SAND)
6333 if (game.light_time_left > 0)
6334 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6336 TEST_DrawLevelField(x, y);
6338 // uncrumble neighbour fields, if needed
6339 if (element == EL_INVISIBLE_SAND)
6340 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6342 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6343 element == EL_INVISIBLE_WALL_ACTIVE ||
6344 element == EL_INVISIBLE_SAND_ACTIVE)
6346 if (game.light_time_left == 0)
6347 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6349 TEST_DrawLevelField(x, y);
6351 // re-crumble neighbour fields, if needed
6352 if (element == EL_INVISIBLE_SAND)
6353 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6358 static void RedrawAllInvisibleElementsForLenses(void)
6362 SCAN_PLAYFIELD(x, y)
6364 int element = Tile[x][y];
6366 if (element == EL_EMC_DRIPPER &&
6367 game.lenses_time_left > 0)
6369 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6370 TEST_DrawLevelField(x, y);
6372 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6373 game.lenses_time_left == 0)
6375 Tile[x][y] = EL_EMC_DRIPPER;
6376 TEST_DrawLevelField(x, y);
6378 else if (element == EL_INVISIBLE_STEELWALL ||
6379 element == EL_INVISIBLE_WALL ||
6380 element == EL_INVISIBLE_SAND)
6382 if (game.lenses_time_left > 0)
6383 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6385 TEST_DrawLevelField(x, y);
6387 // uncrumble neighbour fields, if needed
6388 if (element == EL_INVISIBLE_SAND)
6389 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6391 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6392 element == EL_INVISIBLE_WALL_ACTIVE ||
6393 element == EL_INVISIBLE_SAND_ACTIVE)
6395 if (game.lenses_time_left == 0)
6396 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6398 TEST_DrawLevelField(x, y);
6400 // re-crumble neighbour fields, if needed
6401 if (element == EL_INVISIBLE_SAND)
6402 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6407 static void RedrawAllInvisibleElementsForMagnifier(void)
6411 SCAN_PLAYFIELD(x, y)
6413 int element = Tile[x][y];
6415 if (element == EL_EMC_FAKE_GRASS &&
6416 game.magnify_time_left > 0)
6418 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6419 TEST_DrawLevelField(x, y);
6421 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6422 game.magnify_time_left == 0)
6424 Tile[x][y] = EL_EMC_FAKE_GRASS;
6425 TEST_DrawLevelField(x, y);
6427 else if (IS_GATE_GRAY(element) &&
6428 game.magnify_time_left > 0)
6430 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6431 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6432 IS_EM_GATE_GRAY(element) ?
6433 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6434 IS_EMC_GATE_GRAY(element) ?
6435 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6436 IS_DC_GATE_GRAY(element) ?
6437 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6439 TEST_DrawLevelField(x, y);
6441 else if (IS_GATE_GRAY_ACTIVE(element) &&
6442 game.magnify_time_left == 0)
6444 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6445 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6446 IS_EM_GATE_GRAY_ACTIVE(element) ?
6447 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6448 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6449 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6450 IS_DC_GATE_GRAY_ACTIVE(element) ?
6451 EL_DC_GATE_WHITE_GRAY :
6453 TEST_DrawLevelField(x, y);
6458 static void ToggleLightSwitch(int x, int y)
6460 int element = Tile[x][y];
6462 game.light_time_left =
6463 (element == EL_LIGHT_SWITCH ?
6464 level.time_light * FRAMES_PER_SECOND : 0);
6466 RedrawAllLightSwitchesAndInvisibleElements();
6469 static void ActivateTimegateSwitch(int x, int y)
6473 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6475 SCAN_PLAYFIELD(xx, yy)
6477 int element = Tile[xx][yy];
6479 if (element == EL_TIMEGATE_CLOSED ||
6480 element == EL_TIMEGATE_CLOSING)
6482 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6483 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6487 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6489 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6490 TEST_DrawLevelField(xx, yy);
6496 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6497 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6500 static void Impact(int x, int y)
6502 boolean last_line = (y == lev_fieldy - 1);
6503 boolean object_hit = FALSE;
6504 boolean impact = (last_line || object_hit);
6505 int element = Tile[x][y];
6506 int smashed = EL_STEELWALL;
6508 if (!last_line) // check if element below was hit
6510 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6513 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6514 MovDir[x][y + 1] != MV_DOWN ||
6515 MovPos[x][y + 1] <= TILEY / 2));
6517 // do not smash moving elements that left the smashed field in time
6518 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6519 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6522 #if USE_QUICKSAND_IMPACT_BUGFIX
6523 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6525 RemoveMovingField(x, y + 1);
6526 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6527 Tile[x][y + 2] = EL_ROCK;
6528 TEST_DrawLevelField(x, y + 2);
6533 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6535 RemoveMovingField(x, y + 1);
6536 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6537 Tile[x][y + 2] = EL_ROCK;
6538 TEST_DrawLevelField(x, y + 2);
6545 smashed = MovingOrBlocked2Element(x, y + 1);
6547 impact = (last_line || object_hit);
6550 if (!last_line && smashed == EL_ACID) // element falls into acid
6552 SplashAcid(x, y + 1);
6556 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6557 // only reset graphic animation if graphic really changes after impact
6559 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6561 ResetGfxAnimation(x, y);
6562 TEST_DrawLevelField(x, y);
6565 if (impact && CAN_EXPLODE_IMPACT(element))
6570 else if (impact && element == EL_PEARL &&
6571 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6573 ResetGfxAnimation(x, y);
6575 Tile[x][y] = EL_PEARL_BREAKING;
6576 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6579 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6581 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6586 if (impact && element == EL_AMOEBA_DROP)
6588 if (object_hit && IS_PLAYER(x, y + 1))
6589 KillPlayerUnlessEnemyProtected(x, y + 1);
6590 else if (object_hit && smashed == EL_PENGUIN)
6594 Tile[x][y] = EL_AMOEBA_GROWING;
6595 Store[x][y] = EL_AMOEBA_WET;
6597 ResetRandomAnimationValue(x, y);
6602 if (object_hit) // check which object was hit
6604 if ((CAN_PASS_MAGIC_WALL(element) &&
6605 (smashed == EL_MAGIC_WALL ||
6606 smashed == EL_BD_MAGIC_WALL)) ||
6607 (CAN_PASS_DC_MAGIC_WALL(element) &&
6608 smashed == EL_DC_MAGIC_WALL))
6611 int activated_magic_wall =
6612 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6613 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6614 EL_DC_MAGIC_WALL_ACTIVE);
6616 // activate magic wall / mill
6617 SCAN_PLAYFIELD(xx, yy)
6619 if (Tile[xx][yy] == smashed)
6620 Tile[xx][yy] = activated_magic_wall;
6623 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6624 game.magic_wall_active = TRUE;
6626 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6627 SND_MAGIC_WALL_ACTIVATING :
6628 smashed == EL_BD_MAGIC_WALL ?
6629 SND_BD_MAGIC_WALL_ACTIVATING :
6630 SND_DC_MAGIC_WALL_ACTIVATING));
6633 if (IS_PLAYER(x, y + 1))
6635 if (CAN_SMASH_PLAYER(element))
6637 KillPlayerUnlessEnemyProtected(x, y + 1);
6641 else if (smashed == EL_PENGUIN)
6643 if (CAN_SMASH_PLAYER(element))
6649 else if (element == EL_BD_DIAMOND)
6651 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6657 else if (((element == EL_SP_INFOTRON ||
6658 element == EL_SP_ZONK) &&
6659 (smashed == EL_SP_SNIKSNAK ||
6660 smashed == EL_SP_ELECTRON ||
6661 smashed == EL_SP_DISK_ORANGE)) ||
6662 (element == EL_SP_INFOTRON &&
6663 smashed == EL_SP_DISK_YELLOW))
6668 else if (CAN_SMASH_EVERYTHING(element))
6670 if (IS_CLASSIC_ENEMY(smashed) ||
6671 CAN_EXPLODE_SMASHED(smashed))
6676 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6678 if (smashed == EL_LAMP ||
6679 smashed == EL_LAMP_ACTIVE)
6684 else if (smashed == EL_NUT)
6686 Tile[x][y + 1] = EL_NUT_BREAKING;
6687 PlayLevelSound(x, y, SND_NUT_BREAKING);
6688 RaiseScoreElement(EL_NUT);
6691 else if (smashed == EL_PEARL)
6693 ResetGfxAnimation(x, y);
6695 Tile[x][y + 1] = EL_PEARL_BREAKING;
6696 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6699 else if (smashed == EL_DIAMOND)
6701 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6702 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6705 else if (IS_BELT_SWITCH(smashed))
6707 ToggleBeltSwitch(x, y + 1);
6709 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6710 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6711 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6712 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6714 ToggleSwitchgateSwitch(x, y + 1);
6716 else if (smashed == EL_LIGHT_SWITCH ||
6717 smashed == EL_LIGHT_SWITCH_ACTIVE)
6719 ToggleLightSwitch(x, y + 1);
6723 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6725 CheckElementChangeBySide(x, y + 1, smashed, element,
6726 CE_SWITCHED, CH_SIDE_TOP);
6727 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6733 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6738 // play sound of magic wall / mill
6740 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6741 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6742 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6744 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6745 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6746 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6747 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6748 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6749 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6754 // play sound of object that hits the ground
6755 if (last_line || object_hit)
6756 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6759 static void TurnRoundExt(int x, int y)
6771 { 0, 0 }, { 0, 0 }, { 0, 0 },
6776 int left, right, back;
6780 { MV_DOWN, MV_UP, MV_RIGHT },
6781 { MV_UP, MV_DOWN, MV_LEFT },
6783 { MV_LEFT, MV_RIGHT, MV_DOWN },
6787 { MV_RIGHT, MV_LEFT, MV_UP }
6790 int element = Tile[x][y];
6791 int move_pattern = element_info[element].move_pattern;
6793 int old_move_dir = MovDir[x][y];
6794 int left_dir = turn[old_move_dir].left;
6795 int right_dir = turn[old_move_dir].right;
6796 int back_dir = turn[old_move_dir].back;
6798 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6799 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6800 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6801 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6803 int left_x = x + left_dx, left_y = y + left_dy;
6804 int right_x = x + right_dx, right_y = y + right_dy;
6805 int move_x = x + move_dx, move_y = y + move_dy;
6809 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6811 TestIfBadThingTouchesOtherBadThing(x, y);
6813 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6814 MovDir[x][y] = right_dir;
6815 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6816 MovDir[x][y] = left_dir;
6818 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6820 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6823 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6825 TestIfBadThingTouchesOtherBadThing(x, y);
6827 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6828 MovDir[x][y] = left_dir;
6829 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6830 MovDir[x][y] = right_dir;
6832 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6834 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6837 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6839 TestIfBadThingTouchesOtherBadThing(x, y);
6841 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6842 MovDir[x][y] = left_dir;
6843 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6844 MovDir[x][y] = right_dir;
6846 if (MovDir[x][y] != old_move_dir)
6849 else if (element == EL_YAMYAM)
6851 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6852 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6854 if (can_turn_left && can_turn_right)
6855 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6856 else if (can_turn_left)
6857 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6858 else if (can_turn_right)
6859 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6861 MovDir[x][y] = back_dir;
6863 MovDelay[x][y] = 16 + 16 * RND(3);
6865 else if (element == EL_DARK_YAMYAM)
6867 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6869 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6872 if (can_turn_left && can_turn_right)
6873 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6874 else if (can_turn_left)
6875 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6876 else if (can_turn_right)
6877 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6879 MovDir[x][y] = back_dir;
6881 MovDelay[x][y] = 16 + 16 * RND(3);
6883 else if (element == EL_PACMAN)
6885 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6886 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6888 if (can_turn_left && can_turn_right)
6889 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6890 else if (can_turn_left)
6891 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6892 else if (can_turn_right)
6893 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6895 MovDir[x][y] = back_dir;
6897 MovDelay[x][y] = 6 + RND(40);
6899 else if (element == EL_PIG)
6901 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6902 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6903 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6904 boolean should_turn_left, should_turn_right, should_move_on;
6906 int rnd = RND(rnd_value);
6908 should_turn_left = (can_turn_left &&
6910 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6911 y + back_dy + left_dy)));
6912 should_turn_right = (can_turn_right &&
6914 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6915 y + back_dy + right_dy)));
6916 should_move_on = (can_move_on &&
6919 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6920 y + move_dy + left_dy) ||
6921 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6922 y + move_dy + right_dy)));
6924 if (should_turn_left || should_turn_right || should_move_on)
6926 if (should_turn_left && should_turn_right && should_move_on)
6927 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6928 rnd < 2 * rnd_value / 3 ? right_dir :
6930 else if (should_turn_left && should_turn_right)
6931 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6932 else if (should_turn_left && should_move_on)
6933 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6934 else if (should_turn_right && should_move_on)
6935 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6936 else if (should_turn_left)
6937 MovDir[x][y] = left_dir;
6938 else if (should_turn_right)
6939 MovDir[x][y] = right_dir;
6940 else if (should_move_on)
6941 MovDir[x][y] = old_move_dir;
6943 else if (can_move_on && rnd > rnd_value / 8)
6944 MovDir[x][y] = old_move_dir;
6945 else if (can_turn_left && can_turn_right)
6946 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6947 else if (can_turn_left && rnd > rnd_value / 8)
6948 MovDir[x][y] = left_dir;
6949 else if (can_turn_right && rnd > rnd_value/8)
6950 MovDir[x][y] = right_dir;
6952 MovDir[x][y] = back_dir;
6954 xx = x + move_xy[MovDir[x][y]].dx;
6955 yy = y + move_xy[MovDir[x][y]].dy;
6957 if (!IN_LEV_FIELD(xx, yy) ||
6958 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6959 MovDir[x][y] = old_move_dir;
6963 else if (element == EL_DRAGON)
6965 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6966 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6967 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6969 int rnd = RND(rnd_value);
6971 if (can_move_on && rnd > rnd_value / 8)
6972 MovDir[x][y] = old_move_dir;
6973 else if (can_turn_left && can_turn_right)
6974 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6975 else if (can_turn_left && rnd > rnd_value / 8)
6976 MovDir[x][y] = left_dir;
6977 else if (can_turn_right && rnd > rnd_value / 8)
6978 MovDir[x][y] = right_dir;
6980 MovDir[x][y] = back_dir;
6982 xx = x + move_xy[MovDir[x][y]].dx;
6983 yy = y + move_xy[MovDir[x][y]].dy;
6985 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6986 MovDir[x][y] = old_move_dir;
6990 else if (element == EL_MOLE)
6992 boolean can_move_on =
6993 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6994 IS_AMOEBOID(Tile[move_x][move_y]) ||
6995 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6998 boolean can_turn_left =
6999 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7000 IS_AMOEBOID(Tile[left_x][left_y])));
7002 boolean can_turn_right =
7003 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7004 IS_AMOEBOID(Tile[right_x][right_y])));
7006 if (can_turn_left && can_turn_right)
7007 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7008 else if (can_turn_left)
7009 MovDir[x][y] = left_dir;
7011 MovDir[x][y] = right_dir;
7014 if (MovDir[x][y] != old_move_dir)
7017 else if (element == EL_BALLOON)
7019 MovDir[x][y] = game.wind_direction;
7022 else if (element == EL_SPRING)
7024 if (MovDir[x][y] & MV_HORIZONTAL)
7026 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7027 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7029 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7030 ResetGfxAnimation(move_x, move_y);
7031 TEST_DrawLevelField(move_x, move_y);
7033 MovDir[x][y] = back_dir;
7035 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7036 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7037 MovDir[x][y] = MV_NONE;
7042 else if (element == EL_ROBOT ||
7043 element == EL_SATELLITE ||
7044 element == EL_PENGUIN ||
7045 element == EL_EMC_ANDROID)
7047 int attr_x = -1, attr_y = -1;
7049 if (game.all_players_gone)
7051 attr_x = game.exit_x;
7052 attr_y = game.exit_y;
7058 for (i = 0; i < MAX_PLAYERS; i++)
7060 struct PlayerInfo *player = &stored_player[i];
7061 int jx = player->jx, jy = player->jy;
7063 if (!player->active)
7067 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7075 if (element == EL_ROBOT &&
7076 game.robot_wheel_x >= 0 &&
7077 game.robot_wheel_y >= 0 &&
7078 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7079 game.engine_version < VERSION_IDENT(3,1,0,0)))
7081 attr_x = game.robot_wheel_x;
7082 attr_y = game.robot_wheel_y;
7085 if (element == EL_PENGUIN)
7088 static int xy[4][2] =
7096 for (i = 0; i < NUM_DIRECTIONS; i++)
7098 int ex = x + xy[i][0];
7099 int ey = y + xy[i][1];
7101 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7102 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7103 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7104 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7113 MovDir[x][y] = MV_NONE;
7115 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7116 else if (attr_x > x)
7117 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7119 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7120 else if (attr_y > y)
7121 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7123 if (element == EL_ROBOT)
7127 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7128 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7129 Moving2Blocked(x, y, &newx, &newy);
7131 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7132 MovDelay[x][y] = 8 + 8 * !RND(3);
7134 MovDelay[x][y] = 16;
7136 else if (element == EL_PENGUIN)
7142 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7144 boolean first_horiz = RND(2);
7145 int new_move_dir = MovDir[x][y];
7148 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7149 Moving2Blocked(x, y, &newx, &newy);
7151 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7155 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7156 Moving2Blocked(x, y, &newx, &newy);
7158 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7161 MovDir[x][y] = old_move_dir;
7165 else if (element == EL_SATELLITE)
7171 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7173 boolean first_horiz = RND(2);
7174 int new_move_dir = MovDir[x][y];
7177 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7178 Moving2Blocked(x, y, &newx, &newy);
7180 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7184 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7185 Moving2Blocked(x, y, &newx, &newy);
7187 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7190 MovDir[x][y] = old_move_dir;
7194 else if (element == EL_EMC_ANDROID)
7196 static int check_pos[16] =
7198 -1, // 0 => (invalid)
7201 -1, // 3 => (invalid)
7203 0, // 5 => MV_LEFT | MV_UP
7204 2, // 6 => MV_RIGHT | MV_UP
7205 -1, // 7 => (invalid)
7207 6, // 9 => MV_LEFT | MV_DOWN
7208 4, // 10 => MV_RIGHT | MV_DOWN
7209 -1, // 11 => (invalid)
7210 -1, // 12 => (invalid)
7211 -1, // 13 => (invalid)
7212 -1, // 14 => (invalid)
7213 -1, // 15 => (invalid)
7221 { -1, -1, MV_LEFT | MV_UP },
7223 { +1, -1, MV_RIGHT | MV_UP },
7224 { +1, 0, MV_RIGHT },
7225 { +1, +1, MV_RIGHT | MV_DOWN },
7227 { -1, +1, MV_LEFT | MV_DOWN },
7230 int start_pos, check_order;
7231 boolean can_clone = FALSE;
7234 // check if there is any free field around current position
7235 for (i = 0; i < 8; i++)
7237 int newx = x + check_xy[i].dx;
7238 int newy = y + check_xy[i].dy;
7240 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7248 if (can_clone) // randomly find an element to clone
7252 start_pos = check_pos[RND(8)];
7253 check_order = (RND(2) ? -1 : +1);
7255 for (i = 0; i < 8; i++)
7257 int pos_raw = start_pos + i * check_order;
7258 int pos = (pos_raw + 8) % 8;
7259 int newx = x + check_xy[pos].dx;
7260 int newy = y + check_xy[pos].dy;
7262 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7264 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7265 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7267 Store[x][y] = Tile[newx][newy];
7276 if (can_clone) // randomly find a direction to move
7280 start_pos = check_pos[RND(8)];
7281 check_order = (RND(2) ? -1 : +1);
7283 for (i = 0; i < 8; i++)
7285 int pos_raw = start_pos + i * check_order;
7286 int pos = (pos_raw + 8) % 8;
7287 int newx = x + check_xy[pos].dx;
7288 int newy = y + check_xy[pos].dy;
7289 int new_move_dir = check_xy[pos].dir;
7291 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7293 MovDir[x][y] = new_move_dir;
7294 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7303 if (can_clone) // cloning and moving successful
7306 // cannot clone -- try to move towards player
7308 start_pos = check_pos[MovDir[x][y] & 0x0f];
7309 check_order = (RND(2) ? -1 : +1);
7311 for (i = 0; i < 3; i++)
7313 // first check start_pos, then previous/next or (next/previous) pos
7314 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7315 int pos = (pos_raw + 8) % 8;
7316 int newx = x + check_xy[pos].dx;
7317 int newy = y + check_xy[pos].dy;
7318 int new_move_dir = check_xy[pos].dir;
7320 if (IS_PLAYER(newx, newy))
7323 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7325 MovDir[x][y] = new_move_dir;
7326 MovDelay[x][y] = level.android_move_time * 8 + 1;
7333 else if (move_pattern == MV_TURNING_LEFT ||
7334 move_pattern == MV_TURNING_RIGHT ||
7335 move_pattern == MV_TURNING_LEFT_RIGHT ||
7336 move_pattern == MV_TURNING_RIGHT_LEFT ||
7337 move_pattern == MV_TURNING_RANDOM ||
7338 move_pattern == MV_ALL_DIRECTIONS)
7340 boolean can_turn_left =
7341 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7342 boolean can_turn_right =
7343 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7345 if (element_info[element].move_stepsize == 0) // "not moving"
7348 if (move_pattern == MV_TURNING_LEFT)
7349 MovDir[x][y] = left_dir;
7350 else if (move_pattern == MV_TURNING_RIGHT)
7351 MovDir[x][y] = right_dir;
7352 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7353 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7354 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7355 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7356 else if (move_pattern == MV_TURNING_RANDOM)
7357 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7358 can_turn_right && !can_turn_left ? right_dir :
7359 RND(2) ? left_dir : right_dir);
7360 else if (can_turn_left && can_turn_right)
7361 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7362 else if (can_turn_left)
7363 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7364 else if (can_turn_right)
7365 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7367 MovDir[x][y] = back_dir;
7369 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7371 else if (move_pattern == MV_HORIZONTAL ||
7372 move_pattern == MV_VERTICAL)
7374 if (move_pattern & old_move_dir)
7375 MovDir[x][y] = back_dir;
7376 else if (move_pattern == MV_HORIZONTAL)
7377 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7378 else if (move_pattern == MV_VERTICAL)
7379 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7381 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7383 else if (move_pattern & MV_ANY_DIRECTION)
7385 MovDir[x][y] = move_pattern;
7386 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7388 else if (move_pattern & MV_WIND_DIRECTION)
7390 MovDir[x][y] = game.wind_direction;
7391 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7393 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7395 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7396 MovDir[x][y] = left_dir;
7397 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7398 MovDir[x][y] = right_dir;
7400 if (MovDir[x][y] != old_move_dir)
7401 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7403 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7405 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7406 MovDir[x][y] = right_dir;
7407 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7408 MovDir[x][y] = left_dir;
7410 if (MovDir[x][y] != old_move_dir)
7411 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7413 else if (move_pattern == MV_TOWARDS_PLAYER ||
7414 move_pattern == MV_AWAY_FROM_PLAYER)
7416 int attr_x = -1, attr_y = -1;
7418 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7420 if (game.all_players_gone)
7422 attr_x = game.exit_x;
7423 attr_y = game.exit_y;
7429 for (i = 0; i < MAX_PLAYERS; i++)
7431 struct PlayerInfo *player = &stored_player[i];
7432 int jx = player->jx, jy = player->jy;
7434 if (!player->active)
7438 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7446 MovDir[x][y] = MV_NONE;
7448 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7449 else if (attr_x > x)
7450 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7452 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7453 else if (attr_y > y)
7454 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7456 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7458 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7460 boolean first_horiz = RND(2);
7461 int new_move_dir = MovDir[x][y];
7463 if (element_info[element].move_stepsize == 0) // "not moving"
7465 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7466 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7472 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7473 Moving2Blocked(x, y, &newx, &newy);
7475 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7479 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7480 Moving2Blocked(x, y, &newx, &newy);
7482 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7485 MovDir[x][y] = old_move_dir;
7488 else if (move_pattern == MV_WHEN_PUSHED ||
7489 move_pattern == MV_WHEN_DROPPED)
7491 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7492 MovDir[x][y] = MV_NONE;
7496 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7498 static int test_xy[7][2] =
7508 static int test_dir[7] =
7518 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7519 int move_preference = -1000000; // start with very low preference
7520 int new_move_dir = MV_NONE;
7521 int start_test = RND(4);
7524 for (i = 0; i < NUM_DIRECTIONS; i++)
7526 int move_dir = test_dir[start_test + i];
7527 int move_dir_preference;
7529 xx = x + test_xy[start_test + i][0];
7530 yy = y + test_xy[start_test + i][1];
7532 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7533 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7535 new_move_dir = move_dir;
7540 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7543 move_dir_preference = -1 * RunnerVisit[xx][yy];
7544 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7545 move_dir_preference = PlayerVisit[xx][yy];
7547 if (move_dir_preference > move_preference)
7549 // prefer field that has not been visited for the longest time
7550 move_preference = move_dir_preference;
7551 new_move_dir = move_dir;
7553 else if (move_dir_preference == move_preference &&
7554 move_dir == old_move_dir)
7556 // prefer last direction when all directions are preferred equally
7557 move_preference = move_dir_preference;
7558 new_move_dir = move_dir;
7562 MovDir[x][y] = new_move_dir;
7563 if (old_move_dir != new_move_dir)
7564 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7568 static void TurnRound(int x, int y)
7570 int direction = MovDir[x][y];
7574 GfxDir[x][y] = MovDir[x][y];
7576 if (direction != MovDir[x][y])
7580 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7582 ResetGfxFrame(x, y);
7585 static boolean JustBeingPushed(int x, int y)
7589 for (i = 0; i < MAX_PLAYERS; i++)
7591 struct PlayerInfo *player = &stored_player[i];
7593 if (player->active && player->is_pushing && player->MovPos)
7595 int next_jx = player->jx + (player->jx - player->last_jx);
7596 int next_jy = player->jy + (player->jy - player->last_jy);
7598 if (x == next_jx && y == next_jy)
7606 static void StartMoving(int x, int y)
7608 boolean started_moving = FALSE; // some elements can fall _and_ move
7609 int element = Tile[x][y];
7614 if (MovDelay[x][y] == 0)
7615 GfxAction[x][y] = ACTION_DEFAULT;
7617 if (CAN_FALL(element) && y < lev_fieldy - 1)
7619 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7620 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7621 if (JustBeingPushed(x, y))
7624 if (element == EL_QUICKSAND_FULL)
7626 if (IS_FREE(x, y + 1))
7628 InitMovingField(x, y, MV_DOWN);
7629 started_moving = TRUE;
7631 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7632 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7633 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7634 Store[x][y] = EL_ROCK;
7636 Store[x][y] = EL_ROCK;
7639 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7641 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7643 if (!MovDelay[x][y])
7645 MovDelay[x][y] = TILEY + 1;
7647 ResetGfxAnimation(x, y);
7648 ResetGfxAnimation(x, y + 1);
7653 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7654 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7661 Tile[x][y] = EL_QUICKSAND_EMPTY;
7662 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7663 Store[x][y + 1] = Store[x][y];
7666 PlayLevelSoundAction(x, y, ACTION_FILLING);
7668 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7670 if (!MovDelay[x][y])
7672 MovDelay[x][y] = TILEY + 1;
7674 ResetGfxAnimation(x, y);
7675 ResetGfxAnimation(x, y + 1);
7680 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7681 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7688 Tile[x][y] = EL_QUICKSAND_EMPTY;
7689 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7690 Store[x][y + 1] = Store[x][y];
7693 PlayLevelSoundAction(x, y, ACTION_FILLING);
7696 else if (element == EL_QUICKSAND_FAST_FULL)
7698 if (IS_FREE(x, y + 1))
7700 InitMovingField(x, y, MV_DOWN);
7701 started_moving = TRUE;
7703 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7704 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7705 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7706 Store[x][y] = EL_ROCK;
7708 Store[x][y] = EL_ROCK;
7711 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7713 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7715 if (!MovDelay[x][y])
7717 MovDelay[x][y] = TILEY + 1;
7719 ResetGfxAnimation(x, y);
7720 ResetGfxAnimation(x, y + 1);
7725 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7726 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7733 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7734 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7735 Store[x][y + 1] = Store[x][y];
7738 PlayLevelSoundAction(x, y, ACTION_FILLING);
7740 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7742 if (!MovDelay[x][y])
7744 MovDelay[x][y] = TILEY + 1;
7746 ResetGfxAnimation(x, y);
7747 ResetGfxAnimation(x, y + 1);
7752 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7753 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7760 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7761 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7762 Store[x][y + 1] = Store[x][y];
7765 PlayLevelSoundAction(x, y, ACTION_FILLING);
7768 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7769 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7771 InitMovingField(x, y, MV_DOWN);
7772 started_moving = TRUE;
7774 Tile[x][y] = EL_QUICKSAND_FILLING;
7775 Store[x][y] = element;
7777 PlayLevelSoundAction(x, y, ACTION_FILLING);
7779 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7780 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7782 InitMovingField(x, y, MV_DOWN);
7783 started_moving = TRUE;
7785 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7786 Store[x][y] = element;
7788 PlayLevelSoundAction(x, y, ACTION_FILLING);
7790 else if (element == EL_MAGIC_WALL_FULL)
7792 if (IS_FREE(x, y + 1))
7794 InitMovingField(x, y, MV_DOWN);
7795 started_moving = TRUE;
7797 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7798 Store[x][y] = EL_CHANGED(Store[x][y]);
7800 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7802 if (!MovDelay[x][y])
7803 MovDelay[x][y] = TILEY / 4 + 1;
7812 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7813 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7814 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7818 else if (element == EL_BD_MAGIC_WALL_FULL)
7820 if (IS_FREE(x, y + 1))
7822 InitMovingField(x, y, MV_DOWN);
7823 started_moving = TRUE;
7825 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7826 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7828 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7830 if (!MovDelay[x][y])
7831 MovDelay[x][y] = TILEY / 4 + 1;
7840 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7841 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7842 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7846 else if (element == EL_DC_MAGIC_WALL_FULL)
7848 if (IS_FREE(x, y + 1))
7850 InitMovingField(x, y, MV_DOWN);
7851 started_moving = TRUE;
7853 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7854 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7856 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7858 if (!MovDelay[x][y])
7859 MovDelay[x][y] = TILEY / 4 + 1;
7868 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7869 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7870 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7874 else if ((CAN_PASS_MAGIC_WALL(element) &&
7875 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7876 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7877 (CAN_PASS_DC_MAGIC_WALL(element) &&
7878 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7881 InitMovingField(x, y, MV_DOWN);
7882 started_moving = TRUE;
7885 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7886 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7887 EL_DC_MAGIC_WALL_FILLING);
7888 Store[x][y] = element;
7890 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7892 SplashAcid(x, y + 1);
7894 InitMovingField(x, y, MV_DOWN);
7895 started_moving = TRUE;
7897 Store[x][y] = EL_ACID;
7900 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7901 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7902 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7903 CAN_FALL(element) && WasJustFalling[x][y] &&
7904 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7906 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7907 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7908 (Tile[x][y + 1] == EL_BLOCKED)))
7910 /* this is needed for a special case not covered by calling "Impact()"
7911 from "ContinueMoving()": if an element moves to a tile directly below
7912 another element which was just falling on that tile (which was empty
7913 in the previous frame), the falling element above would just stop
7914 instead of smashing the element below (in previous version, the above
7915 element was just checked for "moving" instead of "falling", resulting
7916 in incorrect smashes caused by horizontal movement of the above
7917 element; also, the case of the player being the element to smash was
7918 simply not covered here... :-/ ) */
7920 CheckCollision[x][y] = 0;
7921 CheckImpact[x][y] = 0;
7925 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7927 if (MovDir[x][y] == MV_NONE)
7929 InitMovingField(x, y, MV_DOWN);
7930 started_moving = TRUE;
7933 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7935 if (WasJustFalling[x][y]) // prevent animation from being restarted
7936 MovDir[x][y] = MV_DOWN;
7938 InitMovingField(x, y, MV_DOWN);
7939 started_moving = TRUE;
7941 else if (element == EL_AMOEBA_DROP)
7943 Tile[x][y] = EL_AMOEBA_GROWING;
7944 Store[x][y] = EL_AMOEBA_WET;
7946 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7947 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7948 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7949 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7951 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7952 (IS_FREE(x - 1, y + 1) ||
7953 Tile[x - 1][y + 1] == EL_ACID));
7954 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7955 (IS_FREE(x + 1, y + 1) ||
7956 Tile[x + 1][y + 1] == EL_ACID));
7957 boolean can_fall_any = (can_fall_left || can_fall_right);
7958 boolean can_fall_both = (can_fall_left && can_fall_right);
7959 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7961 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7963 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7964 can_fall_right = FALSE;
7965 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7966 can_fall_left = FALSE;
7967 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7968 can_fall_right = FALSE;
7969 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7970 can_fall_left = FALSE;
7972 can_fall_any = (can_fall_left || can_fall_right);
7973 can_fall_both = FALSE;
7978 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7979 can_fall_right = FALSE; // slip down on left side
7981 can_fall_left = !(can_fall_right = RND(2));
7983 can_fall_both = FALSE;
7988 // if not determined otherwise, prefer left side for slipping down
7989 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7990 started_moving = TRUE;
7993 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7995 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7996 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7997 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7998 int belt_dir = game.belt_dir[belt_nr];
8000 if ((belt_dir == MV_LEFT && left_is_free) ||
8001 (belt_dir == MV_RIGHT && right_is_free))
8003 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8005 InitMovingField(x, y, belt_dir);
8006 started_moving = TRUE;
8008 Pushed[x][y] = TRUE;
8009 Pushed[nextx][y] = TRUE;
8011 GfxAction[x][y] = ACTION_DEFAULT;
8015 MovDir[x][y] = 0; // if element was moving, stop it
8020 // not "else if" because of elements that can fall and move (EL_SPRING)
8021 if (CAN_MOVE(element) && !started_moving)
8023 int move_pattern = element_info[element].move_pattern;
8026 Moving2Blocked(x, y, &newx, &newy);
8028 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8031 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8032 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8034 WasJustMoving[x][y] = 0;
8035 CheckCollision[x][y] = 0;
8037 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8039 if (Tile[x][y] != element) // element has changed
8043 if (!MovDelay[x][y]) // start new movement phase
8045 // all objects that can change their move direction after each step
8046 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8048 if (element != EL_YAMYAM &&
8049 element != EL_DARK_YAMYAM &&
8050 element != EL_PACMAN &&
8051 !(move_pattern & MV_ANY_DIRECTION) &&
8052 move_pattern != MV_TURNING_LEFT &&
8053 move_pattern != MV_TURNING_RIGHT &&
8054 move_pattern != MV_TURNING_LEFT_RIGHT &&
8055 move_pattern != MV_TURNING_RIGHT_LEFT &&
8056 move_pattern != MV_TURNING_RANDOM)
8060 if (MovDelay[x][y] && (element == EL_BUG ||
8061 element == EL_SPACESHIP ||
8062 element == EL_SP_SNIKSNAK ||
8063 element == EL_SP_ELECTRON ||
8064 element == EL_MOLE))
8065 TEST_DrawLevelField(x, y);
8069 if (MovDelay[x][y]) // wait some time before next movement
8073 if (element == EL_ROBOT ||
8074 element == EL_YAMYAM ||
8075 element == EL_DARK_YAMYAM)
8077 DrawLevelElementAnimationIfNeeded(x, y, element);
8078 PlayLevelSoundAction(x, y, ACTION_WAITING);
8080 else if (element == EL_SP_ELECTRON)
8081 DrawLevelElementAnimationIfNeeded(x, y, element);
8082 else if (element == EL_DRAGON)
8085 int dir = MovDir[x][y];
8086 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8087 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8088 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8089 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8090 dir == MV_UP ? IMG_FLAMES_1_UP :
8091 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8092 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8094 GfxAction[x][y] = ACTION_ATTACKING;
8096 if (IS_PLAYER(x, y))
8097 DrawPlayerField(x, y);
8099 TEST_DrawLevelField(x, y);
8101 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8103 for (i = 1; i <= 3; i++)
8105 int xx = x + i * dx;
8106 int yy = y + i * dy;
8107 int sx = SCREENX(xx);
8108 int sy = SCREENY(yy);
8109 int flame_graphic = graphic + (i - 1);
8111 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8116 int flamed = MovingOrBlocked2Element(xx, yy);
8118 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8121 RemoveMovingField(xx, yy);
8123 ChangeDelay[xx][yy] = 0;
8125 Tile[xx][yy] = EL_FLAMES;
8127 if (IN_SCR_FIELD(sx, sy))
8129 TEST_DrawLevelFieldCrumbled(xx, yy);
8130 DrawGraphic(sx, sy, flame_graphic, frame);
8135 if (Tile[xx][yy] == EL_FLAMES)
8136 Tile[xx][yy] = EL_EMPTY;
8137 TEST_DrawLevelField(xx, yy);
8142 if (MovDelay[x][y]) // element still has to wait some time
8144 PlayLevelSoundAction(x, y, ACTION_WAITING);
8150 // now make next step
8152 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8154 if (DONT_COLLIDE_WITH(element) &&
8155 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8156 !PLAYER_ENEMY_PROTECTED(newx, newy))
8158 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8163 else if (CAN_MOVE_INTO_ACID(element) &&
8164 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8165 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8166 (MovDir[x][y] == MV_DOWN ||
8167 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8169 SplashAcid(newx, newy);
8170 Store[x][y] = EL_ACID;
8172 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8174 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8175 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8176 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8177 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8180 TEST_DrawLevelField(x, y);
8182 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8183 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8184 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8186 game.friends_still_needed--;
8187 if (!game.friends_still_needed &&
8189 game.all_players_gone)
8194 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8196 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8197 TEST_DrawLevelField(newx, newy);
8199 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8201 else if (!IS_FREE(newx, newy))
8203 GfxAction[x][y] = ACTION_WAITING;
8205 if (IS_PLAYER(x, y))
8206 DrawPlayerField(x, y);
8208 TEST_DrawLevelField(x, y);
8213 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8215 if (IS_FOOD_PIG(Tile[newx][newy]))
8217 if (IS_MOVING(newx, newy))
8218 RemoveMovingField(newx, newy);
8221 Tile[newx][newy] = EL_EMPTY;
8222 TEST_DrawLevelField(newx, newy);
8225 PlayLevelSound(x, y, SND_PIG_DIGGING);
8227 else if (!IS_FREE(newx, newy))
8229 if (IS_PLAYER(x, y))
8230 DrawPlayerField(x, y);
8232 TEST_DrawLevelField(x, y);
8237 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8239 if (Store[x][y] != EL_EMPTY)
8241 boolean can_clone = FALSE;
8244 // check if element to clone is still there
8245 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8247 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8255 // cannot clone or target field not free anymore -- do not clone
8256 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8257 Store[x][y] = EL_EMPTY;
8260 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8262 if (IS_MV_DIAGONAL(MovDir[x][y]))
8264 int diagonal_move_dir = MovDir[x][y];
8265 int stored = Store[x][y];
8266 int change_delay = 8;
8269 // android is moving diagonally
8271 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8273 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8274 GfxElement[x][y] = EL_EMC_ANDROID;
8275 GfxAction[x][y] = ACTION_SHRINKING;
8276 GfxDir[x][y] = diagonal_move_dir;
8277 ChangeDelay[x][y] = change_delay;
8279 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8282 DrawLevelGraphicAnimation(x, y, graphic);
8283 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8285 if (Tile[newx][newy] == EL_ACID)
8287 SplashAcid(newx, newy);
8292 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8294 Store[newx][newy] = EL_EMC_ANDROID;
8295 GfxElement[newx][newy] = EL_EMC_ANDROID;
8296 GfxAction[newx][newy] = ACTION_GROWING;
8297 GfxDir[newx][newy] = diagonal_move_dir;
8298 ChangeDelay[newx][newy] = change_delay;
8300 graphic = el_act_dir2img(GfxElement[newx][newy],
8301 GfxAction[newx][newy], GfxDir[newx][newy]);
8303 DrawLevelGraphicAnimation(newx, newy, graphic);
8304 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8310 Tile[newx][newy] = EL_EMPTY;
8311 TEST_DrawLevelField(newx, newy);
8313 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8316 else if (!IS_FREE(newx, newy))
8321 else if (IS_CUSTOM_ELEMENT(element) &&
8322 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8324 if (!DigFieldByCE(newx, newy, element))
8327 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8329 RunnerVisit[x][y] = FrameCounter;
8330 PlayerVisit[x][y] /= 8; // expire player visit path
8333 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8335 if (!IS_FREE(newx, newy))
8337 if (IS_PLAYER(x, y))
8338 DrawPlayerField(x, y);
8340 TEST_DrawLevelField(x, y);
8346 boolean wanna_flame = !RND(10);
8347 int dx = newx - x, dy = newy - y;
8348 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8349 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8350 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8351 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8352 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8353 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8356 IS_CLASSIC_ENEMY(element1) ||
8357 IS_CLASSIC_ENEMY(element2)) &&
8358 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8359 element1 != EL_FLAMES && element2 != EL_FLAMES)
8361 ResetGfxAnimation(x, y);
8362 GfxAction[x][y] = ACTION_ATTACKING;
8364 if (IS_PLAYER(x, y))
8365 DrawPlayerField(x, y);
8367 TEST_DrawLevelField(x, y);
8369 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8371 MovDelay[x][y] = 50;
8373 Tile[newx][newy] = EL_FLAMES;
8374 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8375 Tile[newx1][newy1] = EL_FLAMES;
8376 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8377 Tile[newx2][newy2] = EL_FLAMES;
8383 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8384 Tile[newx][newy] == EL_DIAMOND)
8386 if (IS_MOVING(newx, newy))
8387 RemoveMovingField(newx, newy);
8390 Tile[newx][newy] = EL_EMPTY;
8391 TEST_DrawLevelField(newx, newy);
8394 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8396 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8397 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8399 if (AmoebaNr[newx][newy])
8401 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8402 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8403 Tile[newx][newy] == EL_BD_AMOEBA)
8404 AmoebaCnt[AmoebaNr[newx][newy]]--;
8407 if (IS_MOVING(newx, newy))
8409 RemoveMovingField(newx, newy);
8413 Tile[newx][newy] = EL_EMPTY;
8414 TEST_DrawLevelField(newx, newy);
8417 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8419 else if ((element == EL_PACMAN || element == EL_MOLE)
8420 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8422 if (AmoebaNr[newx][newy])
8424 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8425 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8426 Tile[newx][newy] == EL_BD_AMOEBA)
8427 AmoebaCnt[AmoebaNr[newx][newy]]--;
8430 if (element == EL_MOLE)
8432 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8433 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8435 ResetGfxAnimation(x, y);
8436 GfxAction[x][y] = ACTION_DIGGING;
8437 TEST_DrawLevelField(x, y);
8439 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8441 return; // wait for shrinking amoeba
8443 else // element == EL_PACMAN
8445 Tile[newx][newy] = EL_EMPTY;
8446 TEST_DrawLevelField(newx, newy);
8447 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8450 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8451 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8452 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8454 // wait for shrinking amoeba to completely disappear
8457 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8459 // object was running against a wall
8463 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8464 DrawLevelElementAnimation(x, y, element);
8466 if (DONT_TOUCH(element))
8467 TestIfBadThingTouchesPlayer(x, y);
8472 InitMovingField(x, y, MovDir[x][y]);
8474 PlayLevelSoundAction(x, y, ACTION_MOVING);
8478 ContinueMoving(x, y);
8481 void ContinueMoving(int x, int y)
8483 int element = Tile[x][y];
8484 struct ElementInfo *ei = &element_info[element];
8485 int direction = MovDir[x][y];
8486 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8487 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8488 int newx = x + dx, newy = y + dy;
8489 int stored = Store[x][y];
8490 int stored_new = Store[newx][newy];
8491 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8492 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8493 boolean last_line = (newy == lev_fieldy - 1);
8495 MovPos[x][y] += getElementMoveStepsize(x, y);
8497 if (pushed_by_player) // special case: moving object pushed by player
8498 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8500 if (ABS(MovPos[x][y]) < TILEX)
8502 TEST_DrawLevelField(x, y);
8504 return; // element is still moving
8507 // element reached destination field
8509 Tile[x][y] = EL_EMPTY;
8510 Tile[newx][newy] = element;
8511 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8513 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8515 element = Tile[newx][newy] = EL_ACID;
8517 else if (element == EL_MOLE)
8519 Tile[x][y] = EL_SAND;
8521 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8523 else if (element == EL_QUICKSAND_FILLING)
8525 element = Tile[newx][newy] = get_next_element(element);
8526 Store[newx][newy] = Store[x][y];
8528 else if (element == EL_QUICKSAND_EMPTYING)
8530 Tile[x][y] = get_next_element(element);
8531 element = Tile[newx][newy] = Store[x][y];
8533 else if (element == EL_QUICKSAND_FAST_FILLING)
8535 element = Tile[newx][newy] = get_next_element(element);
8536 Store[newx][newy] = Store[x][y];
8538 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8540 Tile[x][y] = get_next_element(element);
8541 element = Tile[newx][newy] = Store[x][y];
8543 else if (element == EL_MAGIC_WALL_FILLING)
8545 element = Tile[newx][newy] = get_next_element(element);
8546 if (!game.magic_wall_active)
8547 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8548 Store[newx][newy] = Store[x][y];
8550 else if (element == EL_MAGIC_WALL_EMPTYING)
8552 Tile[x][y] = get_next_element(element);
8553 if (!game.magic_wall_active)
8554 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8555 element = Tile[newx][newy] = Store[x][y];
8557 InitField(newx, newy, FALSE);
8559 else if (element == EL_BD_MAGIC_WALL_FILLING)
8561 element = Tile[newx][newy] = get_next_element(element);
8562 if (!game.magic_wall_active)
8563 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8564 Store[newx][newy] = Store[x][y];
8566 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8568 Tile[x][y] = get_next_element(element);
8569 if (!game.magic_wall_active)
8570 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8571 element = Tile[newx][newy] = Store[x][y];
8573 InitField(newx, newy, FALSE);
8575 else if (element == EL_DC_MAGIC_WALL_FILLING)
8577 element = Tile[newx][newy] = get_next_element(element);
8578 if (!game.magic_wall_active)
8579 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8580 Store[newx][newy] = Store[x][y];
8582 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8584 Tile[x][y] = get_next_element(element);
8585 if (!game.magic_wall_active)
8586 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8587 element = Tile[newx][newy] = Store[x][y];
8589 InitField(newx, newy, FALSE);
8591 else if (element == EL_AMOEBA_DROPPING)
8593 Tile[x][y] = get_next_element(element);
8594 element = Tile[newx][newy] = Store[x][y];
8596 else if (element == EL_SOKOBAN_OBJECT)
8599 Tile[x][y] = Back[x][y];
8601 if (Back[newx][newy])
8602 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8604 Back[x][y] = Back[newx][newy] = 0;
8607 Store[x][y] = EL_EMPTY;
8612 MovDelay[newx][newy] = 0;
8614 if (CAN_CHANGE_OR_HAS_ACTION(element))
8616 // copy element change control values to new field
8617 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8618 ChangePage[newx][newy] = ChangePage[x][y];
8619 ChangeCount[newx][newy] = ChangeCount[x][y];
8620 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8623 CustomValue[newx][newy] = CustomValue[x][y];
8625 ChangeDelay[x][y] = 0;
8626 ChangePage[x][y] = -1;
8627 ChangeCount[x][y] = 0;
8628 ChangeEvent[x][y] = -1;
8630 CustomValue[x][y] = 0;
8632 // copy animation control values to new field
8633 GfxFrame[newx][newy] = GfxFrame[x][y];
8634 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8635 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8636 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8638 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8640 // some elements can leave other elements behind after moving
8641 if (ei->move_leave_element != EL_EMPTY &&
8642 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8643 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8645 int move_leave_element = ei->move_leave_element;
8647 // this makes it possible to leave the removed element again
8648 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8649 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8651 Tile[x][y] = move_leave_element;
8653 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8654 MovDir[x][y] = direction;
8656 InitField(x, y, FALSE);
8658 if (GFX_CRUMBLED(Tile[x][y]))
8659 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8661 if (ELEM_IS_PLAYER(move_leave_element))
8662 RelocatePlayer(x, y, move_leave_element);
8665 // do this after checking for left-behind element
8666 ResetGfxAnimation(x, y); // reset animation values for old field
8668 if (!CAN_MOVE(element) ||
8669 (CAN_FALL(element) && direction == MV_DOWN &&
8670 (element == EL_SPRING ||
8671 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8672 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8673 GfxDir[x][y] = MovDir[newx][newy] = 0;
8675 TEST_DrawLevelField(x, y);
8676 TEST_DrawLevelField(newx, newy);
8678 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8680 // prevent pushed element from moving on in pushed direction
8681 if (pushed_by_player && CAN_MOVE(element) &&
8682 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8683 !(element_info[element].move_pattern & direction))
8684 TurnRound(newx, newy);
8686 // prevent elements on conveyor belt from moving on in last direction
8687 if (pushed_by_conveyor && CAN_FALL(element) &&
8688 direction & MV_HORIZONTAL)
8689 MovDir[newx][newy] = 0;
8691 if (!pushed_by_player)
8693 int nextx = newx + dx, nexty = newy + dy;
8694 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8696 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8698 if (CAN_FALL(element) && direction == MV_DOWN)
8699 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8701 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8702 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8704 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8705 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8708 if (DONT_TOUCH(element)) // object may be nasty to player or others
8710 TestIfBadThingTouchesPlayer(newx, newy);
8711 TestIfBadThingTouchesFriend(newx, newy);
8713 if (!IS_CUSTOM_ELEMENT(element))
8714 TestIfBadThingTouchesOtherBadThing(newx, newy);
8716 else if (element == EL_PENGUIN)
8717 TestIfFriendTouchesBadThing(newx, newy);
8719 if (DONT_GET_HIT_BY(element))
8721 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8724 // give the player one last chance (one more frame) to move away
8725 if (CAN_FALL(element) && direction == MV_DOWN &&
8726 (last_line || (!IS_FREE(x, newy + 1) &&
8727 (!IS_PLAYER(x, newy + 1) ||
8728 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8731 if (pushed_by_player && !game.use_change_when_pushing_bug)
8733 int push_side = MV_DIR_OPPOSITE(direction);
8734 struct PlayerInfo *player = PLAYERINFO(x, y);
8736 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8737 player->index_bit, push_side);
8738 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8739 player->index_bit, push_side);
8742 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8743 MovDelay[newx][newy] = 1;
8745 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8747 TestIfElementTouchesCustomElement(x, y); // empty or new element
8748 TestIfElementHitsCustomElement(newx, newy, direction);
8749 TestIfPlayerTouchesCustomElement(newx, newy);
8750 TestIfElementTouchesCustomElement(newx, newy);
8752 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8753 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8754 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8755 MV_DIR_OPPOSITE(direction));
8758 int AmoebaNeighbourNr(int ax, int ay)
8761 int element = Tile[ax][ay];
8763 static int xy[4][2] =
8771 for (i = 0; i < NUM_DIRECTIONS; i++)
8773 int x = ax + xy[i][0];
8774 int y = ay + xy[i][1];
8776 if (!IN_LEV_FIELD(x, y))
8779 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8780 group_nr = AmoebaNr[x][y];
8786 static void AmoebaMerge(int ax, int ay)
8788 int i, x, y, xx, yy;
8789 int new_group_nr = AmoebaNr[ax][ay];
8790 static int xy[4][2] =
8798 if (new_group_nr == 0)
8801 for (i = 0; i < NUM_DIRECTIONS; i++)
8806 if (!IN_LEV_FIELD(x, y))
8809 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8810 Tile[x][y] == EL_BD_AMOEBA ||
8811 Tile[x][y] == EL_AMOEBA_DEAD) &&
8812 AmoebaNr[x][y] != new_group_nr)
8814 int old_group_nr = AmoebaNr[x][y];
8816 if (old_group_nr == 0)
8819 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8820 AmoebaCnt[old_group_nr] = 0;
8821 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8822 AmoebaCnt2[old_group_nr] = 0;
8824 SCAN_PLAYFIELD(xx, yy)
8826 if (AmoebaNr[xx][yy] == old_group_nr)
8827 AmoebaNr[xx][yy] = new_group_nr;
8833 void AmoebaToDiamond(int ax, int ay)
8837 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8839 int group_nr = AmoebaNr[ax][ay];
8844 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8845 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8851 SCAN_PLAYFIELD(x, y)
8853 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8856 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8860 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8861 SND_AMOEBA_TURNING_TO_GEM :
8862 SND_AMOEBA_TURNING_TO_ROCK));
8867 static int xy[4][2] =
8875 for (i = 0; i < NUM_DIRECTIONS; i++)
8880 if (!IN_LEV_FIELD(x, y))
8883 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8885 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8886 SND_AMOEBA_TURNING_TO_GEM :
8887 SND_AMOEBA_TURNING_TO_ROCK));
8894 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8897 int group_nr = AmoebaNr[ax][ay];
8898 boolean done = FALSE;
8903 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8904 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8910 SCAN_PLAYFIELD(x, y)
8912 if (AmoebaNr[x][y] == group_nr &&
8913 (Tile[x][y] == EL_AMOEBA_DEAD ||
8914 Tile[x][y] == EL_BD_AMOEBA ||
8915 Tile[x][y] == EL_AMOEBA_GROWING))
8918 Tile[x][y] = new_element;
8919 InitField(x, y, FALSE);
8920 TEST_DrawLevelField(x, y);
8926 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8927 SND_BD_AMOEBA_TURNING_TO_ROCK :
8928 SND_BD_AMOEBA_TURNING_TO_GEM));
8931 static void AmoebaGrowing(int x, int y)
8933 static unsigned int sound_delay = 0;
8934 static unsigned int sound_delay_value = 0;
8936 if (!MovDelay[x][y]) // start new growing cycle
8940 if (DelayReached(&sound_delay, sound_delay_value))
8942 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8943 sound_delay_value = 30;
8947 if (MovDelay[x][y]) // wait some time before growing bigger
8950 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8952 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8953 6 - MovDelay[x][y]);
8955 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8958 if (!MovDelay[x][y])
8960 Tile[x][y] = Store[x][y];
8962 TEST_DrawLevelField(x, y);
8967 static void AmoebaShrinking(int x, int y)
8969 static unsigned int sound_delay = 0;
8970 static unsigned int sound_delay_value = 0;
8972 if (!MovDelay[x][y]) // start new shrinking cycle
8976 if (DelayReached(&sound_delay, sound_delay_value))
8977 sound_delay_value = 30;
8980 if (MovDelay[x][y]) // wait some time before shrinking
8983 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8985 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8986 6 - MovDelay[x][y]);
8988 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8991 if (!MovDelay[x][y])
8993 Tile[x][y] = EL_EMPTY;
8994 TEST_DrawLevelField(x, y);
8996 // don't let mole enter this field in this cycle;
8997 // (give priority to objects falling to this field from above)
9003 static void AmoebaReproduce(int ax, int ay)
9006 int element = Tile[ax][ay];
9007 int graphic = el2img(element);
9008 int newax = ax, neway = ay;
9009 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9010 static int xy[4][2] =
9018 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9020 Tile[ax][ay] = EL_AMOEBA_DEAD;
9021 TEST_DrawLevelField(ax, ay);
9025 if (IS_ANIMATED(graphic))
9026 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9028 if (!MovDelay[ax][ay]) // start making new amoeba field
9029 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9031 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9034 if (MovDelay[ax][ay])
9038 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9041 int x = ax + xy[start][0];
9042 int y = ay + xy[start][1];
9044 if (!IN_LEV_FIELD(x, y))
9047 if (IS_FREE(x, y) ||
9048 CAN_GROW_INTO(Tile[x][y]) ||
9049 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9050 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9056 if (newax == ax && neway == ay)
9059 else // normal or "filled" (BD style) amoeba
9062 boolean waiting_for_player = FALSE;
9064 for (i = 0; i < NUM_DIRECTIONS; i++)
9066 int j = (start + i) % 4;
9067 int x = ax + xy[j][0];
9068 int y = ay + xy[j][1];
9070 if (!IN_LEV_FIELD(x, y))
9073 if (IS_FREE(x, y) ||
9074 CAN_GROW_INTO(Tile[x][y]) ||
9075 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9076 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9082 else if (IS_PLAYER(x, y))
9083 waiting_for_player = TRUE;
9086 if (newax == ax && neway == ay) // amoeba cannot grow
9088 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9090 Tile[ax][ay] = EL_AMOEBA_DEAD;
9091 TEST_DrawLevelField(ax, ay);
9092 AmoebaCnt[AmoebaNr[ax][ay]]--;
9094 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9096 if (element == EL_AMOEBA_FULL)
9097 AmoebaToDiamond(ax, ay);
9098 else if (element == EL_BD_AMOEBA)
9099 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9104 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9106 // amoeba gets larger by growing in some direction
9108 int new_group_nr = AmoebaNr[ax][ay];
9111 if (new_group_nr == 0)
9113 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9115 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9121 AmoebaNr[newax][neway] = new_group_nr;
9122 AmoebaCnt[new_group_nr]++;
9123 AmoebaCnt2[new_group_nr]++;
9125 // if amoeba touches other amoeba(s) after growing, unify them
9126 AmoebaMerge(newax, neway);
9128 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9130 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9136 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9137 (neway == lev_fieldy - 1 && newax != ax))
9139 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9140 Store[newax][neway] = element;
9142 else if (neway == ay || element == EL_EMC_DRIPPER)
9144 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9146 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9150 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9151 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9152 Store[ax][ay] = EL_AMOEBA_DROP;
9153 ContinueMoving(ax, ay);
9157 TEST_DrawLevelField(newax, neway);
9160 static void Life(int ax, int ay)
9164 int element = Tile[ax][ay];
9165 int graphic = el2img(element);
9166 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9168 boolean changed = FALSE;
9170 if (IS_ANIMATED(graphic))
9171 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9176 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9177 MovDelay[ax][ay] = life_time;
9179 if (MovDelay[ax][ay]) // wait some time before next cycle
9182 if (MovDelay[ax][ay])
9186 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9188 int xx = ax+x1, yy = ay+y1;
9189 int old_element = Tile[xx][yy];
9190 int num_neighbours = 0;
9192 if (!IN_LEV_FIELD(xx, yy))
9195 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9197 int x = xx+x2, y = yy+y2;
9199 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9202 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9203 boolean is_neighbour = FALSE;
9205 if (level.use_life_bugs)
9207 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9208 (IS_FREE(x, y) && Stop[x][y]));
9211 (Last[x][y] == element || is_player_cell);
9217 boolean is_free = FALSE;
9219 if (level.use_life_bugs)
9220 is_free = (IS_FREE(xx, yy));
9222 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9224 if (xx == ax && yy == ay) // field in the middle
9226 if (num_neighbours < life_parameter[0] ||
9227 num_neighbours > life_parameter[1])
9229 Tile[xx][yy] = EL_EMPTY;
9230 if (Tile[xx][yy] != old_element)
9231 TEST_DrawLevelField(xx, yy);
9232 Stop[xx][yy] = TRUE;
9236 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9237 { // free border field
9238 if (num_neighbours >= life_parameter[2] &&
9239 num_neighbours <= life_parameter[3])
9241 Tile[xx][yy] = element;
9242 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9243 if (Tile[xx][yy] != old_element)
9244 TEST_DrawLevelField(xx, yy);
9245 Stop[xx][yy] = TRUE;
9252 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9253 SND_GAME_OF_LIFE_GROWING);
9256 static void InitRobotWheel(int x, int y)
9258 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9261 static void RunRobotWheel(int x, int y)
9263 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9266 static void StopRobotWheel(int x, int y)
9268 if (game.robot_wheel_x == x &&
9269 game.robot_wheel_y == y)
9271 game.robot_wheel_x = -1;
9272 game.robot_wheel_y = -1;
9273 game.robot_wheel_active = FALSE;
9277 static void InitTimegateWheel(int x, int y)
9279 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9282 static void RunTimegateWheel(int x, int y)
9284 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9287 static void InitMagicBallDelay(int x, int y)
9289 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9292 static void ActivateMagicBall(int bx, int by)
9296 if (level.ball_random)
9298 int pos_border = RND(8); // select one of the eight border elements
9299 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9300 int xx = pos_content % 3;
9301 int yy = pos_content / 3;
9306 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9307 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9311 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9313 int xx = x - bx + 1;
9314 int yy = y - by + 1;
9316 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9317 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9321 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9324 static void CheckExit(int x, int y)
9326 if (game.gems_still_needed > 0 ||
9327 game.sokoban_fields_still_needed > 0 ||
9328 game.sokoban_objects_still_needed > 0 ||
9329 game.lights_still_needed > 0)
9331 int element = Tile[x][y];
9332 int graphic = el2img(element);
9334 if (IS_ANIMATED(graphic))
9335 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9340 // do not re-open exit door closed after last player
9341 if (game.all_players_gone)
9344 Tile[x][y] = EL_EXIT_OPENING;
9346 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9349 static void CheckExitEM(int x, int y)
9351 if (game.gems_still_needed > 0 ||
9352 game.sokoban_fields_still_needed > 0 ||
9353 game.sokoban_objects_still_needed > 0 ||
9354 game.lights_still_needed > 0)
9356 int element = Tile[x][y];
9357 int graphic = el2img(element);
9359 if (IS_ANIMATED(graphic))
9360 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9365 // do not re-open exit door closed after last player
9366 if (game.all_players_gone)
9369 Tile[x][y] = EL_EM_EXIT_OPENING;
9371 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9374 static void CheckExitSteel(int x, int y)
9376 if (game.gems_still_needed > 0 ||
9377 game.sokoban_fields_still_needed > 0 ||
9378 game.sokoban_objects_still_needed > 0 ||
9379 game.lights_still_needed > 0)
9381 int element = Tile[x][y];
9382 int graphic = el2img(element);
9384 if (IS_ANIMATED(graphic))
9385 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9390 // do not re-open exit door closed after last player
9391 if (game.all_players_gone)
9394 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9396 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9399 static void CheckExitSteelEM(int x, int y)
9401 if (game.gems_still_needed > 0 ||
9402 game.sokoban_fields_still_needed > 0 ||
9403 game.sokoban_objects_still_needed > 0 ||
9404 game.lights_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_EM_STEEL_EXIT_OPENING;
9421 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9424 static void CheckExitSP(int x, int y)
9426 if (game.gems_still_needed > 0)
9428 int element = Tile[x][y];
9429 int graphic = el2img(element);
9431 if (IS_ANIMATED(graphic))
9432 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9437 // do not re-open exit door closed after last player
9438 if (game.all_players_gone)
9441 Tile[x][y] = EL_SP_EXIT_OPENING;
9443 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9446 static void CloseAllOpenTimegates(void)
9450 SCAN_PLAYFIELD(x, y)
9452 int element = Tile[x][y];
9454 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9456 Tile[x][y] = EL_TIMEGATE_CLOSING;
9458 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9463 static void DrawTwinkleOnField(int x, int y)
9465 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9468 if (Tile[x][y] == EL_BD_DIAMOND)
9471 if (MovDelay[x][y] == 0) // next animation frame
9472 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9474 if (MovDelay[x][y] != 0) // wait some time before next frame
9478 DrawLevelElementAnimation(x, y, Tile[x][y]);
9480 if (MovDelay[x][y] != 0)
9482 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9483 10 - MovDelay[x][y]);
9485 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9490 static void MauerWaechst(int x, int y)
9494 if (!MovDelay[x][y]) // next animation frame
9495 MovDelay[x][y] = 3 * delay;
9497 if (MovDelay[x][y]) // wait some time before next frame
9501 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9503 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9504 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9506 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9509 if (!MovDelay[x][y])
9511 if (MovDir[x][y] == MV_LEFT)
9513 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9514 TEST_DrawLevelField(x - 1, y);
9516 else if (MovDir[x][y] == MV_RIGHT)
9518 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9519 TEST_DrawLevelField(x + 1, y);
9521 else if (MovDir[x][y] == MV_UP)
9523 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9524 TEST_DrawLevelField(x, y - 1);
9528 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9529 TEST_DrawLevelField(x, y + 1);
9532 Tile[x][y] = Store[x][y];
9534 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9535 TEST_DrawLevelField(x, y);
9540 static void MauerAbleger(int ax, int ay)
9542 int element = Tile[ax][ay];
9543 int graphic = el2img(element);
9544 boolean oben_frei = FALSE, unten_frei = FALSE;
9545 boolean links_frei = FALSE, rechts_frei = FALSE;
9546 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9547 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9548 boolean new_wall = FALSE;
9550 if (IS_ANIMATED(graphic))
9551 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9553 if (!MovDelay[ax][ay]) // start building new wall
9554 MovDelay[ax][ay] = 6;
9556 if (MovDelay[ax][ay]) // wait some time before building new wall
9559 if (MovDelay[ax][ay])
9563 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9565 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9567 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9569 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9572 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9573 element == EL_EXPANDABLE_WALL_ANY)
9577 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9578 Store[ax][ay-1] = element;
9579 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9580 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9581 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9582 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9587 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9588 Store[ax][ay+1] = element;
9589 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9590 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9591 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9592 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9597 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9598 element == EL_EXPANDABLE_WALL_ANY ||
9599 element == EL_EXPANDABLE_WALL ||
9600 element == EL_BD_EXPANDABLE_WALL)
9604 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9605 Store[ax-1][ay] = element;
9606 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9607 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9608 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9609 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9615 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9616 Store[ax+1][ay] = element;
9617 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9618 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9619 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9620 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9625 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9626 TEST_DrawLevelField(ax, ay);
9628 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9630 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9631 unten_massiv = TRUE;
9632 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9633 links_massiv = TRUE;
9634 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9635 rechts_massiv = TRUE;
9637 if (((oben_massiv && unten_massiv) ||
9638 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9639 element == EL_EXPANDABLE_WALL) &&
9640 ((links_massiv && rechts_massiv) ||
9641 element == EL_EXPANDABLE_WALL_VERTICAL))
9642 Tile[ax][ay] = EL_WALL;
9645 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9648 static void MauerAblegerStahl(int ax, int ay)
9650 int element = Tile[ax][ay];
9651 int graphic = el2img(element);
9652 boolean oben_frei = FALSE, unten_frei = FALSE;
9653 boolean links_frei = FALSE, rechts_frei = FALSE;
9654 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9655 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9656 boolean new_wall = FALSE;
9658 if (IS_ANIMATED(graphic))
9659 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9661 if (!MovDelay[ax][ay]) // start building new wall
9662 MovDelay[ax][ay] = 6;
9664 if (MovDelay[ax][ay]) // wait some time before building new wall
9667 if (MovDelay[ax][ay])
9671 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9673 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9675 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9677 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9680 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9681 element == EL_EXPANDABLE_STEELWALL_ANY)
9685 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9686 Store[ax][ay-1] = element;
9687 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9688 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9689 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9690 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9695 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9696 Store[ax][ay+1] = element;
9697 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9698 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9699 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9700 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9705 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9706 element == EL_EXPANDABLE_STEELWALL_ANY)
9710 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9711 Store[ax-1][ay] = element;
9712 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9713 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9714 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9715 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9721 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9722 Store[ax+1][ay] = element;
9723 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9724 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9725 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9726 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9731 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9733 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9734 unten_massiv = TRUE;
9735 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9736 links_massiv = TRUE;
9737 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9738 rechts_massiv = TRUE;
9740 if (((oben_massiv && unten_massiv) ||
9741 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9742 ((links_massiv && rechts_massiv) ||
9743 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9744 Tile[ax][ay] = EL_STEELWALL;
9747 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9750 static void CheckForDragon(int x, int y)
9753 boolean dragon_found = FALSE;
9754 static int xy[4][2] =
9762 for (i = 0; i < NUM_DIRECTIONS; i++)
9764 for (j = 0; j < 4; j++)
9766 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9768 if (IN_LEV_FIELD(xx, yy) &&
9769 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9771 if (Tile[xx][yy] == EL_DRAGON)
9772 dragon_found = TRUE;
9781 for (i = 0; i < NUM_DIRECTIONS; i++)
9783 for (j = 0; j < 3; j++)
9785 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9787 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9789 Tile[xx][yy] = EL_EMPTY;
9790 TEST_DrawLevelField(xx, yy);
9799 static void InitBuggyBase(int x, int y)
9801 int element = Tile[x][y];
9802 int activating_delay = FRAMES_PER_SECOND / 4;
9805 (element == EL_SP_BUGGY_BASE ?
9806 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9807 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9809 element == EL_SP_BUGGY_BASE_ACTIVE ?
9810 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9813 static void WarnBuggyBase(int x, int y)
9816 static int xy[4][2] =
9824 for (i = 0; i < NUM_DIRECTIONS; i++)
9826 int xx = x + xy[i][0];
9827 int yy = y + xy[i][1];
9829 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9831 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9838 static void InitTrap(int x, int y)
9840 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9843 static void ActivateTrap(int x, int y)
9845 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9848 static void ChangeActiveTrap(int x, int y)
9850 int graphic = IMG_TRAP_ACTIVE;
9852 // if new animation frame was drawn, correct crumbled sand border
9853 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9854 TEST_DrawLevelFieldCrumbled(x, y);
9857 static int getSpecialActionElement(int element, int number, int base_element)
9859 return (element != EL_EMPTY ? element :
9860 number != -1 ? base_element + number - 1 :
9864 static int getModifiedActionNumber(int value_old, int operator, int operand,
9865 int value_min, int value_max)
9867 int value_new = (operator == CA_MODE_SET ? operand :
9868 operator == CA_MODE_ADD ? value_old + operand :
9869 operator == CA_MODE_SUBTRACT ? value_old - operand :
9870 operator == CA_MODE_MULTIPLY ? value_old * operand :
9871 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9872 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9875 return (value_new < value_min ? value_min :
9876 value_new > value_max ? value_max :
9880 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9882 struct ElementInfo *ei = &element_info[element];
9883 struct ElementChangeInfo *change = &ei->change_page[page];
9884 int target_element = change->target_element;
9885 int action_type = change->action_type;
9886 int action_mode = change->action_mode;
9887 int action_arg = change->action_arg;
9888 int action_element = change->action_element;
9891 if (!change->has_action)
9894 // ---------- determine action paramater values -----------------------------
9896 int level_time_value =
9897 (level.time > 0 ? TimeLeft :
9900 int action_arg_element_raw =
9901 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9902 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9903 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9904 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9905 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9906 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9907 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9909 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9911 int action_arg_direction =
9912 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9913 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9914 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9915 change->actual_trigger_side :
9916 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9917 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9920 int action_arg_number_min =
9921 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9924 int action_arg_number_max =
9925 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9926 action_type == CA_SET_LEVEL_GEMS ? 999 :
9927 action_type == CA_SET_LEVEL_TIME ? 9999 :
9928 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9929 action_type == CA_SET_CE_VALUE ? 9999 :
9930 action_type == CA_SET_CE_SCORE ? 9999 :
9933 int action_arg_number_reset =
9934 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9935 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9936 action_type == CA_SET_LEVEL_TIME ? level.time :
9937 action_type == CA_SET_LEVEL_SCORE ? 0 :
9938 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9939 action_type == CA_SET_CE_SCORE ? 0 :
9942 int action_arg_number =
9943 (action_arg <= CA_ARG_MAX ? action_arg :
9944 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9945 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9946 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9947 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9948 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9949 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9950 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9951 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9952 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9953 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9954 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9955 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9956 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9957 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9958 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9959 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9960 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9961 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9962 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9963 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9964 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9967 int action_arg_number_old =
9968 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9969 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9970 action_type == CA_SET_LEVEL_SCORE ? game.score :
9971 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9972 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9975 int action_arg_number_new =
9976 getModifiedActionNumber(action_arg_number_old,
9977 action_mode, action_arg_number,
9978 action_arg_number_min, action_arg_number_max);
9980 int trigger_player_bits =
9981 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9982 change->actual_trigger_player_bits : change->trigger_player);
9984 int action_arg_player_bits =
9985 (action_arg >= CA_ARG_PLAYER_1 &&
9986 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9987 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9988 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9991 // ---------- execute action -----------------------------------------------
9993 switch (action_type)
10000 // ---------- level actions ----------------------------------------------
10002 case CA_RESTART_LEVEL:
10004 game.restart_level = TRUE;
10009 case CA_SHOW_ENVELOPE:
10011 int element = getSpecialActionElement(action_arg_element,
10012 action_arg_number, EL_ENVELOPE_1);
10014 if (IS_ENVELOPE(element))
10015 local_player->show_envelope = element;
10020 case CA_SET_LEVEL_TIME:
10022 if (level.time > 0) // only modify limited time value
10024 TimeLeft = action_arg_number_new;
10026 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10028 DisplayGameControlValues();
10030 if (!TimeLeft && setup.time_limit)
10031 for (i = 0; i < MAX_PLAYERS; i++)
10032 KillPlayer(&stored_player[i]);
10038 case CA_SET_LEVEL_SCORE:
10040 game.score = action_arg_number_new;
10042 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10044 DisplayGameControlValues();
10049 case CA_SET_LEVEL_GEMS:
10051 game.gems_still_needed = action_arg_number_new;
10053 game.snapshot.collected_item = TRUE;
10055 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10057 DisplayGameControlValues();
10062 case CA_SET_LEVEL_WIND:
10064 game.wind_direction = action_arg_direction;
10069 case CA_SET_LEVEL_RANDOM_SEED:
10071 // ensure that setting a new random seed while playing is predictable
10072 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10077 // ---------- player actions ---------------------------------------------
10079 case CA_MOVE_PLAYER:
10080 case CA_MOVE_PLAYER_NEW:
10082 // automatically move to the next field in specified direction
10083 for (i = 0; i < MAX_PLAYERS; i++)
10084 if (trigger_player_bits & (1 << i))
10085 if (action_type == CA_MOVE_PLAYER ||
10086 stored_player[i].MovPos == 0)
10087 stored_player[i].programmed_action = action_arg_direction;
10092 case CA_EXIT_PLAYER:
10094 for (i = 0; i < MAX_PLAYERS; i++)
10095 if (action_arg_player_bits & (1 << i))
10096 ExitPlayer(&stored_player[i]);
10098 if (game.players_still_needed == 0)
10104 case CA_KILL_PLAYER:
10106 for (i = 0; i < MAX_PLAYERS; i++)
10107 if (action_arg_player_bits & (1 << i))
10108 KillPlayer(&stored_player[i]);
10113 case CA_SET_PLAYER_KEYS:
10115 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10116 int element = getSpecialActionElement(action_arg_element,
10117 action_arg_number, EL_KEY_1);
10119 if (IS_KEY(element))
10121 for (i = 0; i < MAX_PLAYERS; i++)
10123 if (trigger_player_bits & (1 << i))
10125 stored_player[i].key[KEY_NR(element)] = key_state;
10127 DrawGameDoorValues();
10135 case CA_SET_PLAYER_SPEED:
10137 for (i = 0; i < MAX_PLAYERS; i++)
10139 if (trigger_player_bits & (1 << i))
10141 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10143 if (action_arg == CA_ARG_SPEED_FASTER &&
10144 stored_player[i].cannot_move)
10146 action_arg_number = STEPSIZE_VERY_SLOW;
10148 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10149 action_arg == CA_ARG_SPEED_FASTER)
10151 action_arg_number = 2;
10152 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10155 else if (action_arg == CA_ARG_NUMBER_RESET)
10157 action_arg_number = level.initial_player_stepsize[i];
10161 getModifiedActionNumber(move_stepsize,
10164 action_arg_number_min,
10165 action_arg_number_max);
10167 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10174 case CA_SET_PLAYER_SHIELD:
10176 for (i = 0; i < MAX_PLAYERS; i++)
10178 if (trigger_player_bits & (1 << i))
10180 if (action_arg == CA_ARG_SHIELD_OFF)
10182 stored_player[i].shield_normal_time_left = 0;
10183 stored_player[i].shield_deadly_time_left = 0;
10185 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10187 stored_player[i].shield_normal_time_left = 999999;
10189 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10191 stored_player[i].shield_normal_time_left = 999999;
10192 stored_player[i].shield_deadly_time_left = 999999;
10200 case CA_SET_PLAYER_GRAVITY:
10202 for (i = 0; i < MAX_PLAYERS; i++)
10204 if (trigger_player_bits & (1 << i))
10206 stored_player[i].gravity =
10207 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10208 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10209 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10210 stored_player[i].gravity);
10217 case CA_SET_PLAYER_ARTWORK:
10219 for (i = 0; i < MAX_PLAYERS; i++)
10221 if (trigger_player_bits & (1 << i))
10223 int artwork_element = action_arg_element;
10225 if (action_arg == CA_ARG_ELEMENT_RESET)
10227 (level.use_artwork_element[i] ? level.artwork_element[i] :
10228 stored_player[i].element_nr);
10230 if (stored_player[i].artwork_element != artwork_element)
10231 stored_player[i].Frame = 0;
10233 stored_player[i].artwork_element = artwork_element;
10235 SetPlayerWaiting(&stored_player[i], FALSE);
10237 // set number of special actions for bored and sleeping animation
10238 stored_player[i].num_special_action_bored =
10239 get_num_special_action(artwork_element,
10240 ACTION_BORING_1, ACTION_BORING_LAST);
10241 stored_player[i].num_special_action_sleeping =
10242 get_num_special_action(artwork_element,
10243 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10250 case CA_SET_PLAYER_INVENTORY:
10252 for (i = 0; i < MAX_PLAYERS; i++)
10254 struct PlayerInfo *player = &stored_player[i];
10257 if (trigger_player_bits & (1 << i))
10259 int inventory_element = action_arg_element;
10261 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10262 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10263 action_arg == CA_ARG_ELEMENT_ACTION)
10265 int element = inventory_element;
10266 int collect_count = element_info[element].collect_count_initial;
10268 if (!IS_CUSTOM_ELEMENT(element))
10271 if (collect_count == 0)
10272 player->inventory_infinite_element = element;
10274 for (k = 0; k < collect_count; k++)
10275 if (player->inventory_size < MAX_INVENTORY_SIZE)
10276 player->inventory_element[player->inventory_size++] =
10279 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10280 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10281 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10283 if (player->inventory_infinite_element != EL_UNDEFINED &&
10284 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10285 action_arg_element_raw))
10286 player->inventory_infinite_element = EL_UNDEFINED;
10288 for (k = 0, j = 0; j < player->inventory_size; j++)
10290 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10291 action_arg_element_raw))
10292 player->inventory_element[k++] = player->inventory_element[j];
10295 player->inventory_size = k;
10297 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10299 if (player->inventory_size > 0)
10301 for (j = 0; j < player->inventory_size - 1; j++)
10302 player->inventory_element[j] = player->inventory_element[j + 1];
10304 player->inventory_size--;
10307 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10309 if (player->inventory_size > 0)
10310 player->inventory_size--;
10312 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10314 player->inventory_infinite_element = EL_UNDEFINED;
10315 player->inventory_size = 0;
10317 else if (action_arg == CA_ARG_INVENTORY_RESET)
10319 player->inventory_infinite_element = EL_UNDEFINED;
10320 player->inventory_size = 0;
10322 if (level.use_initial_inventory[i])
10324 for (j = 0; j < level.initial_inventory_size[i]; j++)
10326 int element = level.initial_inventory_content[i][j];
10327 int collect_count = element_info[element].collect_count_initial;
10329 if (!IS_CUSTOM_ELEMENT(element))
10332 if (collect_count == 0)
10333 player->inventory_infinite_element = element;
10335 for (k = 0; k < collect_count; k++)
10336 if (player->inventory_size < MAX_INVENTORY_SIZE)
10337 player->inventory_element[player->inventory_size++] =
10348 // ---------- CE actions -------------------------------------------------
10350 case CA_SET_CE_VALUE:
10352 int last_ce_value = CustomValue[x][y];
10354 CustomValue[x][y] = action_arg_number_new;
10356 if (CustomValue[x][y] != last_ce_value)
10358 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10359 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10361 if (CustomValue[x][y] == 0)
10363 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10364 ChangeCount[x][y] = 0; // allow at least one more change
10366 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10367 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10374 case CA_SET_CE_SCORE:
10376 int last_ce_score = ei->collect_score;
10378 ei->collect_score = action_arg_number_new;
10380 if (ei->collect_score != last_ce_score)
10382 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10383 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10385 if (ei->collect_score == 0)
10389 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10390 ChangeCount[x][y] = 0; // allow at least one more change
10392 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10393 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10396 This is a very special case that seems to be a mixture between
10397 CheckElementChange() and CheckTriggeredElementChange(): while
10398 the first one only affects single elements that are triggered
10399 directly, the second one affects multiple elements in the playfield
10400 that are triggered indirectly by another element. This is a third
10401 case: Changing the CE score always affects multiple identical CEs,
10402 so every affected CE must be checked, not only the single CE for
10403 which the CE score was changed in the first place (as every instance
10404 of that CE shares the same CE score, and therefore also can change)!
10406 SCAN_PLAYFIELD(xx, yy)
10408 if (Tile[xx][yy] == element)
10409 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10410 CE_SCORE_GETS_ZERO);
10418 case CA_SET_CE_ARTWORK:
10420 int artwork_element = action_arg_element;
10421 boolean reset_frame = FALSE;
10424 if (action_arg == CA_ARG_ELEMENT_RESET)
10425 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10428 if (ei->gfx_element != artwork_element)
10429 reset_frame = TRUE;
10431 ei->gfx_element = artwork_element;
10433 SCAN_PLAYFIELD(xx, yy)
10435 if (Tile[xx][yy] == element)
10439 ResetGfxAnimation(xx, yy);
10440 ResetRandomAnimationValue(xx, yy);
10443 TEST_DrawLevelField(xx, yy);
10450 // ---------- engine actions ---------------------------------------------
10452 case CA_SET_ENGINE_SCAN_MODE:
10454 InitPlayfieldScanMode(action_arg);
10464 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10466 int old_element = Tile[x][y];
10467 int new_element = GetElementFromGroupElement(element);
10468 int previous_move_direction = MovDir[x][y];
10469 int last_ce_value = CustomValue[x][y];
10470 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10471 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10472 boolean add_player_onto_element = (new_element_is_player &&
10473 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10474 IS_WALKABLE(old_element));
10476 if (!add_player_onto_element)
10478 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10479 RemoveMovingField(x, y);
10483 Tile[x][y] = new_element;
10485 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10486 MovDir[x][y] = previous_move_direction;
10488 if (element_info[new_element].use_last_ce_value)
10489 CustomValue[x][y] = last_ce_value;
10491 InitField_WithBug1(x, y, FALSE);
10493 new_element = Tile[x][y]; // element may have changed
10495 ResetGfxAnimation(x, y);
10496 ResetRandomAnimationValue(x, y);
10498 TEST_DrawLevelField(x, y);
10500 if (GFX_CRUMBLED(new_element))
10501 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10504 // check if element under the player changes from accessible to unaccessible
10505 // (needed for special case of dropping element which then changes)
10506 // (must be checked after creating new element for walkable group elements)
10507 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10508 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10515 // "ChangeCount" not set yet to allow "entered by player" change one time
10516 if (new_element_is_player)
10517 RelocatePlayer(x, y, new_element);
10520 ChangeCount[x][y]++; // count number of changes in the same frame
10522 TestIfBadThingTouchesPlayer(x, y);
10523 TestIfPlayerTouchesCustomElement(x, y);
10524 TestIfElementTouchesCustomElement(x, y);
10527 static void CreateField(int x, int y, int element)
10529 CreateFieldExt(x, y, element, FALSE);
10532 static void CreateElementFromChange(int x, int y, int element)
10534 element = GET_VALID_RUNTIME_ELEMENT(element);
10536 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10538 int old_element = Tile[x][y];
10540 // prevent changed element from moving in same engine frame
10541 // unless both old and new element can either fall or move
10542 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10543 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10547 CreateFieldExt(x, y, element, TRUE);
10550 static boolean ChangeElement(int x, int y, int element, int page)
10552 struct ElementInfo *ei = &element_info[element];
10553 struct ElementChangeInfo *change = &ei->change_page[page];
10554 int ce_value = CustomValue[x][y];
10555 int ce_score = ei->collect_score;
10556 int target_element;
10557 int old_element = Tile[x][y];
10559 // always use default change event to prevent running into a loop
10560 if (ChangeEvent[x][y] == -1)
10561 ChangeEvent[x][y] = CE_DELAY;
10563 if (ChangeEvent[x][y] == CE_DELAY)
10565 // reset actual trigger element, trigger player and action element
10566 change->actual_trigger_element = EL_EMPTY;
10567 change->actual_trigger_player = EL_EMPTY;
10568 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10569 change->actual_trigger_side = CH_SIDE_NONE;
10570 change->actual_trigger_ce_value = 0;
10571 change->actual_trigger_ce_score = 0;
10574 // do not change elements more than a specified maximum number of changes
10575 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10578 ChangeCount[x][y]++; // count number of changes in the same frame
10580 if (change->explode)
10587 if (change->use_target_content)
10589 boolean complete_replace = TRUE;
10590 boolean can_replace[3][3];
10593 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10596 boolean is_walkable;
10597 boolean is_diggable;
10598 boolean is_collectible;
10599 boolean is_removable;
10600 boolean is_destructible;
10601 int ex = x + xx - 1;
10602 int ey = y + yy - 1;
10603 int content_element = change->target_content.e[xx][yy];
10606 can_replace[xx][yy] = TRUE;
10608 if (ex == x && ey == y) // do not check changing element itself
10611 if (content_element == EL_EMPTY_SPACE)
10613 can_replace[xx][yy] = FALSE; // do not replace border with space
10618 if (!IN_LEV_FIELD(ex, ey))
10620 can_replace[xx][yy] = FALSE;
10621 complete_replace = FALSE;
10628 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10629 e = MovingOrBlocked2Element(ex, ey);
10631 is_empty = (IS_FREE(ex, ey) ||
10632 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10634 is_walkable = (is_empty || IS_WALKABLE(e));
10635 is_diggable = (is_empty || IS_DIGGABLE(e));
10636 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10637 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10638 is_removable = (is_diggable || is_collectible);
10640 can_replace[xx][yy] =
10641 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10642 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10643 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10644 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10645 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10646 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10647 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10649 if (!can_replace[xx][yy])
10650 complete_replace = FALSE;
10653 if (!change->only_if_complete || complete_replace)
10655 boolean something_has_changed = FALSE;
10657 if (change->only_if_complete && change->use_random_replace &&
10658 RND(100) < change->random_percentage)
10661 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10663 int ex = x + xx - 1;
10664 int ey = y + yy - 1;
10665 int content_element;
10667 if (can_replace[xx][yy] && (!change->use_random_replace ||
10668 RND(100) < change->random_percentage))
10670 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10671 RemoveMovingField(ex, ey);
10673 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10675 content_element = change->target_content.e[xx][yy];
10676 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10677 ce_value, ce_score);
10679 CreateElementFromChange(ex, ey, target_element);
10681 something_has_changed = TRUE;
10683 // for symmetry reasons, freeze newly created border elements
10684 if (ex != x || ey != y)
10685 Stop[ex][ey] = TRUE; // no more moving in this frame
10689 if (something_has_changed)
10691 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10692 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10698 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10699 ce_value, ce_score);
10701 if (element == EL_DIAGONAL_GROWING ||
10702 element == EL_DIAGONAL_SHRINKING)
10704 target_element = Store[x][y];
10706 Store[x][y] = EL_EMPTY;
10709 CreateElementFromChange(x, y, target_element);
10711 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10712 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10715 // this uses direct change before indirect change
10716 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10721 static void HandleElementChange(int x, int y, int page)
10723 int element = MovingOrBlocked2Element(x, y);
10724 struct ElementInfo *ei = &element_info[element];
10725 struct ElementChangeInfo *change = &ei->change_page[page];
10726 boolean handle_action_before_change = FALSE;
10729 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10730 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10732 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10733 x, y, element, element_info[element].token_name);
10734 Debug("game:playing:HandleElementChange", "This should never happen!");
10738 // this can happen with classic bombs on walkable, changing elements
10739 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10744 if (ChangeDelay[x][y] == 0) // initialize element change
10746 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10748 if (change->can_change)
10750 // !!! not clear why graphic animation should be reset at all here !!!
10751 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10752 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10755 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10757 When using an animation frame delay of 1 (this only happens with
10758 "sp_zonk.moving.left/right" in the classic graphics), the default
10759 (non-moving) animation shows wrong animation frames (while the
10760 moving animation, like "sp_zonk.moving.left/right", is correct,
10761 so this graphical bug never shows up with the classic graphics).
10762 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10763 be drawn instead of the correct frames 0,1,2,3. This is caused by
10764 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10765 an element change: First when the change delay ("ChangeDelay[][]")
10766 counter has reached zero after decrementing, then a second time in
10767 the next frame (after "GfxFrame[][]" was already incremented) when
10768 "ChangeDelay[][]" is reset to the initial delay value again.
10770 This causes frame 0 to be drawn twice, while the last frame won't
10771 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10773 As some animations may already be cleverly designed around this bug
10774 (at least the "Snake Bite" snake tail animation does this), it cannot
10775 simply be fixed here without breaking such existing animations.
10776 Unfortunately, it cannot easily be detected if a graphics set was
10777 designed "before" or "after" the bug was fixed. As a workaround,
10778 a new graphics set option "game.graphics_engine_version" was added
10779 to be able to specify the game's major release version for which the
10780 graphics set was designed, which can then be used to decide if the
10781 bugfix should be used (version 4 and above) or not (version 3 or
10782 below, or if no version was specified at all, as with old sets).
10784 (The wrong/fixed animation frames can be tested with the test level set
10785 "test_gfxframe" and level "000", which contains a specially prepared
10786 custom element at level position (x/y) == (11/9) which uses the zonk
10787 animation mentioned above. Using "game.graphics_engine_version: 4"
10788 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10789 This can also be seen from the debug output for this test element.)
10792 // when a custom element is about to change (for example by change delay),
10793 // do not reset graphic animation when the custom element is moving
10794 if (game.graphics_engine_version < 4 &&
10797 ResetGfxAnimation(x, y);
10798 ResetRandomAnimationValue(x, y);
10801 if (change->pre_change_function)
10802 change->pre_change_function(x, y);
10806 ChangeDelay[x][y]--;
10808 if (ChangeDelay[x][y] != 0) // continue element change
10810 if (change->can_change)
10812 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10814 if (IS_ANIMATED(graphic))
10815 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10817 if (change->change_function)
10818 change->change_function(x, y);
10821 else // finish element change
10823 if (ChangePage[x][y] != -1) // remember page from delayed change
10825 page = ChangePage[x][y];
10826 ChangePage[x][y] = -1;
10828 change = &ei->change_page[page];
10831 if (IS_MOVING(x, y)) // never change a running system ;-)
10833 ChangeDelay[x][y] = 1; // try change after next move step
10834 ChangePage[x][y] = page; // remember page to use for change
10839 // special case: set new level random seed before changing element
10840 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10841 handle_action_before_change = TRUE;
10843 if (change->has_action && handle_action_before_change)
10844 ExecuteCustomElementAction(x, y, element, page);
10846 if (change->can_change)
10848 if (ChangeElement(x, y, element, page))
10850 if (change->post_change_function)
10851 change->post_change_function(x, y);
10855 if (change->has_action && !handle_action_before_change)
10856 ExecuteCustomElementAction(x, y, element, page);
10860 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10861 int trigger_element,
10863 int trigger_player,
10867 boolean change_done_any = FALSE;
10868 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10871 if (!(trigger_events[trigger_element][trigger_event]))
10874 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10876 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10878 int element = EL_CUSTOM_START + i;
10879 boolean change_done = FALSE;
10882 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10883 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10886 for (p = 0; p < element_info[element].num_change_pages; p++)
10888 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10890 if (change->can_change_or_has_action &&
10891 change->has_event[trigger_event] &&
10892 change->trigger_side & trigger_side &&
10893 change->trigger_player & trigger_player &&
10894 change->trigger_page & trigger_page_bits &&
10895 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10897 change->actual_trigger_element = trigger_element;
10898 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10899 change->actual_trigger_player_bits = trigger_player;
10900 change->actual_trigger_side = trigger_side;
10901 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10902 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10904 if ((change->can_change && !change_done) || change->has_action)
10908 SCAN_PLAYFIELD(x, y)
10910 if (Tile[x][y] == element)
10912 if (change->can_change && !change_done)
10914 // if element already changed in this frame, not only prevent
10915 // another element change (checked in ChangeElement()), but
10916 // also prevent additional element actions for this element
10918 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10919 !level.use_action_after_change_bug)
10922 ChangeDelay[x][y] = 1;
10923 ChangeEvent[x][y] = trigger_event;
10925 HandleElementChange(x, y, p);
10927 else if (change->has_action)
10929 // if element already changed in this frame, not only prevent
10930 // another element change (checked in ChangeElement()), but
10931 // also prevent additional element actions for this element
10933 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10934 !level.use_action_after_change_bug)
10937 ExecuteCustomElementAction(x, y, element, p);
10938 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10943 if (change->can_change)
10945 change_done = TRUE;
10946 change_done_any = TRUE;
10953 RECURSION_LOOP_DETECTION_END();
10955 return change_done_any;
10958 static boolean CheckElementChangeExt(int x, int y,
10960 int trigger_element,
10962 int trigger_player,
10965 boolean change_done = FALSE;
10968 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10969 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10972 if (Tile[x][y] == EL_BLOCKED)
10974 Blocked2Moving(x, y, &x, &y);
10975 element = Tile[x][y];
10978 // check if element has already changed or is about to change after moving
10979 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10980 Tile[x][y] != element) ||
10982 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10983 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10984 ChangePage[x][y] != -1)))
10987 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10989 for (p = 0; p < element_info[element].num_change_pages; p++)
10991 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10993 /* check trigger element for all events where the element that is checked
10994 for changing interacts with a directly adjacent element -- this is
10995 different to element changes that affect other elements to change on the
10996 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10997 boolean check_trigger_element =
10998 (trigger_event == CE_TOUCHING_X ||
10999 trigger_event == CE_HITTING_X ||
11000 trigger_event == CE_HIT_BY_X ||
11001 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11003 if (change->can_change_or_has_action &&
11004 change->has_event[trigger_event] &&
11005 change->trigger_side & trigger_side &&
11006 change->trigger_player & trigger_player &&
11007 (!check_trigger_element ||
11008 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11010 change->actual_trigger_element = trigger_element;
11011 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11012 change->actual_trigger_player_bits = trigger_player;
11013 change->actual_trigger_side = trigger_side;
11014 change->actual_trigger_ce_value = CustomValue[x][y];
11015 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11017 // special case: trigger element not at (x,y) position for some events
11018 if (check_trigger_element)
11030 { 0, 0 }, { 0, 0 }, { 0, 0 },
11034 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11035 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11037 change->actual_trigger_ce_value = CustomValue[xx][yy];
11038 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11041 if (change->can_change && !change_done)
11043 ChangeDelay[x][y] = 1;
11044 ChangeEvent[x][y] = trigger_event;
11046 HandleElementChange(x, y, p);
11048 change_done = TRUE;
11050 else if (change->has_action)
11052 ExecuteCustomElementAction(x, y, element, p);
11053 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11058 RECURSION_LOOP_DETECTION_END();
11060 return change_done;
11063 static void PlayPlayerSound(struct PlayerInfo *player)
11065 int jx = player->jx, jy = player->jy;
11066 int sound_element = player->artwork_element;
11067 int last_action = player->last_action_waiting;
11068 int action = player->action_waiting;
11070 if (player->is_waiting)
11072 if (action != last_action)
11073 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11075 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11079 if (action != last_action)
11080 StopSound(element_info[sound_element].sound[last_action]);
11082 if (last_action == ACTION_SLEEPING)
11083 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11087 static void PlayAllPlayersSound(void)
11091 for (i = 0; i < MAX_PLAYERS; i++)
11092 if (stored_player[i].active)
11093 PlayPlayerSound(&stored_player[i]);
11096 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11098 boolean last_waiting = player->is_waiting;
11099 int move_dir = player->MovDir;
11101 player->dir_waiting = move_dir;
11102 player->last_action_waiting = player->action_waiting;
11106 if (!last_waiting) // not waiting -> waiting
11108 player->is_waiting = TRUE;
11110 player->frame_counter_bored =
11112 game.player_boring_delay_fixed +
11113 GetSimpleRandom(game.player_boring_delay_random);
11114 player->frame_counter_sleeping =
11116 game.player_sleeping_delay_fixed +
11117 GetSimpleRandom(game.player_sleeping_delay_random);
11119 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11122 if (game.player_sleeping_delay_fixed +
11123 game.player_sleeping_delay_random > 0 &&
11124 player->anim_delay_counter == 0 &&
11125 player->post_delay_counter == 0 &&
11126 FrameCounter >= player->frame_counter_sleeping)
11127 player->is_sleeping = TRUE;
11128 else if (game.player_boring_delay_fixed +
11129 game.player_boring_delay_random > 0 &&
11130 FrameCounter >= player->frame_counter_bored)
11131 player->is_bored = TRUE;
11133 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11134 player->is_bored ? ACTION_BORING :
11137 if (player->is_sleeping && player->use_murphy)
11139 // special case for sleeping Murphy when leaning against non-free tile
11141 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11142 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11143 !IS_MOVING(player->jx - 1, player->jy)))
11144 move_dir = MV_LEFT;
11145 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11146 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11147 !IS_MOVING(player->jx + 1, player->jy)))
11148 move_dir = MV_RIGHT;
11150 player->is_sleeping = FALSE;
11152 player->dir_waiting = move_dir;
11155 if (player->is_sleeping)
11157 if (player->num_special_action_sleeping > 0)
11159 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11161 int last_special_action = player->special_action_sleeping;
11162 int num_special_action = player->num_special_action_sleeping;
11163 int special_action =
11164 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11165 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11166 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11167 last_special_action + 1 : ACTION_SLEEPING);
11168 int special_graphic =
11169 el_act_dir2img(player->artwork_element, special_action, move_dir);
11171 player->anim_delay_counter =
11172 graphic_info[special_graphic].anim_delay_fixed +
11173 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11174 player->post_delay_counter =
11175 graphic_info[special_graphic].post_delay_fixed +
11176 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11178 player->special_action_sleeping = special_action;
11181 if (player->anim_delay_counter > 0)
11183 player->action_waiting = player->special_action_sleeping;
11184 player->anim_delay_counter--;
11186 else if (player->post_delay_counter > 0)
11188 player->post_delay_counter--;
11192 else if (player->is_bored)
11194 if (player->num_special_action_bored > 0)
11196 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11198 int special_action =
11199 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11200 int special_graphic =
11201 el_act_dir2img(player->artwork_element, special_action, move_dir);
11203 player->anim_delay_counter =
11204 graphic_info[special_graphic].anim_delay_fixed +
11205 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11206 player->post_delay_counter =
11207 graphic_info[special_graphic].post_delay_fixed +
11208 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11210 player->special_action_bored = special_action;
11213 if (player->anim_delay_counter > 0)
11215 player->action_waiting = player->special_action_bored;
11216 player->anim_delay_counter--;
11218 else if (player->post_delay_counter > 0)
11220 player->post_delay_counter--;
11225 else if (last_waiting) // waiting -> not waiting
11227 player->is_waiting = FALSE;
11228 player->is_bored = FALSE;
11229 player->is_sleeping = FALSE;
11231 player->frame_counter_bored = -1;
11232 player->frame_counter_sleeping = -1;
11234 player->anim_delay_counter = 0;
11235 player->post_delay_counter = 0;
11237 player->dir_waiting = player->MovDir;
11238 player->action_waiting = ACTION_DEFAULT;
11240 player->special_action_bored = ACTION_DEFAULT;
11241 player->special_action_sleeping = ACTION_DEFAULT;
11245 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11247 if ((!player->is_moving && player->was_moving) ||
11248 (player->MovPos == 0 && player->was_moving) ||
11249 (player->is_snapping && !player->was_snapping) ||
11250 (player->is_dropping && !player->was_dropping))
11252 if (!CheckSaveEngineSnapshotToList())
11255 player->was_moving = FALSE;
11256 player->was_snapping = TRUE;
11257 player->was_dropping = TRUE;
11261 if (player->is_moving)
11262 player->was_moving = TRUE;
11264 if (!player->is_snapping)
11265 player->was_snapping = FALSE;
11267 if (!player->is_dropping)
11268 player->was_dropping = FALSE;
11271 static struct MouseActionInfo mouse_action_last = { 0 };
11272 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11273 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11276 CheckSaveEngineSnapshotToList();
11278 mouse_action_last = mouse_action;
11281 static void CheckSingleStepMode(struct PlayerInfo *player)
11283 if (tape.single_step && tape.recording && !tape.pausing)
11285 /* as it is called "single step mode", just return to pause mode when the
11286 player stopped moving after one tile (or never starts moving at all) */
11287 if (!player->is_moving &&
11288 !player->is_pushing &&
11289 !player->is_dropping_pressed &&
11290 !player->effective_mouse_action.button)
11291 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11294 CheckSaveEngineSnapshot(player);
11297 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11299 int left = player_action & JOY_LEFT;
11300 int right = player_action & JOY_RIGHT;
11301 int up = player_action & JOY_UP;
11302 int down = player_action & JOY_DOWN;
11303 int button1 = player_action & JOY_BUTTON_1;
11304 int button2 = player_action & JOY_BUTTON_2;
11305 int dx = (left ? -1 : right ? 1 : 0);
11306 int dy = (up ? -1 : down ? 1 : 0);
11308 if (!player->active || tape.pausing)
11314 SnapField(player, dx, dy);
11318 DropElement(player);
11320 MovePlayer(player, dx, dy);
11323 CheckSingleStepMode(player);
11325 SetPlayerWaiting(player, FALSE);
11327 return player_action;
11331 // no actions for this player (no input at player's configured device)
11333 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11334 SnapField(player, 0, 0);
11335 CheckGravityMovementWhenNotMoving(player);
11337 if (player->MovPos == 0)
11338 SetPlayerWaiting(player, TRUE);
11340 if (player->MovPos == 0) // needed for tape.playing
11341 player->is_moving = FALSE;
11343 player->is_dropping = FALSE;
11344 player->is_dropping_pressed = FALSE;
11345 player->drop_pressed_delay = 0;
11347 CheckSingleStepMode(player);
11353 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11356 if (!tape.use_mouse_actions)
11359 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11360 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11361 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11364 static void SetTapeActionFromMouseAction(byte *tape_action,
11365 struct MouseActionInfo *mouse_action)
11367 if (!tape.use_mouse_actions)
11370 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11371 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11372 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11375 static void CheckLevelSolved(void)
11377 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11379 if (game_em.level_solved &&
11380 !game_em.game_over) // game won
11384 game_em.game_over = TRUE;
11386 game.all_players_gone = TRUE;
11389 if (game_em.game_over) // game lost
11390 game.all_players_gone = TRUE;
11392 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11394 if (game_sp.level_solved &&
11395 !game_sp.game_over) // game won
11399 game_sp.game_over = TRUE;
11401 game.all_players_gone = TRUE;
11404 if (game_sp.game_over) // game lost
11405 game.all_players_gone = TRUE;
11407 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11409 if (game_mm.level_solved &&
11410 !game_mm.game_over) // game won
11414 game_mm.game_over = TRUE;
11416 game.all_players_gone = TRUE;
11419 if (game_mm.game_over) // game lost
11420 game.all_players_gone = TRUE;
11424 static void CheckLevelTime(void)
11428 if (TimeFrames >= FRAMES_PER_SECOND)
11433 for (i = 0; i < MAX_PLAYERS; i++)
11435 struct PlayerInfo *player = &stored_player[i];
11437 if (SHIELD_ON(player))
11439 player->shield_normal_time_left--;
11441 if (player->shield_deadly_time_left > 0)
11442 player->shield_deadly_time_left--;
11446 if (!game.LevelSolved && !level.use_step_counter)
11454 if (TimeLeft <= 10 && setup.time_limit)
11455 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11457 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11458 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11460 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11462 if (!TimeLeft && setup.time_limit)
11464 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11465 game_em.lev->killed_out_of_time = TRUE;
11467 for (i = 0; i < MAX_PLAYERS; i++)
11468 KillPlayer(&stored_player[i]);
11471 else if (game.no_time_limit && !game.all_players_gone)
11473 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11476 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11479 if (tape.recording || tape.playing)
11480 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11483 if (tape.recording || tape.playing)
11484 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11486 UpdateAndDisplayGameControlValues();
11489 void AdvanceFrameAndPlayerCounters(int player_nr)
11493 // advance frame counters (global frame counter and time frame counter)
11497 // advance player counters (counters for move delay, move animation etc.)
11498 for (i = 0; i < MAX_PLAYERS; i++)
11500 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11501 int move_delay_value = stored_player[i].move_delay_value;
11502 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11504 if (!advance_player_counters) // not all players may be affected
11507 if (move_frames == 0) // less than one move per game frame
11509 int stepsize = TILEX / move_delay_value;
11510 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11511 int count = (stored_player[i].is_moving ?
11512 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11514 if (count % delay == 0)
11518 stored_player[i].Frame += move_frames;
11520 if (stored_player[i].MovPos != 0)
11521 stored_player[i].StepFrame += move_frames;
11523 if (stored_player[i].move_delay > 0)
11524 stored_player[i].move_delay--;
11526 // due to bugs in previous versions, counter must count up, not down
11527 if (stored_player[i].push_delay != -1)
11528 stored_player[i].push_delay++;
11530 if (stored_player[i].drop_delay > 0)
11531 stored_player[i].drop_delay--;
11533 if (stored_player[i].is_dropping_pressed)
11534 stored_player[i].drop_pressed_delay++;
11538 void StartGameActions(boolean init_network_game, boolean record_tape,
11541 unsigned int new_random_seed = InitRND(random_seed);
11544 TapeStartRecording(new_random_seed);
11546 if (init_network_game)
11548 SendToServer_LevelFile();
11549 SendToServer_StartPlaying();
11557 static void GameActionsExt(void)
11560 static unsigned int game_frame_delay = 0;
11562 unsigned int game_frame_delay_value;
11563 byte *recorded_player_action;
11564 byte summarized_player_action = 0;
11565 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11568 // detect endless loops, caused by custom element programming
11569 if (recursion_loop_detected && recursion_loop_depth == 0)
11571 char *message = getStringCat3("Internal Error! Element ",
11572 EL_NAME(recursion_loop_element),
11573 " caused endless loop! Quit the game?");
11575 Warn("element '%s' caused endless loop in game engine",
11576 EL_NAME(recursion_loop_element));
11578 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11580 recursion_loop_detected = FALSE; // if game should be continued
11587 if (game.restart_level)
11588 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11590 CheckLevelSolved();
11592 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11595 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11598 if (game_status != GAME_MODE_PLAYING) // status might have changed
11601 game_frame_delay_value =
11602 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11604 if (tape.playing && tape.warp_forward && !tape.pausing)
11605 game_frame_delay_value = 0;
11607 SetVideoFrameDelay(game_frame_delay_value);
11609 // (de)activate virtual buttons depending on current game status
11610 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11612 if (game.all_players_gone) // if no players there to be controlled anymore
11613 SetOverlayActive(FALSE);
11614 else if (!tape.playing) // if game continues after tape stopped playing
11615 SetOverlayActive(TRUE);
11620 // ---------- main game synchronization point ----------
11622 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11624 Debug("game:playing:skip", "skip == %d", skip);
11627 // ---------- main game synchronization point ----------
11629 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11633 if (network_playing && !network_player_action_received)
11635 // try to get network player actions in time
11637 // last chance to get network player actions without main loop delay
11638 HandleNetworking();
11640 // game was quit by network peer
11641 if (game_status != GAME_MODE_PLAYING)
11644 // check if network player actions still missing and game still running
11645 if (!network_player_action_received && !checkGameEnded())
11646 return; // failed to get network player actions in time
11648 // do not yet reset "network_player_action_received" (for tape.pausing)
11654 // at this point we know that we really continue executing the game
11656 network_player_action_received = FALSE;
11658 // when playing tape, read previously recorded player input from tape data
11659 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11661 local_player->effective_mouse_action = local_player->mouse_action;
11663 if (recorded_player_action != NULL)
11664 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11665 recorded_player_action);
11667 // TapePlayAction() may return NULL when toggling to "pause before death"
11671 if (tape.set_centered_player)
11673 game.centered_player_nr_next = tape.centered_player_nr_next;
11674 game.set_centered_player = TRUE;
11677 for (i = 0; i < MAX_PLAYERS; i++)
11679 summarized_player_action |= stored_player[i].action;
11681 if (!network_playing && (game.team_mode || tape.playing))
11682 stored_player[i].effective_action = stored_player[i].action;
11685 if (network_playing && !checkGameEnded())
11686 SendToServer_MovePlayer(summarized_player_action);
11688 // summarize all actions at local players mapped input device position
11689 // (this allows using different input devices in single player mode)
11690 if (!network.enabled && !game.team_mode)
11691 stored_player[map_player_action[local_player->index_nr]].effective_action =
11692 summarized_player_action;
11694 // summarize all actions at centered player in local team mode
11695 if (tape.recording &&
11696 setup.team_mode && !network.enabled &&
11697 setup.input_on_focus &&
11698 game.centered_player_nr != -1)
11700 for (i = 0; i < MAX_PLAYERS; i++)
11701 stored_player[map_player_action[i]].effective_action =
11702 (i == game.centered_player_nr ? summarized_player_action : 0);
11705 if (recorded_player_action != NULL)
11706 for (i = 0; i < MAX_PLAYERS; i++)
11707 stored_player[i].effective_action = recorded_player_action[i];
11709 for (i = 0; i < MAX_PLAYERS; i++)
11711 tape_action[i] = stored_player[i].effective_action;
11713 /* (this may happen in the RND game engine if a player was not present on
11714 the playfield on level start, but appeared later from a custom element */
11715 if (setup.team_mode &&
11718 !tape.player_participates[i])
11719 tape.player_participates[i] = TRUE;
11722 SetTapeActionFromMouseAction(tape_action,
11723 &local_player->effective_mouse_action);
11725 // only record actions from input devices, but not programmed actions
11726 if (tape.recording)
11727 TapeRecordAction(tape_action);
11729 // remember if game was played (especially after tape stopped playing)
11730 if (!tape.playing && summarized_player_action)
11731 game.GamePlayed = TRUE;
11733 #if USE_NEW_PLAYER_ASSIGNMENTS
11734 // !!! also map player actions in single player mode !!!
11735 // if (game.team_mode)
11738 byte mapped_action[MAX_PLAYERS];
11740 #if DEBUG_PLAYER_ACTIONS
11741 for (i = 0; i < MAX_PLAYERS; i++)
11742 DebugContinued("", "%d, ", stored_player[i].effective_action);
11745 for (i = 0; i < MAX_PLAYERS; i++)
11746 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11748 for (i = 0; i < MAX_PLAYERS; i++)
11749 stored_player[i].effective_action = mapped_action[i];
11751 #if DEBUG_PLAYER_ACTIONS
11752 DebugContinued("", "=> ");
11753 for (i = 0; i < MAX_PLAYERS; i++)
11754 DebugContinued("", "%d, ", stored_player[i].effective_action);
11755 DebugContinued("game:playing:player", "\n");
11758 #if DEBUG_PLAYER_ACTIONS
11761 for (i = 0; i < MAX_PLAYERS; i++)
11762 DebugContinued("", "%d, ", stored_player[i].effective_action);
11763 DebugContinued("game:playing:player", "\n");
11768 for (i = 0; i < MAX_PLAYERS; i++)
11770 // allow engine snapshot in case of changed movement attempt
11771 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11772 (stored_player[i].effective_action & KEY_MOTION))
11773 game.snapshot.changed_action = TRUE;
11775 // allow engine snapshot in case of snapping/dropping attempt
11776 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11777 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11778 game.snapshot.changed_action = TRUE;
11780 game.snapshot.last_action[i] = stored_player[i].effective_action;
11783 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11785 GameActions_EM_Main();
11787 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11789 GameActions_SP_Main();
11791 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11793 GameActions_MM_Main();
11797 GameActions_RND_Main();
11800 BlitScreenToBitmap(backbuffer);
11802 CheckLevelSolved();
11805 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11807 if (global.show_frames_per_second)
11809 static unsigned int fps_counter = 0;
11810 static int fps_frames = 0;
11811 unsigned int fps_delay_ms = Counter() - fps_counter;
11815 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11817 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11820 fps_counter = Counter();
11822 // always draw FPS to screen after FPS value was updated
11823 redraw_mask |= REDRAW_FPS;
11826 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11827 if (GetDrawDeactivationMask() == REDRAW_NONE)
11828 redraw_mask |= REDRAW_FPS;
11832 static void GameActions_CheckSaveEngineSnapshot(void)
11834 if (!game.snapshot.save_snapshot)
11837 // clear flag for saving snapshot _before_ saving snapshot
11838 game.snapshot.save_snapshot = FALSE;
11840 SaveEngineSnapshotToList();
11843 void GameActions(void)
11847 GameActions_CheckSaveEngineSnapshot();
11850 void GameActions_EM_Main(void)
11852 byte effective_action[MAX_PLAYERS];
11853 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11856 for (i = 0; i < MAX_PLAYERS; i++)
11857 effective_action[i] = stored_player[i].effective_action;
11859 GameActions_EM(effective_action, warp_mode);
11862 void GameActions_SP_Main(void)
11864 byte effective_action[MAX_PLAYERS];
11865 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11868 for (i = 0; i < MAX_PLAYERS; i++)
11869 effective_action[i] = stored_player[i].effective_action;
11871 GameActions_SP(effective_action, warp_mode);
11873 for (i = 0; i < MAX_PLAYERS; i++)
11875 if (stored_player[i].force_dropping)
11876 stored_player[i].action |= KEY_BUTTON_DROP;
11878 stored_player[i].force_dropping = FALSE;
11882 void GameActions_MM_Main(void)
11884 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11886 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11889 void GameActions_RND_Main(void)
11894 void GameActions_RND(void)
11896 static struct MouseActionInfo mouse_action_last = { 0 };
11897 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11898 int magic_wall_x = 0, magic_wall_y = 0;
11899 int i, x, y, element, graphic, last_gfx_frame;
11901 InitPlayfieldScanModeVars();
11903 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11905 SCAN_PLAYFIELD(x, y)
11907 ChangeCount[x][y] = 0;
11908 ChangeEvent[x][y] = -1;
11912 if (game.set_centered_player)
11914 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11916 // switching to "all players" only possible if all players fit to screen
11917 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11919 game.centered_player_nr_next = game.centered_player_nr;
11920 game.set_centered_player = FALSE;
11923 // do not switch focus to non-existing (or non-active) player
11924 if (game.centered_player_nr_next >= 0 &&
11925 !stored_player[game.centered_player_nr_next].active)
11927 game.centered_player_nr_next = game.centered_player_nr;
11928 game.set_centered_player = FALSE;
11932 if (game.set_centered_player &&
11933 ScreenMovPos == 0) // screen currently aligned at tile position
11937 if (game.centered_player_nr_next == -1)
11939 setScreenCenteredToAllPlayers(&sx, &sy);
11943 sx = stored_player[game.centered_player_nr_next].jx;
11944 sy = stored_player[game.centered_player_nr_next].jy;
11947 game.centered_player_nr = game.centered_player_nr_next;
11948 game.set_centered_player = FALSE;
11950 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11951 DrawGameDoorValues();
11954 for (i = 0; i < MAX_PLAYERS; i++)
11956 int actual_player_action = stored_player[i].effective_action;
11959 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11960 - rnd_equinox_tetrachloride 048
11961 - rnd_equinox_tetrachloride_ii 096
11962 - rnd_emanuel_schmieg 002
11963 - doctor_sloan_ww 001, 020
11965 if (stored_player[i].MovPos == 0)
11966 CheckGravityMovement(&stored_player[i]);
11969 // overwrite programmed action with tape action
11970 if (stored_player[i].programmed_action)
11971 actual_player_action = stored_player[i].programmed_action;
11973 PlayerActions(&stored_player[i], actual_player_action);
11975 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11978 ScrollScreen(NULL, SCROLL_GO_ON);
11980 /* for backwards compatibility, the following code emulates a fixed bug that
11981 occured when pushing elements (causing elements that just made their last
11982 pushing step to already (if possible) make their first falling step in the
11983 same game frame, which is bad); this code is also needed to use the famous
11984 "spring push bug" which is used in older levels and might be wanted to be
11985 used also in newer levels, but in this case the buggy pushing code is only
11986 affecting the "spring" element and no other elements */
11988 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11990 for (i = 0; i < MAX_PLAYERS; i++)
11992 struct PlayerInfo *player = &stored_player[i];
11993 int x = player->jx;
11994 int y = player->jy;
11996 if (player->active && player->is_pushing && player->is_moving &&
11998 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11999 Tile[x][y] == EL_SPRING))
12001 ContinueMoving(x, y);
12003 // continue moving after pushing (this is actually a bug)
12004 if (!IS_MOVING(x, y))
12005 Stop[x][y] = FALSE;
12010 SCAN_PLAYFIELD(x, y)
12012 Last[x][y] = Tile[x][y];
12014 ChangeCount[x][y] = 0;
12015 ChangeEvent[x][y] = -1;
12017 // this must be handled before main playfield loop
12018 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12021 if (MovDelay[x][y] <= 0)
12025 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12028 if (MovDelay[x][y] <= 0)
12031 TEST_DrawLevelField(x, y);
12033 TestIfElementTouchesCustomElement(x, y); // for empty space
12038 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12040 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12042 Debug("game:playing:GameActions_RND", "This should never happen!");
12044 ChangePage[x][y] = -1;
12048 Stop[x][y] = FALSE;
12049 if (WasJustMoving[x][y] > 0)
12050 WasJustMoving[x][y]--;
12051 if (WasJustFalling[x][y] > 0)
12052 WasJustFalling[x][y]--;
12053 if (CheckCollision[x][y] > 0)
12054 CheckCollision[x][y]--;
12055 if (CheckImpact[x][y] > 0)
12056 CheckImpact[x][y]--;
12060 /* reset finished pushing action (not done in ContinueMoving() to allow
12061 continuous pushing animation for elements with zero push delay) */
12062 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12064 ResetGfxAnimation(x, y);
12065 TEST_DrawLevelField(x, y);
12069 if (IS_BLOCKED(x, y))
12073 Blocked2Moving(x, y, &oldx, &oldy);
12074 if (!IS_MOVING(oldx, oldy))
12076 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12077 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12078 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12079 Debug("game:playing:GameActions_RND", "This should never happen!");
12085 if (mouse_action.button)
12087 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12089 x = mouse_action.lx;
12090 y = mouse_action.ly;
12091 element = Tile[x][y];
12095 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12096 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12099 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12100 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12103 SCAN_PLAYFIELD(x, y)
12105 element = Tile[x][y];
12106 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12107 last_gfx_frame = GfxFrame[x][y];
12109 ResetGfxFrame(x, y);
12111 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12112 DrawLevelGraphicAnimation(x, y, graphic);
12114 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12115 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12116 ResetRandomAnimationValue(x, y);
12118 SetRandomAnimationValue(x, y);
12120 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12122 if (IS_INACTIVE(element))
12124 if (IS_ANIMATED(graphic))
12125 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12130 // this may take place after moving, so 'element' may have changed
12131 if (IS_CHANGING(x, y) &&
12132 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12134 int page = element_info[element].event_page_nr[CE_DELAY];
12136 HandleElementChange(x, y, page);
12138 element = Tile[x][y];
12139 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12142 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12146 element = Tile[x][y];
12147 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12149 if (IS_ANIMATED(graphic) &&
12150 !IS_MOVING(x, y) &&
12152 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12154 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12155 TEST_DrawTwinkleOnField(x, y);
12157 else if (element == EL_ACID)
12160 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12162 else if ((element == EL_EXIT_OPEN ||
12163 element == EL_EM_EXIT_OPEN ||
12164 element == EL_SP_EXIT_OPEN ||
12165 element == EL_STEEL_EXIT_OPEN ||
12166 element == EL_EM_STEEL_EXIT_OPEN ||
12167 element == EL_SP_TERMINAL ||
12168 element == EL_SP_TERMINAL_ACTIVE ||
12169 element == EL_EXTRA_TIME ||
12170 element == EL_SHIELD_NORMAL ||
12171 element == EL_SHIELD_DEADLY) &&
12172 IS_ANIMATED(graphic))
12173 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12174 else if (IS_MOVING(x, y))
12175 ContinueMoving(x, y);
12176 else if (IS_ACTIVE_BOMB(element))
12177 CheckDynamite(x, y);
12178 else if (element == EL_AMOEBA_GROWING)
12179 AmoebaGrowing(x, y);
12180 else if (element == EL_AMOEBA_SHRINKING)
12181 AmoebaShrinking(x, y);
12183 #if !USE_NEW_AMOEBA_CODE
12184 else if (IS_AMOEBALIVE(element))
12185 AmoebaReproduce(x, y);
12188 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12190 else if (element == EL_EXIT_CLOSED)
12192 else if (element == EL_EM_EXIT_CLOSED)
12194 else if (element == EL_STEEL_EXIT_CLOSED)
12195 CheckExitSteel(x, y);
12196 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12197 CheckExitSteelEM(x, y);
12198 else if (element == EL_SP_EXIT_CLOSED)
12200 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12201 element == EL_EXPANDABLE_STEELWALL_GROWING)
12202 MauerWaechst(x, y);
12203 else if (element == EL_EXPANDABLE_WALL ||
12204 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12205 element == EL_EXPANDABLE_WALL_VERTICAL ||
12206 element == EL_EXPANDABLE_WALL_ANY ||
12207 element == EL_BD_EXPANDABLE_WALL)
12208 MauerAbleger(x, y);
12209 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12210 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12211 element == EL_EXPANDABLE_STEELWALL_ANY)
12212 MauerAblegerStahl(x, y);
12213 else if (element == EL_FLAMES)
12214 CheckForDragon(x, y);
12215 else if (element == EL_EXPLOSION)
12216 ; // drawing of correct explosion animation is handled separately
12217 else if (element == EL_ELEMENT_SNAPPING ||
12218 element == EL_DIAGONAL_SHRINKING ||
12219 element == EL_DIAGONAL_GROWING)
12221 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12223 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12225 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12226 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12228 if (IS_BELT_ACTIVE(element))
12229 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12231 if (game.magic_wall_active)
12233 int jx = local_player->jx, jy = local_player->jy;
12235 // play the element sound at the position nearest to the player
12236 if ((element == EL_MAGIC_WALL_FULL ||
12237 element == EL_MAGIC_WALL_ACTIVE ||
12238 element == EL_MAGIC_WALL_EMPTYING ||
12239 element == EL_BD_MAGIC_WALL_FULL ||
12240 element == EL_BD_MAGIC_WALL_ACTIVE ||
12241 element == EL_BD_MAGIC_WALL_EMPTYING ||
12242 element == EL_DC_MAGIC_WALL_FULL ||
12243 element == EL_DC_MAGIC_WALL_ACTIVE ||
12244 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12245 ABS(x - jx) + ABS(y - jy) <
12246 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12254 #if USE_NEW_AMOEBA_CODE
12255 // new experimental amoeba growth stuff
12256 if (!(FrameCounter % 8))
12258 static unsigned int random = 1684108901;
12260 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12262 x = RND(lev_fieldx);
12263 y = RND(lev_fieldy);
12264 element = Tile[x][y];
12266 if (!IS_PLAYER(x,y) &&
12267 (element == EL_EMPTY ||
12268 CAN_GROW_INTO(element) ||
12269 element == EL_QUICKSAND_EMPTY ||
12270 element == EL_QUICKSAND_FAST_EMPTY ||
12271 element == EL_ACID_SPLASH_LEFT ||
12272 element == EL_ACID_SPLASH_RIGHT))
12274 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12275 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12276 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12277 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12278 Tile[x][y] = EL_AMOEBA_DROP;
12281 random = random * 129 + 1;
12286 game.explosions_delayed = FALSE;
12288 SCAN_PLAYFIELD(x, y)
12290 element = Tile[x][y];
12292 if (ExplodeField[x][y])
12293 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12294 else if (element == EL_EXPLOSION)
12295 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12297 ExplodeField[x][y] = EX_TYPE_NONE;
12300 game.explosions_delayed = TRUE;
12302 if (game.magic_wall_active)
12304 if (!(game.magic_wall_time_left % 4))
12306 int element = Tile[magic_wall_x][magic_wall_y];
12308 if (element == EL_BD_MAGIC_WALL_FULL ||
12309 element == EL_BD_MAGIC_WALL_ACTIVE ||
12310 element == EL_BD_MAGIC_WALL_EMPTYING)
12311 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12312 else if (element == EL_DC_MAGIC_WALL_FULL ||
12313 element == EL_DC_MAGIC_WALL_ACTIVE ||
12314 element == EL_DC_MAGIC_WALL_EMPTYING)
12315 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12317 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12320 if (game.magic_wall_time_left > 0)
12322 game.magic_wall_time_left--;
12324 if (!game.magic_wall_time_left)
12326 SCAN_PLAYFIELD(x, y)
12328 element = Tile[x][y];
12330 if (element == EL_MAGIC_WALL_ACTIVE ||
12331 element == EL_MAGIC_WALL_FULL)
12333 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12334 TEST_DrawLevelField(x, y);
12336 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12337 element == EL_BD_MAGIC_WALL_FULL)
12339 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12340 TEST_DrawLevelField(x, y);
12342 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12343 element == EL_DC_MAGIC_WALL_FULL)
12345 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12346 TEST_DrawLevelField(x, y);
12350 game.magic_wall_active = FALSE;
12355 if (game.light_time_left > 0)
12357 game.light_time_left--;
12359 if (game.light_time_left == 0)
12360 RedrawAllLightSwitchesAndInvisibleElements();
12363 if (game.timegate_time_left > 0)
12365 game.timegate_time_left--;
12367 if (game.timegate_time_left == 0)
12368 CloseAllOpenTimegates();
12371 if (game.lenses_time_left > 0)
12373 game.lenses_time_left--;
12375 if (game.lenses_time_left == 0)
12376 RedrawAllInvisibleElementsForLenses();
12379 if (game.magnify_time_left > 0)
12381 game.magnify_time_left--;
12383 if (game.magnify_time_left == 0)
12384 RedrawAllInvisibleElementsForMagnifier();
12387 for (i = 0; i < MAX_PLAYERS; i++)
12389 struct PlayerInfo *player = &stored_player[i];
12391 if (SHIELD_ON(player))
12393 if (player->shield_deadly_time_left)
12394 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12395 else if (player->shield_normal_time_left)
12396 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12400 #if USE_DELAYED_GFX_REDRAW
12401 SCAN_PLAYFIELD(x, y)
12403 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12405 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12406 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12408 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12409 DrawLevelField(x, y);
12411 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12412 DrawLevelFieldCrumbled(x, y);
12414 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12415 DrawLevelFieldCrumbledNeighbours(x, y);
12417 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12418 DrawTwinkleOnField(x, y);
12421 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12426 PlayAllPlayersSound();
12428 for (i = 0; i < MAX_PLAYERS; i++)
12430 struct PlayerInfo *player = &stored_player[i];
12432 if (player->show_envelope != 0 && (!player->active ||
12433 player->MovPos == 0))
12435 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12437 player->show_envelope = 0;
12441 // use random number generator in every frame to make it less predictable
12442 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12445 mouse_action_last = mouse_action;
12448 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12450 int min_x = x, min_y = y, max_x = x, max_y = y;
12453 for (i = 0; i < MAX_PLAYERS; i++)
12455 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12457 if (!stored_player[i].active || &stored_player[i] == player)
12460 min_x = MIN(min_x, jx);
12461 min_y = MIN(min_y, jy);
12462 max_x = MAX(max_x, jx);
12463 max_y = MAX(max_y, jy);
12466 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12469 static boolean AllPlayersInVisibleScreen(void)
12473 for (i = 0; i < MAX_PLAYERS; i++)
12475 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12477 if (!stored_player[i].active)
12480 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12487 void ScrollLevel(int dx, int dy)
12489 int scroll_offset = 2 * TILEX_VAR;
12492 BlitBitmap(drawto_field, drawto_field,
12493 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12494 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12495 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12496 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12497 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12498 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12502 x = (dx == 1 ? BX1 : BX2);
12503 for (y = BY1; y <= BY2; y++)
12504 DrawScreenField(x, y);
12509 y = (dy == 1 ? BY1 : BY2);
12510 for (x = BX1; x <= BX2; x++)
12511 DrawScreenField(x, y);
12514 redraw_mask |= REDRAW_FIELD;
12517 static boolean canFallDown(struct PlayerInfo *player)
12519 int jx = player->jx, jy = player->jy;
12521 return (IN_LEV_FIELD(jx, jy + 1) &&
12522 (IS_FREE(jx, jy + 1) ||
12523 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12524 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12525 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12528 static boolean canPassField(int x, int y, int move_dir)
12530 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12531 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12532 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12533 int nextx = x + dx;
12534 int nexty = y + dy;
12535 int element = Tile[x][y];
12537 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12538 !CAN_MOVE(element) &&
12539 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12540 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12541 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12544 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12546 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12547 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12548 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12552 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12553 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12554 (IS_DIGGABLE(Tile[newx][newy]) ||
12555 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12556 canPassField(newx, newy, move_dir)));
12559 static void CheckGravityMovement(struct PlayerInfo *player)
12561 if (player->gravity && !player->programmed_action)
12563 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12564 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12565 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12566 int jx = player->jx, jy = player->jy;
12567 boolean player_is_moving_to_valid_field =
12568 (!player_is_snapping &&
12569 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12570 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12571 boolean player_can_fall_down = canFallDown(player);
12573 if (player_can_fall_down &&
12574 !player_is_moving_to_valid_field)
12575 player->programmed_action = MV_DOWN;
12579 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12581 return CheckGravityMovement(player);
12583 if (player->gravity && !player->programmed_action)
12585 int jx = player->jx, jy = player->jy;
12586 boolean field_under_player_is_free =
12587 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12588 boolean player_is_standing_on_valid_field =
12589 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12590 (IS_WALKABLE(Tile[jx][jy]) &&
12591 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12593 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12594 player->programmed_action = MV_DOWN;
12599 MovePlayerOneStep()
12600 -----------------------------------------------------------------------------
12601 dx, dy: direction (non-diagonal) to try to move the player to
12602 real_dx, real_dy: direction as read from input device (can be diagonal)
12605 boolean MovePlayerOneStep(struct PlayerInfo *player,
12606 int dx, int dy, int real_dx, int real_dy)
12608 int jx = player->jx, jy = player->jy;
12609 int new_jx = jx + dx, new_jy = jy + dy;
12611 boolean player_can_move = !player->cannot_move;
12613 if (!player->active || (!dx && !dy))
12614 return MP_NO_ACTION;
12616 player->MovDir = (dx < 0 ? MV_LEFT :
12617 dx > 0 ? MV_RIGHT :
12619 dy > 0 ? MV_DOWN : MV_NONE);
12621 if (!IN_LEV_FIELD(new_jx, new_jy))
12622 return MP_NO_ACTION;
12624 if (!player_can_move)
12626 if (player->MovPos == 0)
12628 player->is_moving = FALSE;
12629 player->is_digging = FALSE;
12630 player->is_collecting = FALSE;
12631 player->is_snapping = FALSE;
12632 player->is_pushing = FALSE;
12636 if (!network.enabled && game.centered_player_nr == -1 &&
12637 !AllPlayersInSight(player, new_jx, new_jy))
12638 return MP_NO_ACTION;
12640 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12641 if (can_move != MP_MOVING)
12644 // check if DigField() has caused relocation of the player
12645 if (player->jx != jx || player->jy != jy)
12646 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12648 StorePlayer[jx][jy] = 0;
12649 player->last_jx = jx;
12650 player->last_jy = jy;
12651 player->jx = new_jx;
12652 player->jy = new_jy;
12653 StorePlayer[new_jx][new_jy] = player->element_nr;
12655 if (player->move_delay_value_next != -1)
12657 player->move_delay_value = player->move_delay_value_next;
12658 player->move_delay_value_next = -1;
12662 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12664 player->step_counter++;
12666 PlayerVisit[jx][jy] = FrameCounter;
12668 player->is_moving = TRUE;
12671 // should better be called in MovePlayer(), but this breaks some tapes
12672 ScrollPlayer(player, SCROLL_INIT);
12678 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12680 int jx = player->jx, jy = player->jy;
12681 int old_jx = jx, old_jy = jy;
12682 int moved = MP_NO_ACTION;
12684 if (!player->active)
12689 if (player->MovPos == 0)
12691 player->is_moving = FALSE;
12692 player->is_digging = FALSE;
12693 player->is_collecting = FALSE;
12694 player->is_snapping = FALSE;
12695 player->is_pushing = FALSE;
12701 if (player->move_delay > 0)
12704 player->move_delay = -1; // set to "uninitialized" value
12706 // store if player is automatically moved to next field
12707 player->is_auto_moving = (player->programmed_action != MV_NONE);
12709 // remove the last programmed player action
12710 player->programmed_action = 0;
12712 if (player->MovPos)
12714 // should only happen if pre-1.2 tape recordings are played
12715 // this is only for backward compatibility
12717 int original_move_delay_value = player->move_delay_value;
12720 Debug("game:playing:MovePlayer",
12721 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12725 // scroll remaining steps with finest movement resolution
12726 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12728 while (player->MovPos)
12730 ScrollPlayer(player, SCROLL_GO_ON);
12731 ScrollScreen(NULL, SCROLL_GO_ON);
12733 AdvanceFrameAndPlayerCounters(player->index_nr);
12736 BackToFront_WithFrameDelay(0);
12739 player->move_delay_value = original_move_delay_value;
12742 player->is_active = FALSE;
12744 if (player->last_move_dir & MV_HORIZONTAL)
12746 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12747 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12751 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12752 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12755 if (!moved && !player->is_active)
12757 player->is_moving = FALSE;
12758 player->is_digging = FALSE;
12759 player->is_collecting = FALSE;
12760 player->is_snapping = FALSE;
12761 player->is_pushing = FALSE;
12767 if (moved & MP_MOVING && !ScreenMovPos &&
12768 (player->index_nr == game.centered_player_nr ||
12769 game.centered_player_nr == -1))
12771 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12773 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12775 // actual player has left the screen -- scroll in that direction
12776 if (jx != old_jx) // player has moved horizontally
12777 scroll_x += (jx - old_jx);
12778 else // player has moved vertically
12779 scroll_y += (jy - old_jy);
12783 int offset_raw = game.scroll_delay_value;
12785 if (jx != old_jx) // player has moved horizontally
12787 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12788 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12789 int new_scroll_x = jx - MIDPOSX + offset_x;
12791 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12792 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12793 scroll_x = new_scroll_x;
12795 // don't scroll over playfield boundaries
12796 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12798 // don't scroll more than one field at a time
12799 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12801 // don't scroll against the player's moving direction
12802 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12803 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12804 scroll_x = old_scroll_x;
12806 else // player has moved vertically
12808 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12809 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12810 int new_scroll_y = jy - MIDPOSY + offset_y;
12812 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12813 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12814 scroll_y = new_scroll_y;
12816 // don't scroll over playfield boundaries
12817 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12819 // don't scroll more than one field at a time
12820 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12822 // don't scroll against the player's moving direction
12823 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12824 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12825 scroll_y = old_scroll_y;
12829 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12831 if (!network.enabled && game.centered_player_nr == -1 &&
12832 !AllPlayersInVisibleScreen())
12834 scroll_x = old_scroll_x;
12835 scroll_y = old_scroll_y;
12839 ScrollScreen(player, SCROLL_INIT);
12840 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12845 player->StepFrame = 0;
12847 if (moved & MP_MOVING)
12849 if (old_jx != jx && old_jy == jy)
12850 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12851 else if (old_jx == jx && old_jy != jy)
12852 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12854 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12856 player->last_move_dir = player->MovDir;
12857 player->is_moving = TRUE;
12858 player->is_snapping = FALSE;
12859 player->is_switching = FALSE;
12860 player->is_dropping = FALSE;
12861 player->is_dropping_pressed = FALSE;
12862 player->drop_pressed_delay = 0;
12865 // should better be called here than above, but this breaks some tapes
12866 ScrollPlayer(player, SCROLL_INIT);
12871 CheckGravityMovementWhenNotMoving(player);
12873 player->is_moving = FALSE;
12875 /* at this point, the player is allowed to move, but cannot move right now
12876 (e.g. because of something blocking the way) -- ensure that the player
12877 is also allowed to move in the next frame (in old versions before 3.1.1,
12878 the player was forced to wait again for eight frames before next try) */
12880 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12881 player->move_delay = 0; // allow direct movement in the next frame
12884 if (player->move_delay == -1) // not yet initialized by DigField()
12885 player->move_delay = player->move_delay_value;
12887 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12889 TestIfPlayerTouchesBadThing(jx, jy);
12890 TestIfPlayerTouchesCustomElement(jx, jy);
12893 if (!player->active)
12894 RemovePlayer(player);
12899 void ScrollPlayer(struct PlayerInfo *player, int mode)
12901 int jx = player->jx, jy = player->jy;
12902 int last_jx = player->last_jx, last_jy = player->last_jy;
12903 int move_stepsize = TILEX / player->move_delay_value;
12905 if (!player->active)
12908 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12911 if (mode == SCROLL_INIT)
12913 player->actual_frame_counter = FrameCounter;
12914 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12916 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12917 Tile[last_jx][last_jy] == EL_EMPTY)
12919 int last_field_block_delay = 0; // start with no blocking at all
12920 int block_delay_adjustment = player->block_delay_adjustment;
12922 // if player blocks last field, add delay for exactly one move
12923 if (player->block_last_field)
12925 last_field_block_delay += player->move_delay_value;
12927 // when blocking enabled, prevent moving up despite gravity
12928 if (player->gravity && player->MovDir == MV_UP)
12929 block_delay_adjustment = -1;
12932 // add block delay adjustment (also possible when not blocking)
12933 last_field_block_delay += block_delay_adjustment;
12935 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12936 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12939 if (player->MovPos != 0) // player has not yet reached destination
12942 else if (!FrameReached(&player->actual_frame_counter, 1))
12945 if (player->MovPos != 0)
12947 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12948 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12950 // before DrawPlayer() to draw correct player graphic for this case
12951 if (player->MovPos == 0)
12952 CheckGravityMovement(player);
12955 if (player->MovPos == 0) // player reached destination field
12957 if (player->move_delay_reset_counter > 0)
12959 player->move_delay_reset_counter--;
12961 if (player->move_delay_reset_counter == 0)
12963 // continue with normal speed after quickly moving through gate
12964 HALVE_PLAYER_SPEED(player);
12966 // be able to make the next move without delay
12967 player->move_delay = 0;
12971 player->last_jx = jx;
12972 player->last_jy = jy;
12974 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12975 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12976 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12977 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12978 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12979 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12980 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12981 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12983 ExitPlayer(player);
12985 if (game.players_still_needed == 0 &&
12986 (game.friends_still_needed == 0 ||
12987 IS_SP_ELEMENT(Tile[jx][jy])))
12991 // this breaks one level: "machine", level 000
12993 int move_direction = player->MovDir;
12994 int enter_side = MV_DIR_OPPOSITE(move_direction);
12995 int leave_side = move_direction;
12996 int old_jx = last_jx;
12997 int old_jy = last_jy;
12998 int old_element = Tile[old_jx][old_jy];
12999 int new_element = Tile[jx][jy];
13001 if (IS_CUSTOM_ELEMENT(old_element))
13002 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13004 player->index_bit, leave_side);
13006 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13007 CE_PLAYER_LEAVES_X,
13008 player->index_bit, leave_side);
13010 if (IS_CUSTOM_ELEMENT(new_element))
13011 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13012 player->index_bit, enter_side);
13014 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13015 CE_PLAYER_ENTERS_X,
13016 player->index_bit, enter_side);
13018 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13019 CE_MOVE_OF_X, move_direction);
13022 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13024 TestIfPlayerTouchesBadThing(jx, jy);
13025 TestIfPlayerTouchesCustomElement(jx, jy);
13027 /* needed because pushed element has not yet reached its destination,
13028 so it would trigger a change event at its previous field location */
13029 if (!player->is_pushing)
13030 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13032 if (!player->active)
13033 RemovePlayer(player);
13036 if (!game.LevelSolved && level.use_step_counter)
13046 if (TimeLeft <= 10 && setup.time_limit)
13047 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13049 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13051 DisplayGameControlValues();
13053 if (!TimeLeft && setup.time_limit)
13054 for (i = 0; i < MAX_PLAYERS; i++)
13055 KillPlayer(&stored_player[i]);
13057 else if (game.no_time_limit && !game.all_players_gone)
13059 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13061 DisplayGameControlValues();
13065 if (tape.single_step && tape.recording && !tape.pausing &&
13066 !player->programmed_action)
13067 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13069 if (!player->programmed_action)
13070 CheckSaveEngineSnapshot(player);
13074 void ScrollScreen(struct PlayerInfo *player, int mode)
13076 static unsigned int screen_frame_counter = 0;
13078 if (mode == SCROLL_INIT)
13080 // set scrolling step size according to actual player's moving speed
13081 ScrollStepSize = TILEX / player->move_delay_value;
13083 screen_frame_counter = FrameCounter;
13084 ScreenMovDir = player->MovDir;
13085 ScreenMovPos = player->MovPos;
13086 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13089 else if (!FrameReached(&screen_frame_counter, 1))
13094 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13095 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13096 redraw_mask |= REDRAW_FIELD;
13099 ScreenMovDir = MV_NONE;
13102 void TestIfPlayerTouchesCustomElement(int x, int y)
13104 static int xy[4][2] =
13111 static int trigger_sides[4][2] =
13113 // center side border side
13114 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13115 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13116 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13117 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13119 static int touch_dir[4] =
13121 MV_LEFT | MV_RIGHT,
13126 int center_element = Tile[x][y]; // should always be non-moving!
13129 for (i = 0; i < NUM_DIRECTIONS; i++)
13131 int xx = x + xy[i][0];
13132 int yy = y + xy[i][1];
13133 int center_side = trigger_sides[i][0];
13134 int border_side = trigger_sides[i][1];
13135 int border_element;
13137 if (!IN_LEV_FIELD(xx, yy))
13140 if (IS_PLAYER(x, y)) // player found at center element
13142 struct PlayerInfo *player = PLAYERINFO(x, y);
13144 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13145 border_element = Tile[xx][yy]; // may be moving!
13146 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13147 border_element = Tile[xx][yy];
13148 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13149 border_element = MovingOrBlocked2Element(xx, yy);
13151 continue; // center and border element do not touch
13153 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13154 player->index_bit, border_side);
13155 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13156 CE_PLAYER_TOUCHES_X,
13157 player->index_bit, border_side);
13160 /* use player element that is initially defined in the level playfield,
13161 not the player element that corresponds to the runtime player number
13162 (example: a level that contains EL_PLAYER_3 as the only player would
13163 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13164 int player_element = PLAYERINFO(x, y)->initial_element;
13166 CheckElementChangeBySide(xx, yy, border_element, player_element,
13167 CE_TOUCHING_X, border_side);
13170 else if (IS_PLAYER(xx, yy)) // player found at border element
13172 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13174 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13176 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13177 continue; // center and border element do not touch
13180 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13181 player->index_bit, center_side);
13182 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13183 CE_PLAYER_TOUCHES_X,
13184 player->index_bit, center_side);
13187 /* use player element that is initially defined in the level playfield,
13188 not the player element that corresponds to the runtime player number
13189 (example: a level that contains EL_PLAYER_3 as the only player would
13190 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13191 int player_element = PLAYERINFO(xx, yy)->initial_element;
13193 CheckElementChangeBySide(x, y, center_element, player_element,
13194 CE_TOUCHING_X, center_side);
13202 void TestIfElementTouchesCustomElement(int x, int y)
13204 static int xy[4][2] =
13211 static int trigger_sides[4][2] =
13213 // center side border side
13214 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13215 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13216 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13217 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13219 static int touch_dir[4] =
13221 MV_LEFT | MV_RIGHT,
13226 boolean change_center_element = FALSE;
13227 int center_element = Tile[x][y]; // should always be non-moving!
13228 int border_element_old[NUM_DIRECTIONS];
13231 for (i = 0; i < NUM_DIRECTIONS; i++)
13233 int xx = x + xy[i][0];
13234 int yy = y + xy[i][1];
13235 int border_element;
13237 border_element_old[i] = -1;
13239 if (!IN_LEV_FIELD(xx, yy))
13242 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13243 border_element = Tile[xx][yy]; // may be moving!
13244 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13245 border_element = Tile[xx][yy];
13246 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13247 border_element = MovingOrBlocked2Element(xx, yy);
13249 continue; // center and border element do not touch
13251 border_element_old[i] = border_element;
13254 for (i = 0; i < NUM_DIRECTIONS; i++)
13256 int xx = x + xy[i][0];
13257 int yy = y + xy[i][1];
13258 int center_side = trigger_sides[i][0];
13259 int border_element = border_element_old[i];
13261 if (border_element == -1)
13264 // check for change of border element
13265 CheckElementChangeBySide(xx, yy, border_element, center_element,
13266 CE_TOUCHING_X, center_side);
13268 // (center element cannot be player, so we dont have to check this here)
13271 for (i = 0; i < NUM_DIRECTIONS; i++)
13273 int xx = x + xy[i][0];
13274 int yy = y + xy[i][1];
13275 int border_side = trigger_sides[i][1];
13276 int border_element = border_element_old[i];
13278 if (border_element == -1)
13281 // check for change of center element (but change it only once)
13282 if (!change_center_element)
13283 change_center_element =
13284 CheckElementChangeBySide(x, y, center_element, border_element,
13285 CE_TOUCHING_X, border_side);
13287 if (IS_PLAYER(xx, yy))
13289 /* use player element that is initially defined in the level playfield,
13290 not the player element that corresponds to the runtime player number
13291 (example: a level that contains EL_PLAYER_3 as the only player would
13292 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13293 int player_element = PLAYERINFO(xx, yy)->initial_element;
13295 CheckElementChangeBySide(x, y, center_element, player_element,
13296 CE_TOUCHING_X, border_side);
13301 void TestIfElementHitsCustomElement(int x, int y, int direction)
13303 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13304 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13305 int hitx = x + dx, hity = y + dy;
13306 int hitting_element = Tile[x][y];
13307 int touched_element;
13309 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13312 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13313 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13315 if (IN_LEV_FIELD(hitx, hity))
13317 int opposite_direction = MV_DIR_OPPOSITE(direction);
13318 int hitting_side = direction;
13319 int touched_side = opposite_direction;
13320 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13321 MovDir[hitx][hity] != direction ||
13322 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13328 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13329 CE_HITTING_X, touched_side);
13331 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13332 CE_HIT_BY_X, hitting_side);
13334 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13335 CE_HIT_BY_SOMETHING, opposite_direction);
13337 if (IS_PLAYER(hitx, hity))
13339 /* use player element that is initially defined in the level playfield,
13340 not the player element that corresponds to the runtime player number
13341 (example: a level that contains EL_PLAYER_3 as the only player would
13342 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13343 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13345 CheckElementChangeBySide(x, y, hitting_element, player_element,
13346 CE_HITTING_X, touched_side);
13351 // "hitting something" is also true when hitting the playfield border
13352 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13353 CE_HITTING_SOMETHING, direction);
13356 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13358 int i, kill_x = -1, kill_y = -1;
13360 int bad_element = -1;
13361 static int test_xy[4][2] =
13368 static int test_dir[4] =
13376 for (i = 0; i < NUM_DIRECTIONS; i++)
13378 int test_x, test_y, test_move_dir, test_element;
13380 test_x = good_x + test_xy[i][0];
13381 test_y = good_y + test_xy[i][1];
13383 if (!IN_LEV_FIELD(test_x, test_y))
13387 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13389 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13391 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13392 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13394 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13395 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13399 bad_element = test_element;
13405 if (kill_x != -1 || kill_y != -1)
13407 if (IS_PLAYER(good_x, good_y))
13409 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13411 if (player->shield_deadly_time_left > 0 &&
13412 !IS_INDESTRUCTIBLE(bad_element))
13413 Bang(kill_x, kill_y);
13414 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13415 KillPlayer(player);
13418 Bang(good_x, good_y);
13422 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13424 int i, kill_x = -1, kill_y = -1;
13425 int bad_element = Tile[bad_x][bad_y];
13426 static int test_xy[4][2] =
13433 static int touch_dir[4] =
13435 MV_LEFT | MV_RIGHT,
13440 static int test_dir[4] =
13448 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13451 for (i = 0; i < NUM_DIRECTIONS; i++)
13453 int test_x, test_y, test_move_dir, test_element;
13455 test_x = bad_x + test_xy[i][0];
13456 test_y = bad_y + test_xy[i][1];
13458 if (!IN_LEV_FIELD(test_x, test_y))
13462 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13464 test_element = Tile[test_x][test_y];
13466 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13467 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13469 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13470 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13472 // good thing is player or penguin that does not move away
13473 if (IS_PLAYER(test_x, test_y))
13475 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13477 if (bad_element == EL_ROBOT && player->is_moving)
13478 continue; // robot does not kill player if he is moving
13480 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13482 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13483 continue; // center and border element do not touch
13491 else if (test_element == EL_PENGUIN)
13501 if (kill_x != -1 || kill_y != -1)
13503 if (IS_PLAYER(kill_x, kill_y))
13505 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13507 if (player->shield_deadly_time_left > 0 &&
13508 !IS_INDESTRUCTIBLE(bad_element))
13509 Bang(bad_x, bad_y);
13510 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13511 KillPlayer(player);
13514 Bang(kill_x, kill_y);
13518 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13520 int bad_element = Tile[bad_x][bad_y];
13521 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13522 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13523 int test_x = bad_x + dx, test_y = bad_y + dy;
13524 int test_move_dir, test_element;
13525 int kill_x = -1, kill_y = -1;
13527 if (!IN_LEV_FIELD(test_x, test_y))
13531 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13533 test_element = Tile[test_x][test_y];
13535 if (test_move_dir != bad_move_dir)
13537 // good thing can be player or penguin that does not move away
13538 if (IS_PLAYER(test_x, test_y))
13540 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13542 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13543 player as being hit when he is moving towards the bad thing, because
13544 the "get hit by" condition would be lost after the player stops) */
13545 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13546 return; // player moves away from bad thing
13551 else if (test_element == EL_PENGUIN)
13558 if (kill_x != -1 || kill_y != -1)
13560 if (IS_PLAYER(kill_x, kill_y))
13562 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13564 if (player->shield_deadly_time_left > 0 &&
13565 !IS_INDESTRUCTIBLE(bad_element))
13566 Bang(bad_x, bad_y);
13567 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13568 KillPlayer(player);
13571 Bang(kill_x, kill_y);
13575 void TestIfPlayerTouchesBadThing(int x, int y)
13577 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13580 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13582 TestIfGoodThingHitsBadThing(x, y, move_dir);
13585 void TestIfBadThingTouchesPlayer(int x, int y)
13587 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13590 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13592 TestIfBadThingHitsGoodThing(x, y, move_dir);
13595 void TestIfFriendTouchesBadThing(int x, int y)
13597 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13600 void TestIfBadThingTouchesFriend(int x, int y)
13602 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13605 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13607 int i, kill_x = bad_x, kill_y = bad_y;
13608 static int xy[4][2] =
13616 for (i = 0; i < NUM_DIRECTIONS; i++)
13620 x = bad_x + xy[i][0];
13621 y = bad_y + xy[i][1];
13622 if (!IN_LEV_FIELD(x, y))
13625 element = Tile[x][y];
13626 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13627 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13635 if (kill_x != bad_x || kill_y != bad_y)
13636 Bang(bad_x, bad_y);
13639 void KillPlayer(struct PlayerInfo *player)
13641 int jx = player->jx, jy = player->jy;
13643 if (!player->active)
13647 Debug("game:playing:KillPlayer",
13648 "0: killed == %d, active == %d, reanimated == %d",
13649 player->killed, player->active, player->reanimated);
13652 /* the following code was introduced to prevent an infinite loop when calling
13654 -> CheckTriggeredElementChangeExt()
13655 -> ExecuteCustomElementAction()
13657 -> (infinitely repeating the above sequence of function calls)
13658 which occurs when killing the player while having a CE with the setting
13659 "kill player X when explosion of <player X>"; the solution using a new
13660 field "player->killed" was chosen for backwards compatibility, although
13661 clever use of the fields "player->active" etc. would probably also work */
13663 if (player->killed)
13667 player->killed = TRUE;
13669 // remove accessible field at the player's position
13670 Tile[jx][jy] = EL_EMPTY;
13672 // deactivate shield (else Bang()/Explode() would not work right)
13673 player->shield_normal_time_left = 0;
13674 player->shield_deadly_time_left = 0;
13677 Debug("game:playing:KillPlayer",
13678 "1: killed == %d, active == %d, reanimated == %d",
13679 player->killed, player->active, player->reanimated);
13685 Debug("game:playing:KillPlayer",
13686 "2: killed == %d, active == %d, reanimated == %d",
13687 player->killed, player->active, player->reanimated);
13690 if (player->reanimated) // killed player may have been reanimated
13691 player->killed = player->reanimated = FALSE;
13693 BuryPlayer(player);
13696 static void KillPlayerUnlessEnemyProtected(int x, int y)
13698 if (!PLAYER_ENEMY_PROTECTED(x, y))
13699 KillPlayer(PLAYERINFO(x, y));
13702 static void KillPlayerUnlessExplosionProtected(int x, int y)
13704 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13705 KillPlayer(PLAYERINFO(x, y));
13708 void BuryPlayer(struct PlayerInfo *player)
13710 int jx = player->jx, jy = player->jy;
13712 if (!player->active)
13715 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13716 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13718 RemovePlayer(player);
13720 player->buried = TRUE;
13722 if (game.all_players_gone)
13723 game.GameOver = TRUE;
13726 void RemovePlayer(struct PlayerInfo *player)
13728 int jx = player->jx, jy = player->jy;
13729 int i, found = FALSE;
13731 player->present = FALSE;
13732 player->active = FALSE;
13734 // required for some CE actions (even if the player is not active anymore)
13735 player->MovPos = 0;
13737 if (!ExplodeField[jx][jy])
13738 StorePlayer[jx][jy] = 0;
13740 if (player->is_moving)
13741 TEST_DrawLevelField(player->last_jx, player->last_jy);
13743 for (i = 0; i < MAX_PLAYERS; i++)
13744 if (stored_player[i].active)
13749 game.all_players_gone = TRUE;
13750 game.GameOver = TRUE;
13753 game.exit_x = game.robot_wheel_x = jx;
13754 game.exit_y = game.robot_wheel_y = jy;
13757 void ExitPlayer(struct PlayerInfo *player)
13759 DrawPlayer(player); // needed here only to cleanup last field
13760 RemovePlayer(player);
13762 if (game.players_still_needed > 0)
13763 game.players_still_needed--;
13766 static void setFieldForSnapping(int x, int y, int element, int direction)
13768 struct ElementInfo *ei = &element_info[element];
13769 int direction_bit = MV_DIR_TO_BIT(direction);
13770 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13771 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13772 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13774 Tile[x][y] = EL_ELEMENT_SNAPPING;
13775 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13777 ResetGfxAnimation(x, y);
13779 GfxElement[x][y] = element;
13780 GfxAction[x][y] = action;
13781 GfxDir[x][y] = direction;
13782 GfxFrame[x][y] = -1;
13786 =============================================================================
13787 checkDiagonalPushing()
13788 -----------------------------------------------------------------------------
13789 check if diagonal input device direction results in pushing of object
13790 (by checking if the alternative direction is walkable, diggable, ...)
13791 =============================================================================
13794 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13795 int x, int y, int real_dx, int real_dy)
13797 int jx, jy, dx, dy, xx, yy;
13799 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13802 // diagonal direction: check alternative direction
13807 xx = jx + (dx == 0 ? real_dx : 0);
13808 yy = jy + (dy == 0 ? real_dy : 0);
13810 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13814 =============================================================================
13816 -----------------------------------------------------------------------------
13817 x, y: field next to player (non-diagonal) to try to dig to
13818 real_dx, real_dy: direction as read from input device (can be diagonal)
13819 =============================================================================
13822 static int DigField(struct PlayerInfo *player,
13823 int oldx, int oldy, int x, int y,
13824 int real_dx, int real_dy, int mode)
13826 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13827 boolean player_was_pushing = player->is_pushing;
13828 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13829 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13830 int jx = oldx, jy = oldy;
13831 int dx = x - jx, dy = y - jy;
13832 int nextx = x + dx, nexty = y + dy;
13833 int move_direction = (dx == -1 ? MV_LEFT :
13834 dx == +1 ? MV_RIGHT :
13836 dy == +1 ? MV_DOWN : MV_NONE);
13837 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13838 int dig_side = MV_DIR_OPPOSITE(move_direction);
13839 int old_element = Tile[jx][jy];
13840 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13843 if (is_player) // function can also be called by EL_PENGUIN
13845 if (player->MovPos == 0)
13847 player->is_digging = FALSE;
13848 player->is_collecting = FALSE;
13851 if (player->MovPos == 0) // last pushing move finished
13852 player->is_pushing = FALSE;
13854 if (mode == DF_NO_PUSH) // player just stopped pushing
13856 player->is_switching = FALSE;
13857 player->push_delay = -1;
13859 return MP_NO_ACTION;
13863 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13864 old_element = Back[jx][jy];
13866 // in case of element dropped at player position, check background
13867 else if (Back[jx][jy] != EL_EMPTY &&
13868 game.engine_version >= VERSION_IDENT(2,2,0,0))
13869 old_element = Back[jx][jy];
13871 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13872 return MP_NO_ACTION; // field has no opening in this direction
13874 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13875 return MP_NO_ACTION; // field has no opening in this direction
13877 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13881 Tile[jx][jy] = player->artwork_element;
13882 InitMovingField(jx, jy, MV_DOWN);
13883 Store[jx][jy] = EL_ACID;
13884 ContinueMoving(jx, jy);
13885 BuryPlayer(player);
13887 return MP_DONT_RUN_INTO;
13890 if (player_can_move && DONT_RUN_INTO(element))
13892 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13894 return MP_DONT_RUN_INTO;
13897 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13898 return MP_NO_ACTION;
13900 collect_count = element_info[element].collect_count_initial;
13902 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13903 return MP_NO_ACTION;
13905 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13906 player_can_move = player_can_move_or_snap;
13908 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13909 game.engine_version >= VERSION_IDENT(2,2,0,0))
13911 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13912 player->index_bit, dig_side);
13913 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13914 player->index_bit, dig_side);
13916 if (element == EL_DC_LANDMINE)
13919 if (Tile[x][y] != element) // field changed by snapping
13922 return MP_NO_ACTION;
13925 if (player->gravity && is_player && !player->is_auto_moving &&
13926 canFallDown(player) && move_direction != MV_DOWN &&
13927 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13928 return MP_NO_ACTION; // player cannot walk here due to gravity
13930 if (player_can_move &&
13931 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13933 int sound_element = SND_ELEMENT(element);
13934 int sound_action = ACTION_WALKING;
13936 if (IS_RND_GATE(element))
13938 if (!player->key[RND_GATE_NR(element)])
13939 return MP_NO_ACTION;
13941 else if (IS_RND_GATE_GRAY(element))
13943 if (!player->key[RND_GATE_GRAY_NR(element)])
13944 return MP_NO_ACTION;
13946 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13948 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13949 return MP_NO_ACTION;
13951 else if (element == EL_EXIT_OPEN ||
13952 element == EL_EM_EXIT_OPEN ||
13953 element == EL_EM_EXIT_OPENING ||
13954 element == EL_STEEL_EXIT_OPEN ||
13955 element == EL_EM_STEEL_EXIT_OPEN ||
13956 element == EL_EM_STEEL_EXIT_OPENING ||
13957 element == EL_SP_EXIT_OPEN ||
13958 element == EL_SP_EXIT_OPENING)
13960 sound_action = ACTION_PASSING; // player is passing exit
13962 else if (element == EL_EMPTY)
13964 sound_action = ACTION_MOVING; // nothing to walk on
13967 // play sound from background or player, whatever is available
13968 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13969 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13971 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13973 else if (player_can_move &&
13974 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13976 if (!ACCESS_FROM(element, opposite_direction))
13977 return MP_NO_ACTION; // field not accessible from this direction
13979 if (CAN_MOVE(element)) // only fixed elements can be passed!
13980 return MP_NO_ACTION;
13982 if (IS_EM_GATE(element))
13984 if (!player->key[EM_GATE_NR(element)])
13985 return MP_NO_ACTION;
13987 else if (IS_EM_GATE_GRAY(element))
13989 if (!player->key[EM_GATE_GRAY_NR(element)])
13990 return MP_NO_ACTION;
13992 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13994 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13995 return MP_NO_ACTION;
13997 else if (IS_EMC_GATE(element))
13999 if (!player->key[EMC_GATE_NR(element)])
14000 return MP_NO_ACTION;
14002 else if (IS_EMC_GATE_GRAY(element))
14004 if (!player->key[EMC_GATE_GRAY_NR(element)])
14005 return MP_NO_ACTION;
14007 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14009 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14010 return MP_NO_ACTION;
14012 else if (element == EL_DC_GATE_WHITE ||
14013 element == EL_DC_GATE_WHITE_GRAY ||
14014 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14016 if (player->num_white_keys == 0)
14017 return MP_NO_ACTION;
14019 player->num_white_keys--;
14021 else if (IS_SP_PORT(element))
14023 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14024 element == EL_SP_GRAVITY_PORT_RIGHT ||
14025 element == EL_SP_GRAVITY_PORT_UP ||
14026 element == EL_SP_GRAVITY_PORT_DOWN)
14027 player->gravity = !player->gravity;
14028 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14029 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14030 element == EL_SP_GRAVITY_ON_PORT_UP ||
14031 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14032 player->gravity = TRUE;
14033 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14034 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14035 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14036 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14037 player->gravity = FALSE;
14040 // automatically move to the next field with double speed
14041 player->programmed_action = move_direction;
14043 if (player->move_delay_reset_counter == 0)
14045 player->move_delay_reset_counter = 2; // two double speed steps
14047 DOUBLE_PLAYER_SPEED(player);
14050 PlayLevelSoundAction(x, y, ACTION_PASSING);
14052 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14056 if (mode != DF_SNAP)
14058 GfxElement[x][y] = GFX_ELEMENT(element);
14059 player->is_digging = TRUE;
14062 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14064 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14065 player->index_bit, dig_side);
14067 // if digging triggered player relocation, finish digging tile
14068 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14069 setFieldForSnapping(x, y, element, move_direction);
14071 if (mode == DF_SNAP)
14073 if (level.block_snap_field)
14074 setFieldForSnapping(x, y, element, move_direction);
14076 TestIfElementTouchesCustomElement(x, y); // for empty space
14078 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14079 player->index_bit, dig_side);
14082 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14086 if (is_player && mode != DF_SNAP)
14088 GfxElement[x][y] = element;
14089 player->is_collecting = TRUE;
14092 if (element == EL_SPEED_PILL)
14094 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14096 else if (element == EL_EXTRA_TIME && level.time > 0)
14098 TimeLeft += level.extra_time;
14100 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14102 DisplayGameControlValues();
14104 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14106 player->shield_normal_time_left += level.shield_normal_time;
14107 if (element == EL_SHIELD_DEADLY)
14108 player->shield_deadly_time_left += level.shield_deadly_time;
14110 else if (element == EL_DYNAMITE ||
14111 element == EL_EM_DYNAMITE ||
14112 element == EL_SP_DISK_RED)
14114 if (player->inventory_size < MAX_INVENTORY_SIZE)
14115 player->inventory_element[player->inventory_size++] = element;
14117 DrawGameDoorValues();
14119 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14121 player->dynabomb_count++;
14122 player->dynabombs_left++;
14124 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14126 player->dynabomb_size++;
14128 else if (element == EL_DYNABOMB_INCREASE_POWER)
14130 player->dynabomb_xl = TRUE;
14132 else if (IS_KEY(element))
14134 player->key[KEY_NR(element)] = TRUE;
14136 DrawGameDoorValues();
14138 else if (element == EL_DC_KEY_WHITE)
14140 player->num_white_keys++;
14142 // display white keys?
14143 // DrawGameDoorValues();
14145 else if (IS_ENVELOPE(element))
14147 player->show_envelope = element;
14149 else if (element == EL_EMC_LENSES)
14151 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14153 RedrawAllInvisibleElementsForLenses();
14155 else if (element == EL_EMC_MAGNIFIER)
14157 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14159 RedrawAllInvisibleElementsForMagnifier();
14161 else if (IS_DROPPABLE(element) ||
14162 IS_THROWABLE(element)) // can be collected and dropped
14166 if (collect_count == 0)
14167 player->inventory_infinite_element = element;
14169 for (i = 0; i < collect_count; i++)
14170 if (player->inventory_size < MAX_INVENTORY_SIZE)
14171 player->inventory_element[player->inventory_size++] = element;
14173 DrawGameDoorValues();
14175 else if (collect_count > 0)
14177 game.gems_still_needed -= collect_count;
14178 if (game.gems_still_needed < 0)
14179 game.gems_still_needed = 0;
14181 game.snapshot.collected_item = TRUE;
14183 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14185 DisplayGameControlValues();
14188 RaiseScoreElement(element);
14189 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14193 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14194 player->index_bit, dig_side);
14196 // if collecting triggered player relocation, finish collecting tile
14197 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14198 setFieldForSnapping(x, y, element, move_direction);
14201 if (mode == DF_SNAP)
14203 if (level.block_snap_field)
14204 setFieldForSnapping(x, y, element, move_direction);
14206 TestIfElementTouchesCustomElement(x, y); // for empty space
14208 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14209 player->index_bit, dig_side);
14212 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14214 if (mode == DF_SNAP && element != EL_BD_ROCK)
14215 return MP_NO_ACTION;
14217 if (CAN_FALL(element) && dy)
14218 return MP_NO_ACTION;
14220 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14221 !(element == EL_SPRING && level.use_spring_bug))
14222 return MP_NO_ACTION;
14224 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14225 ((move_direction & MV_VERTICAL &&
14226 ((element_info[element].move_pattern & MV_LEFT &&
14227 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14228 (element_info[element].move_pattern & MV_RIGHT &&
14229 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14230 (move_direction & MV_HORIZONTAL &&
14231 ((element_info[element].move_pattern & MV_UP &&
14232 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14233 (element_info[element].move_pattern & MV_DOWN &&
14234 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14235 return MP_NO_ACTION;
14237 // do not push elements already moving away faster than player
14238 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14239 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14240 return MP_NO_ACTION;
14242 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14244 if (player->push_delay_value == -1 || !player_was_pushing)
14245 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14247 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14249 if (player->push_delay_value == -1)
14250 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14252 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14254 if (!player->is_pushing)
14255 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14258 player->is_pushing = TRUE;
14259 player->is_active = TRUE;
14261 if (!(IN_LEV_FIELD(nextx, nexty) &&
14262 (IS_FREE(nextx, nexty) ||
14263 (IS_SB_ELEMENT(element) &&
14264 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14265 (IS_CUSTOM_ELEMENT(element) &&
14266 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14267 return MP_NO_ACTION;
14269 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14270 return MP_NO_ACTION;
14272 if (player->push_delay == -1) // new pushing; restart delay
14273 player->push_delay = 0;
14275 if (player->push_delay < player->push_delay_value &&
14276 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14277 element != EL_SPRING && element != EL_BALLOON)
14279 // make sure that there is no move delay before next try to push
14280 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14281 player->move_delay = 0;
14283 return MP_NO_ACTION;
14286 if (IS_CUSTOM_ELEMENT(element) &&
14287 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14289 if (!DigFieldByCE(nextx, nexty, element))
14290 return MP_NO_ACTION;
14293 if (IS_SB_ELEMENT(element))
14295 boolean sokoban_task_solved = FALSE;
14297 if (element == EL_SOKOBAN_FIELD_FULL)
14299 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14301 IncrementSokobanFieldsNeeded();
14302 IncrementSokobanObjectsNeeded();
14305 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14307 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14309 DecrementSokobanFieldsNeeded();
14310 DecrementSokobanObjectsNeeded();
14312 // sokoban object was pushed from empty field to sokoban field
14313 if (Back[x][y] == EL_EMPTY)
14314 sokoban_task_solved = TRUE;
14317 Tile[x][y] = EL_SOKOBAN_OBJECT;
14319 if (Back[x][y] == Back[nextx][nexty])
14320 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14321 else if (Back[x][y] != 0)
14322 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14325 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14328 if (sokoban_task_solved &&
14329 game.sokoban_fields_still_needed == 0 &&
14330 game.sokoban_objects_still_needed == 0 &&
14331 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14333 game.players_still_needed = 0;
14337 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14341 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14343 InitMovingField(x, y, move_direction);
14344 GfxAction[x][y] = ACTION_PUSHING;
14346 if (mode == DF_SNAP)
14347 ContinueMoving(x, y);
14349 MovPos[x][y] = (dx != 0 ? dx : dy);
14351 Pushed[x][y] = TRUE;
14352 Pushed[nextx][nexty] = TRUE;
14354 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14355 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14357 player->push_delay_value = -1; // get new value later
14359 // check for element change _after_ element has been pushed
14360 if (game.use_change_when_pushing_bug)
14362 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14363 player->index_bit, dig_side);
14364 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14365 player->index_bit, dig_side);
14368 else if (IS_SWITCHABLE(element))
14370 if (PLAYER_SWITCHING(player, x, y))
14372 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14373 player->index_bit, dig_side);
14378 player->is_switching = TRUE;
14379 player->switch_x = x;
14380 player->switch_y = y;
14382 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14384 if (element == EL_ROBOT_WHEEL)
14386 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14388 game.robot_wheel_x = x;
14389 game.robot_wheel_y = y;
14390 game.robot_wheel_active = TRUE;
14392 TEST_DrawLevelField(x, y);
14394 else if (element == EL_SP_TERMINAL)
14398 SCAN_PLAYFIELD(xx, yy)
14400 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14404 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14406 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14408 ResetGfxAnimation(xx, yy);
14409 TEST_DrawLevelField(xx, yy);
14413 else if (IS_BELT_SWITCH(element))
14415 ToggleBeltSwitch(x, y);
14417 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14418 element == EL_SWITCHGATE_SWITCH_DOWN ||
14419 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14420 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14422 ToggleSwitchgateSwitch(x, y);
14424 else if (element == EL_LIGHT_SWITCH ||
14425 element == EL_LIGHT_SWITCH_ACTIVE)
14427 ToggleLightSwitch(x, y);
14429 else if (element == EL_TIMEGATE_SWITCH ||
14430 element == EL_DC_TIMEGATE_SWITCH)
14432 ActivateTimegateSwitch(x, y);
14434 else if (element == EL_BALLOON_SWITCH_LEFT ||
14435 element == EL_BALLOON_SWITCH_RIGHT ||
14436 element == EL_BALLOON_SWITCH_UP ||
14437 element == EL_BALLOON_SWITCH_DOWN ||
14438 element == EL_BALLOON_SWITCH_NONE ||
14439 element == EL_BALLOON_SWITCH_ANY)
14441 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14442 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14443 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14444 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14445 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14448 else if (element == EL_LAMP)
14450 Tile[x][y] = EL_LAMP_ACTIVE;
14451 game.lights_still_needed--;
14453 ResetGfxAnimation(x, y);
14454 TEST_DrawLevelField(x, y);
14456 else if (element == EL_TIME_ORB_FULL)
14458 Tile[x][y] = EL_TIME_ORB_EMPTY;
14460 if (level.time > 0 || level.use_time_orb_bug)
14462 TimeLeft += level.time_orb_time;
14463 game.no_time_limit = FALSE;
14465 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14467 DisplayGameControlValues();
14470 ResetGfxAnimation(x, y);
14471 TEST_DrawLevelField(x, y);
14473 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14474 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14478 game.ball_active = !game.ball_active;
14480 SCAN_PLAYFIELD(xx, yy)
14482 int e = Tile[xx][yy];
14484 if (game.ball_active)
14486 if (e == EL_EMC_MAGIC_BALL)
14487 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14488 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14489 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14493 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14494 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14495 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14496 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14501 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14502 player->index_bit, dig_side);
14504 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14505 player->index_bit, dig_side);
14507 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14508 player->index_bit, dig_side);
14514 if (!PLAYER_SWITCHING(player, x, y))
14516 player->is_switching = TRUE;
14517 player->switch_x = x;
14518 player->switch_y = y;
14520 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14521 player->index_bit, dig_side);
14522 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14523 player->index_bit, dig_side);
14525 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14526 player->index_bit, dig_side);
14527 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14528 player->index_bit, dig_side);
14531 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14532 player->index_bit, dig_side);
14533 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14534 player->index_bit, dig_side);
14536 return MP_NO_ACTION;
14539 player->push_delay = -1;
14541 if (is_player) // function can also be called by EL_PENGUIN
14543 if (Tile[x][y] != element) // really digged/collected something
14545 player->is_collecting = !player->is_digging;
14546 player->is_active = TRUE;
14553 static boolean DigFieldByCE(int x, int y, int digging_element)
14555 int element = Tile[x][y];
14557 if (!IS_FREE(x, y))
14559 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14560 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14563 // no element can dig solid indestructible elements
14564 if (IS_INDESTRUCTIBLE(element) &&
14565 !IS_DIGGABLE(element) &&
14566 !IS_COLLECTIBLE(element))
14569 if (AmoebaNr[x][y] &&
14570 (element == EL_AMOEBA_FULL ||
14571 element == EL_BD_AMOEBA ||
14572 element == EL_AMOEBA_GROWING))
14574 AmoebaCnt[AmoebaNr[x][y]]--;
14575 AmoebaCnt2[AmoebaNr[x][y]]--;
14578 if (IS_MOVING(x, y))
14579 RemoveMovingField(x, y);
14583 TEST_DrawLevelField(x, y);
14586 // if digged element was about to explode, prevent the explosion
14587 ExplodeField[x][y] = EX_TYPE_NONE;
14589 PlayLevelSoundAction(x, y, action);
14592 Store[x][y] = EL_EMPTY;
14594 // this makes it possible to leave the removed element again
14595 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14596 Store[x][y] = element;
14601 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14603 int jx = player->jx, jy = player->jy;
14604 int x = jx + dx, y = jy + dy;
14605 int snap_direction = (dx == -1 ? MV_LEFT :
14606 dx == +1 ? MV_RIGHT :
14608 dy == +1 ? MV_DOWN : MV_NONE);
14609 boolean can_continue_snapping = (level.continuous_snapping &&
14610 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14612 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14615 if (!player->active || !IN_LEV_FIELD(x, y))
14623 if (player->MovPos == 0)
14624 player->is_pushing = FALSE;
14626 player->is_snapping = FALSE;
14628 if (player->MovPos == 0)
14630 player->is_moving = FALSE;
14631 player->is_digging = FALSE;
14632 player->is_collecting = FALSE;
14638 // prevent snapping with already pressed snap key when not allowed
14639 if (player->is_snapping && !can_continue_snapping)
14642 player->MovDir = snap_direction;
14644 if (player->MovPos == 0)
14646 player->is_moving = FALSE;
14647 player->is_digging = FALSE;
14648 player->is_collecting = FALSE;
14651 player->is_dropping = FALSE;
14652 player->is_dropping_pressed = FALSE;
14653 player->drop_pressed_delay = 0;
14655 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14658 player->is_snapping = TRUE;
14659 player->is_active = TRUE;
14661 if (player->MovPos == 0)
14663 player->is_moving = FALSE;
14664 player->is_digging = FALSE;
14665 player->is_collecting = FALSE;
14668 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14669 TEST_DrawLevelField(player->last_jx, player->last_jy);
14671 TEST_DrawLevelField(x, y);
14676 static boolean DropElement(struct PlayerInfo *player)
14678 int old_element, new_element;
14679 int dropx = player->jx, dropy = player->jy;
14680 int drop_direction = player->MovDir;
14681 int drop_side = drop_direction;
14682 int drop_element = get_next_dropped_element(player);
14684 /* do not drop an element on top of another element; when holding drop key
14685 pressed without moving, dropped element must move away before the next
14686 element can be dropped (this is especially important if the next element
14687 is dynamite, which can be placed on background for historical reasons) */
14688 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14691 if (IS_THROWABLE(drop_element))
14693 dropx += GET_DX_FROM_DIR(drop_direction);
14694 dropy += GET_DY_FROM_DIR(drop_direction);
14696 if (!IN_LEV_FIELD(dropx, dropy))
14700 old_element = Tile[dropx][dropy]; // old element at dropping position
14701 new_element = drop_element; // default: no change when dropping
14703 // check if player is active, not moving and ready to drop
14704 if (!player->active || player->MovPos || player->drop_delay > 0)
14707 // check if player has anything that can be dropped
14708 if (new_element == EL_UNDEFINED)
14711 // only set if player has anything that can be dropped
14712 player->is_dropping_pressed = TRUE;
14714 // check if drop key was pressed long enough for EM style dynamite
14715 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14718 // check if anything can be dropped at the current position
14719 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14722 // collected custom elements can only be dropped on empty fields
14723 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14726 if (old_element != EL_EMPTY)
14727 Back[dropx][dropy] = old_element; // store old element on this field
14729 ResetGfxAnimation(dropx, dropy);
14730 ResetRandomAnimationValue(dropx, dropy);
14732 if (player->inventory_size > 0 ||
14733 player->inventory_infinite_element != EL_UNDEFINED)
14735 if (player->inventory_size > 0)
14737 player->inventory_size--;
14739 DrawGameDoorValues();
14741 if (new_element == EL_DYNAMITE)
14742 new_element = EL_DYNAMITE_ACTIVE;
14743 else if (new_element == EL_EM_DYNAMITE)
14744 new_element = EL_EM_DYNAMITE_ACTIVE;
14745 else if (new_element == EL_SP_DISK_RED)
14746 new_element = EL_SP_DISK_RED_ACTIVE;
14749 Tile[dropx][dropy] = new_element;
14751 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14752 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14753 el2img(Tile[dropx][dropy]), 0);
14755 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14757 // needed if previous element just changed to "empty" in the last frame
14758 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14760 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14761 player->index_bit, drop_side);
14762 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14764 player->index_bit, drop_side);
14766 TestIfElementTouchesCustomElement(dropx, dropy);
14768 else // player is dropping a dyna bomb
14770 player->dynabombs_left--;
14772 Tile[dropx][dropy] = new_element;
14774 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14775 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14776 el2img(Tile[dropx][dropy]), 0);
14778 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14781 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14782 InitField_WithBug1(dropx, dropy, FALSE);
14784 new_element = Tile[dropx][dropy]; // element might have changed
14786 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14787 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14789 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14790 MovDir[dropx][dropy] = drop_direction;
14792 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14794 // do not cause impact style collision by dropping elements that can fall
14795 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14798 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14799 player->is_dropping = TRUE;
14801 player->drop_pressed_delay = 0;
14802 player->is_dropping_pressed = FALSE;
14804 player->drop_x = dropx;
14805 player->drop_y = dropy;
14810 // ----------------------------------------------------------------------------
14811 // game sound playing functions
14812 // ----------------------------------------------------------------------------
14814 static int *loop_sound_frame = NULL;
14815 static int *loop_sound_volume = NULL;
14817 void InitPlayLevelSound(void)
14819 int num_sounds = getSoundListSize();
14821 checked_free(loop_sound_frame);
14822 checked_free(loop_sound_volume);
14824 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14825 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14828 static void PlayLevelSound(int x, int y, int nr)
14830 int sx = SCREENX(x), sy = SCREENY(y);
14831 int volume, stereo_position;
14832 int max_distance = 8;
14833 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14835 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14836 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14839 if (!IN_LEV_FIELD(x, y) ||
14840 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14841 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14844 volume = SOUND_MAX_VOLUME;
14846 if (!IN_SCR_FIELD(sx, sy))
14848 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14849 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14851 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14854 stereo_position = (SOUND_MAX_LEFT +
14855 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14856 (SCR_FIELDX + 2 * max_distance));
14858 if (IS_LOOP_SOUND(nr))
14860 /* This assures that quieter loop sounds do not overwrite louder ones,
14861 while restarting sound volume comparison with each new game frame. */
14863 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14866 loop_sound_volume[nr] = volume;
14867 loop_sound_frame[nr] = FrameCounter;
14870 PlaySoundExt(nr, volume, stereo_position, type);
14873 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14875 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14876 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14877 y < LEVELY(BY1) ? LEVELY(BY1) :
14878 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14882 static void PlayLevelSoundAction(int x, int y, int action)
14884 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14887 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14889 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14891 if (sound_effect != SND_UNDEFINED)
14892 PlayLevelSound(x, y, sound_effect);
14895 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14898 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14900 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14901 PlayLevelSound(x, y, sound_effect);
14904 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14906 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14908 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14909 PlayLevelSound(x, y, sound_effect);
14912 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14914 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14916 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14917 StopSound(sound_effect);
14920 static int getLevelMusicNr(void)
14922 if (levelset.music[level_nr] != MUS_UNDEFINED)
14923 return levelset.music[level_nr]; // from config file
14925 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14928 static void FadeLevelSounds(void)
14933 static void FadeLevelMusic(void)
14935 int music_nr = getLevelMusicNr();
14936 char *curr_music = getCurrentlyPlayingMusicFilename();
14937 char *next_music = getMusicInfoEntryFilename(music_nr);
14939 if (!strEqual(curr_music, next_music))
14943 void FadeLevelSoundsAndMusic(void)
14949 static void PlayLevelMusic(void)
14951 int music_nr = getLevelMusicNr();
14952 char *curr_music = getCurrentlyPlayingMusicFilename();
14953 char *next_music = getMusicInfoEntryFilename(music_nr);
14955 if (!strEqual(curr_music, next_music))
14956 PlayMusicLoop(music_nr);
14959 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14961 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14963 int x = xx - offset;
14964 int y = yy - offset;
14969 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14973 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14977 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14981 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14985 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14989 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14993 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14996 case SOUND_android_clone:
14997 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15000 case SOUND_android_move:
15001 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15005 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15009 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15013 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15016 case SOUND_eater_eat:
15017 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15021 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15024 case SOUND_collect:
15025 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15028 case SOUND_diamond:
15029 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15033 // !!! CHECK THIS !!!
15035 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15037 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15041 case SOUND_wonderfall:
15042 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15046 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15050 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15054 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15058 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15062 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15066 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15070 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15074 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15077 case SOUND_exit_open:
15078 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15081 case SOUND_exit_leave:
15082 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15085 case SOUND_dynamite:
15086 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15090 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15094 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15098 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15102 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15106 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15110 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15114 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15119 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15121 int element = map_element_SP_to_RND(element_sp);
15122 int action = map_action_SP_to_RND(action_sp);
15123 int offset = (setup.sp_show_border_elements ? 0 : 1);
15124 int x = xx - offset;
15125 int y = yy - offset;
15127 PlayLevelSoundElementAction(x, y, element, action);
15130 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15132 int element = map_element_MM_to_RND(element_mm);
15133 int action = map_action_MM_to_RND(action_mm);
15135 int x = xx - offset;
15136 int y = yy - offset;
15138 if (!IS_MM_ELEMENT(element))
15139 element = EL_MM_DEFAULT;
15141 PlayLevelSoundElementAction(x, y, element, action);
15144 void PlaySound_MM(int sound_mm)
15146 int sound = map_sound_MM_to_RND(sound_mm);
15148 if (sound == SND_UNDEFINED)
15154 void PlaySoundLoop_MM(int sound_mm)
15156 int sound = map_sound_MM_to_RND(sound_mm);
15158 if (sound == SND_UNDEFINED)
15161 PlaySoundLoop(sound);
15164 void StopSound_MM(int sound_mm)
15166 int sound = map_sound_MM_to_RND(sound_mm);
15168 if (sound == SND_UNDEFINED)
15174 void RaiseScore(int value)
15176 game.score += value;
15178 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15180 DisplayGameControlValues();
15183 void RaiseScoreElement(int element)
15188 case EL_BD_DIAMOND:
15189 case EL_EMERALD_YELLOW:
15190 case EL_EMERALD_RED:
15191 case EL_EMERALD_PURPLE:
15192 case EL_SP_INFOTRON:
15193 RaiseScore(level.score[SC_EMERALD]);
15196 RaiseScore(level.score[SC_DIAMOND]);
15199 RaiseScore(level.score[SC_CRYSTAL]);
15202 RaiseScore(level.score[SC_PEARL]);
15205 case EL_BD_BUTTERFLY:
15206 case EL_SP_ELECTRON:
15207 RaiseScore(level.score[SC_BUG]);
15210 case EL_BD_FIREFLY:
15211 case EL_SP_SNIKSNAK:
15212 RaiseScore(level.score[SC_SPACESHIP]);
15215 case EL_DARK_YAMYAM:
15216 RaiseScore(level.score[SC_YAMYAM]);
15219 RaiseScore(level.score[SC_ROBOT]);
15222 RaiseScore(level.score[SC_PACMAN]);
15225 RaiseScore(level.score[SC_NUT]);
15228 case EL_EM_DYNAMITE:
15229 case EL_SP_DISK_RED:
15230 case EL_DYNABOMB_INCREASE_NUMBER:
15231 case EL_DYNABOMB_INCREASE_SIZE:
15232 case EL_DYNABOMB_INCREASE_POWER:
15233 RaiseScore(level.score[SC_DYNAMITE]);
15235 case EL_SHIELD_NORMAL:
15236 case EL_SHIELD_DEADLY:
15237 RaiseScore(level.score[SC_SHIELD]);
15239 case EL_EXTRA_TIME:
15240 RaiseScore(level.extra_time_score);
15254 case EL_DC_KEY_WHITE:
15255 RaiseScore(level.score[SC_KEY]);
15258 RaiseScore(element_info[element].collect_score);
15263 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15265 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15267 // closing door required in case of envelope style request dialogs
15270 // prevent short reactivation of overlay buttons while closing door
15271 SetOverlayActive(FALSE);
15273 CloseDoor(DOOR_CLOSE_1);
15276 if (network.enabled)
15277 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15281 FadeSkipNextFadeIn();
15283 SetGameStatus(GAME_MODE_MAIN);
15288 else // continue playing the game
15290 if (tape.playing && tape.deactivate_display)
15291 TapeDeactivateDisplayOff(TRUE);
15293 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15295 if (tape.playing && tape.deactivate_display)
15296 TapeDeactivateDisplayOn();
15300 void RequestQuitGame(boolean ask_if_really_quit)
15302 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15303 boolean skip_request = game.all_players_gone || quick_quit;
15305 RequestQuitGameExt(skip_request, quick_quit,
15306 "Do you really want to quit the game?");
15309 void RequestRestartGame(char *message)
15311 game.restart_game_message = NULL;
15313 boolean has_started_game = hasStartedNetworkGame();
15314 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15316 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15318 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15322 // needed in case of envelope request to close game panel
15323 CloseDoor(DOOR_CLOSE_1);
15325 SetGameStatus(GAME_MODE_MAIN);
15331 void CheckGameOver(void)
15333 static boolean last_game_over = FALSE;
15334 static int game_over_delay = 0;
15335 int game_over_delay_value = 50;
15336 boolean game_over = checkGameFailed();
15338 // do not handle game over if request dialog is already active
15339 if (game.request_active)
15342 // do not ask to play again if game was never actually played
15343 if (!game.GamePlayed)
15348 last_game_over = FALSE;
15349 game_over_delay = game_over_delay_value;
15354 if (game_over_delay > 0)
15361 if (last_game_over != game_over)
15362 game.restart_game_message = (hasStartedNetworkGame() ?
15363 "Game over! Play it again?" :
15366 last_game_over = game_over;
15369 boolean checkGameSolved(void)
15371 // set for all game engines if level was solved
15372 return game.LevelSolved_GameEnd;
15375 boolean checkGameFailed(void)
15377 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15378 return (game_em.game_over && !game_em.level_solved);
15379 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15380 return (game_sp.game_over && !game_sp.level_solved);
15381 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15382 return (game_mm.game_over && !game_mm.level_solved);
15383 else // GAME_ENGINE_TYPE_RND
15384 return (game.GameOver && !game.LevelSolved);
15387 boolean checkGameEnded(void)
15389 return (checkGameSolved() || checkGameFailed());
15393 // ----------------------------------------------------------------------------
15394 // random generator functions
15395 // ----------------------------------------------------------------------------
15397 unsigned int InitEngineRandom_RND(int seed)
15399 game.num_random_calls = 0;
15401 return InitEngineRandom(seed);
15404 unsigned int RND(int max)
15408 game.num_random_calls++;
15410 return GetEngineRandom(max);
15417 // ----------------------------------------------------------------------------
15418 // game engine snapshot handling functions
15419 // ----------------------------------------------------------------------------
15421 struct EngineSnapshotInfo
15423 // runtime values for custom element collect score
15424 int collect_score[NUM_CUSTOM_ELEMENTS];
15426 // runtime values for group element choice position
15427 int choice_pos[NUM_GROUP_ELEMENTS];
15429 // runtime values for belt position animations
15430 int belt_graphic[4][NUM_BELT_PARTS];
15431 int belt_anim_mode[4][NUM_BELT_PARTS];
15434 static struct EngineSnapshotInfo engine_snapshot_rnd;
15435 static char *snapshot_level_identifier = NULL;
15436 static int snapshot_level_nr = -1;
15438 static void SaveEngineSnapshotValues_RND(void)
15440 static int belt_base_active_element[4] =
15442 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15443 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15444 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15445 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15449 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15451 int element = EL_CUSTOM_START + i;
15453 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15456 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15458 int element = EL_GROUP_START + i;
15460 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15463 for (i = 0; i < 4; i++)
15465 for (j = 0; j < NUM_BELT_PARTS; j++)
15467 int element = belt_base_active_element[i] + j;
15468 int graphic = el2img(element);
15469 int anim_mode = graphic_info[graphic].anim_mode;
15471 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15472 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15477 static void LoadEngineSnapshotValues_RND(void)
15479 unsigned int num_random_calls = game.num_random_calls;
15482 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15484 int element = EL_CUSTOM_START + i;
15486 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15489 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15491 int element = EL_GROUP_START + i;
15493 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15496 for (i = 0; i < 4; i++)
15498 for (j = 0; j < NUM_BELT_PARTS; j++)
15500 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15501 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15503 graphic_info[graphic].anim_mode = anim_mode;
15507 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15509 InitRND(tape.random_seed);
15510 for (i = 0; i < num_random_calls; i++)
15514 if (game.num_random_calls != num_random_calls)
15516 Error("number of random calls out of sync");
15517 Error("number of random calls should be %d", num_random_calls);
15518 Error("number of random calls is %d", game.num_random_calls);
15520 Fail("this should not happen -- please debug");
15524 void FreeEngineSnapshotSingle(void)
15526 FreeSnapshotSingle();
15528 setString(&snapshot_level_identifier, NULL);
15529 snapshot_level_nr = -1;
15532 void FreeEngineSnapshotList(void)
15534 FreeSnapshotList();
15537 static ListNode *SaveEngineSnapshotBuffers(void)
15539 ListNode *buffers = NULL;
15541 // copy some special values to a structure better suited for the snapshot
15543 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15544 SaveEngineSnapshotValues_RND();
15545 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15546 SaveEngineSnapshotValues_EM();
15547 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15548 SaveEngineSnapshotValues_SP(&buffers);
15549 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15550 SaveEngineSnapshotValues_MM(&buffers);
15552 // save values stored in special snapshot structure
15554 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15555 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15556 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15557 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15558 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15559 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15560 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15561 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15563 // save further RND engine values
15565 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15566 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15567 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15569 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15570 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15571 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15572 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15573 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15575 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15576 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15577 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15579 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15581 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15582 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15584 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15585 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15586 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15587 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15588 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15589 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15590 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15591 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15592 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15593 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15594 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15595 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15596 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15597 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15598 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15599 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15600 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15601 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15603 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15604 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15606 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15607 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15608 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15610 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15611 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15613 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15614 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15615 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15616 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15617 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15619 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15620 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15623 ListNode *node = engine_snapshot_list_rnd;
15626 while (node != NULL)
15628 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15633 Debug("game:playing:SaveEngineSnapshotBuffers",
15634 "size of engine snapshot: %d bytes", num_bytes);
15640 void SaveEngineSnapshotSingle(void)
15642 ListNode *buffers = SaveEngineSnapshotBuffers();
15644 // finally save all snapshot buffers to single snapshot
15645 SaveSnapshotSingle(buffers);
15647 // save level identification information
15648 setString(&snapshot_level_identifier, leveldir_current->identifier);
15649 snapshot_level_nr = level_nr;
15652 boolean CheckSaveEngineSnapshotToList(void)
15654 boolean save_snapshot =
15655 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15656 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15657 game.snapshot.changed_action) ||
15658 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15659 game.snapshot.collected_item));
15661 game.snapshot.changed_action = FALSE;
15662 game.snapshot.collected_item = FALSE;
15663 game.snapshot.save_snapshot = save_snapshot;
15665 return save_snapshot;
15668 void SaveEngineSnapshotToList(void)
15670 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15674 ListNode *buffers = SaveEngineSnapshotBuffers();
15676 // finally save all snapshot buffers to snapshot list
15677 SaveSnapshotToList(buffers);
15680 void SaveEngineSnapshotToListInitial(void)
15682 FreeEngineSnapshotList();
15684 SaveEngineSnapshotToList();
15687 static void LoadEngineSnapshotValues(void)
15689 // restore special values from snapshot structure
15691 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15692 LoadEngineSnapshotValues_RND();
15693 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15694 LoadEngineSnapshotValues_EM();
15695 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15696 LoadEngineSnapshotValues_SP();
15697 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15698 LoadEngineSnapshotValues_MM();
15701 void LoadEngineSnapshotSingle(void)
15703 LoadSnapshotSingle();
15705 LoadEngineSnapshotValues();
15708 static void LoadEngineSnapshot_Undo(int steps)
15710 LoadSnapshotFromList_Older(steps);
15712 LoadEngineSnapshotValues();
15715 static void LoadEngineSnapshot_Redo(int steps)
15717 LoadSnapshotFromList_Newer(steps);
15719 LoadEngineSnapshotValues();
15722 boolean CheckEngineSnapshotSingle(void)
15724 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15725 snapshot_level_nr == level_nr);
15728 boolean CheckEngineSnapshotList(void)
15730 return CheckSnapshotList();
15734 // ---------- new game button stuff -------------------------------------------
15741 boolean *setup_value;
15742 boolean allowed_on_tape;
15743 boolean is_touch_button;
15745 } gamebutton_info[NUM_GAME_BUTTONS] =
15748 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15749 GAME_CTRL_ID_STOP, NULL,
15750 TRUE, FALSE, "stop game"
15753 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15754 GAME_CTRL_ID_PAUSE, NULL,
15755 TRUE, FALSE, "pause game"
15758 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15759 GAME_CTRL_ID_PLAY, NULL,
15760 TRUE, FALSE, "play game"
15763 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15764 GAME_CTRL_ID_UNDO, NULL,
15765 TRUE, FALSE, "undo step"
15768 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15769 GAME_CTRL_ID_REDO, NULL,
15770 TRUE, FALSE, "redo step"
15773 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15774 GAME_CTRL_ID_SAVE, NULL,
15775 TRUE, FALSE, "save game"
15778 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15779 GAME_CTRL_ID_PAUSE2, NULL,
15780 TRUE, FALSE, "pause game"
15783 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15784 GAME_CTRL_ID_LOAD, NULL,
15785 TRUE, FALSE, "load game"
15788 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15789 GAME_CTRL_ID_PANEL_STOP, NULL,
15790 FALSE, FALSE, "stop game"
15793 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15794 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15795 FALSE, FALSE, "pause game"
15798 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15799 GAME_CTRL_ID_PANEL_PLAY, NULL,
15800 FALSE, FALSE, "play game"
15803 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15804 GAME_CTRL_ID_TOUCH_STOP, NULL,
15805 FALSE, TRUE, "stop game"
15808 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15809 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15810 FALSE, TRUE, "pause game"
15813 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15814 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15815 TRUE, FALSE, "background music on/off"
15818 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15819 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15820 TRUE, FALSE, "sound loops on/off"
15823 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15824 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15825 TRUE, FALSE, "normal sounds on/off"
15828 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15829 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15830 FALSE, FALSE, "background music on/off"
15833 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15834 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15835 FALSE, FALSE, "sound loops on/off"
15838 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15839 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15840 FALSE, FALSE, "normal sounds on/off"
15844 void CreateGameButtons(void)
15848 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15850 int graphic = gamebutton_info[i].graphic;
15851 struct GraphicInfo *gfx = &graphic_info[graphic];
15852 struct XY *pos = gamebutton_info[i].pos;
15853 struct GadgetInfo *gi;
15856 unsigned int event_mask;
15857 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15858 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15859 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15860 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15861 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15862 int gd_x = gfx->src_x;
15863 int gd_y = gfx->src_y;
15864 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15865 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15866 int gd_xa = gfx->src_x + gfx->active_xoffset;
15867 int gd_ya = gfx->src_y + gfx->active_yoffset;
15868 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15869 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15870 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15871 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15874 if (gfx->bitmap == NULL)
15876 game_gadget[id] = NULL;
15881 if (id == GAME_CTRL_ID_STOP ||
15882 id == GAME_CTRL_ID_PANEL_STOP ||
15883 id == GAME_CTRL_ID_TOUCH_STOP ||
15884 id == GAME_CTRL_ID_PLAY ||
15885 id == GAME_CTRL_ID_PANEL_PLAY ||
15886 id == GAME_CTRL_ID_SAVE ||
15887 id == GAME_CTRL_ID_LOAD)
15889 button_type = GD_TYPE_NORMAL_BUTTON;
15891 event_mask = GD_EVENT_RELEASED;
15893 else if (id == GAME_CTRL_ID_UNDO ||
15894 id == GAME_CTRL_ID_REDO)
15896 button_type = GD_TYPE_NORMAL_BUTTON;
15898 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15902 button_type = GD_TYPE_CHECK_BUTTON;
15903 checked = (gamebutton_info[i].setup_value != NULL ?
15904 *gamebutton_info[i].setup_value : FALSE);
15905 event_mask = GD_EVENT_PRESSED;
15908 gi = CreateGadget(GDI_CUSTOM_ID, id,
15909 GDI_IMAGE_ID, graphic,
15910 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15913 GDI_WIDTH, gfx->width,
15914 GDI_HEIGHT, gfx->height,
15915 GDI_TYPE, button_type,
15916 GDI_STATE, GD_BUTTON_UNPRESSED,
15917 GDI_CHECKED, checked,
15918 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15919 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15920 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15921 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15922 GDI_DIRECT_DRAW, FALSE,
15923 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15924 GDI_EVENT_MASK, event_mask,
15925 GDI_CALLBACK_ACTION, HandleGameButtons,
15929 Fail("cannot create gadget");
15931 game_gadget[id] = gi;
15935 void FreeGameButtons(void)
15939 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15940 FreeGadget(game_gadget[i]);
15943 static void UnmapGameButtonsAtSamePosition(int id)
15947 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15949 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15950 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15951 UnmapGadget(game_gadget[i]);
15954 static void UnmapGameButtonsAtSamePosition_All(void)
15956 if (setup.show_snapshot_buttons)
15958 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15959 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15960 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15964 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15965 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15966 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15968 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15969 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15970 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15974 static void MapGameButtonsAtSamePosition(int id)
15978 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15980 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15981 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15982 MapGadget(game_gadget[i]);
15984 UnmapGameButtonsAtSamePosition_All();
15987 void MapUndoRedoButtons(void)
15989 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15990 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15992 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15993 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15996 void UnmapUndoRedoButtons(void)
15998 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15999 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16001 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16002 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16005 void ModifyPauseButtons(void)
16009 GAME_CTRL_ID_PAUSE,
16010 GAME_CTRL_ID_PAUSE2,
16011 GAME_CTRL_ID_PANEL_PAUSE,
16012 GAME_CTRL_ID_TOUCH_PAUSE,
16017 for (i = 0; ids[i] > -1; i++)
16018 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16021 static void MapGameButtonsExt(boolean on_tape)
16025 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16026 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16027 i != GAME_CTRL_ID_UNDO &&
16028 i != GAME_CTRL_ID_REDO)
16029 MapGadget(game_gadget[i]);
16031 UnmapGameButtonsAtSamePosition_All();
16033 RedrawGameButtons();
16036 static void UnmapGameButtonsExt(boolean on_tape)
16040 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16041 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16042 UnmapGadget(game_gadget[i]);
16045 static void RedrawGameButtonsExt(boolean on_tape)
16049 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16050 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16051 RedrawGadget(game_gadget[i]);
16054 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16059 gi->checked = state;
16062 static void RedrawSoundButtonGadget(int id)
16064 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16065 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16066 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16067 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16068 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16069 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16072 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16073 RedrawGadget(game_gadget[id2]);
16076 void MapGameButtons(void)
16078 MapGameButtonsExt(FALSE);
16081 void UnmapGameButtons(void)
16083 UnmapGameButtonsExt(FALSE);
16086 void RedrawGameButtons(void)
16088 RedrawGameButtonsExt(FALSE);
16091 void MapGameButtonsOnTape(void)
16093 MapGameButtonsExt(TRUE);
16096 void UnmapGameButtonsOnTape(void)
16098 UnmapGameButtonsExt(TRUE);
16101 void RedrawGameButtonsOnTape(void)
16103 RedrawGameButtonsExt(TRUE);
16106 static void GameUndoRedoExt(void)
16108 ClearPlayerAction();
16110 tape.pausing = TRUE;
16113 UpdateAndDisplayGameControlValues();
16115 DrawCompleteVideoDisplay();
16116 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16117 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16118 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16123 static void GameUndo(int steps)
16125 if (!CheckEngineSnapshotList())
16128 LoadEngineSnapshot_Undo(steps);
16133 static void GameRedo(int steps)
16135 if (!CheckEngineSnapshotList())
16138 LoadEngineSnapshot_Redo(steps);
16143 static void HandleGameButtonsExt(int id, int button)
16145 static boolean game_undo_executed = FALSE;
16146 int steps = BUTTON_STEPSIZE(button);
16147 boolean handle_game_buttons =
16148 (game_status == GAME_MODE_PLAYING ||
16149 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16151 if (!handle_game_buttons)
16156 case GAME_CTRL_ID_STOP:
16157 case GAME_CTRL_ID_PANEL_STOP:
16158 case GAME_CTRL_ID_TOUCH_STOP:
16159 if (game_status == GAME_MODE_MAIN)
16165 RequestQuitGame(TRUE);
16169 case GAME_CTRL_ID_PAUSE:
16170 case GAME_CTRL_ID_PAUSE2:
16171 case GAME_CTRL_ID_PANEL_PAUSE:
16172 case GAME_CTRL_ID_TOUCH_PAUSE:
16173 if (network.enabled && game_status == GAME_MODE_PLAYING)
16176 SendToServer_ContinuePlaying();
16178 SendToServer_PausePlaying();
16181 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16183 game_undo_executed = FALSE;
16187 case GAME_CTRL_ID_PLAY:
16188 case GAME_CTRL_ID_PANEL_PLAY:
16189 if (game_status == GAME_MODE_MAIN)
16191 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16193 else if (tape.pausing)
16195 if (network.enabled)
16196 SendToServer_ContinuePlaying();
16198 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16202 case GAME_CTRL_ID_UNDO:
16203 // Important: When using "save snapshot when collecting an item" mode,
16204 // load last (current) snapshot for first "undo" after pressing "pause"
16205 // (else the last-but-one snapshot would be loaded, because the snapshot
16206 // pointer already points to the last snapshot when pressing "pause",
16207 // which is fine for "every step/move" mode, but not for "every collect")
16208 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16209 !game_undo_executed)
16212 game_undo_executed = TRUE;
16217 case GAME_CTRL_ID_REDO:
16221 case GAME_CTRL_ID_SAVE:
16225 case GAME_CTRL_ID_LOAD:
16229 case SOUND_CTRL_ID_MUSIC:
16230 case SOUND_CTRL_ID_PANEL_MUSIC:
16231 if (setup.sound_music)
16233 setup.sound_music = FALSE;
16237 else if (audio.music_available)
16239 setup.sound = setup.sound_music = TRUE;
16241 SetAudioMode(setup.sound);
16243 if (game_status == GAME_MODE_PLAYING)
16247 RedrawSoundButtonGadget(id);
16251 case SOUND_CTRL_ID_LOOPS:
16252 case SOUND_CTRL_ID_PANEL_LOOPS:
16253 if (setup.sound_loops)
16254 setup.sound_loops = FALSE;
16255 else if (audio.loops_available)
16257 setup.sound = setup.sound_loops = TRUE;
16259 SetAudioMode(setup.sound);
16262 RedrawSoundButtonGadget(id);
16266 case SOUND_CTRL_ID_SIMPLE:
16267 case SOUND_CTRL_ID_PANEL_SIMPLE:
16268 if (setup.sound_simple)
16269 setup.sound_simple = FALSE;
16270 else if (audio.sound_available)
16272 setup.sound = setup.sound_simple = TRUE;
16274 SetAudioMode(setup.sound);
16277 RedrawSoundButtonGadget(id);
16286 static void HandleGameButtons(struct GadgetInfo *gi)
16288 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16291 void HandleSoundButtonKeys(Key key)
16293 if (key == setup.shortcut.sound_simple)
16294 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16295 else if (key == setup.shortcut.sound_loops)
16296 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16297 else if (key == setup.shortcut.sound_music)
16298 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);