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) :
2255 UpdatePlayfieldElementCount();
2257 // update game panel control values
2259 // used instead of "level_nr" (for network games)
2260 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2261 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2263 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2264 for (i = 0; i < MAX_NUM_KEYS; i++)
2265 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2266 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2267 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2269 if (game.centered_player_nr == -1)
2271 for (i = 0; i < MAX_PLAYERS; i++)
2273 // only one player in Supaplex game engine
2274 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2277 for (k = 0; k < MAX_NUM_KEYS; k++)
2279 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2281 if (game_em.ply[i]->keys & (1 << k))
2282 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2283 get_key_element_from_nr(k);
2285 else if (stored_player[i].key[k])
2286 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2287 get_key_element_from_nr(k);
2290 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2291 getPlayerInventorySize(i);
2293 if (stored_player[i].num_white_keys > 0)
2294 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2297 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2298 stored_player[i].num_white_keys;
2303 int player_nr = game.centered_player_nr;
2305 for (k = 0; k < MAX_NUM_KEYS; k++)
2307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2309 if (game_em.ply[player_nr]->keys & (1 << k))
2310 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2311 get_key_element_from_nr(k);
2313 else if (stored_player[player_nr].key[k])
2314 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2315 get_key_element_from_nr(k);
2318 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2319 getPlayerInventorySize(player_nr);
2321 if (stored_player[player_nr].num_white_keys > 0)
2322 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2324 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2325 stored_player[player_nr].num_white_keys;
2328 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2330 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2331 get_inventory_element_from_pos(local_player, i);
2332 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2333 get_inventory_element_from_pos(local_player, -i - 1);
2336 game_panel_controls[GAME_PANEL_SCORE].value = score;
2337 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2339 game_panel_controls[GAME_PANEL_TIME].value = time;
2341 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2342 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2343 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2345 if (level.time == 0)
2346 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2348 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2350 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2351 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2353 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2355 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2356 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2358 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2359 local_player->shield_normal_time_left;
2360 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2361 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2363 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2364 local_player->shield_deadly_time_left;
2366 game_panel_controls[GAME_PANEL_EXIT].value =
2367 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2369 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2370 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2371 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2372 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2373 EL_EMC_MAGIC_BALL_SWITCH);
2375 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2376 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2377 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2378 game.light_time_left;
2380 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2381 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2382 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2383 game.timegate_time_left;
2385 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2386 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2388 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2389 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2390 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2391 game.lenses_time_left;
2393 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2394 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2395 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2396 game.magnify_time_left;
2398 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2399 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2400 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2401 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2402 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2403 EL_BALLOON_SWITCH_NONE);
2405 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2406 local_player->dynabomb_count;
2407 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2408 local_player->dynabomb_size;
2409 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2410 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2412 game_panel_controls[GAME_PANEL_PENGUINS].value =
2413 game.friends_still_needed;
2415 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2416 game.sokoban_objects_still_needed;
2417 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2418 game.sokoban_fields_still_needed;
2420 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2421 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2423 for (i = 0; i < NUM_BELTS; i++)
2425 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2426 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2427 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2428 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2429 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2432 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2433 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2434 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2435 game.magic_wall_time_left;
2437 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2438 local_player->gravity;
2440 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2441 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2443 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2444 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2445 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2446 game.panel.element[i].id : EL_UNDEFINED);
2448 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2449 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2450 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2451 element_info[game.panel.element_count[i].id].element_count : 0);
2453 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2454 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2455 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2456 element_info[game.panel.ce_score[i].id].collect_score : 0);
2458 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2459 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2460 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2461 element_info[game.panel.ce_score_element[i].id].collect_score :
2464 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2465 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2466 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2468 // update game panel control frames
2470 for (i = 0; game_panel_controls[i].nr != -1; i++)
2472 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2474 if (gpc->type == TYPE_ELEMENT)
2476 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2478 int last_anim_random_frame = gfx.anim_random_frame;
2479 int element = gpc->value;
2480 int graphic = el2panelimg(element);
2482 if (gpc->value != gpc->last_value)
2485 gpc->gfx_random = INIT_GFX_RANDOM();
2491 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2492 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2493 gpc->gfx_random = INIT_GFX_RANDOM();
2496 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2497 gfx.anim_random_frame = gpc->gfx_random;
2499 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2500 gpc->gfx_frame = element_info[element].collect_score;
2502 gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
2505 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2506 gfx.anim_random_frame = last_anim_random_frame;
2509 else if (gpc->type == TYPE_GRAPHIC)
2511 if (gpc->graphic != IMG_UNDEFINED)
2513 int last_anim_random_frame = gfx.anim_random_frame;
2514 int graphic = gpc->graphic;
2516 if (gpc->value != gpc->last_value)
2519 gpc->gfx_random = INIT_GFX_RANDOM();
2525 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2526 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2527 gpc->gfx_random = INIT_GFX_RANDOM();
2530 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2531 gfx.anim_random_frame = gpc->gfx_random;
2533 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2535 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2536 gfx.anim_random_frame = last_anim_random_frame;
2542 static void DisplayGameControlValues(void)
2544 boolean redraw_panel = FALSE;
2547 for (i = 0; game_panel_controls[i].nr != -1; i++)
2549 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2551 if (PANEL_DEACTIVATED(gpc->pos))
2554 if (gpc->value == gpc->last_value &&
2555 gpc->frame == gpc->last_frame)
2558 redraw_panel = TRUE;
2564 // copy default game door content to main double buffer
2566 // !!! CHECK AGAIN !!!
2567 SetPanelBackground();
2568 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2569 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2571 // redraw game control buttons
2572 RedrawGameButtons();
2574 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2576 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2578 int nr = game_panel_order[i].nr;
2579 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2580 struct TextPosInfo *pos = gpc->pos;
2581 int type = gpc->type;
2582 int value = gpc->value;
2583 int frame = gpc->frame;
2584 int size = pos->size;
2585 int font = pos->font;
2586 boolean draw_masked = pos->draw_masked;
2587 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2589 if (PANEL_DEACTIVATED(pos))
2592 gpc->last_value = value;
2593 gpc->last_frame = frame;
2595 if (type == TYPE_INTEGER)
2597 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2598 nr == GAME_PANEL_TIME)
2600 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2602 if (use_dynamic_size) // use dynamic number of digits
2604 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2605 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2606 int size2 = size1 + 1;
2607 int font1 = pos->font;
2608 int font2 = pos->font_alt;
2610 size = (value < value_change ? size1 : size2);
2611 font = (value < value_change ? font1 : font2);
2615 // correct text size if "digits" is zero or less
2617 size = strlen(int2str(value, size));
2619 // dynamically correct text alignment
2620 pos->width = size * getFontWidth(font);
2622 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2623 int2str(value, size), font, mask_mode);
2625 else if (type == TYPE_ELEMENT)
2627 int element, graphic;
2631 int dst_x = PANEL_XPOS(pos);
2632 int dst_y = PANEL_YPOS(pos);
2634 if (value != EL_UNDEFINED && value != EL_EMPTY)
2637 graphic = el2panelimg(value);
2640 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2641 element, EL_NAME(element), size);
2644 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2647 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2650 width = graphic_info[graphic].width * size / TILESIZE;
2651 height = graphic_info[graphic].height * size / TILESIZE;
2654 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2657 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2661 else if (type == TYPE_GRAPHIC)
2663 int graphic = gpc->graphic;
2664 int graphic_active = gpc->graphic_active;
2668 int dst_x = PANEL_XPOS(pos);
2669 int dst_y = PANEL_YPOS(pos);
2670 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2671 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2673 if (graphic != IMG_UNDEFINED && !skip)
2675 if (pos->style == STYLE_REVERSE)
2676 value = 100 - value;
2678 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2680 if (pos->direction & MV_HORIZONTAL)
2682 width = graphic_info[graphic_active].width * value / 100;
2683 height = graphic_info[graphic_active].height;
2685 if (pos->direction == MV_LEFT)
2687 src_x += graphic_info[graphic_active].width - width;
2688 dst_x += graphic_info[graphic_active].width - width;
2693 width = graphic_info[graphic_active].width;
2694 height = graphic_info[graphic_active].height * value / 100;
2696 if (pos->direction == MV_UP)
2698 src_y += graphic_info[graphic_active].height - height;
2699 dst_y += graphic_info[graphic_active].height - height;
2704 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2707 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2710 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2712 if (pos->direction & MV_HORIZONTAL)
2714 if (pos->direction == MV_RIGHT)
2721 dst_x = PANEL_XPOS(pos);
2724 width = graphic_info[graphic].width - width;
2728 if (pos->direction == MV_DOWN)
2735 dst_y = PANEL_YPOS(pos);
2738 height = graphic_info[graphic].height - height;
2742 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2745 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2749 else if (type == TYPE_STRING)
2751 boolean active = (value != 0);
2752 char *state_normal = "off";
2753 char *state_active = "on";
2754 char *state = (active ? state_active : state_normal);
2755 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2756 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2757 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2758 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2760 if (nr == GAME_PANEL_GRAVITY_STATE)
2762 int font1 = pos->font; // (used for normal state)
2763 int font2 = pos->font_alt; // (used for active state)
2765 font = (active ? font2 : font1);
2774 // don't truncate output if "chars" is zero or less
2777 // dynamically correct text alignment
2778 pos->width = size * getFontWidth(font);
2781 s_cut = getStringCopyN(s, size);
2783 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2784 s_cut, font, mask_mode);
2790 redraw_mask |= REDRAW_DOOR_1;
2793 SetGameStatus(GAME_MODE_PLAYING);
2796 void UpdateAndDisplayGameControlValues(void)
2798 if (tape.deactivate_display)
2801 UpdateGameControlValues();
2802 DisplayGameControlValues();
2806 static void UpdateGameDoorValues(void)
2808 UpdateGameControlValues();
2812 void DrawGameDoorValues(void)
2814 DisplayGameControlValues();
2818 // ============================================================================
2820 // ----------------------------------------------------------------------------
2821 // initialize game engine due to level / tape version number
2822 // ============================================================================
2824 static void InitGameEngine(void)
2826 int i, j, k, l, x, y;
2828 // set game engine from tape file when re-playing, else from level file
2829 game.engine_version = (tape.playing ? tape.engine_version :
2830 level.game_version);
2832 // set single or multi-player game mode (needed for re-playing tapes)
2833 game.team_mode = setup.team_mode;
2837 int num_players = 0;
2839 for (i = 0; i < MAX_PLAYERS; i++)
2840 if (tape.player_participates[i])
2843 // multi-player tapes contain input data for more than one player
2844 game.team_mode = (num_players > 1);
2848 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2849 level.game_version);
2850 Debug("game:init:level", " tape.file_version == %06d",
2852 Debug("game:init:level", " tape.game_version == %06d",
2854 Debug("game:init:level", " tape.engine_version == %06d",
2855 tape.engine_version);
2856 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2857 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2860 // --------------------------------------------------------------------------
2861 // set flags for bugs and changes according to active game engine version
2862 // --------------------------------------------------------------------------
2866 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2868 Bug was introduced in version:
2871 Bug was fixed in version:
2875 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2876 but the property "can fall" was missing, which caused some levels to be
2877 unsolvable. This was fixed in version 4.2.0.0.
2879 Affected levels/tapes:
2880 An example for a tape that was fixed by this bugfix is tape 029 from the
2881 level set "rnd_sam_bateman".
2882 The wrong behaviour will still be used for all levels or tapes that were
2883 created/recorded with it. An example for this is tape 023 from the level
2884 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2887 boolean use_amoeba_dropping_cannot_fall_bug =
2888 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2889 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2891 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2892 tape.game_version < VERSION_IDENT(4,2,0,0)));
2895 Summary of bugfix/change:
2896 Fixed move speed of elements entering or leaving magic wall.
2898 Fixed/changed in version:
2902 Before 2.0.1, move speed of elements entering or leaving magic wall was
2903 twice as fast as it is now.
2904 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2906 Affected levels/tapes:
2907 The first condition is generally needed for all levels/tapes before version
2908 2.0.1, which might use the old behaviour before it was changed; known tapes
2909 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2910 The second condition is an exception from the above case and is needed for
2911 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2912 above, but before it was known that this change would break tapes like the
2913 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2914 although the engine version while recording maybe was before 2.0.1. There
2915 are a lot of tapes that are affected by this exception, like tape 006 from
2916 the level set "rnd_conor_mancone".
2919 boolean use_old_move_stepsize_for_magic_wall =
2920 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2922 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2923 tape.game_version < VERSION_IDENT(4,2,0,0)));
2926 Summary of bugfix/change:
2927 Fixed handling for custom elements that change when pushed by the player.
2929 Fixed/changed in version:
2933 Before 3.1.0, custom elements that "change when pushing" changed directly
2934 after the player started pushing them (until then handled in "DigField()").
2935 Since 3.1.0, these custom elements are not changed until the "pushing"
2936 move of the element is finished (now handled in "ContinueMoving()").
2938 Affected levels/tapes:
2939 The first condition is generally needed for all levels/tapes before version
2940 3.1.0, which might use the old behaviour before it was changed; known tapes
2941 that are affected are some tapes from the level set "Walpurgis Gardens" by
2943 The second condition is an exception from the above case and is needed for
2944 the special case of tapes recorded with game (not engine!) version 3.1.0 or
2945 above (including some development versions of 3.1.0), but before it was
2946 known that this change would break tapes like the above and was fixed in
2947 3.1.1, so that the changed behaviour was active although the engine version
2948 while recording maybe was before 3.1.0. There is at least one tape that is
2949 affected by this exception, which is the tape for the one-level set "Bug
2950 Machine" by Juergen Bonhagen.
2953 game.use_change_when_pushing_bug =
2954 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2956 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
2957 tape.game_version < VERSION_IDENT(3,1,1,0)));
2960 Summary of bugfix/change:
2961 Fixed handling for blocking the field the player leaves when moving.
2963 Fixed/changed in version:
2967 Before 3.1.1, when "block last field when moving" was enabled, the field
2968 the player is leaving when moving was blocked for the time of the move,
2969 and was directly unblocked afterwards. This resulted in the last field
2970 being blocked for exactly one less than the number of frames of one player
2971 move. Additionally, even when blocking was disabled, the last field was
2972 blocked for exactly one frame.
2973 Since 3.1.1, due to changes in player movement handling, the last field
2974 is not blocked at all when blocking is disabled. When blocking is enabled,
2975 the last field is blocked for exactly the number of frames of one player
2976 move. Additionally, if the player is Murphy, the hero of Supaplex, the
2977 last field is blocked for exactly one more than the number of frames of
2980 Affected levels/tapes:
2981 (!!! yet to be determined -- probably many !!!)
2984 game.use_block_last_field_bug =
2985 (game.engine_version < VERSION_IDENT(3,1,1,0));
2987 /* various special flags and settings for native Emerald Mine game engine */
2989 game_em.use_single_button =
2990 (game.engine_version > VERSION_IDENT(4,0,0,2));
2992 game_em.use_snap_key_bug =
2993 (game.engine_version < VERSION_IDENT(4,0,1,0));
2995 game_em.use_random_bug =
2996 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
2998 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3000 game_em.use_old_explosions = use_old_em_engine;
3001 game_em.use_old_android = use_old_em_engine;
3002 game_em.use_old_push_elements = use_old_em_engine;
3003 game_em.use_old_push_into_acid = use_old_em_engine;
3005 game_em.use_wrap_around = !use_old_em_engine;
3007 // --------------------------------------------------------------------------
3009 // set maximal allowed number of custom element changes per game frame
3010 game.max_num_changes_per_frame = 1;
3012 // default scan direction: scan playfield from top/left to bottom/right
3013 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3015 // dynamically adjust element properties according to game engine version
3016 InitElementPropertiesEngine(game.engine_version);
3018 // ---------- initialize special element properties -------------------------
3020 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3021 if (use_amoeba_dropping_cannot_fall_bug)
3022 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3024 // ---------- initialize player's initial move delay ------------------------
3026 // dynamically adjust player properties according to level information
3027 for (i = 0; i < MAX_PLAYERS; i++)
3028 game.initial_move_delay_value[i] =
3029 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3031 // dynamically adjust player properties according to game engine version
3032 for (i = 0; i < MAX_PLAYERS; i++)
3033 game.initial_move_delay[i] =
3034 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3035 game.initial_move_delay_value[i] : 0);
3037 // ---------- initialize player's initial push delay ------------------------
3039 // dynamically adjust player properties according to game engine version
3040 game.initial_push_delay_value =
3041 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3043 // ---------- initialize changing elements ----------------------------------
3045 // initialize changing elements information
3046 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3048 struct ElementInfo *ei = &element_info[i];
3050 // this pointer might have been changed in the level editor
3051 ei->change = &ei->change_page[0];
3053 if (!IS_CUSTOM_ELEMENT(i))
3055 ei->change->target_element = EL_EMPTY_SPACE;
3056 ei->change->delay_fixed = 0;
3057 ei->change->delay_random = 0;
3058 ei->change->delay_frames = 1;
3061 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3063 ei->has_change_event[j] = FALSE;
3065 ei->event_page_nr[j] = 0;
3066 ei->event_page[j] = &ei->change_page[0];
3070 // add changing elements from pre-defined list
3071 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3073 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3074 struct ElementInfo *ei = &element_info[ch_delay->element];
3076 ei->change->target_element = ch_delay->target_element;
3077 ei->change->delay_fixed = ch_delay->change_delay;
3079 ei->change->pre_change_function = ch_delay->pre_change_function;
3080 ei->change->change_function = ch_delay->change_function;
3081 ei->change->post_change_function = ch_delay->post_change_function;
3083 ei->change->can_change = TRUE;
3084 ei->change->can_change_or_has_action = TRUE;
3086 ei->has_change_event[CE_DELAY] = TRUE;
3088 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3089 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3092 // ---------- initialize internal run-time variables ------------------------
3094 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3096 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3098 for (j = 0; j < ei->num_change_pages; j++)
3100 ei->change_page[j].can_change_or_has_action =
3101 (ei->change_page[j].can_change |
3102 ei->change_page[j].has_action);
3106 // add change events from custom element configuration
3107 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3109 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3111 for (j = 0; j < ei->num_change_pages; j++)
3113 if (!ei->change_page[j].can_change_or_has_action)
3116 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3118 // only add event page for the first page found with this event
3119 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3121 ei->has_change_event[k] = TRUE;
3123 ei->event_page_nr[k] = j;
3124 ei->event_page[k] = &ei->change_page[j];
3130 // ---------- initialize reference elements in change conditions ------------
3132 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3134 int element = EL_CUSTOM_START + i;
3135 struct ElementInfo *ei = &element_info[element];
3137 for (j = 0; j < ei->num_change_pages; j++)
3139 int trigger_element = ei->change_page[j].initial_trigger_element;
3141 if (trigger_element >= EL_PREV_CE_8 &&
3142 trigger_element <= EL_NEXT_CE_8)
3143 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3145 ei->change_page[j].trigger_element = trigger_element;
3149 // ---------- initialize run-time trigger player and element ----------------
3151 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3153 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3155 for (j = 0; j < ei->num_change_pages; j++)
3157 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3158 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3159 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3160 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3161 ei->change_page[j].actual_trigger_ce_value = 0;
3162 ei->change_page[j].actual_trigger_ce_score = 0;
3166 // ---------- initialize trigger events -------------------------------------
3168 // initialize trigger events information
3169 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3170 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3171 trigger_events[i][j] = FALSE;
3173 // add trigger events from element change event properties
3174 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3176 struct ElementInfo *ei = &element_info[i];
3178 for (j = 0; j < ei->num_change_pages; j++)
3180 if (!ei->change_page[j].can_change_or_has_action)
3183 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3185 int trigger_element = ei->change_page[j].trigger_element;
3187 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3189 if (ei->change_page[j].has_event[k])
3191 if (IS_GROUP_ELEMENT(trigger_element))
3193 struct ElementGroupInfo *group =
3194 element_info[trigger_element].group;
3196 for (l = 0; l < group->num_elements_resolved; l++)
3197 trigger_events[group->element_resolved[l]][k] = TRUE;
3199 else if (trigger_element == EL_ANY_ELEMENT)
3200 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3201 trigger_events[l][k] = TRUE;
3203 trigger_events[trigger_element][k] = TRUE;
3210 // ---------- initialize push delay -----------------------------------------
3212 // initialize push delay values to default
3213 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3215 if (!IS_CUSTOM_ELEMENT(i))
3217 // set default push delay values (corrected since version 3.0.7-1)
3218 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3220 element_info[i].push_delay_fixed = 2;
3221 element_info[i].push_delay_random = 8;
3225 element_info[i].push_delay_fixed = 8;
3226 element_info[i].push_delay_random = 8;
3231 // set push delay value for certain elements from pre-defined list
3232 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3234 int e = push_delay_list[i].element;
3236 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3237 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3240 // set push delay value for Supaplex elements for newer engine versions
3241 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3243 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3245 if (IS_SP_ELEMENT(i))
3247 // set SP push delay to just enough to push under a falling zonk
3248 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3250 element_info[i].push_delay_fixed = delay;
3251 element_info[i].push_delay_random = 0;
3256 // ---------- initialize move stepsize --------------------------------------
3258 // initialize move stepsize values to default
3259 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3260 if (!IS_CUSTOM_ELEMENT(i))
3261 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3263 // set move stepsize value for certain elements from pre-defined list
3264 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3266 int e = move_stepsize_list[i].element;
3268 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3270 // set move stepsize value for certain elements for older engine versions
3271 if (use_old_move_stepsize_for_magic_wall)
3273 if (e == EL_MAGIC_WALL_FILLING ||
3274 e == EL_MAGIC_WALL_EMPTYING ||
3275 e == EL_BD_MAGIC_WALL_FILLING ||
3276 e == EL_BD_MAGIC_WALL_EMPTYING)
3277 element_info[e].move_stepsize *= 2;
3281 // ---------- initialize collect score --------------------------------------
3283 // initialize collect score values for custom elements from initial value
3284 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3285 if (IS_CUSTOM_ELEMENT(i))
3286 element_info[i].collect_score = element_info[i].collect_score_initial;
3288 // ---------- initialize collect count --------------------------------------
3290 // initialize collect count values for non-custom elements
3291 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3292 if (!IS_CUSTOM_ELEMENT(i))
3293 element_info[i].collect_count_initial = 0;
3295 // add collect count values for all elements from pre-defined list
3296 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3297 element_info[collect_count_list[i].element].collect_count_initial =
3298 collect_count_list[i].count;
3300 // ---------- initialize access direction -----------------------------------
3302 // initialize access direction values to default (access from every side)
3303 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3304 if (!IS_CUSTOM_ELEMENT(i))
3305 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3307 // set access direction value for certain elements from pre-defined list
3308 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3309 element_info[access_direction_list[i].element].access_direction =
3310 access_direction_list[i].direction;
3312 // ---------- initialize explosion content ----------------------------------
3313 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3315 if (IS_CUSTOM_ELEMENT(i))
3318 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3320 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3322 element_info[i].content.e[x][y] =
3323 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3324 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3325 i == EL_PLAYER_3 ? EL_EMERALD :
3326 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3327 i == EL_MOLE ? EL_EMERALD_RED :
3328 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3329 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3330 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3331 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3332 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3333 i == EL_WALL_EMERALD ? EL_EMERALD :
3334 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3335 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3336 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3337 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3338 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3339 i == EL_WALL_PEARL ? EL_PEARL :
3340 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3345 // ---------- initialize recursion detection --------------------------------
3346 recursion_loop_depth = 0;
3347 recursion_loop_detected = FALSE;
3348 recursion_loop_element = EL_UNDEFINED;
3350 // ---------- initialize graphics engine ------------------------------------
3351 game.scroll_delay_value =
3352 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3353 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3354 !setup.forced_scroll_delay ? 0 :
3355 setup.scroll_delay ? setup.scroll_delay_value : 0);
3356 game.scroll_delay_value =
3357 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3359 // ---------- initialize game engine snapshots ------------------------------
3360 for (i = 0; i < MAX_PLAYERS; i++)
3361 game.snapshot.last_action[i] = 0;
3362 game.snapshot.changed_action = FALSE;
3363 game.snapshot.collected_item = FALSE;
3364 game.snapshot.mode =
3365 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3366 SNAPSHOT_MODE_EVERY_STEP :
3367 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3368 SNAPSHOT_MODE_EVERY_MOVE :
3369 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3370 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3371 game.snapshot.save_snapshot = FALSE;
3373 // ---------- initialize level time for Supaplex engine ---------------------
3374 // Supaplex levels with time limit currently unsupported -- should be added
3375 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3378 // ---------- initialize flags for handling game actions --------------------
3380 // set flags for game actions to default values
3381 game.use_key_actions = TRUE;
3382 game.use_mouse_actions = FALSE;
3384 // when using Mirror Magic game engine, handle mouse events only
3385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3387 game.use_key_actions = FALSE;
3388 game.use_mouse_actions = TRUE;
3391 // check for custom elements with mouse click events
3392 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3394 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3396 int element = EL_CUSTOM_START + i;
3398 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3399 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3400 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3401 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3402 game.use_mouse_actions = TRUE;
3407 static int get_num_special_action(int element, int action_first,
3410 int num_special_action = 0;
3413 for (i = action_first; i <= action_last; i++)
3415 boolean found = FALSE;
3417 for (j = 0; j < NUM_DIRECTIONS; j++)
3418 if (el_act_dir2img(element, i, j) !=
3419 el_act_dir2img(element, ACTION_DEFAULT, j))
3423 num_special_action++;
3428 return num_special_action;
3432 // ============================================================================
3434 // ----------------------------------------------------------------------------
3435 // initialize and start new game
3436 // ============================================================================
3438 #if DEBUG_INIT_PLAYER
3439 static void DebugPrintPlayerStatus(char *message)
3446 Debug("game:init:player", "%s:", message);
3448 for (i = 0; i < MAX_PLAYERS; i++)
3450 struct PlayerInfo *player = &stored_player[i];
3452 Debug("game:init:player",
3453 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3457 player->connected_locally,
3458 player->connected_network,
3460 (local_player == player ? " (local player)" : ""));
3467 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3468 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3469 int fade_mask = REDRAW_FIELD;
3471 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3472 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3473 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3474 int initial_move_dir = MV_DOWN;
3477 // required here to update video display before fading (FIX THIS)
3478 DrawMaskedBorder(REDRAW_DOOR_2);
3480 if (!game.restart_level)
3481 CloseDoor(DOOR_CLOSE_1);
3483 SetGameStatus(GAME_MODE_PLAYING);
3485 if (level_editor_test_game)
3486 FadeSkipNextFadeOut();
3488 FadeSetEnterScreen();
3491 fade_mask = REDRAW_ALL;
3493 FadeLevelSoundsAndMusic();
3495 ExpireSoundLoops(TRUE);
3499 if (level_editor_test_game)
3500 FadeSkipNextFadeIn();
3502 // needed if different viewport properties defined for playing
3503 ChangeViewportPropertiesIfNeeded();
3507 DrawCompleteVideoDisplay();
3509 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3512 InitGameControlValues();
3514 // initialize tape actions from game when recording tape
3517 tape.use_key_actions = game.use_key_actions;
3518 tape.use_mouse_actions = game.use_mouse_actions;
3521 // don't play tapes over network
3522 network_playing = (network.enabled && !tape.playing);
3524 for (i = 0; i < MAX_PLAYERS; i++)
3526 struct PlayerInfo *player = &stored_player[i];
3528 player->index_nr = i;
3529 player->index_bit = (1 << i);
3530 player->element_nr = EL_PLAYER_1 + i;
3532 player->present = FALSE;
3533 player->active = FALSE;
3534 player->mapped = FALSE;
3536 player->killed = FALSE;
3537 player->reanimated = FALSE;
3538 player->buried = FALSE;
3541 player->effective_action = 0;
3542 player->programmed_action = 0;
3543 player->snap_action = 0;
3545 player->mouse_action.lx = 0;
3546 player->mouse_action.ly = 0;
3547 player->mouse_action.button = 0;
3548 player->mouse_action.button_hint = 0;
3550 player->effective_mouse_action.lx = 0;
3551 player->effective_mouse_action.ly = 0;
3552 player->effective_mouse_action.button = 0;
3553 player->effective_mouse_action.button_hint = 0;
3555 for (j = 0; j < MAX_NUM_KEYS; j++)
3556 player->key[j] = FALSE;
3558 player->num_white_keys = 0;
3560 player->dynabomb_count = 0;
3561 player->dynabomb_size = 1;
3562 player->dynabombs_left = 0;
3563 player->dynabomb_xl = FALSE;
3565 player->MovDir = initial_move_dir;
3568 player->GfxDir = initial_move_dir;
3569 player->GfxAction = ACTION_DEFAULT;
3571 player->StepFrame = 0;
3573 player->initial_element = player->element_nr;
3574 player->artwork_element =
3575 (level.use_artwork_element[i] ? level.artwork_element[i] :
3576 player->element_nr);
3577 player->use_murphy = FALSE;
3579 player->block_last_field = FALSE; // initialized in InitPlayerField()
3580 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3582 player->gravity = level.initial_player_gravity[i];
3584 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3586 player->actual_frame_counter = 0;
3588 player->step_counter = 0;
3590 player->last_move_dir = initial_move_dir;
3592 player->is_active = FALSE;
3594 player->is_waiting = FALSE;
3595 player->is_moving = FALSE;
3596 player->is_auto_moving = FALSE;
3597 player->is_digging = FALSE;
3598 player->is_snapping = FALSE;
3599 player->is_collecting = FALSE;
3600 player->is_pushing = FALSE;
3601 player->is_switching = FALSE;
3602 player->is_dropping = FALSE;
3603 player->is_dropping_pressed = FALSE;
3605 player->is_bored = FALSE;
3606 player->is_sleeping = FALSE;
3608 player->was_waiting = TRUE;
3609 player->was_moving = FALSE;
3610 player->was_snapping = FALSE;
3611 player->was_dropping = FALSE;
3613 player->force_dropping = FALSE;
3615 player->frame_counter_bored = -1;
3616 player->frame_counter_sleeping = -1;
3618 player->anim_delay_counter = 0;
3619 player->post_delay_counter = 0;
3621 player->dir_waiting = initial_move_dir;
3622 player->action_waiting = ACTION_DEFAULT;
3623 player->last_action_waiting = ACTION_DEFAULT;
3624 player->special_action_bored = ACTION_DEFAULT;
3625 player->special_action_sleeping = ACTION_DEFAULT;
3627 player->switch_x = -1;
3628 player->switch_y = -1;
3630 player->drop_x = -1;
3631 player->drop_y = -1;
3633 player->show_envelope = 0;
3635 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3637 player->push_delay = -1; // initialized when pushing starts
3638 player->push_delay_value = game.initial_push_delay_value;
3640 player->drop_delay = 0;
3641 player->drop_pressed_delay = 0;
3643 player->last_jx = -1;
3644 player->last_jy = -1;
3648 player->shield_normal_time_left = 0;
3649 player->shield_deadly_time_left = 0;
3651 player->inventory_infinite_element = EL_UNDEFINED;
3652 player->inventory_size = 0;
3654 if (level.use_initial_inventory[i])
3656 for (j = 0; j < level.initial_inventory_size[i]; j++)
3658 int element = level.initial_inventory_content[i][j];
3659 int collect_count = element_info[element].collect_count_initial;
3662 if (!IS_CUSTOM_ELEMENT(element))
3665 if (collect_count == 0)
3666 player->inventory_infinite_element = element;
3668 for (k = 0; k < collect_count; k++)
3669 if (player->inventory_size < MAX_INVENTORY_SIZE)
3670 player->inventory_element[player->inventory_size++] = element;
3674 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3675 SnapField(player, 0, 0);
3677 map_player_action[i] = i;
3680 network_player_action_received = FALSE;
3682 // initial null action
3683 if (network_playing)
3684 SendToServer_MovePlayer(MV_NONE);
3689 TimeLeft = level.time;
3692 ScreenMovDir = MV_NONE;
3696 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3698 game.robot_wheel_x = -1;
3699 game.robot_wheel_y = -1;
3704 game.all_players_gone = FALSE;
3706 game.LevelSolved = FALSE;
3707 game.GameOver = FALSE;
3709 game.GamePlayed = !tape.playing;
3711 game.LevelSolved_GameWon = FALSE;
3712 game.LevelSolved_GameEnd = FALSE;
3713 game.LevelSolved_SaveTape = FALSE;
3714 game.LevelSolved_SaveScore = FALSE;
3716 game.LevelSolved_CountingTime = 0;
3717 game.LevelSolved_CountingScore = 0;
3718 game.LevelSolved_CountingHealth = 0;
3720 game.panel.active = TRUE;
3722 game.no_time_limit = (level.time == 0);
3724 game.yamyam_content_nr = 0;
3725 game.robot_wheel_active = FALSE;
3726 game.magic_wall_active = FALSE;
3727 game.magic_wall_time_left = 0;
3728 game.light_time_left = 0;
3729 game.timegate_time_left = 0;
3730 game.switchgate_pos = 0;
3731 game.wind_direction = level.wind_direction_initial;
3734 game.score_final = 0;
3736 game.health = MAX_HEALTH;
3737 game.health_final = MAX_HEALTH;
3739 game.gems_still_needed = level.gems_needed;
3740 game.sokoban_fields_still_needed = 0;
3741 game.sokoban_objects_still_needed = 0;
3742 game.lights_still_needed = 0;
3743 game.players_still_needed = 0;
3744 game.friends_still_needed = 0;
3746 game.lenses_time_left = 0;
3747 game.magnify_time_left = 0;
3749 game.ball_active = level.ball_active_initial;
3750 game.ball_content_nr = 0;
3752 game.explosions_delayed = TRUE;
3754 game.envelope_active = FALSE;
3756 for (i = 0; i < NUM_BELTS; i++)
3758 game.belt_dir[i] = MV_NONE;
3759 game.belt_dir_nr[i] = 3; // not moving, next moving left
3762 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3763 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3765 #if DEBUG_INIT_PLAYER
3766 DebugPrintPlayerStatus("Player status at level initialization");
3769 SCAN_PLAYFIELD(x, y)
3771 Tile[x][y] = Last[x][y] = level.field[x][y];
3772 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3773 ChangeDelay[x][y] = 0;
3774 ChangePage[x][y] = -1;
3775 CustomValue[x][y] = 0; // initialized in InitField()
3776 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3778 WasJustMoving[x][y] = 0;
3779 WasJustFalling[x][y] = 0;
3780 CheckCollision[x][y] = 0;
3781 CheckImpact[x][y] = 0;
3783 Pushed[x][y] = FALSE;
3785 ChangeCount[x][y] = 0;
3786 ChangeEvent[x][y] = -1;
3788 ExplodePhase[x][y] = 0;
3789 ExplodeDelay[x][y] = 0;
3790 ExplodeField[x][y] = EX_TYPE_NONE;
3792 RunnerVisit[x][y] = 0;
3793 PlayerVisit[x][y] = 0;
3796 GfxRandom[x][y] = INIT_GFX_RANDOM();
3797 GfxElement[x][y] = EL_UNDEFINED;
3798 GfxAction[x][y] = ACTION_DEFAULT;
3799 GfxDir[x][y] = MV_NONE;
3800 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3803 SCAN_PLAYFIELD(x, y)
3805 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3807 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3809 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3812 InitField(x, y, TRUE);
3814 ResetGfxAnimation(x, y);
3819 for (i = 0; i < MAX_PLAYERS; i++)
3821 struct PlayerInfo *player = &stored_player[i];
3823 // set number of special actions for bored and sleeping animation
3824 player->num_special_action_bored =
3825 get_num_special_action(player->artwork_element,
3826 ACTION_BORING_1, ACTION_BORING_LAST);
3827 player->num_special_action_sleeping =
3828 get_num_special_action(player->artwork_element,
3829 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3832 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3833 emulate_sb ? EMU_SOKOBAN :
3834 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3836 // initialize type of slippery elements
3837 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3839 if (!IS_CUSTOM_ELEMENT(i))
3841 // default: elements slip down either to the left or right randomly
3842 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3844 // SP style elements prefer to slip down on the left side
3845 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3846 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3848 // BD style elements prefer to slip down on the left side
3849 if (game.emulation == EMU_BOULDERDASH)
3850 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3854 // initialize explosion and ignition delay
3855 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3857 if (!IS_CUSTOM_ELEMENT(i))
3860 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3861 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3862 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3863 int last_phase = (num_phase + 1) * delay;
3864 int half_phase = (num_phase / 2) * delay;
3866 element_info[i].explosion_delay = last_phase - 1;
3867 element_info[i].ignition_delay = half_phase;
3869 if (i == EL_BLACK_ORB)
3870 element_info[i].ignition_delay = 1;
3874 // correct non-moving belts to start moving left
3875 for (i = 0; i < NUM_BELTS; i++)
3876 if (game.belt_dir[i] == MV_NONE)
3877 game.belt_dir_nr[i] = 3; // not moving, next moving left
3879 #if USE_NEW_PLAYER_ASSIGNMENTS
3880 // use preferred player also in local single-player mode
3881 if (!network.enabled && !game.team_mode)
3883 int new_index_nr = setup.network_player_nr;
3885 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3887 for (i = 0; i < MAX_PLAYERS; i++)
3888 stored_player[i].connected_locally = FALSE;
3890 stored_player[new_index_nr].connected_locally = TRUE;
3894 for (i = 0; i < MAX_PLAYERS; i++)
3896 stored_player[i].connected = FALSE;
3898 // in network game mode, the local player might not be the first player
3899 if (stored_player[i].connected_locally)
3900 local_player = &stored_player[i];
3903 if (!network.enabled)
3904 local_player->connected = TRUE;
3908 for (i = 0; i < MAX_PLAYERS; i++)
3909 stored_player[i].connected = tape.player_participates[i];
3911 else if (network.enabled)
3913 // add team mode players connected over the network (needed for correct
3914 // assignment of player figures from level to locally playing players)
3916 for (i = 0; i < MAX_PLAYERS; i++)
3917 if (stored_player[i].connected_network)
3918 stored_player[i].connected = TRUE;
3920 else if (game.team_mode)
3922 // try to guess locally connected team mode players (needed for correct
3923 // assignment of player figures from level to locally playing players)
3925 for (i = 0; i < MAX_PLAYERS; i++)
3926 if (setup.input[i].use_joystick ||
3927 setup.input[i].key.left != KSYM_UNDEFINED)
3928 stored_player[i].connected = TRUE;
3931 #if DEBUG_INIT_PLAYER
3932 DebugPrintPlayerStatus("Player status after level initialization");
3935 #if DEBUG_INIT_PLAYER
3936 Debug("game:init:player", "Reassigning players ...");
3939 // check if any connected player was not found in playfield
3940 for (i = 0; i < MAX_PLAYERS; i++)
3942 struct PlayerInfo *player = &stored_player[i];
3944 if (player->connected && !player->present)
3946 struct PlayerInfo *field_player = NULL;
3948 #if DEBUG_INIT_PLAYER
3949 Debug("game:init:player",
3950 "- looking for field player for player %d ...", i + 1);
3953 // assign first free player found that is present in the playfield
3955 // first try: look for unmapped playfield player that is not connected
3956 for (j = 0; j < MAX_PLAYERS; j++)
3957 if (field_player == NULL &&
3958 stored_player[j].present &&
3959 !stored_player[j].mapped &&
3960 !stored_player[j].connected)
3961 field_player = &stored_player[j];
3963 // second try: look for *any* unmapped playfield player
3964 for (j = 0; j < MAX_PLAYERS; j++)
3965 if (field_player == NULL &&
3966 stored_player[j].present &&
3967 !stored_player[j].mapped)
3968 field_player = &stored_player[j];
3970 if (field_player != NULL)
3972 int jx = field_player->jx, jy = field_player->jy;
3974 #if DEBUG_INIT_PLAYER
3975 Debug("game:init:player", "- found player %d",
3976 field_player->index_nr + 1);
3979 player->present = FALSE;
3980 player->active = FALSE;
3982 field_player->present = TRUE;
3983 field_player->active = TRUE;
3986 player->initial_element = field_player->initial_element;
3987 player->artwork_element = field_player->artwork_element;
3989 player->block_last_field = field_player->block_last_field;
3990 player->block_delay_adjustment = field_player->block_delay_adjustment;
3993 StorePlayer[jx][jy] = field_player->element_nr;
3995 field_player->jx = field_player->last_jx = jx;
3996 field_player->jy = field_player->last_jy = jy;
3998 if (local_player == player)
3999 local_player = field_player;
4001 map_player_action[field_player->index_nr] = i;
4003 field_player->mapped = TRUE;
4005 #if DEBUG_INIT_PLAYER
4006 Debug("game:init:player", "- map_player_action[%d] == %d",
4007 field_player->index_nr + 1, i + 1);
4012 if (player->connected && player->present)
4013 player->mapped = TRUE;
4016 #if DEBUG_INIT_PLAYER
4017 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4022 // check if any connected player was not found in playfield
4023 for (i = 0; i < MAX_PLAYERS; i++)
4025 struct PlayerInfo *player = &stored_player[i];
4027 if (player->connected && !player->present)
4029 for (j = 0; j < MAX_PLAYERS; j++)
4031 struct PlayerInfo *field_player = &stored_player[j];
4032 int jx = field_player->jx, jy = field_player->jy;
4034 // assign first free player found that is present in the playfield
4035 if (field_player->present && !field_player->connected)
4037 player->present = TRUE;
4038 player->active = TRUE;
4040 field_player->present = FALSE;
4041 field_player->active = FALSE;
4043 player->initial_element = field_player->initial_element;
4044 player->artwork_element = field_player->artwork_element;
4046 player->block_last_field = field_player->block_last_field;
4047 player->block_delay_adjustment = field_player->block_delay_adjustment;
4049 StorePlayer[jx][jy] = player->element_nr;
4051 player->jx = player->last_jx = jx;
4052 player->jy = player->last_jy = jy;
4062 Debug("game:init:player", "local_player->present == %d",
4063 local_player->present);
4066 // set focus to local player for network games, else to all players
4067 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4068 game.centered_player_nr_next = game.centered_player_nr;
4069 game.set_centered_player = FALSE;
4070 game.set_centered_player_wrap = FALSE;
4072 if (network_playing && tape.recording)
4074 // store client dependent player focus when recording network games
4075 tape.centered_player_nr_next = game.centered_player_nr_next;
4076 tape.set_centered_player = TRUE;
4081 // when playing a tape, eliminate all players who do not participate
4083 #if USE_NEW_PLAYER_ASSIGNMENTS
4085 if (!game.team_mode)
4087 for (i = 0; i < MAX_PLAYERS; i++)
4089 if (stored_player[i].active &&
4090 !tape.player_participates[map_player_action[i]])
4092 struct PlayerInfo *player = &stored_player[i];
4093 int jx = player->jx, jy = player->jy;
4095 #if DEBUG_INIT_PLAYER
4096 Debug("game:init:player", "Removing player %d at (%d, %d)",
4100 player->active = FALSE;
4101 StorePlayer[jx][jy] = 0;
4102 Tile[jx][jy] = EL_EMPTY;
4109 for (i = 0; i < MAX_PLAYERS; i++)
4111 if (stored_player[i].active &&
4112 !tape.player_participates[i])
4114 struct PlayerInfo *player = &stored_player[i];
4115 int jx = player->jx, jy = player->jy;
4117 player->active = FALSE;
4118 StorePlayer[jx][jy] = 0;
4119 Tile[jx][jy] = EL_EMPTY;
4124 else if (!network.enabled && !game.team_mode) // && !tape.playing
4126 // when in single player mode, eliminate all but the local player
4128 for (i = 0; i < MAX_PLAYERS; i++)
4130 struct PlayerInfo *player = &stored_player[i];
4132 if (player->active && player != local_player)
4134 int jx = player->jx, jy = player->jy;
4136 player->active = FALSE;
4137 player->present = FALSE;
4139 StorePlayer[jx][jy] = 0;
4140 Tile[jx][jy] = EL_EMPTY;
4145 for (i = 0; i < MAX_PLAYERS; i++)
4146 if (stored_player[i].active)
4147 game.players_still_needed++;
4149 if (level.solved_by_one_player)
4150 game.players_still_needed = 1;
4152 // when recording the game, store which players take part in the game
4155 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 for (i = 0; i < MAX_PLAYERS; i++)
4157 if (stored_player[i].connected)
4158 tape.player_participates[i] = TRUE;
4160 for (i = 0; i < MAX_PLAYERS; i++)
4161 if (stored_player[i].active)
4162 tape.player_participates[i] = TRUE;
4166 #if DEBUG_INIT_PLAYER
4167 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4170 if (BorderElement == EL_EMPTY)
4173 SBX_Right = lev_fieldx - SCR_FIELDX;
4175 SBY_Lower = lev_fieldy - SCR_FIELDY;
4180 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4182 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4185 if (full_lev_fieldx <= SCR_FIELDX)
4186 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4187 if (full_lev_fieldy <= SCR_FIELDY)
4188 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4190 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4192 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4195 // if local player not found, look for custom element that might create
4196 // the player (make some assumptions about the right custom element)
4197 if (!local_player->present)
4199 int start_x = 0, start_y = 0;
4200 int found_rating = 0;
4201 int found_element = EL_UNDEFINED;
4202 int player_nr = local_player->index_nr;
4204 SCAN_PLAYFIELD(x, y)
4206 int element = Tile[x][y];
4211 if (level.use_start_element[player_nr] &&
4212 level.start_element[player_nr] == element &&
4219 found_element = element;
4222 if (!IS_CUSTOM_ELEMENT(element))
4225 if (CAN_CHANGE(element))
4227 for (i = 0; i < element_info[element].num_change_pages; i++)
4229 // check for player created from custom element as single target
4230 content = element_info[element].change_page[i].target_element;
4231 is_player = ELEM_IS_PLAYER(content);
4233 if (is_player && (found_rating < 3 ||
4234 (found_rating == 3 && element < found_element)))
4240 found_element = element;
4245 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4247 // check for player created from custom element as explosion content
4248 content = element_info[element].content.e[xx][yy];
4249 is_player = ELEM_IS_PLAYER(content);
4251 if (is_player && (found_rating < 2 ||
4252 (found_rating == 2 && element < found_element)))
4254 start_x = x + xx - 1;
4255 start_y = y + yy - 1;
4258 found_element = element;
4261 if (!CAN_CHANGE(element))
4264 for (i = 0; i < element_info[element].num_change_pages; i++)
4266 // check for player created from custom element as extended target
4268 element_info[element].change_page[i].target_content.e[xx][yy];
4270 is_player = ELEM_IS_PLAYER(content);
4272 if (is_player && (found_rating < 1 ||
4273 (found_rating == 1 && element < found_element)))
4275 start_x = x + xx - 1;
4276 start_y = y + yy - 1;
4279 found_element = element;
4285 scroll_x = SCROLL_POSITION_X(start_x);
4286 scroll_y = SCROLL_POSITION_Y(start_y);
4290 scroll_x = SCROLL_POSITION_X(local_player->jx);
4291 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4294 // !!! FIX THIS (START) !!!
4295 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4297 InitGameEngine_EM();
4299 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4301 InitGameEngine_SP();
4303 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4305 InitGameEngine_MM();
4309 DrawLevel(REDRAW_FIELD);
4312 // after drawing the level, correct some elements
4313 if (game.timegate_time_left == 0)
4314 CloseAllOpenTimegates();
4317 // blit playfield from scroll buffer to normal back buffer for fading in
4318 BlitScreenToBitmap(backbuffer);
4319 // !!! FIX THIS (END) !!!
4321 DrawMaskedBorder(fade_mask);
4326 // full screen redraw is required at this point in the following cases:
4327 // - special editor door undrawn when game was started from level editor
4328 // - drawing area (playfield) was changed and has to be removed completely
4329 redraw_mask = REDRAW_ALL;
4333 if (!game.restart_level)
4335 // copy default game door content to main double buffer
4337 // !!! CHECK AGAIN !!!
4338 SetPanelBackground();
4339 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4340 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4343 SetPanelBackground();
4344 SetDrawBackgroundMask(REDRAW_DOOR_1);
4346 UpdateAndDisplayGameControlValues();
4348 if (!game.restart_level)
4354 CreateGameButtons();
4359 // copy actual game door content to door double buffer for OpenDoor()
4360 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4362 OpenDoor(DOOR_OPEN_ALL);
4364 KeyboardAutoRepeatOffUnlessAutoplay();
4366 #if DEBUG_INIT_PLAYER
4367 DebugPrintPlayerStatus("Player status (final)");
4376 if (!game.restart_level && !tape.playing)
4378 LevelStats_incPlayed(level_nr);
4380 SaveLevelSetup_SeriesInfo();
4383 game.restart_level = FALSE;
4384 game.restart_game_message = NULL;
4385 game.request_active = FALSE;
4387 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4388 InitGameActions_MM();
4390 SaveEngineSnapshotToListInitial();
4392 if (!game.restart_level)
4394 PlaySound(SND_GAME_STARTING);
4396 if (setup.sound_music)
4401 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4402 int actual_player_x, int actual_player_y)
4404 // this is used for non-R'n'D game engines to update certain engine values
4406 // needed to determine if sounds are played within the visible screen area
4407 scroll_x = actual_scroll_x;
4408 scroll_y = actual_scroll_y;
4410 // needed to get player position for "follow finger" playing input method
4411 local_player->jx = actual_player_x;
4412 local_player->jy = actual_player_y;
4415 void InitMovDir(int x, int y)
4417 int i, element = Tile[x][y];
4418 static int xy[4][2] =
4425 static int direction[3][4] =
4427 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4428 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4429 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4438 Tile[x][y] = EL_BUG;
4439 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4442 case EL_SPACESHIP_RIGHT:
4443 case EL_SPACESHIP_UP:
4444 case EL_SPACESHIP_LEFT:
4445 case EL_SPACESHIP_DOWN:
4446 Tile[x][y] = EL_SPACESHIP;
4447 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4450 case EL_BD_BUTTERFLY_RIGHT:
4451 case EL_BD_BUTTERFLY_UP:
4452 case EL_BD_BUTTERFLY_LEFT:
4453 case EL_BD_BUTTERFLY_DOWN:
4454 Tile[x][y] = EL_BD_BUTTERFLY;
4455 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4458 case EL_BD_FIREFLY_RIGHT:
4459 case EL_BD_FIREFLY_UP:
4460 case EL_BD_FIREFLY_LEFT:
4461 case EL_BD_FIREFLY_DOWN:
4462 Tile[x][y] = EL_BD_FIREFLY;
4463 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4466 case EL_PACMAN_RIGHT:
4468 case EL_PACMAN_LEFT:
4469 case EL_PACMAN_DOWN:
4470 Tile[x][y] = EL_PACMAN;
4471 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4474 case EL_YAMYAM_LEFT:
4475 case EL_YAMYAM_RIGHT:
4477 case EL_YAMYAM_DOWN:
4478 Tile[x][y] = EL_YAMYAM;
4479 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4482 case EL_SP_SNIKSNAK:
4483 MovDir[x][y] = MV_UP;
4486 case EL_SP_ELECTRON:
4487 MovDir[x][y] = MV_LEFT;
4494 Tile[x][y] = EL_MOLE;
4495 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4498 case EL_SPRING_LEFT:
4499 case EL_SPRING_RIGHT:
4500 Tile[x][y] = EL_SPRING;
4501 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4505 if (IS_CUSTOM_ELEMENT(element))
4507 struct ElementInfo *ei = &element_info[element];
4508 int move_direction_initial = ei->move_direction_initial;
4509 int move_pattern = ei->move_pattern;
4511 if (move_direction_initial == MV_START_PREVIOUS)
4513 if (MovDir[x][y] != MV_NONE)
4516 move_direction_initial = MV_START_AUTOMATIC;
4519 if (move_direction_initial == MV_START_RANDOM)
4520 MovDir[x][y] = 1 << RND(4);
4521 else if (move_direction_initial & MV_ANY_DIRECTION)
4522 MovDir[x][y] = move_direction_initial;
4523 else if (move_pattern == MV_ALL_DIRECTIONS ||
4524 move_pattern == MV_TURNING_LEFT ||
4525 move_pattern == MV_TURNING_RIGHT ||
4526 move_pattern == MV_TURNING_LEFT_RIGHT ||
4527 move_pattern == MV_TURNING_RIGHT_LEFT ||
4528 move_pattern == MV_TURNING_RANDOM)
4529 MovDir[x][y] = 1 << RND(4);
4530 else if (move_pattern == MV_HORIZONTAL)
4531 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4532 else if (move_pattern == MV_VERTICAL)
4533 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4534 else if (move_pattern & MV_ANY_DIRECTION)
4535 MovDir[x][y] = element_info[element].move_pattern;
4536 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4537 move_pattern == MV_ALONG_RIGHT_SIDE)
4539 // use random direction as default start direction
4540 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4541 MovDir[x][y] = 1 << RND(4);
4543 for (i = 0; i < NUM_DIRECTIONS; i++)
4545 int x1 = x + xy[i][0];
4546 int y1 = y + xy[i][1];
4548 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4550 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4551 MovDir[x][y] = direction[0][i];
4553 MovDir[x][y] = direction[1][i];
4562 MovDir[x][y] = 1 << RND(4);
4564 if (element != EL_BUG &&
4565 element != EL_SPACESHIP &&
4566 element != EL_BD_BUTTERFLY &&
4567 element != EL_BD_FIREFLY)
4570 for (i = 0; i < NUM_DIRECTIONS; i++)
4572 int x1 = x + xy[i][0];
4573 int y1 = y + xy[i][1];
4575 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4577 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4579 MovDir[x][y] = direction[0][i];
4582 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4583 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4585 MovDir[x][y] = direction[1][i];
4594 GfxDir[x][y] = MovDir[x][y];
4597 void InitAmoebaNr(int x, int y)
4600 int group_nr = AmoebaNeighbourNr(x, y);
4604 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4606 if (AmoebaCnt[i] == 0)
4614 AmoebaNr[x][y] = group_nr;
4615 AmoebaCnt[group_nr]++;
4616 AmoebaCnt2[group_nr]++;
4619 static void LevelSolved(void)
4621 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4622 game.players_still_needed > 0)
4625 game.LevelSolved = TRUE;
4626 game.GameOver = TRUE;
4628 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4629 game_em.lev->score :
4630 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4633 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4634 MM_HEALTH(game_mm.laser_overload_value) :
4637 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4638 game.LevelSolved_CountingScore = game.score_final;
4639 game.LevelSolved_CountingHealth = game.health_final;
4644 static int time_count_steps;
4645 static int time, time_final;
4646 static int score, score_final;
4647 static int health, health_final;
4648 static int game_over_delay_1 = 0;
4649 static int game_over_delay_2 = 0;
4650 static int game_over_delay_3 = 0;
4651 int game_over_delay_value_1 = 50;
4652 int game_over_delay_value_2 = 25;
4653 int game_over_delay_value_3 = 50;
4655 if (!game.LevelSolved_GameWon)
4659 // do not start end game actions before the player stops moving (to exit)
4660 if (local_player->active && local_player->MovPos)
4663 game.LevelSolved_GameWon = TRUE;
4664 game.LevelSolved_SaveTape = tape.recording;
4665 game.LevelSolved_SaveScore = !tape.playing;
4669 LevelStats_incSolved(level_nr);
4671 SaveLevelSetup_SeriesInfo();
4674 if (tape.auto_play) // tape might already be stopped here
4675 tape.auto_play_level_solved = TRUE;
4679 game_over_delay_1 = 0;
4680 game_over_delay_2 = 0;
4681 game_over_delay_3 = game_over_delay_value_3;
4683 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4684 score = score_final = game.score_final;
4685 health = health_final = game.health_final;
4687 if (level.score[SC_TIME_BONUS] > 0)
4692 score_final += TimeLeft * level.score[SC_TIME_BONUS];
4694 else if (game.no_time_limit && TimePlayed < 999)
4697 score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
4700 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4702 game_over_delay_1 = game_over_delay_value_1;
4704 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4707 score_final += health * level.score[SC_TIME_BONUS];
4709 game_over_delay_2 = game_over_delay_value_2;
4712 game.score_final = score_final;
4713 game.health_final = health_final;
4716 if (level_editor_test_game)
4719 score = score_final;
4721 game.LevelSolved_CountingTime = time;
4722 game.LevelSolved_CountingScore = score;
4724 game_panel_controls[GAME_PANEL_TIME].value = time;
4725 game_panel_controls[GAME_PANEL_SCORE].value = score;
4727 DisplayGameControlValues();
4730 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4732 // check if last player has left the level
4733 if (game.exit_x >= 0 &&
4736 int x = game.exit_x;
4737 int y = game.exit_y;
4738 int element = Tile[x][y];
4740 // close exit door after last player
4741 if ((game.all_players_gone &&
4742 (element == EL_EXIT_OPEN ||
4743 element == EL_SP_EXIT_OPEN ||
4744 element == EL_STEEL_EXIT_OPEN)) ||
4745 element == EL_EM_EXIT_OPEN ||
4746 element == EL_EM_STEEL_EXIT_OPEN)
4750 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4751 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4752 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4753 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4754 EL_EM_STEEL_EXIT_CLOSING);
4756 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4759 // player disappears
4760 DrawLevelField(x, y);
4763 for (i = 0; i < MAX_PLAYERS; i++)
4765 struct PlayerInfo *player = &stored_player[i];
4767 if (player->present)
4769 RemovePlayer(player);
4771 // player disappears
4772 DrawLevelField(player->jx, player->jy);
4777 PlaySound(SND_GAME_WINNING);
4780 if (game_over_delay_1 > 0)
4782 game_over_delay_1--;
4787 if (time != time_final)
4789 int time_to_go = ABS(time_final - time);
4790 int time_count_dir = (time < time_final ? +1 : -1);
4792 if (time_to_go < time_count_steps)
4793 time_count_steps = 1;
4795 time += time_count_steps * time_count_dir;
4796 score += time_count_steps * level.score[SC_TIME_BONUS];
4798 game.LevelSolved_CountingTime = time;
4799 game.LevelSolved_CountingScore = score;
4801 game_panel_controls[GAME_PANEL_TIME].value = time;
4802 game_panel_controls[GAME_PANEL_SCORE].value = score;
4804 DisplayGameControlValues();
4806 if (time == time_final)
4807 StopSound(SND_GAME_LEVELTIME_BONUS);
4808 else if (setup.sound_loops)
4809 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4811 PlaySound(SND_GAME_LEVELTIME_BONUS);
4816 if (game_over_delay_2 > 0)
4818 game_over_delay_2--;
4823 if (health != health_final)
4825 int health_count_dir = (health < health_final ? +1 : -1);
4827 health += health_count_dir;
4828 score += level.score[SC_TIME_BONUS];
4830 game.LevelSolved_CountingHealth = health;
4831 game.LevelSolved_CountingScore = score;
4833 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4834 game_panel_controls[GAME_PANEL_SCORE].value = score;
4836 DisplayGameControlValues();
4838 if (health == health_final)
4839 StopSound(SND_GAME_LEVELTIME_BONUS);
4840 else if (setup.sound_loops)
4841 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4843 PlaySound(SND_GAME_LEVELTIME_BONUS);
4848 game.panel.active = FALSE;
4850 if (game_over_delay_3 > 0)
4852 game_over_delay_3--;
4862 // used instead of "level_nr" (needed for network games)
4863 int last_level_nr = levelset.level_nr;
4866 game.LevelSolved_GameEnd = TRUE;
4868 if (game.LevelSolved_SaveTape)
4870 // make sure that request dialog to save tape does not open door again
4871 if (!global.use_envelope_request)
4872 CloseDoor(DOOR_CLOSE_1);
4874 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4877 // if no tape is to be saved, close both doors simultaneously
4878 CloseDoor(DOOR_CLOSE_ALL);
4880 if (level_editor_test_game)
4882 SetGameStatus(GAME_MODE_MAIN);
4889 if (!game.LevelSolved_SaveScore)
4891 SetGameStatus(GAME_MODE_MAIN);
4898 if (level_nr == leveldir_current->handicap_level)
4900 leveldir_current->handicap_level++;
4902 SaveLevelSetup_SeriesInfo();
4905 if (setup.increment_levels &&
4906 level_nr < leveldir_current->last_level &&
4909 level_nr++; // advance to next level
4910 TapeErase(); // start with empty tape
4912 if (setup.auto_play_next_level)
4914 LoadLevel(level_nr);
4916 SaveLevelSetup_SeriesInfo();
4920 hi_pos = NewHiScore(last_level_nr);
4922 if (hi_pos >= 0 && !setup.skip_scores_after_game)
4924 SetGameStatus(GAME_MODE_SCORES);
4926 DrawHallOfFame(last_level_nr, hi_pos);
4928 else if (setup.auto_play_next_level && setup.increment_levels &&
4929 last_level_nr < leveldir_current->last_level &&
4932 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
4936 SetGameStatus(GAME_MODE_MAIN);
4942 int NewHiScore(int level_nr)
4946 boolean one_score_entry_per_name = !program.many_scores_per_name;
4948 LoadScore(level_nr);
4950 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
4951 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
4954 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
4956 if (game.score_final > highscore[k].Score)
4958 // player has made it to the hall of fame
4960 if (k < MAX_SCORE_ENTRIES - 1)
4962 int m = MAX_SCORE_ENTRIES - 1;
4964 if (one_score_entry_per_name)
4966 for (l = k; l < MAX_SCORE_ENTRIES; l++)
4967 if (strEqual(setup.player_name, highscore[l].Name))
4970 if (m == k) // player's new highscore overwrites his old one
4974 for (l = m; l > k; l--)
4976 strcpy(highscore[l].Name, highscore[l - 1].Name);
4977 highscore[l].Score = highscore[l - 1].Score;
4983 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
4984 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
4985 highscore[k].Score = game.score_final;
4990 else if (one_score_entry_per_name &&
4991 !strncmp(setup.player_name, highscore[k].Name,
4992 MAX_PLAYER_NAME_LEN))
4993 break; // player already there with a higher score
4997 SaveScore(level_nr);
5002 static int getElementMoveStepsizeExt(int x, int y, int direction)
5004 int element = Tile[x][y];
5005 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5006 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5007 int horiz_move = (dx != 0);
5008 int sign = (horiz_move ? dx : dy);
5009 int step = sign * element_info[element].move_stepsize;
5011 // special values for move stepsize for spring and things on conveyor belt
5014 if (CAN_FALL(element) &&
5015 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5016 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5017 else if (element == EL_SPRING)
5018 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5024 static int getElementMoveStepsize(int x, int y)
5026 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5029 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5031 if (player->GfxAction != action || player->GfxDir != dir)
5033 player->GfxAction = action;
5034 player->GfxDir = dir;
5036 player->StepFrame = 0;
5040 static void ResetGfxFrame(int x, int y)
5042 // profiling showed that "autotest" spends 10~20% of its time in this function
5043 if (DrawingDeactivatedField())
5046 int element = Tile[x][y];
5047 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5049 if (graphic_info[graphic].anim_global_sync)
5050 GfxFrame[x][y] = FrameCounter;
5051 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5052 GfxFrame[x][y] = CustomValue[x][y];
5053 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5054 GfxFrame[x][y] = element_info[element].collect_score;
5055 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5056 GfxFrame[x][y] = ChangeDelay[x][y];
5059 static void ResetGfxAnimation(int x, int y)
5061 GfxAction[x][y] = ACTION_DEFAULT;
5062 GfxDir[x][y] = MovDir[x][y];
5065 ResetGfxFrame(x, y);
5068 static void ResetRandomAnimationValue(int x, int y)
5070 GfxRandom[x][y] = INIT_GFX_RANDOM();
5073 static void InitMovingField(int x, int y, int direction)
5075 int element = Tile[x][y];
5076 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5077 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5080 boolean is_moving_before, is_moving_after;
5082 // check if element was/is moving or being moved before/after mode change
5083 is_moving_before = (WasJustMoving[x][y] != 0);
5084 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5086 // reset animation only for moving elements which change direction of moving
5087 // or which just started or stopped moving
5088 // (else CEs with property "can move" / "not moving" are reset each frame)
5089 if (is_moving_before != is_moving_after ||
5090 direction != MovDir[x][y])
5091 ResetGfxAnimation(x, y);
5093 MovDir[x][y] = direction;
5094 GfxDir[x][y] = direction;
5096 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5097 direction == MV_DOWN && CAN_FALL(element) ?
5098 ACTION_FALLING : ACTION_MOVING);
5100 // this is needed for CEs with property "can move" / "not moving"
5102 if (is_moving_after)
5104 if (Tile[newx][newy] == EL_EMPTY)
5105 Tile[newx][newy] = EL_BLOCKED;
5107 MovDir[newx][newy] = MovDir[x][y];
5109 CustomValue[newx][newy] = CustomValue[x][y];
5111 GfxFrame[newx][newy] = GfxFrame[x][y];
5112 GfxRandom[newx][newy] = GfxRandom[x][y];
5113 GfxAction[newx][newy] = GfxAction[x][y];
5114 GfxDir[newx][newy] = GfxDir[x][y];
5118 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5120 int direction = MovDir[x][y];
5121 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5122 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5128 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5130 int oldx = x, oldy = y;
5131 int direction = MovDir[x][y];
5133 if (direction == MV_LEFT)
5135 else if (direction == MV_RIGHT)
5137 else if (direction == MV_UP)
5139 else if (direction == MV_DOWN)
5142 *comes_from_x = oldx;
5143 *comes_from_y = oldy;
5146 static int MovingOrBlocked2Element(int x, int y)
5148 int element = Tile[x][y];
5150 if (element == EL_BLOCKED)
5154 Blocked2Moving(x, y, &oldx, &oldy);
5155 return Tile[oldx][oldy];
5161 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5163 // like MovingOrBlocked2Element(), but if element is moving
5164 // and (x,y) is the field the moving element is just leaving,
5165 // return EL_BLOCKED instead of the element value
5166 int element = Tile[x][y];
5168 if (IS_MOVING(x, y))
5170 if (element == EL_BLOCKED)
5174 Blocked2Moving(x, y, &oldx, &oldy);
5175 return Tile[oldx][oldy];
5184 static void RemoveField(int x, int y)
5186 Tile[x][y] = EL_EMPTY;
5192 CustomValue[x][y] = 0;
5195 ChangeDelay[x][y] = 0;
5196 ChangePage[x][y] = -1;
5197 Pushed[x][y] = FALSE;
5199 GfxElement[x][y] = EL_UNDEFINED;
5200 GfxAction[x][y] = ACTION_DEFAULT;
5201 GfxDir[x][y] = MV_NONE;
5204 static void RemoveMovingField(int x, int y)
5206 int oldx = x, oldy = y, newx = x, newy = y;
5207 int element = Tile[x][y];
5208 int next_element = EL_UNDEFINED;
5210 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5213 if (IS_MOVING(x, y))
5215 Moving2Blocked(x, y, &newx, &newy);
5217 if (Tile[newx][newy] != EL_BLOCKED)
5219 // element is moving, but target field is not free (blocked), but
5220 // already occupied by something different (example: acid pool);
5221 // in this case, only remove the moving field, but not the target
5223 RemoveField(oldx, oldy);
5225 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5227 TEST_DrawLevelField(oldx, oldy);
5232 else if (element == EL_BLOCKED)
5234 Blocked2Moving(x, y, &oldx, &oldy);
5235 if (!IS_MOVING(oldx, oldy))
5239 if (element == EL_BLOCKED &&
5240 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5241 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5242 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5243 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5244 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5245 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5246 next_element = get_next_element(Tile[oldx][oldy]);
5248 RemoveField(oldx, oldy);
5249 RemoveField(newx, newy);
5251 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5253 if (next_element != EL_UNDEFINED)
5254 Tile[oldx][oldy] = next_element;
5256 TEST_DrawLevelField(oldx, oldy);
5257 TEST_DrawLevelField(newx, newy);
5260 void DrawDynamite(int x, int y)
5262 int sx = SCREENX(x), sy = SCREENY(y);
5263 int graphic = el2img(Tile[x][y]);
5266 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5269 if (IS_WALKABLE_INSIDE(Back[x][y]))
5273 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5274 else if (Store[x][y])
5275 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5277 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5279 if (Back[x][y] || Store[x][y])
5280 DrawGraphicThruMask(sx, sy, graphic, frame);
5282 DrawGraphic(sx, sy, graphic, frame);
5285 static void CheckDynamite(int x, int y)
5287 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5291 if (MovDelay[x][y] != 0)
5294 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5300 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5305 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5307 boolean num_checked_players = 0;
5310 for (i = 0; i < MAX_PLAYERS; i++)
5312 if (stored_player[i].active)
5314 int sx = stored_player[i].jx;
5315 int sy = stored_player[i].jy;
5317 if (num_checked_players == 0)
5324 *sx1 = MIN(*sx1, sx);
5325 *sy1 = MIN(*sy1, sy);
5326 *sx2 = MAX(*sx2, sx);
5327 *sy2 = MAX(*sy2, sy);
5330 num_checked_players++;
5335 static boolean checkIfAllPlayersFitToScreen_RND(void)
5337 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5339 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5341 return (sx2 - sx1 < SCR_FIELDX &&
5342 sy2 - sy1 < SCR_FIELDY);
5345 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5347 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5349 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5351 *sx = (sx1 + sx2) / 2;
5352 *sy = (sy1 + sy2) / 2;
5355 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5356 boolean center_screen, boolean quick_relocation)
5358 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5359 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5360 boolean no_delay = (tape.warp_forward);
5361 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5362 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5363 int new_scroll_x, new_scroll_y;
5365 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5367 // case 1: quick relocation inside visible screen (without scrolling)
5374 if (!level.shifted_relocation || center_screen)
5376 // relocation _with_ centering of screen
5378 new_scroll_x = SCROLL_POSITION_X(x);
5379 new_scroll_y = SCROLL_POSITION_Y(y);
5383 // relocation _without_ centering of screen
5385 int center_scroll_x = SCROLL_POSITION_X(old_x);
5386 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5387 int offset_x = x + (scroll_x - center_scroll_x);
5388 int offset_y = y + (scroll_y - center_scroll_y);
5390 // for new screen position, apply previous offset to center position
5391 new_scroll_x = SCROLL_POSITION_X(offset_x);
5392 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5395 if (quick_relocation)
5397 // case 2: quick relocation (redraw without visible scrolling)
5399 scroll_x = new_scroll_x;
5400 scroll_y = new_scroll_y;
5407 // case 3: visible relocation (with scrolling to new position)
5409 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5411 SetVideoFrameDelay(wait_delay_value);
5413 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5415 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5416 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5418 if (dx == 0 && dy == 0) // no scrolling needed at all
5424 // set values for horizontal/vertical screen scrolling (half tile size)
5425 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5426 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5427 int pos_x = dx * TILEX / 2;
5428 int pos_y = dy * TILEY / 2;
5429 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5430 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5432 ScrollLevel(dx, dy);
5435 // scroll in two steps of half tile size to make things smoother
5436 BlitScreenToBitmapExt_RND(window, fx, fy);
5438 // scroll second step to align at full tile size
5439 BlitScreenToBitmap(window);
5445 SetVideoFrameDelay(frame_delay_value_old);
5448 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5450 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5451 int player_nr = GET_PLAYER_NR(el_player);
5452 struct PlayerInfo *player = &stored_player[player_nr];
5453 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5454 boolean no_delay = (tape.warp_forward);
5455 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5456 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5457 int old_jx = player->jx;
5458 int old_jy = player->jy;
5459 int old_element = Tile[old_jx][old_jy];
5460 int element = Tile[jx][jy];
5461 boolean player_relocated = (old_jx != jx || old_jy != jy);
5463 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5464 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5465 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5466 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5467 int leave_side_horiz = move_dir_horiz;
5468 int leave_side_vert = move_dir_vert;
5469 int enter_side = enter_side_horiz | enter_side_vert;
5470 int leave_side = leave_side_horiz | leave_side_vert;
5472 if (player->buried) // do not reanimate dead player
5475 if (!player_relocated) // no need to relocate the player
5478 if (IS_PLAYER(jx, jy)) // player already placed at new position
5480 RemoveField(jx, jy); // temporarily remove newly placed player
5481 DrawLevelField(jx, jy);
5484 if (player->present)
5486 while (player->MovPos)
5488 ScrollPlayer(player, SCROLL_GO_ON);
5489 ScrollScreen(NULL, SCROLL_GO_ON);
5491 AdvanceFrameAndPlayerCounters(player->index_nr);
5495 BackToFront_WithFrameDelay(wait_delay_value);
5498 DrawPlayer(player); // needed here only to cleanup last field
5499 DrawLevelField(player->jx, player->jy); // remove player graphic
5501 player->is_moving = FALSE;
5504 if (IS_CUSTOM_ELEMENT(old_element))
5505 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5507 player->index_bit, leave_side);
5509 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5511 player->index_bit, leave_side);
5513 Tile[jx][jy] = el_player;
5514 InitPlayerField(jx, jy, el_player, TRUE);
5516 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5517 possible that the relocation target field did not contain a player element,
5518 but a walkable element, to which the new player was relocated -- in this
5519 case, restore that (already initialized!) element on the player field */
5520 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5522 Tile[jx][jy] = element; // restore previously existing element
5525 // only visually relocate centered player
5526 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5527 FALSE, level.instant_relocation);
5529 TestIfPlayerTouchesBadThing(jx, jy);
5530 TestIfPlayerTouchesCustomElement(jx, jy);
5532 if (IS_CUSTOM_ELEMENT(element))
5533 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5534 player->index_bit, enter_side);
5536 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5537 player->index_bit, enter_side);
5539 if (player->is_switching)
5541 /* ensure that relocation while still switching an element does not cause
5542 a new element to be treated as also switched directly after relocation
5543 (this is important for teleporter switches that teleport the player to
5544 a place where another teleporter switch is in the same direction, which
5545 would then incorrectly be treated as immediately switched before the
5546 direction key that caused the switch was released) */
5548 player->switch_x += jx - old_jx;
5549 player->switch_y += jy - old_jy;
5553 static void Explode(int ex, int ey, int phase, int mode)
5559 // !!! eliminate this variable !!!
5560 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5562 if (game.explosions_delayed)
5564 ExplodeField[ex][ey] = mode;
5568 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5570 int center_element = Tile[ex][ey];
5571 int artwork_element, explosion_element; // set these values later
5573 // remove things displayed in background while burning dynamite
5574 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5577 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5579 // put moving element to center field (and let it explode there)
5580 center_element = MovingOrBlocked2Element(ex, ey);
5581 RemoveMovingField(ex, ey);
5582 Tile[ex][ey] = center_element;
5585 // now "center_element" is finally determined -- set related values now
5586 artwork_element = center_element; // for custom player artwork
5587 explosion_element = center_element; // for custom player artwork
5589 if (IS_PLAYER(ex, ey))
5591 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5593 artwork_element = stored_player[player_nr].artwork_element;
5595 if (level.use_explosion_element[player_nr])
5597 explosion_element = level.explosion_element[player_nr];
5598 artwork_element = explosion_element;
5602 if (mode == EX_TYPE_NORMAL ||
5603 mode == EX_TYPE_CENTER ||
5604 mode == EX_TYPE_CROSS)
5605 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5607 last_phase = element_info[explosion_element].explosion_delay + 1;
5609 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5611 int xx = x - ex + 1;
5612 int yy = y - ey + 1;
5615 if (!IN_LEV_FIELD(x, y) ||
5616 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5617 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5620 element = Tile[x][y];
5622 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5624 element = MovingOrBlocked2Element(x, y);
5626 if (!IS_EXPLOSION_PROOF(element))
5627 RemoveMovingField(x, y);
5630 // indestructible elements can only explode in center (but not flames)
5631 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5632 mode == EX_TYPE_BORDER)) ||
5633 element == EL_FLAMES)
5636 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5637 behaviour, for example when touching a yamyam that explodes to rocks
5638 with active deadly shield, a rock is created under the player !!! */
5639 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5641 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5642 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5643 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5645 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5648 if (IS_ACTIVE_BOMB(element))
5650 // re-activate things under the bomb like gate or penguin
5651 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5658 // save walkable background elements while explosion on same tile
5659 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5660 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5661 Back[x][y] = element;
5663 // ignite explodable elements reached by other explosion
5664 if (element == EL_EXPLOSION)
5665 element = Store2[x][y];
5667 if (AmoebaNr[x][y] &&
5668 (element == EL_AMOEBA_FULL ||
5669 element == EL_BD_AMOEBA ||
5670 element == EL_AMOEBA_GROWING))
5672 AmoebaCnt[AmoebaNr[x][y]]--;
5673 AmoebaCnt2[AmoebaNr[x][y]]--;
5678 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5680 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5682 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5684 if (PLAYERINFO(ex, ey)->use_murphy)
5685 Store[x][y] = EL_EMPTY;
5688 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5689 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5690 else if (ELEM_IS_PLAYER(center_element))
5691 Store[x][y] = EL_EMPTY;
5692 else if (center_element == EL_YAMYAM)
5693 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5694 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5695 Store[x][y] = element_info[center_element].content.e[xx][yy];
5697 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5698 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5699 // otherwise) -- FIX THIS !!!
5700 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5701 Store[x][y] = element_info[element].content.e[1][1];
5703 else if (!CAN_EXPLODE(element))
5704 Store[x][y] = element_info[element].content.e[1][1];
5707 Store[x][y] = EL_EMPTY;
5709 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5710 center_element == EL_AMOEBA_TO_DIAMOND)
5711 Store2[x][y] = element;
5713 Tile[x][y] = EL_EXPLOSION;
5714 GfxElement[x][y] = artwork_element;
5716 ExplodePhase[x][y] = 1;
5717 ExplodeDelay[x][y] = last_phase;
5722 if (center_element == EL_YAMYAM)
5723 game.yamyam_content_nr =
5724 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5736 GfxFrame[x][y] = 0; // restart explosion animation
5738 last_phase = ExplodeDelay[x][y];
5740 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5742 // this can happen if the player leaves an explosion just in time
5743 if (GfxElement[x][y] == EL_UNDEFINED)
5744 GfxElement[x][y] = EL_EMPTY;
5746 border_element = Store2[x][y];
5747 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5748 border_element = StorePlayer[x][y];
5750 if (phase == element_info[border_element].ignition_delay ||
5751 phase == last_phase)
5753 boolean border_explosion = FALSE;
5755 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5756 !PLAYER_EXPLOSION_PROTECTED(x, y))
5758 KillPlayerUnlessExplosionProtected(x, y);
5759 border_explosion = TRUE;
5761 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5763 Tile[x][y] = Store2[x][y];
5766 border_explosion = TRUE;
5768 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5770 AmoebaToDiamond(x, y);
5772 border_explosion = TRUE;
5775 // if an element just explodes due to another explosion (chain-reaction),
5776 // do not immediately end the new explosion when it was the last frame of
5777 // the explosion (as it would be done in the following "if"-statement!)
5778 if (border_explosion && phase == last_phase)
5782 if (phase == last_phase)
5786 element = Tile[x][y] = Store[x][y];
5787 Store[x][y] = Store2[x][y] = 0;
5788 GfxElement[x][y] = EL_UNDEFINED;
5790 // player can escape from explosions and might therefore be still alive
5791 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5792 element <= EL_PLAYER_IS_EXPLODING_4)
5794 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5795 int explosion_element = EL_PLAYER_1 + player_nr;
5796 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5797 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5799 if (level.use_explosion_element[player_nr])
5800 explosion_element = level.explosion_element[player_nr];
5802 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5803 element_info[explosion_element].content.e[xx][yy]);
5806 // restore probably existing indestructible background element
5807 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5808 element = Tile[x][y] = Back[x][y];
5811 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5812 GfxDir[x][y] = MV_NONE;
5813 ChangeDelay[x][y] = 0;
5814 ChangePage[x][y] = -1;
5816 CustomValue[x][y] = 0;
5818 InitField_WithBug2(x, y, FALSE);
5820 TEST_DrawLevelField(x, y);
5822 TestIfElementTouchesCustomElement(x, y);
5824 if (GFX_CRUMBLED(element))
5825 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5827 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5828 StorePlayer[x][y] = 0;
5830 if (ELEM_IS_PLAYER(element))
5831 RelocatePlayer(x, y, element);
5833 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5835 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5836 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5839 TEST_DrawLevelFieldCrumbled(x, y);
5841 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5843 DrawLevelElement(x, y, Back[x][y]);
5844 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5846 else if (IS_WALKABLE_UNDER(Back[x][y]))
5848 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5849 DrawLevelElementThruMask(x, y, Back[x][y]);
5851 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5852 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5856 static void DynaExplode(int ex, int ey)
5859 int dynabomb_element = Tile[ex][ey];
5860 int dynabomb_size = 1;
5861 boolean dynabomb_xl = FALSE;
5862 struct PlayerInfo *player;
5863 static int xy[4][2] =
5871 if (IS_ACTIVE_BOMB(dynabomb_element))
5873 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5874 dynabomb_size = player->dynabomb_size;
5875 dynabomb_xl = player->dynabomb_xl;
5876 player->dynabombs_left++;
5879 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5881 for (i = 0; i < NUM_DIRECTIONS; i++)
5883 for (j = 1; j <= dynabomb_size; j++)
5885 int x = ex + j * xy[i][0];
5886 int y = ey + j * xy[i][1];
5889 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5892 element = Tile[x][y];
5894 // do not restart explosions of fields with active bombs
5895 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5898 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5900 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5901 !IS_DIGGABLE(element) && !dynabomb_xl)
5907 void Bang(int x, int y)
5909 int element = MovingOrBlocked2Element(x, y);
5910 int explosion_type = EX_TYPE_NORMAL;
5912 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5914 struct PlayerInfo *player = PLAYERINFO(x, y);
5916 element = Tile[x][y] = player->initial_element;
5918 if (level.use_explosion_element[player->index_nr])
5920 int explosion_element = level.explosion_element[player->index_nr];
5922 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
5923 explosion_type = EX_TYPE_CROSS;
5924 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
5925 explosion_type = EX_TYPE_CENTER;
5933 case EL_BD_BUTTERFLY:
5936 case EL_DARK_YAMYAM:
5940 RaiseScoreElement(element);
5943 case EL_DYNABOMB_PLAYER_1_ACTIVE:
5944 case EL_DYNABOMB_PLAYER_2_ACTIVE:
5945 case EL_DYNABOMB_PLAYER_3_ACTIVE:
5946 case EL_DYNABOMB_PLAYER_4_ACTIVE:
5947 case EL_DYNABOMB_INCREASE_NUMBER:
5948 case EL_DYNABOMB_INCREASE_SIZE:
5949 case EL_DYNABOMB_INCREASE_POWER:
5950 explosion_type = EX_TYPE_DYNA;
5953 case EL_DC_LANDMINE:
5954 explosion_type = EX_TYPE_CENTER;
5959 case EL_LAMP_ACTIVE:
5960 case EL_AMOEBA_TO_DIAMOND:
5961 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
5962 explosion_type = EX_TYPE_CENTER;
5966 if (element_info[element].explosion_type == EXPLODES_CROSS)
5967 explosion_type = EX_TYPE_CROSS;
5968 else if (element_info[element].explosion_type == EXPLODES_1X1)
5969 explosion_type = EX_TYPE_CENTER;
5973 if (explosion_type == EX_TYPE_DYNA)
5976 Explode(x, y, EX_PHASE_START, explosion_type);
5978 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
5981 static void SplashAcid(int x, int y)
5983 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
5984 (!IN_LEV_FIELD(x - 1, y - 2) ||
5985 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
5986 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
5988 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
5989 (!IN_LEV_FIELD(x + 1, y - 2) ||
5990 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
5991 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
5993 PlayLevelSound(x, y, SND_ACID_SPLASHING);
5996 static void InitBeltMovement(void)
5998 static int belt_base_element[4] =
6000 EL_CONVEYOR_BELT_1_LEFT,
6001 EL_CONVEYOR_BELT_2_LEFT,
6002 EL_CONVEYOR_BELT_3_LEFT,
6003 EL_CONVEYOR_BELT_4_LEFT
6005 static int belt_base_active_element[4] =
6007 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6008 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6009 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6010 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6015 // set frame order for belt animation graphic according to belt direction
6016 for (i = 0; i < NUM_BELTS; i++)
6020 for (j = 0; j < NUM_BELT_PARTS; j++)
6022 int element = belt_base_active_element[belt_nr] + j;
6023 int graphic_1 = el2img(element);
6024 int graphic_2 = el2panelimg(element);
6026 if (game.belt_dir[i] == MV_LEFT)
6028 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6029 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6033 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6034 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6039 SCAN_PLAYFIELD(x, y)
6041 int element = Tile[x][y];
6043 for (i = 0; i < NUM_BELTS; i++)
6045 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6047 int e_belt_nr = getBeltNrFromBeltElement(element);
6050 if (e_belt_nr == belt_nr)
6052 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6054 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6061 static void ToggleBeltSwitch(int x, int y)
6063 static int belt_base_element[4] =
6065 EL_CONVEYOR_BELT_1_LEFT,
6066 EL_CONVEYOR_BELT_2_LEFT,
6067 EL_CONVEYOR_BELT_3_LEFT,
6068 EL_CONVEYOR_BELT_4_LEFT
6070 static int belt_base_active_element[4] =
6072 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6073 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6074 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6075 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6077 static int belt_base_switch_element[4] =
6079 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6080 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6081 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6082 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6084 static int belt_move_dir[4] =
6092 int element = Tile[x][y];
6093 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6094 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6095 int belt_dir = belt_move_dir[belt_dir_nr];
6098 if (!IS_BELT_SWITCH(element))
6101 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6102 game.belt_dir[belt_nr] = belt_dir;
6104 if (belt_dir_nr == 3)
6107 // set frame order for belt animation graphic according to belt direction
6108 for (i = 0; i < NUM_BELT_PARTS; i++)
6110 int element = belt_base_active_element[belt_nr] + i;
6111 int graphic_1 = el2img(element);
6112 int graphic_2 = el2panelimg(element);
6114 if (belt_dir == MV_LEFT)
6116 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6117 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6121 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6122 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6126 SCAN_PLAYFIELD(xx, yy)
6128 int element = Tile[xx][yy];
6130 if (IS_BELT_SWITCH(element))
6132 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6134 if (e_belt_nr == belt_nr)
6136 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6137 TEST_DrawLevelField(xx, yy);
6140 else if (IS_BELT(element) && belt_dir != MV_NONE)
6142 int e_belt_nr = getBeltNrFromBeltElement(element);
6144 if (e_belt_nr == belt_nr)
6146 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6148 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6149 TEST_DrawLevelField(xx, yy);
6152 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6154 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6156 if (e_belt_nr == belt_nr)
6158 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6160 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6161 TEST_DrawLevelField(xx, yy);
6167 static void ToggleSwitchgateSwitch(int x, int y)
6171 game.switchgate_pos = !game.switchgate_pos;
6173 SCAN_PLAYFIELD(xx, yy)
6175 int element = Tile[xx][yy];
6177 if (element == EL_SWITCHGATE_SWITCH_UP)
6179 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6180 TEST_DrawLevelField(xx, yy);
6182 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6184 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6185 TEST_DrawLevelField(xx, yy);
6187 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6189 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6190 TEST_DrawLevelField(xx, yy);
6192 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6194 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6195 TEST_DrawLevelField(xx, yy);
6197 else if (element == EL_SWITCHGATE_OPEN ||
6198 element == EL_SWITCHGATE_OPENING)
6200 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6202 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6204 else if (element == EL_SWITCHGATE_CLOSED ||
6205 element == EL_SWITCHGATE_CLOSING)
6207 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6209 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6214 static int getInvisibleActiveFromInvisibleElement(int element)
6216 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6217 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6218 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6222 static int getInvisibleFromInvisibleActiveElement(int element)
6224 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6225 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6226 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6230 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6234 SCAN_PLAYFIELD(x, y)
6236 int element = Tile[x][y];
6238 if (element == EL_LIGHT_SWITCH &&
6239 game.light_time_left > 0)
6241 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6242 TEST_DrawLevelField(x, y);
6244 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6245 game.light_time_left == 0)
6247 Tile[x][y] = EL_LIGHT_SWITCH;
6248 TEST_DrawLevelField(x, y);
6250 else if (element == EL_EMC_DRIPPER &&
6251 game.light_time_left > 0)
6253 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6254 TEST_DrawLevelField(x, y);
6256 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6257 game.light_time_left == 0)
6259 Tile[x][y] = EL_EMC_DRIPPER;
6260 TEST_DrawLevelField(x, y);
6262 else if (element == EL_INVISIBLE_STEELWALL ||
6263 element == EL_INVISIBLE_WALL ||
6264 element == EL_INVISIBLE_SAND)
6266 if (game.light_time_left > 0)
6267 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6269 TEST_DrawLevelField(x, y);
6271 // uncrumble neighbour fields, if needed
6272 if (element == EL_INVISIBLE_SAND)
6273 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6275 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6276 element == EL_INVISIBLE_WALL_ACTIVE ||
6277 element == EL_INVISIBLE_SAND_ACTIVE)
6279 if (game.light_time_left == 0)
6280 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6282 TEST_DrawLevelField(x, y);
6284 // re-crumble neighbour fields, if needed
6285 if (element == EL_INVISIBLE_SAND)
6286 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6291 static void RedrawAllInvisibleElementsForLenses(void)
6295 SCAN_PLAYFIELD(x, y)
6297 int element = Tile[x][y];
6299 if (element == EL_EMC_DRIPPER &&
6300 game.lenses_time_left > 0)
6302 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6303 TEST_DrawLevelField(x, y);
6305 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6306 game.lenses_time_left == 0)
6308 Tile[x][y] = EL_EMC_DRIPPER;
6309 TEST_DrawLevelField(x, y);
6311 else if (element == EL_INVISIBLE_STEELWALL ||
6312 element == EL_INVISIBLE_WALL ||
6313 element == EL_INVISIBLE_SAND)
6315 if (game.lenses_time_left > 0)
6316 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6318 TEST_DrawLevelField(x, y);
6320 // uncrumble neighbour fields, if needed
6321 if (element == EL_INVISIBLE_SAND)
6322 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6324 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6325 element == EL_INVISIBLE_WALL_ACTIVE ||
6326 element == EL_INVISIBLE_SAND_ACTIVE)
6328 if (game.lenses_time_left == 0)
6329 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6331 TEST_DrawLevelField(x, y);
6333 // re-crumble neighbour fields, if needed
6334 if (element == EL_INVISIBLE_SAND)
6335 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6340 static void RedrawAllInvisibleElementsForMagnifier(void)
6344 SCAN_PLAYFIELD(x, y)
6346 int element = Tile[x][y];
6348 if (element == EL_EMC_FAKE_GRASS &&
6349 game.magnify_time_left > 0)
6351 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6352 TEST_DrawLevelField(x, y);
6354 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6355 game.magnify_time_left == 0)
6357 Tile[x][y] = EL_EMC_FAKE_GRASS;
6358 TEST_DrawLevelField(x, y);
6360 else if (IS_GATE_GRAY(element) &&
6361 game.magnify_time_left > 0)
6363 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6364 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6365 IS_EM_GATE_GRAY(element) ?
6366 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6367 IS_EMC_GATE_GRAY(element) ?
6368 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6369 IS_DC_GATE_GRAY(element) ?
6370 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6372 TEST_DrawLevelField(x, y);
6374 else if (IS_GATE_GRAY_ACTIVE(element) &&
6375 game.magnify_time_left == 0)
6377 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6378 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6379 IS_EM_GATE_GRAY_ACTIVE(element) ?
6380 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6381 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6382 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6383 IS_DC_GATE_GRAY_ACTIVE(element) ?
6384 EL_DC_GATE_WHITE_GRAY :
6386 TEST_DrawLevelField(x, y);
6391 static void ToggleLightSwitch(int x, int y)
6393 int element = Tile[x][y];
6395 game.light_time_left =
6396 (element == EL_LIGHT_SWITCH ?
6397 level.time_light * FRAMES_PER_SECOND : 0);
6399 RedrawAllLightSwitchesAndInvisibleElements();
6402 static void ActivateTimegateSwitch(int x, int y)
6406 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6408 SCAN_PLAYFIELD(xx, yy)
6410 int element = Tile[xx][yy];
6412 if (element == EL_TIMEGATE_CLOSED ||
6413 element == EL_TIMEGATE_CLOSING)
6415 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6416 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6420 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6422 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6423 TEST_DrawLevelField(xx, yy);
6429 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6430 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6433 static void Impact(int x, int y)
6435 boolean last_line = (y == lev_fieldy - 1);
6436 boolean object_hit = FALSE;
6437 boolean impact = (last_line || object_hit);
6438 int element = Tile[x][y];
6439 int smashed = EL_STEELWALL;
6441 if (!last_line) // check if element below was hit
6443 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6446 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6447 MovDir[x][y + 1] != MV_DOWN ||
6448 MovPos[x][y + 1] <= TILEY / 2));
6450 // do not smash moving elements that left the smashed field in time
6451 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6452 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6455 #if USE_QUICKSAND_IMPACT_BUGFIX
6456 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6458 RemoveMovingField(x, y + 1);
6459 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6460 Tile[x][y + 2] = EL_ROCK;
6461 TEST_DrawLevelField(x, y + 2);
6466 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6468 RemoveMovingField(x, y + 1);
6469 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6470 Tile[x][y + 2] = EL_ROCK;
6471 TEST_DrawLevelField(x, y + 2);
6478 smashed = MovingOrBlocked2Element(x, y + 1);
6480 impact = (last_line || object_hit);
6483 if (!last_line && smashed == EL_ACID) // element falls into acid
6485 SplashAcid(x, y + 1);
6489 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6490 // only reset graphic animation if graphic really changes after impact
6492 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6494 ResetGfxAnimation(x, y);
6495 TEST_DrawLevelField(x, y);
6498 if (impact && CAN_EXPLODE_IMPACT(element))
6503 else if (impact && element == EL_PEARL &&
6504 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6506 ResetGfxAnimation(x, y);
6508 Tile[x][y] = EL_PEARL_BREAKING;
6509 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6512 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6514 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6519 if (impact && element == EL_AMOEBA_DROP)
6521 if (object_hit && IS_PLAYER(x, y + 1))
6522 KillPlayerUnlessEnemyProtected(x, y + 1);
6523 else if (object_hit && smashed == EL_PENGUIN)
6527 Tile[x][y] = EL_AMOEBA_GROWING;
6528 Store[x][y] = EL_AMOEBA_WET;
6530 ResetRandomAnimationValue(x, y);
6535 if (object_hit) // check which object was hit
6537 if ((CAN_PASS_MAGIC_WALL(element) &&
6538 (smashed == EL_MAGIC_WALL ||
6539 smashed == EL_BD_MAGIC_WALL)) ||
6540 (CAN_PASS_DC_MAGIC_WALL(element) &&
6541 smashed == EL_DC_MAGIC_WALL))
6544 int activated_magic_wall =
6545 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6546 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6547 EL_DC_MAGIC_WALL_ACTIVE);
6549 // activate magic wall / mill
6550 SCAN_PLAYFIELD(xx, yy)
6552 if (Tile[xx][yy] == smashed)
6553 Tile[xx][yy] = activated_magic_wall;
6556 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6557 game.magic_wall_active = TRUE;
6559 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6560 SND_MAGIC_WALL_ACTIVATING :
6561 smashed == EL_BD_MAGIC_WALL ?
6562 SND_BD_MAGIC_WALL_ACTIVATING :
6563 SND_DC_MAGIC_WALL_ACTIVATING));
6566 if (IS_PLAYER(x, y + 1))
6568 if (CAN_SMASH_PLAYER(element))
6570 KillPlayerUnlessEnemyProtected(x, y + 1);
6574 else if (smashed == EL_PENGUIN)
6576 if (CAN_SMASH_PLAYER(element))
6582 else if (element == EL_BD_DIAMOND)
6584 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6590 else if (((element == EL_SP_INFOTRON ||
6591 element == EL_SP_ZONK) &&
6592 (smashed == EL_SP_SNIKSNAK ||
6593 smashed == EL_SP_ELECTRON ||
6594 smashed == EL_SP_DISK_ORANGE)) ||
6595 (element == EL_SP_INFOTRON &&
6596 smashed == EL_SP_DISK_YELLOW))
6601 else if (CAN_SMASH_EVERYTHING(element))
6603 if (IS_CLASSIC_ENEMY(smashed) ||
6604 CAN_EXPLODE_SMASHED(smashed))
6609 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6611 if (smashed == EL_LAMP ||
6612 smashed == EL_LAMP_ACTIVE)
6617 else if (smashed == EL_NUT)
6619 Tile[x][y + 1] = EL_NUT_BREAKING;
6620 PlayLevelSound(x, y, SND_NUT_BREAKING);
6621 RaiseScoreElement(EL_NUT);
6624 else if (smashed == EL_PEARL)
6626 ResetGfxAnimation(x, y);
6628 Tile[x][y + 1] = EL_PEARL_BREAKING;
6629 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6632 else if (smashed == EL_DIAMOND)
6634 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6635 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6638 else if (IS_BELT_SWITCH(smashed))
6640 ToggleBeltSwitch(x, y + 1);
6642 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6643 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6644 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6645 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6647 ToggleSwitchgateSwitch(x, y + 1);
6649 else if (smashed == EL_LIGHT_SWITCH ||
6650 smashed == EL_LIGHT_SWITCH_ACTIVE)
6652 ToggleLightSwitch(x, y + 1);
6656 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6658 CheckElementChangeBySide(x, y + 1, smashed, element,
6659 CE_SWITCHED, CH_SIDE_TOP);
6660 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6666 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6671 // play sound of magic wall / mill
6673 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6674 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6675 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6677 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6678 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6679 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6680 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6681 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6682 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6687 // play sound of object that hits the ground
6688 if (last_line || object_hit)
6689 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6692 static void TurnRoundExt(int x, int y)
6704 { 0, 0 }, { 0, 0 }, { 0, 0 },
6709 int left, right, back;
6713 { MV_DOWN, MV_UP, MV_RIGHT },
6714 { MV_UP, MV_DOWN, MV_LEFT },
6716 { MV_LEFT, MV_RIGHT, MV_DOWN },
6720 { MV_RIGHT, MV_LEFT, MV_UP }
6723 int element = Tile[x][y];
6724 int move_pattern = element_info[element].move_pattern;
6726 int old_move_dir = MovDir[x][y];
6727 int left_dir = turn[old_move_dir].left;
6728 int right_dir = turn[old_move_dir].right;
6729 int back_dir = turn[old_move_dir].back;
6731 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6732 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6733 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6734 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6736 int left_x = x + left_dx, left_y = y + left_dy;
6737 int right_x = x + right_dx, right_y = y + right_dy;
6738 int move_x = x + move_dx, move_y = y + move_dy;
6742 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6744 TestIfBadThingTouchesOtherBadThing(x, y);
6746 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6747 MovDir[x][y] = right_dir;
6748 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6749 MovDir[x][y] = left_dir;
6751 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6753 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6756 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6758 TestIfBadThingTouchesOtherBadThing(x, y);
6760 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6761 MovDir[x][y] = left_dir;
6762 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6763 MovDir[x][y] = right_dir;
6765 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6767 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6770 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6772 TestIfBadThingTouchesOtherBadThing(x, y);
6774 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6775 MovDir[x][y] = left_dir;
6776 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6777 MovDir[x][y] = right_dir;
6779 if (MovDir[x][y] != old_move_dir)
6782 else if (element == EL_YAMYAM)
6784 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6785 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6787 if (can_turn_left && can_turn_right)
6788 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6789 else if (can_turn_left)
6790 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6791 else if (can_turn_right)
6792 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6794 MovDir[x][y] = back_dir;
6796 MovDelay[x][y] = 16 + 16 * RND(3);
6798 else if (element == EL_DARK_YAMYAM)
6800 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6802 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6805 if (can_turn_left && can_turn_right)
6806 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6807 else if (can_turn_left)
6808 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6809 else if (can_turn_right)
6810 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6812 MovDir[x][y] = back_dir;
6814 MovDelay[x][y] = 16 + 16 * RND(3);
6816 else if (element == EL_PACMAN)
6818 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6819 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6821 if (can_turn_left && can_turn_right)
6822 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6823 else if (can_turn_left)
6824 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6825 else if (can_turn_right)
6826 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6828 MovDir[x][y] = back_dir;
6830 MovDelay[x][y] = 6 + RND(40);
6832 else if (element == EL_PIG)
6834 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6835 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6836 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6837 boolean should_turn_left, should_turn_right, should_move_on;
6839 int rnd = RND(rnd_value);
6841 should_turn_left = (can_turn_left &&
6843 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6844 y + back_dy + left_dy)));
6845 should_turn_right = (can_turn_right &&
6847 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6848 y + back_dy + right_dy)));
6849 should_move_on = (can_move_on &&
6852 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6853 y + move_dy + left_dy) ||
6854 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6855 y + move_dy + right_dy)));
6857 if (should_turn_left || should_turn_right || should_move_on)
6859 if (should_turn_left && should_turn_right && should_move_on)
6860 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6861 rnd < 2 * rnd_value / 3 ? right_dir :
6863 else if (should_turn_left && should_turn_right)
6864 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6865 else if (should_turn_left && should_move_on)
6866 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6867 else if (should_turn_right && should_move_on)
6868 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6869 else if (should_turn_left)
6870 MovDir[x][y] = left_dir;
6871 else if (should_turn_right)
6872 MovDir[x][y] = right_dir;
6873 else if (should_move_on)
6874 MovDir[x][y] = old_move_dir;
6876 else if (can_move_on && rnd > rnd_value / 8)
6877 MovDir[x][y] = old_move_dir;
6878 else if (can_turn_left && can_turn_right)
6879 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6880 else if (can_turn_left && rnd > rnd_value / 8)
6881 MovDir[x][y] = left_dir;
6882 else if (can_turn_right && rnd > rnd_value/8)
6883 MovDir[x][y] = right_dir;
6885 MovDir[x][y] = back_dir;
6887 xx = x + move_xy[MovDir[x][y]].dx;
6888 yy = y + move_xy[MovDir[x][y]].dy;
6890 if (!IN_LEV_FIELD(xx, yy) ||
6891 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6892 MovDir[x][y] = old_move_dir;
6896 else if (element == EL_DRAGON)
6898 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6899 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6900 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6902 int rnd = RND(rnd_value);
6904 if (can_move_on && rnd > rnd_value / 8)
6905 MovDir[x][y] = old_move_dir;
6906 else if (can_turn_left && can_turn_right)
6907 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6908 else if (can_turn_left && rnd > rnd_value / 8)
6909 MovDir[x][y] = left_dir;
6910 else if (can_turn_right && rnd > rnd_value / 8)
6911 MovDir[x][y] = right_dir;
6913 MovDir[x][y] = back_dir;
6915 xx = x + move_xy[MovDir[x][y]].dx;
6916 yy = y + move_xy[MovDir[x][y]].dy;
6918 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6919 MovDir[x][y] = old_move_dir;
6923 else if (element == EL_MOLE)
6925 boolean can_move_on =
6926 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
6927 IS_AMOEBOID(Tile[move_x][move_y]) ||
6928 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
6931 boolean can_turn_left =
6932 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
6933 IS_AMOEBOID(Tile[left_x][left_y])));
6935 boolean can_turn_right =
6936 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
6937 IS_AMOEBOID(Tile[right_x][right_y])));
6939 if (can_turn_left && can_turn_right)
6940 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
6941 else if (can_turn_left)
6942 MovDir[x][y] = left_dir;
6944 MovDir[x][y] = right_dir;
6947 if (MovDir[x][y] != old_move_dir)
6950 else if (element == EL_BALLOON)
6952 MovDir[x][y] = game.wind_direction;
6955 else if (element == EL_SPRING)
6957 if (MovDir[x][y] & MV_HORIZONTAL)
6959 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
6960 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6962 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
6963 ResetGfxAnimation(move_x, move_y);
6964 TEST_DrawLevelField(move_x, move_y);
6966 MovDir[x][y] = back_dir;
6968 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
6969 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
6970 MovDir[x][y] = MV_NONE;
6975 else if (element == EL_ROBOT ||
6976 element == EL_SATELLITE ||
6977 element == EL_PENGUIN ||
6978 element == EL_EMC_ANDROID)
6980 int attr_x = -1, attr_y = -1;
6982 if (game.all_players_gone)
6984 attr_x = game.exit_x;
6985 attr_y = game.exit_y;
6991 for (i = 0; i < MAX_PLAYERS; i++)
6993 struct PlayerInfo *player = &stored_player[i];
6994 int jx = player->jx, jy = player->jy;
6996 if (!player->active)
7000 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7008 if (element == EL_ROBOT &&
7009 game.robot_wheel_x >= 0 &&
7010 game.robot_wheel_y >= 0 &&
7011 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7012 game.engine_version < VERSION_IDENT(3,1,0,0)))
7014 attr_x = game.robot_wheel_x;
7015 attr_y = game.robot_wheel_y;
7018 if (element == EL_PENGUIN)
7021 static int xy[4][2] =
7029 for (i = 0; i < NUM_DIRECTIONS; i++)
7031 int ex = x + xy[i][0];
7032 int ey = y + xy[i][1];
7034 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7035 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7036 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7037 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7046 MovDir[x][y] = MV_NONE;
7048 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7049 else if (attr_x > x)
7050 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7052 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7053 else if (attr_y > y)
7054 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7056 if (element == EL_ROBOT)
7060 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7061 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7062 Moving2Blocked(x, y, &newx, &newy);
7064 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7065 MovDelay[x][y] = 8 + 8 * !RND(3);
7067 MovDelay[x][y] = 16;
7069 else if (element == EL_PENGUIN)
7075 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7077 boolean first_horiz = RND(2);
7078 int new_move_dir = MovDir[x][y];
7081 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7082 Moving2Blocked(x, y, &newx, &newy);
7084 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7088 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7089 Moving2Blocked(x, y, &newx, &newy);
7091 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7094 MovDir[x][y] = old_move_dir;
7098 else if (element == EL_SATELLITE)
7104 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7106 boolean first_horiz = RND(2);
7107 int new_move_dir = MovDir[x][y];
7110 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7111 Moving2Blocked(x, y, &newx, &newy);
7113 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7117 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7118 Moving2Blocked(x, y, &newx, &newy);
7120 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7123 MovDir[x][y] = old_move_dir;
7127 else if (element == EL_EMC_ANDROID)
7129 static int check_pos[16] =
7131 -1, // 0 => (invalid)
7134 -1, // 3 => (invalid)
7136 0, // 5 => MV_LEFT | MV_UP
7137 2, // 6 => MV_RIGHT | MV_UP
7138 -1, // 7 => (invalid)
7140 6, // 9 => MV_LEFT | MV_DOWN
7141 4, // 10 => MV_RIGHT | MV_DOWN
7142 -1, // 11 => (invalid)
7143 -1, // 12 => (invalid)
7144 -1, // 13 => (invalid)
7145 -1, // 14 => (invalid)
7146 -1, // 15 => (invalid)
7154 { -1, -1, MV_LEFT | MV_UP },
7156 { +1, -1, MV_RIGHT | MV_UP },
7157 { +1, 0, MV_RIGHT },
7158 { +1, +1, MV_RIGHT | MV_DOWN },
7160 { -1, +1, MV_LEFT | MV_DOWN },
7163 int start_pos, check_order;
7164 boolean can_clone = FALSE;
7167 // check if there is any free field around current position
7168 for (i = 0; i < 8; i++)
7170 int newx = x + check_xy[i].dx;
7171 int newy = y + check_xy[i].dy;
7173 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7181 if (can_clone) // randomly find an element to clone
7185 start_pos = check_pos[RND(8)];
7186 check_order = (RND(2) ? -1 : +1);
7188 for (i = 0; i < 8; i++)
7190 int pos_raw = start_pos + i * check_order;
7191 int pos = (pos_raw + 8) % 8;
7192 int newx = x + check_xy[pos].dx;
7193 int newy = y + check_xy[pos].dy;
7195 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7197 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7198 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7200 Store[x][y] = Tile[newx][newy];
7209 if (can_clone) // randomly find a direction to move
7213 start_pos = check_pos[RND(8)];
7214 check_order = (RND(2) ? -1 : +1);
7216 for (i = 0; i < 8; i++)
7218 int pos_raw = start_pos + i * check_order;
7219 int pos = (pos_raw + 8) % 8;
7220 int newx = x + check_xy[pos].dx;
7221 int newy = y + check_xy[pos].dy;
7222 int new_move_dir = check_xy[pos].dir;
7224 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7226 MovDir[x][y] = new_move_dir;
7227 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7236 if (can_clone) // cloning and moving successful
7239 // cannot clone -- try to move towards player
7241 start_pos = check_pos[MovDir[x][y] & 0x0f];
7242 check_order = (RND(2) ? -1 : +1);
7244 for (i = 0; i < 3; i++)
7246 // first check start_pos, then previous/next or (next/previous) pos
7247 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7248 int pos = (pos_raw + 8) % 8;
7249 int newx = x + check_xy[pos].dx;
7250 int newy = y + check_xy[pos].dy;
7251 int new_move_dir = check_xy[pos].dir;
7253 if (IS_PLAYER(newx, newy))
7256 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7258 MovDir[x][y] = new_move_dir;
7259 MovDelay[x][y] = level.android_move_time * 8 + 1;
7266 else if (move_pattern == MV_TURNING_LEFT ||
7267 move_pattern == MV_TURNING_RIGHT ||
7268 move_pattern == MV_TURNING_LEFT_RIGHT ||
7269 move_pattern == MV_TURNING_RIGHT_LEFT ||
7270 move_pattern == MV_TURNING_RANDOM ||
7271 move_pattern == MV_ALL_DIRECTIONS)
7273 boolean can_turn_left =
7274 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7275 boolean can_turn_right =
7276 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7278 if (element_info[element].move_stepsize == 0) // "not moving"
7281 if (move_pattern == MV_TURNING_LEFT)
7282 MovDir[x][y] = left_dir;
7283 else if (move_pattern == MV_TURNING_RIGHT)
7284 MovDir[x][y] = right_dir;
7285 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7286 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7287 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7288 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7289 else if (move_pattern == MV_TURNING_RANDOM)
7290 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7291 can_turn_right && !can_turn_left ? right_dir :
7292 RND(2) ? left_dir : right_dir);
7293 else if (can_turn_left && can_turn_right)
7294 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7295 else if (can_turn_left)
7296 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7297 else if (can_turn_right)
7298 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7300 MovDir[x][y] = back_dir;
7302 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7304 else if (move_pattern == MV_HORIZONTAL ||
7305 move_pattern == MV_VERTICAL)
7307 if (move_pattern & old_move_dir)
7308 MovDir[x][y] = back_dir;
7309 else if (move_pattern == MV_HORIZONTAL)
7310 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7311 else if (move_pattern == MV_VERTICAL)
7312 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7314 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7316 else if (move_pattern & MV_ANY_DIRECTION)
7318 MovDir[x][y] = move_pattern;
7319 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7321 else if (move_pattern & MV_WIND_DIRECTION)
7323 MovDir[x][y] = game.wind_direction;
7324 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7326 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7328 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7329 MovDir[x][y] = left_dir;
7330 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7331 MovDir[x][y] = right_dir;
7333 if (MovDir[x][y] != old_move_dir)
7334 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7336 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7338 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7339 MovDir[x][y] = right_dir;
7340 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7341 MovDir[x][y] = left_dir;
7343 if (MovDir[x][y] != old_move_dir)
7344 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7346 else if (move_pattern == MV_TOWARDS_PLAYER ||
7347 move_pattern == MV_AWAY_FROM_PLAYER)
7349 int attr_x = -1, attr_y = -1;
7351 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7353 if (game.all_players_gone)
7355 attr_x = game.exit_x;
7356 attr_y = game.exit_y;
7362 for (i = 0; i < MAX_PLAYERS; i++)
7364 struct PlayerInfo *player = &stored_player[i];
7365 int jx = player->jx, jy = player->jy;
7367 if (!player->active)
7371 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7379 MovDir[x][y] = MV_NONE;
7381 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7382 else if (attr_x > x)
7383 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7385 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7386 else if (attr_y > y)
7387 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7389 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7391 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7393 boolean first_horiz = RND(2);
7394 int new_move_dir = MovDir[x][y];
7396 if (element_info[element].move_stepsize == 0) // "not moving"
7398 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7399 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7405 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7406 Moving2Blocked(x, y, &newx, &newy);
7408 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7412 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7413 Moving2Blocked(x, y, &newx, &newy);
7415 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7418 MovDir[x][y] = old_move_dir;
7421 else if (move_pattern == MV_WHEN_PUSHED ||
7422 move_pattern == MV_WHEN_DROPPED)
7424 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7425 MovDir[x][y] = MV_NONE;
7429 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7431 static int test_xy[7][2] =
7441 static int test_dir[7] =
7451 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7452 int move_preference = -1000000; // start with very low preference
7453 int new_move_dir = MV_NONE;
7454 int start_test = RND(4);
7457 for (i = 0; i < NUM_DIRECTIONS; i++)
7459 int move_dir = test_dir[start_test + i];
7460 int move_dir_preference;
7462 xx = x + test_xy[start_test + i][0];
7463 yy = y + test_xy[start_test + i][1];
7465 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7466 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7468 new_move_dir = move_dir;
7473 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7476 move_dir_preference = -1 * RunnerVisit[xx][yy];
7477 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7478 move_dir_preference = PlayerVisit[xx][yy];
7480 if (move_dir_preference > move_preference)
7482 // prefer field that has not been visited for the longest time
7483 move_preference = move_dir_preference;
7484 new_move_dir = move_dir;
7486 else if (move_dir_preference == move_preference &&
7487 move_dir == old_move_dir)
7489 // prefer last direction when all directions are preferred equally
7490 move_preference = move_dir_preference;
7491 new_move_dir = move_dir;
7495 MovDir[x][y] = new_move_dir;
7496 if (old_move_dir != new_move_dir)
7497 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7501 static void TurnRound(int x, int y)
7503 int direction = MovDir[x][y];
7507 GfxDir[x][y] = MovDir[x][y];
7509 if (direction != MovDir[x][y])
7513 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7515 ResetGfxFrame(x, y);
7518 static boolean JustBeingPushed(int x, int y)
7522 for (i = 0; i < MAX_PLAYERS; i++)
7524 struct PlayerInfo *player = &stored_player[i];
7526 if (player->active && player->is_pushing && player->MovPos)
7528 int next_jx = player->jx + (player->jx - player->last_jx);
7529 int next_jy = player->jy + (player->jy - player->last_jy);
7531 if (x == next_jx && y == next_jy)
7539 static void StartMoving(int x, int y)
7541 boolean started_moving = FALSE; // some elements can fall _and_ move
7542 int element = Tile[x][y];
7547 if (MovDelay[x][y] == 0)
7548 GfxAction[x][y] = ACTION_DEFAULT;
7550 if (CAN_FALL(element) && y < lev_fieldy - 1)
7552 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7553 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7554 if (JustBeingPushed(x, y))
7557 if (element == EL_QUICKSAND_FULL)
7559 if (IS_FREE(x, y + 1))
7561 InitMovingField(x, y, MV_DOWN);
7562 started_moving = TRUE;
7564 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7565 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7566 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7567 Store[x][y] = EL_ROCK;
7569 Store[x][y] = EL_ROCK;
7572 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7574 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7576 if (!MovDelay[x][y])
7578 MovDelay[x][y] = TILEY + 1;
7580 ResetGfxAnimation(x, y);
7581 ResetGfxAnimation(x, y + 1);
7586 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7587 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7594 Tile[x][y] = EL_QUICKSAND_EMPTY;
7595 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7596 Store[x][y + 1] = Store[x][y];
7599 PlayLevelSoundAction(x, y, ACTION_FILLING);
7601 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7603 if (!MovDelay[x][y])
7605 MovDelay[x][y] = TILEY + 1;
7607 ResetGfxAnimation(x, y);
7608 ResetGfxAnimation(x, y + 1);
7613 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7614 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7621 Tile[x][y] = EL_QUICKSAND_EMPTY;
7622 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7623 Store[x][y + 1] = Store[x][y];
7626 PlayLevelSoundAction(x, y, ACTION_FILLING);
7629 else if (element == EL_QUICKSAND_FAST_FULL)
7631 if (IS_FREE(x, y + 1))
7633 InitMovingField(x, y, MV_DOWN);
7634 started_moving = TRUE;
7636 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7637 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7638 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7639 Store[x][y] = EL_ROCK;
7641 Store[x][y] = EL_ROCK;
7644 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7646 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7648 if (!MovDelay[x][y])
7650 MovDelay[x][y] = TILEY + 1;
7652 ResetGfxAnimation(x, y);
7653 ResetGfxAnimation(x, y + 1);
7658 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7659 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7666 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7667 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7668 Store[x][y + 1] = Store[x][y];
7671 PlayLevelSoundAction(x, y, ACTION_FILLING);
7673 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7675 if (!MovDelay[x][y])
7677 MovDelay[x][y] = TILEY + 1;
7679 ResetGfxAnimation(x, y);
7680 ResetGfxAnimation(x, y + 1);
7685 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7686 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7693 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7694 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7695 Store[x][y + 1] = Store[x][y];
7698 PlayLevelSoundAction(x, y, ACTION_FILLING);
7701 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7702 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7704 InitMovingField(x, y, MV_DOWN);
7705 started_moving = TRUE;
7707 Tile[x][y] = EL_QUICKSAND_FILLING;
7708 Store[x][y] = element;
7710 PlayLevelSoundAction(x, y, ACTION_FILLING);
7712 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7713 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7715 InitMovingField(x, y, MV_DOWN);
7716 started_moving = TRUE;
7718 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7719 Store[x][y] = element;
7721 PlayLevelSoundAction(x, y, ACTION_FILLING);
7723 else if (element == EL_MAGIC_WALL_FULL)
7725 if (IS_FREE(x, y + 1))
7727 InitMovingField(x, y, MV_DOWN);
7728 started_moving = TRUE;
7730 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7731 Store[x][y] = EL_CHANGED(Store[x][y]);
7733 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7735 if (!MovDelay[x][y])
7736 MovDelay[x][y] = TILEY / 4 + 1;
7745 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7746 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7747 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7751 else if (element == EL_BD_MAGIC_WALL_FULL)
7753 if (IS_FREE(x, y + 1))
7755 InitMovingField(x, y, MV_DOWN);
7756 started_moving = TRUE;
7758 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7759 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7761 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7763 if (!MovDelay[x][y])
7764 MovDelay[x][y] = TILEY / 4 + 1;
7773 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7774 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7775 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7779 else if (element == EL_DC_MAGIC_WALL_FULL)
7781 if (IS_FREE(x, y + 1))
7783 InitMovingField(x, y, MV_DOWN);
7784 started_moving = TRUE;
7786 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7787 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7789 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7791 if (!MovDelay[x][y])
7792 MovDelay[x][y] = TILEY / 4 + 1;
7801 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7802 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7803 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7807 else if ((CAN_PASS_MAGIC_WALL(element) &&
7808 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7809 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7810 (CAN_PASS_DC_MAGIC_WALL(element) &&
7811 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7814 InitMovingField(x, y, MV_DOWN);
7815 started_moving = TRUE;
7818 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7819 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7820 EL_DC_MAGIC_WALL_FILLING);
7821 Store[x][y] = element;
7823 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7825 SplashAcid(x, y + 1);
7827 InitMovingField(x, y, MV_DOWN);
7828 started_moving = TRUE;
7830 Store[x][y] = EL_ACID;
7833 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7834 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7835 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7836 CAN_FALL(element) && WasJustFalling[x][y] &&
7837 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7839 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7840 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7841 (Tile[x][y + 1] == EL_BLOCKED)))
7843 /* this is needed for a special case not covered by calling "Impact()"
7844 from "ContinueMoving()": if an element moves to a tile directly below
7845 another element which was just falling on that tile (which was empty
7846 in the previous frame), the falling element above would just stop
7847 instead of smashing the element below (in previous version, the above
7848 element was just checked for "moving" instead of "falling", resulting
7849 in incorrect smashes caused by horizontal movement of the above
7850 element; also, the case of the player being the element to smash was
7851 simply not covered here... :-/ ) */
7853 CheckCollision[x][y] = 0;
7854 CheckImpact[x][y] = 0;
7858 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7860 if (MovDir[x][y] == MV_NONE)
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7866 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7868 if (WasJustFalling[x][y]) // prevent animation from being restarted
7869 MovDir[x][y] = MV_DOWN;
7871 InitMovingField(x, y, MV_DOWN);
7872 started_moving = TRUE;
7874 else if (element == EL_AMOEBA_DROP)
7876 Tile[x][y] = EL_AMOEBA_GROWING;
7877 Store[x][y] = EL_AMOEBA_WET;
7879 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7880 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7881 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7882 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7884 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7885 (IS_FREE(x - 1, y + 1) ||
7886 Tile[x - 1][y + 1] == EL_ACID));
7887 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7888 (IS_FREE(x + 1, y + 1) ||
7889 Tile[x + 1][y + 1] == EL_ACID));
7890 boolean can_fall_any = (can_fall_left || can_fall_right);
7891 boolean can_fall_both = (can_fall_left && can_fall_right);
7892 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7894 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7896 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7897 can_fall_right = FALSE;
7898 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7899 can_fall_left = FALSE;
7900 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7901 can_fall_right = FALSE;
7902 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7903 can_fall_left = FALSE;
7905 can_fall_any = (can_fall_left || can_fall_right);
7906 can_fall_both = FALSE;
7911 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7912 can_fall_right = FALSE; // slip down on left side
7914 can_fall_left = !(can_fall_right = RND(2));
7916 can_fall_both = FALSE;
7921 // if not determined otherwise, prefer left side for slipping down
7922 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
7923 started_moving = TRUE;
7926 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
7928 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
7929 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
7930 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
7931 int belt_dir = game.belt_dir[belt_nr];
7933 if ((belt_dir == MV_LEFT && left_is_free) ||
7934 (belt_dir == MV_RIGHT && right_is_free))
7936 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
7938 InitMovingField(x, y, belt_dir);
7939 started_moving = TRUE;
7941 Pushed[x][y] = TRUE;
7942 Pushed[nextx][y] = TRUE;
7944 GfxAction[x][y] = ACTION_DEFAULT;
7948 MovDir[x][y] = 0; // if element was moving, stop it
7953 // not "else if" because of elements that can fall and move (EL_SPRING)
7954 if (CAN_MOVE(element) && !started_moving)
7956 int move_pattern = element_info[element].move_pattern;
7959 Moving2Blocked(x, y, &newx, &newy);
7961 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
7964 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7965 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7967 WasJustMoving[x][y] = 0;
7968 CheckCollision[x][y] = 0;
7970 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
7972 if (Tile[x][y] != element) // element has changed
7976 if (!MovDelay[x][y]) // start new movement phase
7978 // all objects that can change their move direction after each step
7979 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
7981 if (element != EL_YAMYAM &&
7982 element != EL_DARK_YAMYAM &&
7983 element != EL_PACMAN &&
7984 !(move_pattern & MV_ANY_DIRECTION) &&
7985 move_pattern != MV_TURNING_LEFT &&
7986 move_pattern != MV_TURNING_RIGHT &&
7987 move_pattern != MV_TURNING_LEFT_RIGHT &&
7988 move_pattern != MV_TURNING_RIGHT_LEFT &&
7989 move_pattern != MV_TURNING_RANDOM)
7993 if (MovDelay[x][y] && (element == EL_BUG ||
7994 element == EL_SPACESHIP ||
7995 element == EL_SP_SNIKSNAK ||
7996 element == EL_SP_ELECTRON ||
7997 element == EL_MOLE))
7998 TEST_DrawLevelField(x, y);
8002 if (MovDelay[x][y]) // wait some time before next movement
8006 if (element == EL_ROBOT ||
8007 element == EL_YAMYAM ||
8008 element == EL_DARK_YAMYAM)
8010 DrawLevelElementAnimationIfNeeded(x, y, element);
8011 PlayLevelSoundAction(x, y, ACTION_WAITING);
8013 else if (element == EL_SP_ELECTRON)
8014 DrawLevelElementAnimationIfNeeded(x, y, element);
8015 else if (element == EL_DRAGON)
8018 int dir = MovDir[x][y];
8019 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8020 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8021 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8022 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8023 dir == MV_UP ? IMG_FLAMES_1_UP :
8024 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8025 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8027 GfxAction[x][y] = ACTION_ATTACKING;
8029 if (IS_PLAYER(x, y))
8030 DrawPlayerField(x, y);
8032 TEST_DrawLevelField(x, y);
8034 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8036 for (i = 1; i <= 3; i++)
8038 int xx = x + i * dx;
8039 int yy = y + i * dy;
8040 int sx = SCREENX(xx);
8041 int sy = SCREENY(yy);
8042 int flame_graphic = graphic + (i - 1);
8044 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8049 int flamed = MovingOrBlocked2Element(xx, yy);
8051 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8054 RemoveMovingField(xx, yy);
8056 ChangeDelay[xx][yy] = 0;
8058 Tile[xx][yy] = EL_FLAMES;
8060 if (IN_SCR_FIELD(sx, sy))
8062 TEST_DrawLevelFieldCrumbled(xx, yy);
8063 DrawGraphic(sx, sy, flame_graphic, frame);
8068 if (Tile[xx][yy] == EL_FLAMES)
8069 Tile[xx][yy] = EL_EMPTY;
8070 TEST_DrawLevelField(xx, yy);
8075 if (MovDelay[x][y]) // element still has to wait some time
8077 PlayLevelSoundAction(x, y, ACTION_WAITING);
8083 // now make next step
8085 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8087 if (DONT_COLLIDE_WITH(element) &&
8088 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8089 !PLAYER_ENEMY_PROTECTED(newx, newy))
8091 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8096 else if (CAN_MOVE_INTO_ACID(element) &&
8097 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8098 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8099 (MovDir[x][y] == MV_DOWN ||
8100 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8102 SplashAcid(newx, newy);
8103 Store[x][y] = EL_ACID;
8105 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8107 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8108 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8109 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8110 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8113 TEST_DrawLevelField(x, y);
8115 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8116 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8117 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8119 game.friends_still_needed--;
8120 if (!game.friends_still_needed &&
8122 game.all_players_gone)
8127 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8129 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8130 TEST_DrawLevelField(newx, newy);
8132 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8134 else if (!IS_FREE(newx, newy))
8136 GfxAction[x][y] = ACTION_WAITING;
8138 if (IS_PLAYER(x, y))
8139 DrawPlayerField(x, y);
8141 TEST_DrawLevelField(x, y);
8146 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8148 if (IS_FOOD_PIG(Tile[newx][newy]))
8150 if (IS_MOVING(newx, newy))
8151 RemoveMovingField(newx, newy);
8154 Tile[newx][newy] = EL_EMPTY;
8155 TEST_DrawLevelField(newx, newy);
8158 PlayLevelSound(x, y, SND_PIG_DIGGING);
8160 else if (!IS_FREE(newx, newy))
8162 if (IS_PLAYER(x, y))
8163 DrawPlayerField(x, y);
8165 TEST_DrawLevelField(x, y);
8170 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8172 if (Store[x][y] != EL_EMPTY)
8174 boolean can_clone = FALSE;
8177 // check if element to clone is still there
8178 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8180 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8188 // cannot clone or target field not free anymore -- do not clone
8189 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8190 Store[x][y] = EL_EMPTY;
8193 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8195 if (IS_MV_DIAGONAL(MovDir[x][y]))
8197 int diagonal_move_dir = MovDir[x][y];
8198 int stored = Store[x][y];
8199 int change_delay = 8;
8202 // android is moving diagonally
8204 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8206 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8207 GfxElement[x][y] = EL_EMC_ANDROID;
8208 GfxAction[x][y] = ACTION_SHRINKING;
8209 GfxDir[x][y] = diagonal_move_dir;
8210 ChangeDelay[x][y] = change_delay;
8212 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8215 DrawLevelGraphicAnimation(x, y, graphic);
8216 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8218 if (Tile[newx][newy] == EL_ACID)
8220 SplashAcid(newx, newy);
8225 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8227 Store[newx][newy] = EL_EMC_ANDROID;
8228 GfxElement[newx][newy] = EL_EMC_ANDROID;
8229 GfxAction[newx][newy] = ACTION_GROWING;
8230 GfxDir[newx][newy] = diagonal_move_dir;
8231 ChangeDelay[newx][newy] = change_delay;
8233 graphic = el_act_dir2img(GfxElement[newx][newy],
8234 GfxAction[newx][newy], GfxDir[newx][newy]);
8236 DrawLevelGraphicAnimation(newx, newy, graphic);
8237 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8243 Tile[newx][newy] = EL_EMPTY;
8244 TEST_DrawLevelField(newx, newy);
8246 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8249 else if (!IS_FREE(newx, newy))
8254 else if (IS_CUSTOM_ELEMENT(element) &&
8255 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8257 if (!DigFieldByCE(newx, newy, element))
8260 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8262 RunnerVisit[x][y] = FrameCounter;
8263 PlayerVisit[x][y] /= 8; // expire player visit path
8266 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8268 if (!IS_FREE(newx, newy))
8270 if (IS_PLAYER(x, y))
8271 DrawPlayerField(x, y);
8273 TEST_DrawLevelField(x, y);
8279 boolean wanna_flame = !RND(10);
8280 int dx = newx - x, dy = newy - y;
8281 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8282 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8283 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8284 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8285 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8286 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8289 IS_CLASSIC_ENEMY(element1) ||
8290 IS_CLASSIC_ENEMY(element2)) &&
8291 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8292 element1 != EL_FLAMES && element2 != EL_FLAMES)
8294 ResetGfxAnimation(x, y);
8295 GfxAction[x][y] = ACTION_ATTACKING;
8297 if (IS_PLAYER(x, y))
8298 DrawPlayerField(x, y);
8300 TEST_DrawLevelField(x, y);
8302 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8304 MovDelay[x][y] = 50;
8306 Tile[newx][newy] = EL_FLAMES;
8307 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8308 Tile[newx1][newy1] = EL_FLAMES;
8309 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8310 Tile[newx2][newy2] = EL_FLAMES;
8316 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8317 Tile[newx][newy] == EL_DIAMOND)
8319 if (IS_MOVING(newx, newy))
8320 RemoveMovingField(newx, newy);
8323 Tile[newx][newy] = EL_EMPTY;
8324 TEST_DrawLevelField(newx, newy);
8327 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8329 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8330 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8332 if (AmoebaNr[newx][newy])
8334 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8335 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8336 Tile[newx][newy] == EL_BD_AMOEBA)
8337 AmoebaCnt[AmoebaNr[newx][newy]]--;
8340 if (IS_MOVING(newx, newy))
8342 RemoveMovingField(newx, newy);
8346 Tile[newx][newy] = EL_EMPTY;
8347 TEST_DrawLevelField(newx, newy);
8350 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8352 else if ((element == EL_PACMAN || element == EL_MOLE)
8353 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8355 if (AmoebaNr[newx][newy])
8357 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8358 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8359 Tile[newx][newy] == EL_BD_AMOEBA)
8360 AmoebaCnt[AmoebaNr[newx][newy]]--;
8363 if (element == EL_MOLE)
8365 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8366 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8368 ResetGfxAnimation(x, y);
8369 GfxAction[x][y] = ACTION_DIGGING;
8370 TEST_DrawLevelField(x, y);
8372 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8374 return; // wait for shrinking amoeba
8376 else // element == EL_PACMAN
8378 Tile[newx][newy] = EL_EMPTY;
8379 TEST_DrawLevelField(newx, newy);
8380 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8383 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8384 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8385 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8387 // wait for shrinking amoeba to completely disappear
8390 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8392 // object was running against a wall
8396 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8397 DrawLevelElementAnimation(x, y, element);
8399 if (DONT_TOUCH(element))
8400 TestIfBadThingTouchesPlayer(x, y);
8405 InitMovingField(x, y, MovDir[x][y]);
8407 PlayLevelSoundAction(x, y, ACTION_MOVING);
8411 ContinueMoving(x, y);
8414 void ContinueMoving(int x, int y)
8416 int element = Tile[x][y];
8417 struct ElementInfo *ei = &element_info[element];
8418 int direction = MovDir[x][y];
8419 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8420 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8421 int newx = x + dx, newy = y + dy;
8422 int stored = Store[x][y];
8423 int stored_new = Store[newx][newy];
8424 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8425 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8426 boolean last_line = (newy == lev_fieldy - 1);
8428 MovPos[x][y] += getElementMoveStepsize(x, y);
8430 if (pushed_by_player) // special case: moving object pushed by player
8431 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8433 if (ABS(MovPos[x][y]) < TILEX)
8435 TEST_DrawLevelField(x, y);
8437 return; // element is still moving
8440 // element reached destination field
8442 Tile[x][y] = EL_EMPTY;
8443 Tile[newx][newy] = element;
8444 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8446 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8448 element = Tile[newx][newy] = EL_ACID;
8450 else if (element == EL_MOLE)
8452 Tile[x][y] = EL_SAND;
8454 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8456 else if (element == EL_QUICKSAND_FILLING)
8458 element = Tile[newx][newy] = get_next_element(element);
8459 Store[newx][newy] = Store[x][y];
8461 else if (element == EL_QUICKSAND_EMPTYING)
8463 Tile[x][y] = get_next_element(element);
8464 element = Tile[newx][newy] = Store[x][y];
8466 else if (element == EL_QUICKSAND_FAST_FILLING)
8468 element = Tile[newx][newy] = get_next_element(element);
8469 Store[newx][newy] = Store[x][y];
8471 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8473 Tile[x][y] = get_next_element(element);
8474 element = Tile[newx][newy] = Store[x][y];
8476 else if (element == EL_MAGIC_WALL_FILLING)
8478 element = Tile[newx][newy] = get_next_element(element);
8479 if (!game.magic_wall_active)
8480 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8481 Store[newx][newy] = Store[x][y];
8483 else if (element == EL_MAGIC_WALL_EMPTYING)
8485 Tile[x][y] = get_next_element(element);
8486 if (!game.magic_wall_active)
8487 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8488 element = Tile[newx][newy] = Store[x][y];
8490 InitField(newx, newy, FALSE);
8492 else if (element == EL_BD_MAGIC_WALL_FILLING)
8494 element = Tile[newx][newy] = get_next_element(element);
8495 if (!game.magic_wall_active)
8496 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8497 Store[newx][newy] = Store[x][y];
8499 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8501 Tile[x][y] = get_next_element(element);
8502 if (!game.magic_wall_active)
8503 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8504 element = Tile[newx][newy] = Store[x][y];
8506 InitField(newx, newy, FALSE);
8508 else if (element == EL_DC_MAGIC_WALL_FILLING)
8510 element = Tile[newx][newy] = get_next_element(element);
8511 if (!game.magic_wall_active)
8512 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8513 Store[newx][newy] = Store[x][y];
8515 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8517 Tile[x][y] = get_next_element(element);
8518 if (!game.magic_wall_active)
8519 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8520 element = Tile[newx][newy] = Store[x][y];
8522 InitField(newx, newy, FALSE);
8524 else if (element == EL_AMOEBA_DROPPING)
8526 Tile[x][y] = get_next_element(element);
8527 element = Tile[newx][newy] = Store[x][y];
8529 else if (element == EL_SOKOBAN_OBJECT)
8532 Tile[x][y] = Back[x][y];
8534 if (Back[newx][newy])
8535 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8537 Back[x][y] = Back[newx][newy] = 0;
8540 Store[x][y] = EL_EMPTY;
8545 MovDelay[newx][newy] = 0;
8547 if (CAN_CHANGE_OR_HAS_ACTION(element))
8549 // copy element change control values to new field
8550 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8551 ChangePage[newx][newy] = ChangePage[x][y];
8552 ChangeCount[newx][newy] = ChangeCount[x][y];
8553 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8556 CustomValue[newx][newy] = CustomValue[x][y];
8558 ChangeDelay[x][y] = 0;
8559 ChangePage[x][y] = -1;
8560 ChangeCount[x][y] = 0;
8561 ChangeEvent[x][y] = -1;
8563 CustomValue[x][y] = 0;
8565 // copy animation control values to new field
8566 GfxFrame[newx][newy] = GfxFrame[x][y];
8567 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8568 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8569 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8571 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8573 // some elements can leave other elements behind after moving
8574 if (ei->move_leave_element != EL_EMPTY &&
8575 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8576 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8578 int move_leave_element = ei->move_leave_element;
8580 // this makes it possible to leave the removed element again
8581 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8582 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8584 Tile[x][y] = move_leave_element;
8586 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8587 MovDir[x][y] = direction;
8589 InitField(x, y, FALSE);
8591 if (GFX_CRUMBLED(Tile[x][y]))
8592 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8594 if (ELEM_IS_PLAYER(move_leave_element))
8595 RelocatePlayer(x, y, move_leave_element);
8598 // do this after checking for left-behind element
8599 ResetGfxAnimation(x, y); // reset animation values for old field
8601 if (!CAN_MOVE(element) ||
8602 (CAN_FALL(element) && direction == MV_DOWN &&
8603 (element == EL_SPRING ||
8604 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8605 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8606 GfxDir[x][y] = MovDir[newx][newy] = 0;
8608 TEST_DrawLevelField(x, y);
8609 TEST_DrawLevelField(newx, newy);
8611 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8613 // prevent pushed element from moving on in pushed direction
8614 if (pushed_by_player && CAN_MOVE(element) &&
8615 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8616 !(element_info[element].move_pattern & direction))
8617 TurnRound(newx, newy);
8619 // prevent elements on conveyor belt from moving on in last direction
8620 if (pushed_by_conveyor && CAN_FALL(element) &&
8621 direction & MV_HORIZONTAL)
8622 MovDir[newx][newy] = 0;
8624 if (!pushed_by_player)
8626 int nextx = newx + dx, nexty = newy + dy;
8627 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8629 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8631 if (CAN_FALL(element) && direction == MV_DOWN)
8632 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8634 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8635 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8637 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8638 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8641 if (DONT_TOUCH(element)) // object may be nasty to player or others
8643 TestIfBadThingTouchesPlayer(newx, newy);
8644 TestIfBadThingTouchesFriend(newx, newy);
8646 if (!IS_CUSTOM_ELEMENT(element))
8647 TestIfBadThingTouchesOtherBadThing(newx, newy);
8649 else if (element == EL_PENGUIN)
8650 TestIfFriendTouchesBadThing(newx, newy);
8652 if (DONT_GET_HIT_BY(element))
8654 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8657 // give the player one last chance (one more frame) to move away
8658 if (CAN_FALL(element) && direction == MV_DOWN &&
8659 (last_line || (!IS_FREE(x, newy + 1) &&
8660 (!IS_PLAYER(x, newy + 1) ||
8661 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8664 if (pushed_by_player && !game.use_change_when_pushing_bug)
8666 int push_side = MV_DIR_OPPOSITE(direction);
8667 struct PlayerInfo *player = PLAYERINFO(x, y);
8669 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8670 player->index_bit, push_side);
8671 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8672 player->index_bit, push_side);
8675 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8676 MovDelay[newx][newy] = 1;
8678 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8680 TestIfElementTouchesCustomElement(x, y); // empty or new element
8681 TestIfElementHitsCustomElement(newx, newy, direction);
8682 TestIfPlayerTouchesCustomElement(newx, newy);
8683 TestIfElementTouchesCustomElement(newx, newy);
8685 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8686 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8687 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8688 MV_DIR_OPPOSITE(direction));
8691 int AmoebaNeighbourNr(int ax, int ay)
8694 int element = Tile[ax][ay];
8696 static int xy[4][2] =
8704 for (i = 0; i < NUM_DIRECTIONS; i++)
8706 int x = ax + xy[i][0];
8707 int y = ay + xy[i][1];
8709 if (!IN_LEV_FIELD(x, y))
8712 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8713 group_nr = AmoebaNr[x][y];
8719 static void AmoebaMerge(int ax, int ay)
8721 int i, x, y, xx, yy;
8722 int new_group_nr = AmoebaNr[ax][ay];
8723 static int xy[4][2] =
8731 if (new_group_nr == 0)
8734 for (i = 0; i < NUM_DIRECTIONS; i++)
8739 if (!IN_LEV_FIELD(x, y))
8742 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8743 Tile[x][y] == EL_BD_AMOEBA ||
8744 Tile[x][y] == EL_AMOEBA_DEAD) &&
8745 AmoebaNr[x][y] != new_group_nr)
8747 int old_group_nr = AmoebaNr[x][y];
8749 if (old_group_nr == 0)
8752 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8753 AmoebaCnt[old_group_nr] = 0;
8754 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8755 AmoebaCnt2[old_group_nr] = 0;
8757 SCAN_PLAYFIELD(xx, yy)
8759 if (AmoebaNr[xx][yy] == old_group_nr)
8760 AmoebaNr[xx][yy] = new_group_nr;
8766 void AmoebaToDiamond(int ax, int ay)
8770 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8772 int group_nr = AmoebaNr[ax][ay];
8777 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8778 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8784 SCAN_PLAYFIELD(x, y)
8786 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8789 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8793 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8794 SND_AMOEBA_TURNING_TO_GEM :
8795 SND_AMOEBA_TURNING_TO_ROCK));
8800 static int xy[4][2] =
8808 for (i = 0; i < NUM_DIRECTIONS; i++)
8813 if (!IN_LEV_FIELD(x, y))
8816 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8818 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8819 SND_AMOEBA_TURNING_TO_GEM :
8820 SND_AMOEBA_TURNING_TO_ROCK));
8827 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8830 int group_nr = AmoebaNr[ax][ay];
8831 boolean done = FALSE;
8836 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8837 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8843 SCAN_PLAYFIELD(x, y)
8845 if (AmoebaNr[x][y] == group_nr &&
8846 (Tile[x][y] == EL_AMOEBA_DEAD ||
8847 Tile[x][y] == EL_BD_AMOEBA ||
8848 Tile[x][y] == EL_AMOEBA_GROWING))
8851 Tile[x][y] = new_element;
8852 InitField(x, y, FALSE);
8853 TEST_DrawLevelField(x, y);
8859 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8860 SND_BD_AMOEBA_TURNING_TO_ROCK :
8861 SND_BD_AMOEBA_TURNING_TO_GEM));
8864 static void AmoebaGrowing(int x, int y)
8866 static unsigned int sound_delay = 0;
8867 static unsigned int sound_delay_value = 0;
8869 if (!MovDelay[x][y]) // start new growing cycle
8873 if (DelayReached(&sound_delay, sound_delay_value))
8875 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8876 sound_delay_value = 30;
8880 if (MovDelay[x][y]) // wait some time before growing bigger
8883 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8885 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8886 6 - MovDelay[x][y]);
8888 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8891 if (!MovDelay[x][y])
8893 Tile[x][y] = Store[x][y];
8895 TEST_DrawLevelField(x, y);
8900 static void AmoebaShrinking(int x, int y)
8902 static unsigned int sound_delay = 0;
8903 static unsigned int sound_delay_value = 0;
8905 if (!MovDelay[x][y]) // start new shrinking cycle
8909 if (DelayReached(&sound_delay, sound_delay_value))
8910 sound_delay_value = 30;
8913 if (MovDelay[x][y]) // wait some time before shrinking
8916 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8918 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8919 6 - MovDelay[x][y]);
8921 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
8924 if (!MovDelay[x][y])
8926 Tile[x][y] = EL_EMPTY;
8927 TEST_DrawLevelField(x, y);
8929 // don't let mole enter this field in this cycle;
8930 // (give priority to objects falling to this field from above)
8936 static void AmoebaReproduce(int ax, int ay)
8939 int element = Tile[ax][ay];
8940 int graphic = el2img(element);
8941 int newax = ax, neway = ay;
8942 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
8943 static int xy[4][2] =
8951 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
8953 Tile[ax][ay] = EL_AMOEBA_DEAD;
8954 TEST_DrawLevelField(ax, ay);
8958 if (IS_ANIMATED(graphic))
8959 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
8961 if (!MovDelay[ax][ay]) // start making new amoeba field
8962 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
8964 if (MovDelay[ax][ay]) // wait some time before making new amoeba
8967 if (MovDelay[ax][ay])
8971 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
8974 int x = ax + xy[start][0];
8975 int y = ay + xy[start][1];
8977 if (!IN_LEV_FIELD(x, y))
8980 if (IS_FREE(x, y) ||
8981 CAN_GROW_INTO(Tile[x][y]) ||
8982 Tile[x][y] == EL_QUICKSAND_EMPTY ||
8983 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
8989 if (newax == ax && neway == ay)
8992 else // normal or "filled" (BD style) amoeba
8995 boolean waiting_for_player = FALSE;
8997 for (i = 0; i < NUM_DIRECTIONS; i++)
8999 int j = (start + i) % 4;
9000 int x = ax + xy[j][0];
9001 int y = ay + xy[j][1];
9003 if (!IN_LEV_FIELD(x, y))
9006 if (IS_FREE(x, y) ||
9007 CAN_GROW_INTO(Tile[x][y]) ||
9008 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9009 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9015 else if (IS_PLAYER(x, y))
9016 waiting_for_player = TRUE;
9019 if (newax == ax && neway == ay) // amoeba cannot grow
9021 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9023 Tile[ax][ay] = EL_AMOEBA_DEAD;
9024 TEST_DrawLevelField(ax, ay);
9025 AmoebaCnt[AmoebaNr[ax][ay]]--;
9027 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9029 if (element == EL_AMOEBA_FULL)
9030 AmoebaToDiamond(ax, ay);
9031 else if (element == EL_BD_AMOEBA)
9032 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9037 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9039 // amoeba gets larger by growing in some direction
9041 int new_group_nr = AmoebaNr[ax][ay];
9044 if (new_group_nr == 0)
9046 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9048 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9054 AmoebaNr[newax][neway] = new_group_nr;
9055 AmoebaCnt[new_group_nr]++;
9056 AmoebaCnt2[new_group_nr]++;
9058 // if amoeba touches other amoeba(s) after growing, unify them
9059 AmoebaMerge(newax, neway);
9061 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9063 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9069 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9070 (neway == lev_fieldy - 1 && newax != ax))
9072 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9073 Store[newax][neway] = element;
9075 else if (neway == ay || element == EL_EMC_DRIPPER)
9077 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9079 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9083 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9084 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9085 Store[ax][ay] = EL_AMOEBA_DROP;
9086 ContinueMoving(ax, ay);
9090 TEST_DrawLevelField(newax, neway);
9093 static void Life(int ax, int ay)
9097 int element = Tile[ax][ay];
9098 int graphic = el2img(element);
9099 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9101 boolean changed = FALSE;
9103 if (IS_ANIMATED(graphic))
9104 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9109 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9110 MovDelay[ax][ay] = life_time;
9112 if (MovDelay[ax][ay]) // wait some time before next cycle
9115 if (MovDelay[ax][ay])
9119 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9121 int xx = ax+x1, yy = ay+y1;
9122 int old_element = Tile[xx][yy];
9123 int num_neighbours = 0;
9125 if (!IN_LEV_FIELD(xx, yy))
9128 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9130 int x = xx+x2, y = yy+y2;
9132 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9135 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9136 boolean is_neighbour = FALSE;
9138 if (level.use_life_bugs)
9140 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9141 (IS_FREE(x, y) && Stop[x][y]));
9144 (Last[x][y] == element || is_player_cell);
9150 boolean is_free = FALSE;
9152 if (level.use_life_bugs)
9153 is_free = (IS_FREE(xx, yy));
9155 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9157 if (xx == ax && yy == ay) // field in the middle
9159 if (num_neighbours < life_parameter[0] ||
9160 num_neighbours > life_parameter[1])
9162 Tile[xx][yy] = EL_EMPTY;
9163 if (Tile[xx][yy] != old_element)
9164 TEST_DrawLevelField(xx, yy);
9165 Stop[xx][yy] = TRUE;
9169 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9170 { // free border field
9171 if (num_neighbours >= life_parameter[2] &&
9172 num_neighbours <= life_parameter[3])
9174 Tile[xx][yy] = element;
9175 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9176 if (Tile[xx][yy] != old_element)
9177 TEST_DrawLevelField(xx, yy);
9178 Stop[xx][yy] = TRUE;
9185 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9186 SND_GAME_OF_LIFE_GROWING);
9189 static void InitRobotWheel(int x, int y)
9191 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9194 static void RunRobotWheel(int x, int y)
9196 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9199 static void StopRobotWheel(int x, int y)
9201 if (game.robot_wheel_x == x &&
9202 game.robot_wheel_y == y)
9204 game.robot_wheel_x = -1;
9205 game.robot_wheel_y = -1;
9206 game.robot_wheel_active = FALSE;
9210 static void InitTimegateWheel(int x, int y)
9212 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9215 static void RunTimegateWheel(int x, int y)
9217 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9220 static void InitMagicBallDelay(int x, int y)
9222 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9225 static void ActivateMagicBall(int bx, int by)
9229 if (level.ball_random)
9231 int pos_border = RND(8); // select one of the eight border elements
9232 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9233 int xx = pos_content % 3;
9234 int yy = pos_content / 3;
9239 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9240 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9244 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9246 int xx = x - bx + 1;
9247 int yy = y - by + 1;
9249 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9250 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9254 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9257 static void CheckExit(int x, int y)
9259 if (game.gems_still_needed > 0 ||
9260 game.sokoban_fields_still_needed > 0 ||
9261 game.sokoban_objects_still_needed > 0 ||
9262 game.lights_still_needed > 0)
9264 int element = Tile[x][y];
9265 int graphic = el2img(element);
9267 if (IS_ANIMATED(graphic))
9268 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9273 // do not re-open exit door closed after last player
9274 if (game.all_players_gone)
9277 Tile[x][y] = EL_EXIT_OPENING;
9279 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9282 static void CheckExitEM(int x, int y)
9284 if (game.gems_still_needed > 0 ||
9285 game.sokoban_fields_still_needed > 0 ||
9286 game.sokoban_objects_still_needed > 0 ||
9287 game.lights_still_needed > 0)
9289 int element = Tile[x][y];
9290 int graphic = el2img(element);
9292 if (IS_ANIMATED(graphic))
9293 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9298 // do not re-open exit door closed after last player
9299 if (game.all_players_gone)
9302 Tile[x][y] = EL_EM_EXIT_OPENING;
9304 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9307 static void CheckExitSteel(int x, int y)
9309 if (game.gems_still_needed > 0 ||
9310 game.sokoban_fields_still_needed > 0 ||
9311 game.sokoban_objects_still_needed > 0 ||
9312 game.lights_still_needed > 0)
9314 int element = Tile[x][y];
9315 int graphic = el2img(element);
9317 if (IS_ANIMATED(graphic))
9318 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9323 // do not re-open exit door closed after last player
9324 if (game.all_players_gone)
9327 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9329 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9332 static void CheckExitSteelEM(int x, int y)
9334 if (game.gems_still_needed > 0 ||
9335 game.sokoban_fields_still_needed > 0 ||
9336 game.sokoban_objects_still_needed > 0 ||
9337 game.lights_still_needed > 0)
9339 int element = Tile[x][y];
9340 int graphic = el2img(element);
9342 if (IS_ANIMATED(graphic))
9343 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9348 // do not re-open exit door closed after last player
9349 if (game.all_players_gone)
9352 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9354 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9357 static void CheckExitSP(int x, int y)
9359 if (game.gems_still_needed > 0)
9361 int element = Tile[x][y];
9362 int graphic = el2img(element);
9364 if (IS_ANIMATED(graphic))
9365 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9370 // do not re-open exit door closed after last player
9371 if (game.all_players_gone)
9374 Tile[x][y] = EL_SP_EXIT_OPENING;
9376 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9379 static void CloseAllOpenTimegates(void)
9383 SCAN_PLAYFIELD(x, y)
9385 int element = Tile[x][y];
9387 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9389 Tile[x][y] = EL_TIMEGATE_CLOSING;
9391 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9396 static void DrawTwinkleOnField(int x, int y)
9398 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9401 if (Tile[x][y] == EL_BD_DIAMOND)
9404 if (MovDelay[x][y] == 0) // next animation frame
9405 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9407 if (MovDelay[x][y] != 0) // wait some time before next frame
9411 DrawLevelElementAnimation(x, y, Tile[x][y]);
9413 if (MovDelay[x][y] != 0)
9415 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9416 10 - MovDelay[x][y]);
9418 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9423 static void MauerWaechst(int x, int y)
9427 if (!MovDelay[x][y]) // next animation frame
9428 MovDelay[x][y] = 3 * delay;
9430 if (MovDelay[x][y]) // wait some time before next frame
9434 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9436 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9437 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9439 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9442 if (!MovDelay[x][y])
9444 if (MovDir[x][y] == MV_LEFT)
9446 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9447 TEST_DrawLevelField(x - 1, y);
9449 else if (MovDir[x][y] == MV_RIGHT)
9451 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9452 TEST_DrawLevelField(x + 1, y);
9454 else if (MovDir[x][y] == MV_UP)
9456 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9457 TEST_DrawLevelField(x, y - 1);
9461 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9462 TEST_DrawLevelField(x, y + 1);
9465 Tile[x][y] = Store[x][y];
9467 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9468 TEST_DrawLevelField(x, y);
9473 static void MauerAbleger(int ax, int ay)
9475 int element = Tile[ax][ay];
9476 int graphic = el2img(element);
9477 boolean oben_frei = FALSE, unten_frei = FALSE;
9478 boolean links_frei = FALSE, rechts_frei = FALSE;
9479 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9480 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9481 boolean new_wall = FALSE;
9483 if (IS_ANIMATED(graphic))
9484 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9486 if (!MovDelay[ax][ay]) // start building new wall
9487 MovDelay[ax][ay] = 6;
9489 if (MovDelay[ax][ay]) // wait some time before building new wall
9492 if (MovDelay[ax][ay])
9496 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9498 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9500 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9502 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9505 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9506 element == EL_EXPANDABLE_WALL_ANY)
9510 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9511 Store[ax][ay-1] = element;
9512 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9513 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9514 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9515 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9520 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9521 Store[ax][ay+1] = element;
9522 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9523 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9524 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9525 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9530 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9531 element == EL_EXPANDABLE_WALL_ANY ||
9532 element == EL_EXPANDABLE_WALL ||
9533 element == EL_BD_EXPANDABLE_WALL)
9537 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9538 Store[ax-1][ay] = element;
9539 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9540 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9541 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9542 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9548 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9549 Store[ax+1][ay] = element;
9550 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9551 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9552 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9553 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9558 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9559 TEST_DrawLevelField(ax, ay);
9561 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9563 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9564 unten_massiv = TRUE;
9565 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9566 links_massiv = TRUE;
9567 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9568 rechts_massiv = TRUE;
9570 if (((oben_massiv && unten_massiv) ||
9571 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9572 element == EL_EXPANDABLE_WALL) &&
9573 ((links_massiv && rechts_massiv) ||
9574 element == EL_EXPANDABLE_WALL_VERTICAL))
9575 Tile[ax][ay] = EL_WALL;
9578 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9581 static void MauerAblegerStahl(int ax, int ay)
9583 int element = Tile[ax][ay];
9584 int graphic = el2img(element);
9585 boolean oben_frei = FALSE, unten_frei = FALSE;
9586 boolean links_frei = FALSE, rechts_frei = FALSE;
9587 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9588 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9589 boolean new_wall = FALSE;
9591 if (IS_ANIMATED(graphic))
9592 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9594 if (!MovDelay[ax][ay]) // start building new wall
9595 MovDelay[ax][ay] = 6;
9597 if (MovDelay[ax][ay]) // wait some time before building new wall
9600 if (MovDelay[ax][ay])
9604 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9606 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9608 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9610 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9613 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9614 element == EL_EXPANDABLE_STEELWALL_ANY)
9618 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9619 Store[ax][ay-1] = element;
9620 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9621 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9622 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9623 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9628 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9629 Store[ax][ay+1] = element;
9630 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9631 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9632 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9633 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9638 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9639 element == EL_EXPANDABLE_STEELWALL_ANY)
9643 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9644 Store[ax-1][ay] = element;
9645 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9646 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9647 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9648 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9654 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9655 Store[ax+1][ay] = element;
9656 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9657 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9658 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9659 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9664 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9666 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9667 unten_massiv = TRUE;
9668 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9669 links_massiv = TRUE;
9670 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9671 rechts_massiv = TRUE;
9673 if (((oben_massiv && unten_massiv) ||
9674 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9675 ((links_massiv && rechts_massiv) ||
9676 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9677 Tile[ax][ay] = EL_STEELWALL;
9680 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9683 static void CheckForDragon(int x, int y)
9686 boolean dragon_found = FALSE;
9687 static int xy[4][2] =
9695 for (i = 0; i < NUM_DIRECTIONS; i++)
9697 for (j = 0; j < 4; j++)
9699 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9701 if (IN_LEV_FIELD(xx, yy) &&
9702 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9704 if (Tile[xx][yy] == EL_DRAGON)
9705 dragon_found = TRUE;
9714 for (i = 0; i < NUM_DIRECTIONS; i++)
9716 for (j = 0; j < 3; j++)
9718 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9720 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9722 Tile[xx][yy] = EL_EMPTY;
9723 TEST_DrawLevelField(xx, yy);
9732 static void InitBuggyBase(int x, int y)
9734 int element = Tile[x][y];
9735 int activating_delay = FRAMES_PER_SECOND / 4;
9738 (element == EL_SP_BUGGY_BASE ?
9739 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9740 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9742 element == EL_SP_BUGGY_BASE_ACTIVE ?
9743 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9746 static void WarnBuggyBase(int x, int y)
9749 static int xy[4][2] =
9757 for (i = 0; i < NUM_DIRECTIONS; i++)
9759 int xx = x + xy[i][0];
9760 int yy = y + xy[i][1];
9762 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9764 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9771 static void InitTrap(int x, int y)
9773 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9776 static void ActivateTrap(int x, int y)
9778 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9781 static void ChangeActiveTrap(int x, int y)
9783 int graphic = IMG_TRAP_ACTIVE;
9785 // if new animation frame was drawn, correct crumbled sand border
9786 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9787 TEST_DrawLevelFieldCrumbled(x, y);
9790 static int getSpecialActionElement(int element, int number, int base_element)
9792 return (element != EL_EMPTY ? element :
9793 number != -1 ? base_element + number - 1 :
9797 static int getModifiedActionNumber(int value_old, int operator, int operand,
9798 int value_min, int value_max)
9800 int value_new = (operator == CA_MODE_SET ? operand :
9801 operator == CA_MODE_ADD ? value_old + operand :
9802 operator == CA_MODE_SUBTRACT ? value_old - operand :
9803 operator == CA_MODE_MULTIPLY ? value_old * operand :
9804 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9805 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9808 return (value_new < value_min ? value_min :
9809 value_new > value_max ? value_max :
9813 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9815 struct ElementInfo *ei = &element_info[element];
9816 struct ElementChangeInfo *change = &ei->change_page[page];
9817 int target_element = change->target_element;
9818 int action_type = change->action_type;
9819 int action_mode = change->action_mode;
9820 int action_arg = change->action_arg;
9821 int action_element = change->action_element;
9824 if (!change->has_action)
9827 // ---------- determine action paramater values -----------------------------
9829 int level_time_value =
9830 (level.time > 0 ? TimeLeft :
9833 int action_arg_element_raw =
9834 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9835 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9836 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9837 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9838 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9839 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9840 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9842 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9844 int action_arg_direction =
9845 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9846 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9847 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9848 change->actual_trigger_side :
9849 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9850 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9853 int action_arg_number_min =
9854 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9857 int action_arg_number_max =
9858 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9859 action_type == CA_SET_LEVEL_GEMS ? 999 :
9860 action_type == CA_SET_LEVEL_TIME ? 9999 :
9861 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9862 action_type == CA_SET_CE_VALUE ? 9999 :
9863 action_type == CA_SET_CE_SCORE ? 9999 :
9866 int action_arg_number_reset =
9867 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9868 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9869 action_type == CA_SET_LEVEL_TIME ? level.time :
9870 action_type == CA_SET_LEVEL_SCORE ? 0 :
9871 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9872 action_type == CA_SET_CE_SCORE ? 0 :
9875 int action_arg_number =
9876 (action_arg <= CA_ARG_MAX ? action_arg :
9877 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9878 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9879 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9880 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9881 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9882 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9883 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9884 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9885 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9886 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9887 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9888 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9889 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9890 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9891 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9892 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9893 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9894 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9895 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9896 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9897 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9900 int action_arg_number_old =
9901 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9902 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9903 action_type == CA_SET_LEVEL_SCORE ? game.score :
9904 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9905 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9908 int action_arg_number_new =
9909 getModifiedActionNumber(action_arg_number_old,
9910 action_mode, action_arg_number,
9911 action_arg_number_min, action_arg_number_max);
9913 int trigger_player_bits =
9914 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9915 change->actual_trigger_player_bits : change->trigger_player);
9917 int action_arg_player_bits =
9918 (action_arg >= CA_ARG_PLAYER_1 &&
9919 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9920 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
9921 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
9924 // ---------- execute action -----------------------------------------------
9926 switch (action_type)
9933 // ---------- level actions ----------------------------------------------
9935 case CA_RESTART_LEVEL:
9937 game.restart_level = TRUE;
9942 case CA_SHOW_ENVELOPE:
9944 int element = getSpecialActionElement(action_arg_element,
9945 action_arg_number, EL_ENVELOPE_1);
9947 if (IS_ENVELOPE(element))
9948 local_player->show_envelope = element;
9953 case CA_SET_LEVEL_TIME:
9955 if (level.time > 0) // only modify limited time value
9957 TimeLeft = action_arg_number_new;
9959 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
9961 DisplayGameControlValues();
9963 if (!TimeLeft && setup.time_limit)
9964 for (i = 0; i < MAX_PLAYERS; i++)
9965 KillPlayer(&stored_player[i]);
9971 case CA_SET_LEVEL_SCORE:
9973 game.score = action_arg_number_new;
9975 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
9977 DisplayGameControlValues();
9982 case CA_SET_LEVEL_GEMS:
9984 game.gems_still_needed = action_arg_number_new;
9986 game.snapshot.collected_item = TRUE;
9988 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
9990 DisplayGameControlValues();
9995 case CA_SET_LEVEL_WIND:
9997 game.wind_direction = action_arg_direction;
10002 case CA_SET_LEVEL_RANDOM_SEED:
10004 // ensure that setting a new random seed while playing is predictable
10005 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10010 // ---------- player actions ---------------------------------------------
10012 case CA_MOVE_PLAYER:
10013 case CA_MOVE_PLAYER_NEW:
10015 // automatically move to the next field in specified direction
10016 for (i = 0; i < MAX_PLAYERS; i++)
10017 if (trigger_player_bits & (1 << i))
10018 if (action_type == CA_MOVE_PLAYER ||
10019 stored_player[i].MovPos == 0)
10020 stored_player[i].programmed_action = action_arg_direction;
10025 case CA_EXIT_PLAYER:
10027 for (i = 0; i < MAX_PLAYERS; i++)
10028 if (action_arg_player_bits & (1 << i))
10029 ExitPlayer(&stored_player[i]);
10031 if (game.players_still_needed == 0)
10037 case CA_KILL_PLAYER:
10039 for (i = 0; i < MAX_PLAYERS; i++)
10040 if (action_arg_player_bits & (1 << i))
10041 KillPlayer(&stored_player[i]);
10046 case CA_SET_PLAYER_KEYS:
10048 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10049 int element = getSpecialActionElement(action_arg_element,
10050 action_arg_number, EL_KEY_1);
10052 if (IS_KEY(element))
10054 for (i = 0; i < MAX_PLAYERS; i++)
10056 if (trigger_player_bits & (1 << i))
10058 stored_player[i].key[KEY_NR(element)] = key_state;
10060 DrawGameDoorValues();
10068 case CA_SET_PLAYER_SPEED:
10070 for (i = 0; i < MAX_PLAYERS; i++)
10072 if (trigger_player_bits & (1 << i))
10074 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10076 if (action_arg == CA_ARG_SPEED_FASTER &&
10077 stored_player[i].cannot_move)
10079 action_arg_number = STEPSIZE_VERY_SLOW;
10081 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10082 action_arg == CA_ARG_SPEED_FASTER)
10084 action_arg_number = 2;
10085 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10088 else if (action_arg == CA_ARG_NUMBER_RESET)
10090 action_arg_number = level.initial_player_stepsize[i];
10094 getModifiedActionNumber(move_stepsize,
10097 action_arg_number_min,
10098 action_arg_number_max);
10100 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10107 case CA_SET_PLAYER_SHIELD:
10109 for (i = 0; i < MAX_PLAYERS; i++)
10111 if (trigger_player_bits & (1 << i))
10113 if (action_arg == CA_ARG_SHIELD_OFF)
10115 stored_player[i].shield_normal_time_left = 0;
10116 stored_player[i].shield_deadly_time_left = 0;
10118 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10120 stored_player[i].shield_normal_time_left = 999999;
10122 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10124 stored_player[i].shield_normal_time_left = 999999;
10125 stored_player[i].shield_deadly_time_left = 999999;
10133 case CA_SET_PLAYER_GRAVITY:
10135 for (i = 0; i < MAX_PLAYERS; i++)
10137 if (trigger_player_bits & (1 << i))
10139 stored_player[i].gravity =
10140 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10141 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10142 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10143 stored_player[i].gravity);
10150 case CA_SET_PLAYER_ARTWORK:
10152 for (i = 0; i < MAX_PLAYERS; i++)
10154 if (trigger_player_bits & (1 << i))
10156 int artwork_element = action_arg_element;
10158 if (action_arg == CA_ARG_ELEMENT_RESET)
10160 (level.use_artwork_element[i] ? level.artwork_element[i] :
10161 stored_player[i].element_nr);
10163 if (stored_player[i].artwork_element != artwork_element)
10164 stored_player[i].Frame = 0;
10166 stored_player[i].artwork_element = artwork_element;
10168 SetPlayerWaiting(&stored_player[i], FALSE);
10170 // set number of special actions for bored and sleeping animation
10171 stored_player[i].num_special_action_bored =
10172 get_num_special_action(artwork_element,
10173 ACTION_BORING_1, ACTION_BORING_LAST);
10174 stored_player[i].num_special_action_sleeping =
10175 get_num_special_action(artwork_element,
10176 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10183 case CA_SET_PLAYER_INVENTORY:
10185 for (i = 0; i < MAX_PLAYERS; i++)
10187 struct PlayerInfo *player = &stored_player[i];
10190 if (trigger_player_bits & (1 << i))
10192 int inventory_element = action_arg_element;
10194 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10195 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10196 action_arg == CA_ARG_ELEMENT_ACTION)
10198 int element = inventory_element;
10199 int collect_count = element_info[element].collect_count_initial;
10201 if (!IS_CUSTOM_ELEMENT(element))
10204 if (collect_count == 0)
10205 player->inventory_infinite_element = element;
10207 for (k = 0; k < collect_count; k++)
10208 if (player->inventory_size < MAX_INVENTORY_SIZE)
10209 player->inventory_element[player->inventory_size++] =
10212 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10213 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10214 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10216 if (player->inventory_infinite_element != EL_UNDEFINED &&
10217 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10218 action_arg_element_raw))
10219 player->inventory_infinite_element = EL_UNDEFINED;
10221 for (k = 0, j = 0; j < player->inventory_size; j++)
10223 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10224 action_arg_element_raw))
10225 player->inventory_element[k++] = player->inventory_element[j];
10228 player->inventory_size = k;
10230 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10232 if (player->inventory_size > 0)
10234 for (j = 0; j < player->inventory_size - 1; j++)
10235 player->inventory_element[j] = player->inventory_element[j + 1];
10237 player->inventory_size--;
10240 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10242 if (player->inventory_size > 0)
10243 player->inventory_size--;
10245 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10247 player->inventory_infinite_element = EL_UNDEFINED;
10248 player->inventory_size = 0;
10250 else if (action_arg == CA_ARG_INVENTORY_RESET)
10252 player->inventory_infinite_element = EL_UNDEFINED;
10253 player->inventory_size = 0;
10255 if (level.use_initial_inventory[i])
10257 for (j = 0; j < level.initial_inventory_size[i]; j++)
10259 int element = level.initial_inventory_content[i][j];
10260 int collect_count = element_info[element].collect_count_initial;
10262 if (!IS_CUSTOM_ELEMENT(element))
10265 if (collect_count == 0)
10266 player->inventory_infinite_element = element;
10268 for (k = 0; k < collect_count; k++)
10269 if (player->inventory_size < MAX_INVENTORY_SIZE)
10270 player->inventory_element[player->inventory_size++] =
10281 // ---------- CE actions -------------------------------------------------
10283 case CA_SET_CE_VALUE:
10285 int last_ce_value = CustomValue[x][y];
10287 CustomValue[x][y] = action_arg_number_new;
10289 if (CustomValue[x][y] != last_ce_value)
10291 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10292 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10294 if (CustomValue[x][y] == 0)
10296 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10297 ChangeCount[x][y] = 0; // allow at least one more change
10299 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10300 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10307 case CA_SET_CE_SCORE:
10309 int last_ce_score = ei->collect_score;
10311 ei->collect_score = action_arg_number_new;
10313 if (ei->collect_score != last_ce_score)
10315 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10316 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10318 if (ei->collect_score == 0)
10322 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10323 ChangeCount[x][y] = 0; // allow at least one more change
10325 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10326 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10329 This is a very special case that seems to be a mixture between
10330 CheckElementChange() and CheckTriggeredElementChange(): while
10331 the first one only affects single elements that are triggered
10332 directly, the second one affects multiple elements in the playfield
10333 that are triggered indirectly by another element. This is a third
10334 case: Changing the CE score always affects multiple identical CEs,
10335 so every affected CE must be checked, not only the single CE for
10336 which the CE score was changed in the first place (as every instance
10337 of that CE shares the same CE score, and therefore also can change)!
10339 SCAN_PLAYFIELD(xx, yy)
10341 if (Tile[xx][yy] == element)
10342 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10343 CE_SCORE_GETS_ZERO);
10351 case CA_SET_CE_ARTWORK:
10353 int artwork_element = action_arg_element;
10354 boolean reset_frame = FALSE;
10357 if (action_arg == CA_ARG_ELEMENT_RESET)
10358 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10361 if (ei->gfx_element != artwork_element)
10362 reset_frame = TRUE;
10364 ei->gfx_element = artwork_element;
10366 SCAN_PLAYFIELD(xx, yy)
10368 if (Tile[xx][yy] == element)
10372 ResetGfxAnimation(xx, yy);
10373 ResetRandomAnimationValue(xx, yy);
10376 TEST_DrawLevelField(xx, yy);
10383 // ---------- engine actions ---------------------------------------------
10385 case CA_SET_ENGINE_SCAN_MODE:
10387 InitPlayfieldScanMode(action_arg);
10397 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10399 int old_element = Tile[x][y];
10400 int new_element = GetElementFromGroupElement(element);
10401 int previous_move_direction = MovDir[x][y];
10402 int last_ce_value = CustomValue[x][y];
10403 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10404 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10405 boolean add_player_onto_element = (new_element_is_player &&
10406 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10407 IS_WALKABLE(old_element));
10409 if (!add_player_onto_element)
10411 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10412 RemoveMovingField(x, y);
10416 Tile[x][y] = new_element;
10418 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10419 MovDir[x][y] = previous_move_direction;
10421 if (element_info[new_element].use_last_ce_value)
10422 CustomValue[x][y] = last_ce_value;
10424 InitField_WithBug1(x, y, FALSE);
10426 new_element = Tile[x][y]; // element may have changed
10428 ResetGfxAnimation(x, y);
10429 ResetRandomAnimationValue(x, y);
10431 TEST_DrawLevelField(x, y);
10433 if (GFX_CRUMBLED(new_element))
10434 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10437 // check if element under the player changes from accessible to unaccessible
10438 // (needed for special case of dropping element which then changes)
10439 // (must be checked after creating new element for walkable group elements)
10440 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10441 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10448 // "ChangeCount" not set yet to allow "entered by player" change one time
10449 if (new_element_is_player)
10450 RelocatePlayer(x, y, new_element);
10453 ChangeCount[x][y]++; // count number of changes in the same frame
10455 TestIfBadThingTouchesPlayer(x, y);
10456 TestIfPlayerTouchesCustomElement(x, y);
10457 TestIfElementTouchesCustomElement(x, y);
10460 static void CreateField(int x, int y, int element)
10462 CreateFieldExt(x, y, element, FALSE);
10465 static void CreateElementFromChange(int x, int y, int element)
10467 element = GET_VALID_RUNTIME_ELEMENT(element);
10469 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10471 int old_element = Tile[x][y];
10473 // prevent changed element from moving in same engine frame
10474 // unless both old and new element can either fall or move
10475 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10476 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10480 CreateFieldExt(x, y, element, TRUE);
10483 static boolean ChangeElement(int x, int y, int element, int page)
10485 struct ElementInfo *ei = &element_info[element];
10486 struct ElementChangeInfo *change = &ei->change_page[page];
10487 int ce_value = CustomValue[x][y];
10488 int ce_score = ei->collect_score;
10489 int target_element;
10490 int old_element = Tile[x][y];
10492 // always use default change event to prevent running into a loop
10493 if (ChangeEvent[x][y] == -1)
10494 ChangeEvent[x][y] = CE_DELAY;
10496 if (ChangeEvent[x][y] == CE_DELAY)
10498 // reset actual trigger element, trigger player and action element
10499 change->actual_trigger_element = EL_EMPTY;
10500 change->actual_trigger_player = EL_EMPTY;
10501 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10502 change->actual_trigger_side = CH_SIDE_NONE;
10503 change->actual_trigger_ce_value = 0;
10504 change->actual_trigger_ce_score = 0;
10507 // do not change elements more than a specified maximum number of changes
10508 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10511 ChangeCount[x][y]++; // count number of changes in the same frame
10513 if (change->explode)
10520 if (change->use_target_content)
10522 boolean complete_replace = TRUE;
10523 boolean can_replace[3][3];
10526 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10529 boolean is_walkable;
10530 boolean is_diggable;
10531 boolean is_collectible;
10532 boolean is_removable;
10533 boolean is_destructible;
10534 int ex = x + xx - 1;
10535 int ey = y + yy - 1;
10536 int content_element = change->target_content.e[xx][yy];
10539 can_replace[xx][yy] = TRUE;
10541 if (ex == x && ey == y) // do not check changing element itself
10544 if (content_element == EL_EMPTY_SPACE)
10546 can_replace[xx][yy] = FALSE; // do not replace border with space
10551 if (!IN_LEV_FIELD(ex, ey))
10553 can_replace[xx][yy] = FALSE;
10554 complete_replace = FALSE;
10561 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10562 e = MovingOrBlocked2Element(ex, ey);
10564 is_empty = (IS_FREE(ex, ey) ||
10565 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10567 is_walkable = (is_empty || IS_WALKABLE(e));
10568 is_diggable = (is_empty || IS_DIGGABLE(e));
10569 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10570 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10571 is_removable = (is_diggable || is_collectible);
10573 can_replace[xx][yy] =
10574 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10575 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10576 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10577 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10578 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10579 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10580 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10582 if (!can_replace[xx][yy])
10583 complete_replace = FALSE;
10586 if (!change->only_if_complete || complete_replace)
10588 boolean something_has_changed = FALSE;
10590 if (change->only_if_complete && change->use_random_replace &&
10591 RND(100) < change->random_percentage)
10594 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10596 int ex = x + xx - 1;
10597 int ey = y + yy - 1;
10598 int content_element;
10600 if (can_replace[xx][yy] && (!change->use_random_replace ||
10601 RND(100) < change->random_percentage))
10603 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10604 RemoveMovingField(ex, ey);
10606 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10608 content_element = change->target_content.e[xx][yy];
10609 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10610 ce_value, ce_score);
10612 CreateElementFromChange(ex, ey, target_element);
10614 something_has_changed = TRUE;
10616 // for symmetry reasons, freeze newly created border elements
10617 if (ex != x || ey != y)
10618 Stop[ex][ey] = TRUE; // no more moving in this frame
10622 if (something_has_changed)
10624 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10625 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10631 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10632 ce_value, ce_score);
10634 if (element == EL_DIAGONAL_GROWING ||
10635 element == EL_DIAGONAL_SHRINKING)
10637 target_element = Store[x][y];
10639 Store[x][y] = EL_EMPTY;
10642 CreateElementFromChange(x, y, target_element);
10644 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10645 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10648 // this uses direct change before indirect change
10649 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10654 static void HandleElementChange(int x, int y, int page)
10656 int element = MovingOrBlocked2Element(x, y);
10657 struct ElementInfo *ei = &element_info[element];
10658 struct ElementChangeInfo *change = &ei->change_page[page];
10659 boolean handle_action_before_change = FALSE;
10662 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10663 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10665 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10666 x, y, element, element_info[element].token_name);
10667 Debug("game:playing:HandleElementChange", "This should never happen!");
10671 // this can happen with classic bombs on walkable, changing elements
10672 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10677 if (ChangeDelay[x][y] == 0) // initialize element change
10679 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10681 if (change->can_change)
10683 // !!! not clear why graphic animation should be reset at all here !!!
10684 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10685 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10688 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10690 When using an animation frame delay of 1 (this only happens with
10691 "sp_zonk.moving.left/right" in the classic graphics), the default
10692 (non-moving) animation shows wrong animation frames (while the
10693 moving animation, like "sp_zonk.moving.left/right", is correct,
10694 so this graphical bug never shows up with the classic graphics).
10695 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10696 be drawn instead of the correct frames 0,1,2,3. This is caused by
10697 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10698 an element change: First when the change delay ("ChangeDelay[][]")
10699 counter has reached zero after decrementing, then a second time in
10700 the next frame (after "GfxFrame[][]" was already incremented) when
10701 "ChangeDelay[][]" is reset to the initial delay value again.
10703 This causes frame 0 to be drawn twice, while the last frame won't
10704 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10706 As some animations may already be cleverly designed around this bug
10707 (at least the "Snake Bite" snake tail animation does this), it cannot
10708 simply be fixed here without breaking such existing animations.
10709 Unfortunately, it cannot easily be detected if a graphics set was
10710 designed "before" or "after" the bug was fixed. As a workaround,
10711 a new graphics set option "game.graphics_engine_version" was added
10712 to be able to specify the game's major release version for which the
10713 graphics set was designed, which can then be used to decide if the
10714 bugfix should be used (version 4 and above) or not (version 3 or
10715 below, or if no version was specified at all, as with old sets).
10717 (The wrong/fixed animation frames can be tested with the test level set
10718 "test_gfxframe" and level "000", which contains a specially prepared
10719 custom element at level position (x/y) == (11/9) which uses the zonk
10720 animation mentioned above. Using "game.graphics_engine_version: 4"
10721 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10722 This can also be seen from the debug output for this test element.)
10725 // when a custom element is about to change (for example by change delay),
10726 // do not reset graphic animation when the custom element is moving
10727 if (game.graphics_engine_version < 4 &&
10730 ResetGfxAnimation(x, y);
10731 ResetRandomAnimationValue(x, y);
10734 if (change->pre_change_function)
10735 change->pre_change_function(x, y);
10739 ChangeDelay[x][y]--;
10741 if (ChangeDelay[x][y] != 0) // continue element change
10743 if (change->can_change)
10745 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10747 if (IS_ANIMATED(graphic))
10748 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10750 if (change->change_function)
10751 change->change_function(x, y);
10754 else // finish element change
10756 if (ChangePage[x][y] != -1) // remember page from delayed change
10758 page = ChangePage[x][y];
10759 ChangePage[x][y] = -1;
10761 change = &ei->change_page[page];
10764 if (IS_MOVING(x, y)) // never change a running system ;-)
10766 ChangeDelay[x][y] = 1; // try change after next move step
10767 ChangePage[x][y] = page; // remember page to use for change
10772 // special case: set new level random seed before changing element
10773 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10774 handle_action_before_change = TRUE;
10776 if (change->has_action && handle_action_before_change)
10777 ExecuteCustomElementAction(x, y, element, page);
10779 if (change->can_change)
10781 if (ChangeElement(x, y, element, page))
10783 if (change->post_change_function)
10784 change->post_change_function(x, y);
10788 if (change->has_action && !handle_action_before_change)
10789 ExecuteCustomElementAction(x, y, element, page);
10793 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10794 int trigger_element,
10796 int trigger_player,
10800 boolean change_done_any = FALSE;
10801 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10804 if (!(trigger_events[trigger_element][trigger_event]))
10807 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10809 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10811 int element = EL_CUSTOM_START + i;
10812 boolean change_done = FALSE;
10815 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10816 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10819 for (p = 0; p < element_info[element].num_change_pages; p++)
10821 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10823 if (change->can_change_or_has_action &&
10824 change->has_event[trigger_event] &&
10825 change->trigger_side & trigger_side &&
10826 change->trigger_player & trigger_player &&
10827 change->trigger_page & trigger_page_bits &&
10828 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10830 change->actual_trigger_element = trigger_element;
10831 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10832 change->actual_trigger_player_bits = trigger_player;
10833 change->actual_trigger_side = trigger_side;
10834 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10835 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10837 if ((change->can_change && !change_done) || change->has_action)
10841 SCAN_PLAYFIELD(x, y)
10843 if (Tile[x][y] == element)
10845 if (change->can_change && !change_done)
10847 // if element already changed in this frame, not only prevent
10848 // another element change (checked in ChangeElement()), but
10849 // also prevent additional element actions for this element
10851 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10852 !level.use_action_after_change_bug)
10855 ChangeDelay[x][y] = 1;
10856 ChangeEvent[x][y] = trigger_event;
10858 HandleElementChange(x, y, p);
10860 else if (change->has_action)
10862 // if element already changed in this frame, not only prevent
10863 // another element change (checked in ChangeElement()), but
10864 // also prevent additional element actions for this element
10866 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10867 !level.use_action_after_change_bug)
10870 ExecuteCustomElementAction(x, y, element, p);
10871 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10876 if (change->can_change)
10878 change_done = TRUE;
10879 change_done_any = TRUE;
10886 RECURSION_LOOP_DETECTION_END();
10888 return change_done_any;
10891 static boolean CheckElementChangeExt(int x, int y,
10893 int trigger_element,
10895 int trigger_player,
10898 boolean change_done = FALSE;
10901 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10902 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10905 if (Tile[x][y] == EL_BLOCKED)
10907 Blocked2Moving(x, y, &x, &y);
10908 element = Tile[x][y];
10911 // check if element has already changed or is about to change after moving
10912 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10913 Tile[x][y] != element) ||
10915 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10916 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10917 ChangePage[x][y] != -1)))
10920 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10922 for (p = 0; p < element_info[element].num_change_pages; p++)
10924 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10926 /* check trigger element for all events where the element that is checked
10927 for changing interacts with a directly adjacent element -- this is
10928 different to element changes that affect other elements to change on the
10929 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
10930 boolean check_trigger_element =
10931 (trigger_event == CE_TOUCHING_X ||
10932 trigger_event == CE_HITTING_X ||
10933 trigger_event == CE_HIT_BY_X ||
10934 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
10936 if (change->can_change_or_has_action &&
10937 change->has_event[trigger_event] &&
10938 change->trigger_side & trigger_side &&
10939 change->trigger_player & trigger_player &&
10940 (!check_trigger_element ||
10941 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
10943 change->actual_trigger_element = trigger_element;
10944 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10945 change->actual_trigger_player_bits = trigger_player;
10946 change->actual_trigger_side = trigger_side;
10947 change->actual_trigger_ce_value = CustomValue[x][y];
10948 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10950 // special case: trigger element not at (x,y) position for some events
10951 if (check_trigger_element)
10963 { 0, 0 }, { 0, 0 }, { 0, 0 },
10967 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
10968 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
10970 change->actual_trigger_ce_value = CustomValue[xx][yy];
10971 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10974 if (change->can_change && !change_done)
10976 ChangeDelay[x][y] = 1;
10977 ChangeEvent[x][y] = trigger_event;
10979 HandleElementChange(x, y, p);
10981 change_done = TRUE;
10983 else if (change->has_action)
10985 ExecuteCustomElementAction(x, y, element, p);
10986 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10991 RECURSION_LOOP_DETECTION_END();
10993 return change_done;
10996 static void PlayPlayerSound(struct PlayerInfo *player)
10998 int jx = player->jx, jy = player->jy;
10999 int sound_element = player->artwork_element;
11000 int last_action = player->last_action_waiting;
11001 int action = player->action_waiting;
11003 if (player->is_waiting)
11005 if (action != last_action)
11006 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11008 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11012 if (action != last_action)
11013 StopSound(element_info[sound_element].sound[last_action]);
11015 if (last_action == ACTION_SLEEPING)
11016 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11020 static void PlayAllPlayersSound(void)
11024 for (i = 0; i < MAX_PLAYERS; i++)
11025 if (stored_player[i].active)
11026 PlayPlayerSound(&stored_player[i]);
11029 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11031 boolean last_waiting = player->is_waiting;
11032 int move_dir = player->MovDir;
11034 player->dir_waiting = move_dir;
11035 player->last_action_waiting = player->action_waiting;
11039 if (!last_waiting) // not waiting -> waiting
11041 player->is_waiting = TRUE;
11043 player->frame_counter_bored =
11045 game.player_boring_delay_fixed +
11046 GetSimpleRandom(game.player_boring_delay_random);
11047 player->frame_counter_sleeping =
11049 game.player_sleeping_delay_fixed +
11050 GetSimpleRandom(game.player_sleeping_delay_random);
11052 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11055 if (game.player_sleeping_delay_fixed +
11056 game.player_sleeping_delay_random > 0 &&
11057 player->anim_delay_counter == 0 &&
11058 player->post_delay_counter == 0 &&
11059 FrameCounter >= player->frame_counter_sleeping)
11060 player->is_sleeping = TRUE;
11061 else if (game.player_boring_delay_fixed +
11062 game.player_boring_delay_random > 0 &&
11063 FrameCounter >= player->frame_counter_bored)
11064 player->is_bored = TRUE;
11066 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11067 player->is_bored ? ACTION_BORING :
11070 if (player->is_sleeping && player->use_murphy)
11072 // special case for sleeping Murphy when leaning against non-free tile
11074 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11075 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11076 !IS_MOVING(player->jx - 1, player->jy)))
11077 move_dir = MV_LEFT;
11078 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11079 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11080 !IS_MOVING(player->jx + 1, player->jy)))
11081 move_dir = MV_RIGHT;
11083 player->is_sleeping = FALSE;
11085 player->dir_waiting = move_dir;
11088 if (player->is_sleeping)
11090 if (player->num_special_action_sleeping > 0)
11092 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11094 int last_special_action = player->special_action_sleeping;
11095 int num_special_action = player->num_special_action_sleeping;
11096 int special_action =
11097 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11098 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11099 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11100 last_special_action + 1 : ACTION_SLEEPING);
11101 int special_graphic =
11102 el_act_dir2img(player->artwork_element, special_action, move_dir);
11104 player->anim_delay_counter =
11105 graphic_info[special_graphic].anim_delay_fixed +
11106 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11107 player->post_delay_counter =
11108 graphic_info[special_graphic].post_delay_fixed +
11109 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11111 player->special_action_sleeping = special_action;
11114 if (player->anim_delay_counter > 0)
11116 player->action_waiting = player->special_action_sleeping;
11117 player->anim_delay_counter--;
11119 else if (player->post_delay_counter > 0)
11121 player->post_delay_counter--;
11125 else if (player->is_bored)
11127 if (player->num_special_action_bored > 0)
11129 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11131 int special_action =
11132 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11133 int special_graphic =
11134 el_act_dir2img(player->artwork_element, special_action, move_dir);
11136 player->anim_delay_counter =
11137 graphic_info[special_graphic].anim_delay_fixed +
11138 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11139 player->post_delay_counter =
11140 graphic_info[special_graphic].post_delay_fixed +
11141 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11143 player->special_action_bored = special_action;
11146 if (player->anim_delay_counter > 0)
11148 player->action_waiting = player->special_action_bored;
11149 player->anim_delay_counter--;
11151 else if (player->post_delay_counter > 0)
11153 player->post_delay_counter--;
11158 else if (last_waiting) // waiting -> not waiting
11160 player->is_waiting = FALSE;
11161 player->is_bored = FALSE;
11162 player->is_sleeping = FALSE;
11164 player->frame_counter_bored = -1;
11165 player->frame_counter_sleeping = -1;
11167 player->anim_delay_counter = 0;
11168 player->post_delay_counter = 0;
11170 player->dir_waiting = player->MovDir;
11171 player->action_waiting = ACTION_DEFAULT;
11173 player->special_action_bored = ACTION_DEFAULT;
11174 player->special_action_sleeping = ACTION_DEFAULT;
11178 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11180 if ((!player->is_moving && player->was_moving) ||
11181 (player->MovPos == 0 && player->was_moving) ||
11182 (player->is_snapping && !player->was_snapping) ||
11183 (player->is_dropping && !player->was_dropping))
11185 if (!CheckSaveEngineSnapshotToList())
11188 player->was_moving = FALSE;
11189 player->was_snapping = TRUE;
11190 player->was_dropping = TRUE;
11194 if (player->is_moving)
11195 player->was_moving = TRUE;
11197 if (!player->is_snapping)
11198 player->was_snapping = FALSE;
11200 if (!player->is_dropping)
11201 player->was_dropping = FALSE;
11205 static void CheckSingleStepMode(struct PlayerInfo *player)
11207 if (tape.single_step && tape.recording && !tape.pausing)
11209 /* as it is called "single step mode", just return to pause mode when the
11210 player stopped moving after one tile (or never starts moving at all) */
11211 if (!player->is_moving &&
11212 !player->is_pushing &&
11213 !player->is_dropping_pressed &&
11214 !player->effective_mouse_action.button)
11215 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11218 CheckSaveEngineSnapshot(player);
11221 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11223 int left = player_action & JOY_LEFT;
11224 int right = player_action & JOY_RIGHT;
11225 int up = player_action & JOY_UP;
11226 int down = player_action & JOY_DOWN;
11227 int button1 = player_action & JOY_BUTTON_1;
11228 int button2 = player_action & JOY_BUTTON_2;
11229 int dx = (left ? -1 : right ? 1 : 0);
11230 int dy = (up ? -1 : down ? 1 : 0);
11232 if (!player->active || tape.pausing)
11238 SnapField(player, dx, dy);
11242 DropElement(player);
11244 MovePlayer(player, dx, dy);
11247 CheckSingleStepMode(player);
11249 SetPlayerWaiting(player, FALSE);
11251 return player_action;
11255 // no actions for this player (no input at player's configured device)
11257 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11258 SnapField(player, 0, 0);
11259 CheckGravityMovementWhenNotMoving(player);
11261 if (player->MovPos == 0)
11262 SetPlayerWaiting(player, TRUE);
11264 if (player->MovPos == 0) // needed for tape.playing
11265 player->is_moving = FALSE;
11267 player->is_dropping = FALSE;
11268 player->is_dropping_pressed = FALSE;
11269 player->drop_pressed_delay = 0;
11271 CheckSingleStepMode(player);
11277 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11280 if (!tape.use_mouse_actions)
11283 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11284 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11285 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11288 static void SetTapeActionFromMouseAction(byte *tape_action,
11289 struct MouseActionInfo *mouse_action)
11291 if (!tape.use_mouse_actions)
11294 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11295 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11296 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11299 static void CheckLevelSolved(void)
11301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11303 if (game_em.level_solved &&
11304 !game_em.game_over) // game won
11308 game_em.game_over = TRUE;
11310 game.all_players_gone = TRUE;
11313 if (game_em.game_over) // game lost
11314 game.all_players_gone = TRUE;
11316 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11318 if (game_sp.level_solved &&
11319 !game_sp.game_over) // game won
11323 game_sp.game_over = TRUE;
11325 game.all_players_gone = TRUE;
11328 if (game_sp.game_over) // game lost
11329 game.all_players_gone = TRUE;
11331 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11333 if (game_mm.level_solved &&
11334 !game_mm.game_over) // game won
11338 game_mm.game_over = TRUE;
11340 game.all_players_gone = TRUE;
11343 if (game_mm.game_over) // game lost
11344 game.all_players_gone = TRUE;
11348 static void CheckLevelTime(void)
11352 if (TimeFrames >= FRAMES_PER_SECOND)
11357 for (i = 0; i < MAX_PLAYERS; i++)
11359 struct PlayerInfo *player = &stored_player[i];
11361 if (SHIELD_ON(player))
11363 player->shield_normal_time_left--;
11365 if (player->shield_deadly_time_left > 0)
11366 player->shield_deadly_time_left--;
11370 if (!game.LevelSolved && !level.use_step_counter)
11378 if (TimeLeft <= 10 && setup.time_limit)
11379 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11381 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11382 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11384 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11386 if (!TimeLeft && setup.time_limit)
11388 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11389 game_em.lev->killed_out_of_time = TRUE;
11391 for (i = 0; i < MAX_PLAYERS; i++)
11392 KillPlayer(&stored_player[i]);
11395 else if (game.no_time_limit && !game.all_players_gone)
11397 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11400 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11403 if (tape.recording || tape.playing)
11404 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11407 if (tape.recording || tape.playing)
11408 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11410 UpdateAndDisplayGameControlValues();
11413 void AdvanceFrameAndPlayerCounters(int player_nr)
11417 // advance frame counters (global frame counter and time frame counter)
11421 // advance player counters (counters for move delay, move animation etc.)
11422 for (i = 0; i < MAX_PLAYERS; i++)
11424 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11425 int move_delay_value = stored_player[i].move_delay_value;
11426 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11428 if (!advance_player_counters) // not all players may be affected
11431 if (move_frames == 0) // less than one move per game frame
11433 int stepsize = TILEX / move_delay_value;
11434 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11435 int count = (stored_player[i].is_moving ?
11436 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11438 if (count % delay == 0)
11442 stored_player[i].Frame += move_frames;
11444 if (stored_player[i].MovPos != 0)
11445 stored_player[i].StepFrame += move_frames;
11447 if (stored_player[i].move_delay > 0)
11448 stored_player[i].move_delay--;
11450 // due to bugs in previous versions, counter must count up, not down
11451 if (stored_player[i].push_delay != -1)
11452 stored_player[i].push_delay++;
11454 if (stored_player[i].drop_delay > 0)
11455 stored_player[i].drop_delay--;
11457 if (stored_player[i].is_dropping_pressed)
11458 stored_player[i].drop_pressed_delay++;
11462 void StartGameActions(boolean init_network_game, boolean record_tape,
11465 unsigned int new_random_seed = InitRND(random_seed);
11468 TapeStartRecording(new_random_seed);
11470 if (init_network_game)
11472 SendToServer_LevelFile();
11473 SendToServer_StartPlaying();
11481 static void GameActionsExt(void)
11484 static unsigned int game_frame_delay = 0;
11486 unsigned int game_frame_delay_value;
11487 byte *recorded_player_action;
11488 byte summarized_player_action = 0;
11489 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11492 // detect endless loops, caused by custom element programming
11493 if (recursion_loop_detected && recursion_loop_depth == 0)
11495 char *message = getStringCat3("Internal Error! Element ",
11496 EL_NAME(recursion_loop_element),
11497 " caused endless loop! Quit the game?");
11499 Warn("element '%s' caused endless loop in game engine",
11500 EL_NAME(recursion_loop_element));
11502 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11504 recursion_loop_detected = FALSE; // if game should be continued
11511 if (game.restart_level)
11512 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11514 CheckLevelSolved();
11516 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11519 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11522 if (game_status != GAME_MODE_PLAYING) // status might have changed
11525 game_frame_delay_value =
11526 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11528 if (tape.playing && tape.warp_forward && !tape.pausing)
11529 game_frame_delay_value = 0;
11531 SetVideoFrameDelay(game_frame_delay_value);
11533 // (de)activate virtual buttons depending on current game status
11534 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11536 if (game.all_players_gone) // if no players there to be controlled anymore
11537 SetOverlayActive(FALSE);
11538 else if (!tape.playing) // if game continues after tape stopped playing
11539 SetOverlayActive(TRUE);
11544 // ---------- main game synchronization point ----------
11546 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11548 Debug("game:playing:skip", "skip == %d", skip);
11551 // ---------- main game synchronization point ----------
11553 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11557 if (network_playing && !network_player_action_received)
11559 // try to get network player actions in time
11561 // last chance to get network player actions without main loop delay
11562 HandleNetworking();
11564 // game was quit by network peer
11565 if (game_status != GAME_MODE_PLAYING)
11568 // check if network player actions still missing and game still running
11569 if (!network_player_action_received && !checkGameEnded())
11570 return; // failed to get network player actions in time
11572 // do not yet reset "network_player_action_received" (for tape.pausing)
11578 // at this point we know that we really continue executing the game
11580 network_player_action_received = FALSE;
11582 // when playing tape, read previously recorded player input from tape data
11583 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11585 local_player->effective_mouse_action = local_player->mouse_action;
11587 if (recorded_player_action != NULL)
11588 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11589 recorded_player_action);
11591 // TapePlayAction() may return NULL when toggling to "pause before death"
11595 if (tape.set_centered_player)
11597 game.centered_player_nr_next = tape.centered_player_nr_next;
11598 game.set_centered_player = TRUE;
11601 for (i = 0; i < MAX_PLAYERS; i++)
11603 summarized_player_action |= stored_player[i].action;
11605 if (!network_playing && (game.team_mode || tape.playing))
11606 stored_player[i].effective_action = stored_player[i].action;
11609 if (network_playing && !checkGameEnded())
11610 SendToServer_MovePlayer(summarized_player_action);
11612 // summarize all actions at local players mapped input device position
11613 // (this allows using different input devices in single player mode)
11614 if (!network.enabled && !game.team_mode)
11615 stored_player[map_player_action[local_player->index_nr]].effective_action =
11616 summarized_player_action;
11618 // summarize all actions at centered player in local team mode
11619 if (tape.recording &&
11620 setup.team_mode && !network.enabled &&
11621 setup.input_on_focus &&
11622 game.centered_player_nr != -1)
11624 for (i = 0; i < MAX_PLAYERS; i++)
11625 stored_player[map_player_action[i]].effective_action =
11626 (i == game.centered_player_nr ? summarized_player_action : 0);
11629 if (recorded_player_action != NULL)
11630 for (i = 0; i < MAX_PLAYERS; i++)
11631 stored_player[i].effective_action = recorded_player_action[i];
11633 for (i = 0; i < MAX_PLAYERS; i++)
11635 tape_action[i] = stored_player[i].effective_action;
11637 /* (this may happen in the RND game engine if a player was not present on
11638 the playfield on level start, but appeared later from a custom element */
11639 if (setup.team_mode &&
11642 !tape.player_participates[i])
11643 tape.player_participates[i] = TRUE;
11646 SetTapeActionFromMouseAction(tape_action,
11647 &local_player->effective_mouse_action);
11649 // only record actions from input devices, but not programmed actions
11650 if (tape.recording)
11651 TapeRecordAction(tape_action);
11653 // remember if game was played (especially after tape stopped playing)
11654 if (!tape.playing && summarized_player_action)
11655 game.GamePlayed = TRUE;
11657 #if USE_NEW_PLAYER_ASSIGNMENTS
11658 // !!! also map player actions in single player mode !!!
11659 // if (game.team_mode)
11662 byte mapped_action[MAX_PLAYERS];
11664 #if DEBUG_PLAYER_ACTIONS
11665 for (i = 0; i < MAX_PLAYERS; i++)
11666 DebugContinued("", "%d, ", stored_player[i].effective_action);
11669 for (i = 0; i < MAX_PLAYERS; i++)
11670 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11672 for (i = 0; i < MAX_PLAYERS; i++)
11673 stored_player[i].effective_action = mapped_action[i];
11675 #if DEBUG_PLAYER_ACTIONS
11676 DebugContinued("", "=> ");
11677 for (i = 0; i < MAX_PLAYERS; i++)
11678 DebugContinued("", "%d, ", stored_player[i].effective_action);
11679 DebugContinued("game:playing:player", "\n");
11682 #if DEBUG_PLAYER_ACTIONS
11685 for (i = 0; i < MAX_PLAYERS; i++)
11686 DebugContinued("", "%d, ", stored_player[i].effective_action);
11687 DebugContinued("game:playing:player", "\n");
11692 for (i = 0; i < MAX_PLAYERS; i++)
11694 // allow engine snapshot in case of changed movement attempt
11695 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11696 (stored_player[i].effective_action & KEY_MOTION))
11697 game.snapshot.changed_action = TRUE;
11699 // allow engine snapshot in case of snapping/dropping attempt
11700 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11701 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11702 game.snapshot.changed_action = TRUE;
11704 game.snapshot.last_action[i] = stored_player[i].effective_action;
11707 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11709 GameActions_EM_Main();
11711 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11713 GameActions_SP_Main();
11715 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11717 GameActions_MM_Main();
11721 GameActions_RND_Main();
11724 BlitScreenToBitmap(backbuffer);
11726 CheckLevelSolved();
11729 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11731 if (global.show_frames_per_second)
11733 static unsigned int fps_counter = 0;
11734 static int fps_frames = 0;
11735 unsigned int fps_delay_ms = Counter() - fps_counter;
11739 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11741 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11744 fps_counter = Counter();
11746 // always draw FPS to screen after FPS value was updated
11747 redraw_mask |= REDRAW_FPS;
11750 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11751 if (GetDrawDeactivationMask() == REDRAW_NONE)
11752 redraw_mask |= REDRAW_FPS;
11756 static void GameActions_CheckSaveEngineSnapshot(void)
11758 if (!game.snapshot.save_snapshot)
11761 // clear flag for saving snapshot _before_ saving snapshot
11762 game.snapshot.save_snapshot = FALSE;
11764 SaveEngineSnapshotToList();
11767 void GameActions(void)
11771 GameActions_CheckSaveEngineSnapshot();
11774 void GameActions_EM_Main(void)
11776 byte effective_action[MAX_PLAYERS];
11777 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11780 for (i = 0; i < MAX_PLAYERS; i++)
11781 effective_action[i] = stored_player[i].effective_action;
11783 GameActions_EM(effective_action, warp_mode);
11786 void GameActions_SP_Main(void)
11788 byte effective_action[MAX_PLAYERS];
11789 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11792 for (i = 0; i < MAX_PLAYERS; i++)
11793 effective_action[i] = stored_player[i].effective_action;
11795 GameActions_SP(effective_action, warp_mode);
11797 for (i = 0; i < MAX_PLAYERS; i++)
11799 if (stored_player[i].force_dropping)
11800 stored_player[i].action |= KEY_BUTTON_DROP;
11802 stored_player[i].force_dropping = FALSE;
11806 void GameActions_MM_Main(void)
11808 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11810 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11813 void GameActions_RND_Main(void)
11818 void GameActions_RND(void)
11820 static struct MouseActionInfo mouse_action_last = { 0 };
11821 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11822 int magic_wall_x = 0, magic_wall_y = 0;
11823 int i, x, y, element, graphic, last_gfx_frame;
11825 InitPlayfieldScanModeVars();
11827 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11829 SCAN_PLAYFIELD(x, y)
11831 ChangeCount[x][y] = 0;
11832 ChangeEvent[x][y] = -1;
11836 if (game.set_centered_player)
11838 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11840 // switching to "all players" only possible if all players fit to screen
11841 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11843 game.centered_player_nr_next = game.centered_player_nr;
11844 game.set_centered_player = FALSE;
11847 // do not switch focus to non-existing (or non-active) player
11848 if (game.centered_player_nr_next >= 0 &&
11849 !stored_player[game.centered_player_nr_next].active)
11851 game.centered_player_nr_next = game.centered_player_nr;
11852 game.set_centered_player = FALSE;
11856 if (game.set_centered_player &&
11857 ScreenMovPos == 0) // screen currently aligned at tile position
11861 if (game.centered_player_nr_next == -1)
11863 setScreenCenteredToAllPlayers(&sx, &sy);
11867 sx = stored_player[game.centered_player_nr_next].jx;
11868 sy = stored_player[game.centered_player_nr_next].jy;
11871 game.centered_player_nr = game.centered_player_nr_next;
11872 game.set_centered_player = FALSE;
11874 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11875 DrawGameDoorValues();
11878 for (i = 0; i < MAX_PLAYERS; i++)
11880 int actual_player_action = stored_player[i].effective_action;
11883 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11884 - rnd_equinox_tetrachloride 048
11885 - rnd_equinox_tetrachloride_ii 096
11886 - rnd_emanuel_schmieg 002
11887 - doctor_sloan_ww 001, 020
11889 if (stored_player[i].MovPos == 0)
11890 CheckGravityMovement(&stored_player[i]);
11893 // overwrite programmed action with tape action
11894 if (stored_player[i].programmed_action)
11895 actual_player_action = stored_player[i].programmed_action;
11897 PlayerActions(&stored_player[i], actual_player_action);
11899 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11902 ScrollScreen(NULL, SCROLL_GO_ON);
11904 /* for backwards compatibility, the following code emulates a fixed bug that
11905 occured when pushing elements (causing elements that just made their last
11906 pushing step to already (if possible) make their first falling step in the
11907 same game frame, which is bad); this code is also needed to use the famous
11908 "spring push bug" which is used in older levels and might be wanted to be
11909 used also in newer levels, but in this case the buggy pushing code is only
11910 affecting the "spring" element and no other elements */
11912 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
11914 for (i = 0; i < MAX_PLAYERS; i++)
11916 struct PlayerInfo *player = &stored_player[i];
11917 int x = player->jx;
11918 int y = player->jy;
11920 if (player->active && player->is_pushing && player->is_moving &&
11922 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
11923 Tile[x][y] == EL_SPRING))
11925 ContinueMoving(x, y);
11927 // continue moving after pushing (this is actually a bug)
11928 if (!IS_MOVING(x, y))
11929 Stop[x][y] = FALSE;
11934 SCAN_PLAYFIELD(x, y)
11936 Last[x][y] = Tile[x][y];
11938 ChangeCount[x][y] = 0;
11939 ChangeEvent[x][y] = -1;
11941 // this must be handled before main playfield loop
11942 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
11945 if (MovDelay[x][y] <= 0)
11949 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
11952 if (MovDelay[x][y] <= 0)
11955 TEST_DrawLevelField(x, y);
11957 TestIfElementTouchesCustomElement(x, y); // for empty space
11962 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
11964 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
11966 Debug("game:playing:GameActions_RND", "This should never happen!");
11968 ChangePage[x][y] = -1;
11972 Stop[x][y] = FALSE;
11973 if (WasJustMoving[x][y] > 0)
11974 WasJustMoving[x][y]--;
11975 if (WasJustFalling[x][y] > 0)
11976 WasJustFalling[x][y]--;
11977 if (CheckCollision[x][y] > 0)
11978 CheckCollision[x][y]--;
11979 if (CheckImpact[x][y] > 0)
11980 CheckImpact[x][y]--;
11984 /* reset finished pushing action (not done in ContinueMoving() to allow
11985 continuous pushing animation for elements with zero push delay) */
11986 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
11988 ResetGfxAnimation(x, y);
11989 TEST_DrawLevelField(x, y);
11993 if (IS_BLOCKED(x, y))
11997 Blocked2Moving(x, y, &oldx, &oldy);
11998 if (!IS_MOVING(oldx, oldy))
12000 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12001 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12002 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12003 Debug("game:playing:GameActions_RND", "This should never happen!");
12009 if (mouse_action.button)
12011 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12013 x = mouse_action.lx;
12014 y = mouse_action.ly;
12015 element = Tile[x][y];
12019 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12020 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12023 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12024 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12027 SCAN_PLAYFIELD(x, y)
12029 element = Tile[x][y];
12030 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12031 last_gfx_frame = GfxFrame[x][y];
12033 ResetGfxFrame(x, y);
12035 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12036 DrawLevelGraphicAnimation(x, y, graphic);
12038 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12039 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12040 ResetRandomAnimationValue(x, y);
12042 SetRandomAnimationValue(x, y);
12044 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12046 if (IS_INACTIVE(element))
12048 if (IS_ANIMATED(graphic))
12049 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12054 // this may take place after moving, so 'element' may have changed
12055 if (IS_CHANGING(x, y) &&
12056 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12058 int page = element_info[element].event_page_nr[CE_DELAY];
12060 HandleElementChange(x, y, page);
12062 element = Tile[x][y];
12063 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12066 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12070 element = Tile[x][y];
12071 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12073 if (IS_ANIMATED(graphic) &&
12074 !IS_MOVING(x, y) &&
12076 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12078 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12079 TEST_DrawTwinkleOnField(x, y);
12081 else if (element == EL_ACID)
12084 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12086 else if ((element == EL_EXIT_OPEN ||
12087 element == EL_EM_EXIT_OPEN ||
12088 element == EL_SP_EXIT_OPEN ||
12089 element == EL_STEEL_EXIT_OPEN ||
12090 element == EL_EM_STEEL_EXIT_OPEN ||
12091 element == EL_SP_TERMINAL ||
12092 element == EL_SP_TERMINAL_ACTIVE ||
12093 element == EL_EXTRA_TIME ||
12094 element == EL_SHIELD_NORMAL ||
12095 element == EL_SHIELD_DEADLY) &&
12096 IS_ANIMATED(graphic))
12097 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12098 else if (IS_MOVING(x, y))
12099 ContinueMoving(x, y);
12100 else if (IS_ACTIVE_BOMB(element))
12101 CheckDynamite(x, y);
12102 else if (element == EL_AMOEBA_GROWING)
12103 AmoebaGrowing(x, y);
12104 else if (element == EL_AMOEBA_SHRINKING)
12105 AmoebaShrinking(x, y);
12107 #if !USE_NEW_AMOEBA_CODE
12108 else if (IS_AMOEBALIVE(element))
12109 AmoebaReproduce(x, y);
12112 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12114 else if (element == EL_EXIT_CLOSED)
12116 else if (element == EL_EM_EXIT_CLOSED)
12118 else if (element == EL_STEEL_EXIT_CLOSED)
12119 CheckExitSteel(x, y);
12120 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12121 CheckExitSteelEM(x, y);
12122 else if (element == EL_SP_EXIT_CLOSED)
12124 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12125 element == EL_EXPANDABLE_STEELWALL_GROWING)
12126 MauerWaechst(x, y);
12127 else if (element == EL_EXPANDABLE_WALL ||
12128 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12129 element == EL_EXPANDABLE_WALL_VERTICAL ||
12130 element == EL_EXPANDABLE_WALL_ANY ||
12131 element == EL_BD_EXPANDABLE_WALL)
12132 MauerAbleger(x, y);
12133 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12134 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12135 element == EL_EXPANDABLE_STEELWALL_ANY)
12136 MauerAblegerStahl(x, y);
12137 else if (element == EL_FLAMES)
12138 CheckForDragon(x, y);
12139 else if (element == EL_EXPLOSION)
12140 ; // drawing of correct explosion animation is handled separately
12141 else if (element == EL_ELEMENT_SNAPPING ||
12142 element == EL_DIAGONAL_SHRINKING ||
12143 element == EL_DIAGONAL_GROWING)
12145 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12147 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12149 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12150 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12152 if (IS_BELT_ACTIVE(element))
12153 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12155 if (game.magic_wall_active)
12157 int jx = local_player->jx, jy = local_player->jy;
12159 // play the element sound at the position nearest to the player
12160 if ((element == EL_MAGIC_WALL_FULL ||
12161 element == EL_MAGIC_WALL_ACTIVE ||
12162 element == EL_MAGIC_WALL_EMPTYING ||
12163 element == EL_BD_MAGIC_WALL_FULL ||
12164 element == EL_BD_MAGIC_WALL_ACTIVE ||
12165 element == EL_BD_MAGIC_WALL_EMPTYING ||
12166 element == EL_DC_MAGIC_WALL_FULL ||
12167 element == EL_DC_MAGIC_WALL_ACTIVE ||
12168 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12169 ABS(x - jx) + ABS(y - jy) <
12170 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12178 #if USE_NEW_AMOEBA_CODE
12179 // new experimental amoeba growth stuff
12180 if (!(FrameCounter % 8))
12182 static unsigned int random = 1684108901;
12184 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12186 x = RND(lev_fieldx);
12187 y = RND(lev_fieldy);
12188 element = Tile[x][y];
12190 if (!IS_PLAYER(x,y) &&
12191 (element == EL_EMPTY ||
12192 CAN_GROW_INTO(element) ||
12193 element == EL_QUICKSAND_EMPTY ||
12194 element == EL_QUICKSAND_FAST_EMPTY ||
12195 element == EL_ACID_SPLASH_LEFT ||
12196 element == EL_ACID_SPLASH_RIGHT))
12198 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12199 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12200 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12201 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12202 Tile[x][y] = EL_AMOEBA_DROP;
12205 random = random * 129 + 1;
12210 game.explosions_delayed = FALSE;
12212 SCAN_PLAYFIELD(x, y)
12214 element = Tile[x][y];
12216 if (ExplodeField[x][y])
12217 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12218 else if (element == EL_EXPLOSION)
12219 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12221 ExplodeField[x][y] = EX_TYPE_NONE;
12224 game.explosions_delayed = TRUE;
12226 if (game.magic_wall_active)
12228 if (!(game.magic_wall_time_left % 4))
12230 int element = Tile[magic_wall_x][magic_wall_y];
12232 if (element == EL_BD_MAGIC_WALL_FULL ||
12233 element == EL_BD_MAGIC_WALL_ACTIVE ||
12234 element == EL_BD_MAGIC_WALL_EMPTYING)
12235 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12236 else if (element == EL_DC_MAGIC_WALL_FULL ||
12237 element == EL_DC_MAGIC_WALL_ACTIVE ||
12238 element == EL_DC_MAGIC_WALL_EMPTYING)
12239 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12241 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12244 if (game.magic_wall_time_left > 0)
12246 game.magic_wall_time_left--;
12248 if (!game.magic_wall_time_left)
12250 SCAN_PLAYFIELD(x, y)
12252 element = Tile[x][y];
12254 if (element == EL_MAGIC_WALL_ACTIVE ||
12255 element == EL_MAGIC_WALL_FULL)
12257 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12258 TEST_DrawLevelField(x, y);
12260 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12261 element == EL_BD_MAGIC_WALL_FULL)
12263 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12264 TEST_DrawLevelField(x, y);
12266 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12267 element == EL_DC_MAGIC_WALL_FULL)
12269 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12270 TEST_DrawLevelField(x, y);
12274 game.magic_wall_active = FALSE;
12279 if (game.light_time_left > 0)
12281 game.light_time_left--;
12283 if (game.light_time_left == 0)
12284 RedrawAllLightSwitchesAndInvisibleElements();
12287 if (game.timegate_time_left > 0)
12289 game.timegate_time_left--;
12291 if (game.timegate_time_left == 0)
12292 CloseAllOpenTimegates();
12295 if (game.lenses_time_left > 0)
12297 game.lenses_time_left--;
12299 if (game.lenses_time_left == 0)
12300 RedrawAllInvisibleElementsForLenses();
12303 if (game.magnify_time_left > 0)
12305 game.magnify_time_left--;
12307 if (game.magnify_time_left == 0)
12308 RedrawAllInvisibleElementsForMagnifier();
12311 for (i = 0; i < MAX_PLAYERS; i++)
12313 struct PlayerInfo *player = &stored_player[i];
12315 if (SHIELD_ON(player))
12317 if (player->shield_deadly_time_left)
12318 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12319 else if (player->shield_normal_time_left)
12320 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12324 #if USE_DELAYED_GFX_REDRAW
12325 SCAN_PLAYFIELD(x, y)
12327 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12329 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12330 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12332 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12333 DrawLevelField(x, y);
12335 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12336 DrawLevelFieldCrumbled(x, y);
12338 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12339 DrawLevelFieldCrumbledNeighbours(x, y);
12341 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12342 DrawTwinkleOnField(x, y);
12345 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12350 PlayAllPlayersSound();
12352 for (i = 0; i < MAX_PLAYERS; i++)
12354 struct PlayerInfo *player = &stored_player[i];
12356 if (player->show_envelope != 0 && (!player->active ||
12357 player->MovPos == 0))
12359 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12361 player->show_envelope = 0;
12365 // use random number generator in every frame to make it less predictable
12366 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12369 mouse_action_last = mouse_action;
12372 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12374 int min_x = x, min_y = y, max_x = x, max_y = y;
12377 for (i = 0; i < MAX_PLAYERS; i++)
12379 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12381 if (!stored_player[i].active || &stored_player[i] == player)
12384 min_x = MIN(min_x, jx);
12385 min_y = MIN(min_y, jy);
12386 max_x = MAX(max_x, jx);
12387 max_y = MAX(max_y, jy);
12390 return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
12393 static boolean AllPlayersInVisibleScreen(void)
12397 for (i = 0; i < MAX_PLAYERS; i++)
12399 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12401 if (!stored_player[i].active)
12404 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12411 void ScrollLevel(int dx, int dy)
12413 int scroll_offset = 2 * TILEX_VAR;
12416 BlitBitmap(drawto_field, drawto_field,
12417 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12418 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12419 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12420 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12421 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12422 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12426 x = (dx == 1 ? BX1 : BX2);
12427 for (y = BY1; y <= BY2; y++)
12428 DrawScreenField(x, y);
12433 y = (dy == 1 ? BY1 : BY2);
12434 for (x = BX1; x <= BX2; x++)
12435 DrawScreenField(x, y);
12438 redraw_mask |= REDRAW_FIELD;
12441 static boolean canFallDown(struct PlayerInfo *player)
12443 int jx = player->jx, jy = player->jy;
12445 return (IN_LEV_FIELD(jx, jy + 1) &&
12446 (IS_FREE(jx, jy + 1) ||
12447 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12448 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12449 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12452 static boolean canPassField(int x, int y, int move_dir)
12454 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12455 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12456 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12457 int nextx = x + dx;
12458 int nexty = y + dy;
12459 int element = Tile[x][y];
12461 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12462 !CAN_MOVE(element) &&
12463 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12464 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12465 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12468 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12470 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12471 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12472 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12476 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12477 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12478 (IS_DIGGABLE(Tile[newx][newy]) ||
12479 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12480 canPassField(newx, newy, move_dir)));
12483 static void CheckGravityMovement(struct PlayerInfo *player)
12485 if (player->gravity && !player->programmed_action)
12487 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12488 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12489 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12490 int jx = player->jx, jy = player->jy;
12491 boolean player_is_moving_to_valid_field =
12492 (!player_is_snapping &&
12493 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12494 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12495 boolean player_can_fall_down = canFallDown(player);
12497 if (player_can_fall_down &&
12498 !player_is_moving_to_valid_field)
12499 player->programmed_action = MV_DOWN;
12503 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12505 return CheckGravityMovement(player);
12507 if (player->gravity && !player->programmed_action)
12509 int jx = player->jx, jy = player->jy;
12510 boolean field_under_player_is_free =
12511 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12512 boolean player_is_standing_on_valid_field =
12513 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12514 (IS_WALKABLE(Tile[jx][jy]) &&
12515 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12517 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12518 player->programmed_action = MV_DOWN;
12523 MovePlayerOneStep()
12524 -----------------------------------------------------------------------------
12525 dx, dy: direction (non-diagonal) to try to move the player to
12526 real_dx, real_dy: direction as read from input device (can be diagonal)
12529 boolean MovePlayerOneStep(struct PlayerInfo *player,
12530 int dx, int dy, int real_dx, int real_dy)
12532 int jx = player->jx, jy = player->jy;
12533 int new_jx = jx + dx, new_jy = jy + dy;
12535 boolean player_can_move = !player->cannot_move;
12537 if (!player->active || (!dx && !dy))
12538 return MP_NO_ACTION;
12540 player->MovDir = (dx < 0 ? MV_LEFT :
12541 dx > 0 ? MV_RIGHT :
12543 dy > 0 ? MV_DOWN : MV_NONE);
12545 if (!IN_LEV_FIELD(new_jx, new_jy))
12546 return MP_NO_ACTION;
12548 if (!player_can_move)
12550 if (player->MovPos == 0)
12552 player->is_moving = FALSE;
12553 player->is_digging = FALSE;
12554 player->is_collecting = FALSE;
12555 player->is_snapping = FALSE;
12556 player->is_pushing = FALSE;
12560 if (!network.enabled && game.centered_player_nr == -1 &&
12561 !AllPlayersInSight(player, new_jx, new_jy))
12562 return MP_NO_ACTION;
12564 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12565 if (can_move != MP_MOVING)
12568 // check if DigField() has caused relocation of the player
12569 if (player->jx != jx || player->jy != jy)
12570 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12572 StorePlayer[jx][jy] = 0;
12573 player->last_jx = jx;
12574 player->last_jy = jy;
12575 player->jx = new_jx;
12576 player->jy = new_jy;
12577 StorePlayer[new_jx][new_jy] = player->element_nr;
12579 if (player->move_delay_value_next != -1)
12581 player->move_delay_value = player->move_delay_value_next;
12582 player->move_delay_value_next = -1;
12586 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12588 player->step_counter++;
12590 PlayerVisit[jx][jy] = FrameCounter;
12592 player->is_moving = TRUE;
12595 // should better be called in MovePlayer(), but this breaks some tapes
12596 ScrollPlayer(player, SCROLL_INIT);
12602 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12604 int jx = player->jx, jy = player->jy;
12605 int old_jx = jx, old_jy = jy;
12606 int moved = MP_NO_ACTION;
12608 if (!player->active)
12613 if (player->MovPos == 0)
12615 player->is_moving = FALSE;
12616 player->is_digging = FALSE;
12617 player->is_collecting = FALSE;
12618 player->is_snapping = FALSE;
12619 player->is_pushing = FALSE;
12625 if (player->move_delay > 0)
12628 player->move_delay = -1; // set to "uninitialized" value
12630 // store if player is automatically moved to next field
12631 player->is_auto_moving = (player->programmed_action != MV_NONE);
12633 // remove the last programmed player action
12634 player->programmed_action = 0;
12636 if (player->MovPos)
12638 // should only happen if pre-1.2 tape recordings are played
12639 // this is only for backward compatibility
12641 int original_move_delay_value = player->move_delay_value;
12644 Debug("game:playing:MovePlayer",
12645 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12649 // scroll remaining steps with finest movement resolution
12650 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12652 while (player->MovPos)
12654 ScrollPlayer(player, SCROLL_GO_ON);
12655 ScrollScreen(NULL, SCROLL_GO_ON);
12657 AdvanceFrameAndPlayerCounters(player->index_nr);
12660 BackToFront_WithFrameDelay(0);
12663 player->move_delay_value = original_move_delay_value;
12666 player->is_active = FALSE;
12668 if (player->last_move_dir & MV_HORIZONTAL)
12670 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12671 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12675 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12676 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12679 if (!moved && !player->is_active)
12681 player->is_moving = FALSE;
12682 player->is_digging = FALSE;
12683 player->is_collecting = FALSE;
12684 player->is_snapping = FALSE;
12685 player->is_pushing = FALSE;
12691 if (moved & MP_MOVING && !ScreenMovPos &&
12692 (player->index_nr == game.centered_player_nr ||
12693 game.centered_player_nr == -1))
12695 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12697 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12699 // actual player has left the screen -- scroll in that direction
12700 if (jx != old_jx) // player has moved horizontally
12701 scroll_x += (jx - old_jx);
12702 else // player has moved vertically
12703 scroll_y += (jy - old_jy);
12707 int offset_raw = game.scroll_delay_value;
12709 if (jx != old_jx) // player has moved horizontally
12711 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12712 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12713 int new_scroll_x = jx - MIDPOSX + offset_x;
12715 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12716 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12717 scroll_x = new_scroll_x;
12719 // don't scroll over playfield boundaries
12720 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12722 // don't scroll more than one field at a time
12723 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12725 // don't scroll against the player's moving direction
12726 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12727 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12728 scroll_x = old_scroll_x;
12730 else // player has moved vertically
12732 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12733 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12734 int new_scroll_y = jy - MIDPOSY + offset_y;
12736 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12737 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12738 scroll_y = new_scroll_y;
12740 // don't scroll over playfield boundaries
12741 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12743 // don't scroll more than one field at a time
12744 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12746 // don't scroll against the player's moving direction
12747 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12748 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12749 scroll_y = old_scroll_y;
12753 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12755 if (!network.enabled && game.centered_player_nr == -1 &&
12756 !AllPlayersInVisibleScreen())
12758 scroll_x = old_scroll_x;
12759 scroll_y = old_scroll_y;
12763 ScrollScreen(player, SCROLL_INIT);
12764 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12769 player->StepFrame = 0;
12771 if (moved & MP_MOVING)
12773 if (old_jx != jx && old_jy == jy)
12774 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12775 else if (old_jx == jx && old_jy != jy)
12776 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12778 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12780 player->last_move_dir = player->MovDir;
12781 player->is_moving = TRUE;
12782 player->is_snapping = FALSE;
12783 player->is_switching = FALSE;
12784 player->is_dropping = FALSE;
12785 player->is_dropping_pressed = FALSE;
12786 player->drop_pressed_delay = 0;
12789 // should better be called here than above, but this breaks some tapes
12790 ScrollPlayer(player, SCROLL_INIT);
12795 CheckGravityMovementWhenNotMoving(player);
12797 player->is_moving = FALSE;
12799 /* at this point, the player is allowed to move, but cannot move right now
12800 (e.g. because of something blocking the way) -- ensure that the player
12801 is also allowed to move in the next frame (in old versions before 3.1.1,
12802 the player was forced to wait again for eight frames before next try) */
12804 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12805 player->move_delay = 0; // allow direct movement in the next frame
12808 if (player->move_delay == -1) // not yet initialized by DigField()
12809 player->move_delay = player->move_delay_value;
12811 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12813 TestIfPlayerTouchesBadThing(jx, jy);
12814 TestIfPlayerTouchesCustomElement(jx, jy);
12817 if (!player->active)
12818 RemovePlayer(player);
12823 void ScrollPlayer(struct PlayerInfo *player, int mode)
12825 int jx = player->jx, jy = player->jy;
12826 int last_jx = player->last_jx, last_jy = player->last_jy;
12827 int move_stepsize = TILEX / player->move_delay_value;
12829 if (!player->active)
12832 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12835 if (mode == SCROLL_INIT)
12837 player->actual_frame_counter = FrameCounter;
12838 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12840 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12841 Tile[last_jx][last_jy] == EL_EMPTY)
12843 int last_field_block_delay = 0; // start with no blocking at all
12844 int block_delay_adjustment = player->block_delay_adjustment;
12846 // if player blocks last field, add delay for exactly one move
12847 if (player->block_last_field)
12849 last_field_block_delay += player->move_delay_value;
12851 // when blocking enabled, prevent moving up despite gravity
12852 if (player->gravity && player->MovDir == MV_UP)
12853 block_delay_adjustment = -1;
12856 // add block delay adjustment (also possible when not blocking)
12857 last_field_block_delay += block_delay_adjustment;
12859 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12860 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12863 if (player->MovPos != 0) // player has not yet reached destination
12866 else if (!FrameReached(&player->actual_frame_counter, 1))
12869 if (player->MovPos != 0)
12871 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12872 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12874 // before DrawPlayer() to draw correct player graphic for this case
12875 if (player->MovPos == 0)
12876 CheckGravityMovement(player);
12879 if (player->MovPos == 0) // player reached destination field
12881 if (player->move_delay_reset_counter > 0)
12883 player->move_delay_reset_counter--;
12885 if (player->move_delay_reset_counter == 0)
12887 // continue with normal speed after quickly moving through gate
12888 HALVE_PLAYER_SPEED(player);
12890 // be able to make the next move without delay
12891 player->move_delay = 0;
12895 player->last_jx = jx;
12896 player->last_jy = jy;
12898 if (Tile[jx][jy] == EL_EXIT_OPEN ||
12899 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
12900 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
12901 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
12902 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
12903 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
12904 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
12905 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
12907 ExitPlayer(player);
12909 if (game.players_still_needed == 0 &&
12910 (game.friends_still_needed == 0 ||
12911 IS_SP_ELEMENT(Tile[jx][jy])))
12915 // this breaks one level: "machine", level 000
12917 int move_direction = player->MovDir;
12918 int enter_side = MV_DIR_OPPOSITE(move_direction);
12919 int leave_side = move_direction;
12920 int old_jx = last_jx;
12921 int old_jy = last_jy;
12922 int old_element = Tile[old_jx][old_jy];
12923 int new_element = Tile[jx][jy];
12925 if (IS_CUSTOM_ELEMENT(old_element))
12926 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
12928 player->index_bit, leave_side);
12930 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
12931 CE_PLAYER_LEAVES_X,
12932 player->index_bit, leave_side);
12934 if (IS_CUSTOM_ELEMENT(new_element))
12935 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
12936 player->index_bit, enter_side);
12938 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
12939 CE_PLAYER_ENTERS_X,
12940 player->index_bit, enter_side);
12942 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
12943 CE_MOVE_OF_X, move_direction);
12946 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
12948 TestIfPlayerTouchesBadThing(jx, jy);
12949 TestIfPlayerTouchesCustomElement(jx, jy);
12951 /* needed because pushed element has not yet reached its destination,
12952 so it would trigger a change event at its previous field location */
12953 if (!player->is_pushing)
12954 TestIfElementTouchesCustomElement(jx, jy); // for empty space
12956 if (!player->active)
12957 RemovePlayer(player);
12960 if (!game.LevelSolved && level.use_step_counter)
12970 if (TimeLeft <= 10 && setup.time_limit)
12971 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
12973 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
12975 DisplayGameControlValues();
12977 if (!TimeLeft && setup.time_limit)
12978 for (i = 0; i < MAX_PLAYERS; i++)
12979 KillPlayer(&stored_player[i]);
12981 else if (game.no_time_limit && !game.all_players_gone)
12983 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
12985 DisplayGameControlValues();
12989 if (tape.single_step && tape.recording && !tape.pausing &&
12990 !player->programmed_action)
12991 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12993 if (!player->programmed_action)
12994 CheckSaveEngineSnapshot(player);
12998 void ScrollScreen(struct PlayerInfo *player, int mode)
13000 static unsigned int screen_frame_counter = 0;
13002 if (mode == SCROLL_INIT)
13004 // set scrolling step size according to actual player's moving speed
13005 ScrollStepSize = TILEX / player->move_delay_value;
13007 screen_frame_counter = FrameCounter;
13008 ScreenMovDir = player->MovDir;
13009 ScreenMovPos = player->MovPos;
13010 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13013 else if (!FrameReached(&screen_frame_counter, 1))
13018 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13019 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13020 redraw_mask |= REDRAW_FIELD;
13023 ScreenMovDir = MV_NONE;
13026 void TestIfPlayerTouchesCustomElement(int x, int y)
13028 static int xy[4][2] =
13035 static int trigger_sides[4][2] =
13037 // center side border side
13038 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13039 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13040 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13041 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13043 static int touch_dir[4] =
13045 MV_LEFT | MV_RIGHT,
13050 int center_element = Tile[x][y]; // should always be non-moving!
13053 for (i = 0; i < NUM_DIRECTIONS; i++)
13055 int xx = x + xy[i][0];
13056 int yy = y + xy[i][1];
13057 int center_side = trigger_sides[i][0];
13058 int border_side = trigger_sides[i][1];
13059 int border_element;
13061 if (!IN_LEV_FIELD(xx, yy))
13064 if (IS_PLAYER(x, y)) // player found at center element
13066 struct PlayerInfo *player = PLAYERINFO(x, y);
13068 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13069 border_element = Tile[xx][yy]; // may be moving!
13070 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13071 border_element = Tile[xx][yy];
13072 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13073 border_element = MovingOrBlocked2Element(xx, yy);
13075 continue; // center and border element do not touch
13077 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13078 player->index_bit, border_side);
13079 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13080 CE_PLAYER_TOUCHES_X,
13081 player->index_bit, border_side);
13084 /* use player element that is initially defined in the level playfield,
13085 not the player element that corresponds to the runtime player number
13086 (example: a level that contains EL_PLAYER_3 as the only player would
13087 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13088 int player_element = PLAYERINFO(x, y)->initial_element;
13090 CheckElementChangeBySide(xx, yy, border_element, player_element,
13091 CE_TOUCHING_X, border_side);
13094 else if (IS_PLAYER(xx, yy)) // player found at border element
13096 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13098 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13100 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13101 continue; // center and border element do not touch
13104 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13105 player->index_bit, center_side);
13106 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13107 CE_PLAYER_TOUCHES_X,
13108 player->index_bit, center_side);
13111 /* use player element that is initially defined in the level playfield,
13112 not the player element that corresponds to the runtime player number
13113 (example: a level that contains EL_PLAYER_3 as the only player would
13114 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13115 int player_element = PLAYERINFO(xx, yy)->initial_element;
13117 CheckElementChangeBySide(x, y, center_element, player_element,
13118 CE_TOUCHING_X, center_side);
13126 void TestIfElementTouchesCustomElement(int x, int y)
13128 static int xy[4][2] =
13135 static int trigger_sides[4][2] =
13137 // center side border side
13138 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13139 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13140 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13141 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13143 static int touch_dir[4] =
13145 MV_LEFT | MV_RIGHT,
13150 boolean change_center_element = FALSE;
13151 int center_element = Tile[x][y]; // should always be non-moving!
13152 int border_element_old[NUM_DIRECTIONS];
13155 for (i = 0; i < NUM_DIRECTIONS; i++)
13157 int xx = x + xy[i][0];
13158 int yy = y + xy[i][1];
13159 int border_element;
13161 border_element_old[i] = -1;
13163 if (!IN_LEV_FIELD(xx, yy))
13166 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13167 border_element = Tile[xx][yy]; // may be moving!
13168 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13169 border_element = Tile[xx][yy];
13170 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13171 border_element = MovingOrBlocked2Element(xx, yy);
13173 continue; // center and border element do not touch
13175 border_element_old[i] = border_element;
13178 for (i = 0; i < NUM_DIRECTIONS; i++)
13180 int xx = x + xy[i][0];
13181 int yy = y + xy[i][1];
13182 int center_side = trigger_sides[i][0];
13183 int border_element = border_element_old[i];
13185 if (border_element == -1)
13188 // check for change of border element
13189 CheckElementChangeBySide(xx, yy, border_element, center_element,
13190 CE_TOUCHING_X, center_side);
13192 // (center element cannot be player, so we dont have to check this here)
13195 for (i = 0; i < NUM_DIRECTIONS; i++)
13197 int xx = x + xy[i][0];
13198 int yy = y + xy[i][1];
13199 int border_side = trigger_sides[i][1];
13200 int border_element = border_element_old[i];
13202 if (border_element == -1)
13205 // check for change of center element (but change it only once)
13206 if (!change_center_element)
13207 change_center_element =
13208 CheckElementChangeBySide(x, y, center_element, border_element,
13209 CE_TOUCHING_X, border_side);
13211 if (IS_PLAYER(xx, yy))
13213 /* use player element that is initially defined in the level playfield,
13214 not the player element that corresponds to the runtime player number
13215 (example: a level that contains EL_PLAYER_3 as the only player would
13216 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13217 int player_element = PLAYERINFO(xx, yy)->initial_element;
13219 CheckElementChangeBySide(x, y, center_element, player_element,
13220 CE_TOUCHING_X, border_side);
13225 void TestIfElementHitsCustomElement(int x, int y, int direction)
13227 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13228 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13229 int hitx = x + dx, hity = y + dy;
13230 int hitting_element = Tile[x][y];
13231 int touched_element;
13233 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13236 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13237 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13239 if (IN_LEV_FIELD(hitx, hity))
13241 int opposite_direction = MV_DIR_OPPOSITE(direction);
13242 int hitting_side = direction;
13243 int touched_side = opposite_direction;
13244 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13245 MovDir[hitx][hity] != direction ||
13246 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13252 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13253 CE_HITTING_X, touched_side);
13255 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13256 CE_HIT_BY_X, hitting_side);
13258 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13259 CE_HIT_BY_SOMETHING, opposite_direction);
13261 if (IS_PLAYER(hitx, hity))
13263 /* use player element that is initially defined in the level playfield,
13264 not the player element that corresponds to the runtime player number
13265 (example: a level that contains EL_PLAYER_3 as the only player would
13266 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13267 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13269 CheckElementChangeBySide(x, y, hitting_element, player_element,
13270 CE_HITTING_X, touched_side);
13275 // "hitting something" is also true when hitting the playfield border
13276 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13277 CE_HITTING_SOMETHING, direction);
13280 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13282 int i, kill_x = -1, kill_y = -1;
13284 int bad_element = -1;
13285 static int test_xy[4][2] =
13292 static int test_dir[4] =
13300 for (i = 0; i < NUM_DIRECTIONS; i++)
13302 int test_x, test_y, test_move_dir, test_element;
13304 test_x = good_x + test_xy[i][0];
13305 test_y = good_y + test_xy[i][1];
13307 if (!IN_LEV_FIELD(test_x, test_y))
13311 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13313 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13315 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13316 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13318 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13319 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13323 bad_element = test_element;
13329 if (kill_x != -1 || kill_y != -1)
13331 if (IS_PLAYER(good_x, good_y))
13333 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13335 if (player->shield_deadly_time_left > 0 &&
13336 !IS_INDESTRUCTIBLE(bad_element))
13337 Bang(kill_x, kill_y);
13338 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13339 KillPlayer(player);
13342 Bang(good_x, good_y);
13346 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13348 int i, kill_x = -1, kill_y = -1;
13349 int bad_element = Tile[bad_x][bad_y];
13350 static int test_xy[4][2] =
13357 static int touch_dir[4] =
13359 MV_LEFT | MV_RIGHT,
13364 static int test_dir[4] =
13372 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13375 for (i = 0; i < NUM_DIRECTIONS; i++)
13377 int test_x, test_y, test_move_dir, test_element;
13379 test_x = bad_x + test_xy[i][0];
13380 test_y = bad_y + test_xy[i][1];
13382 if (!IN_LEV_FIELD(test_x, test_y))
13386 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13388 test_element = Tile[test_x][test_y];
13390 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13391 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13393 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13394 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13396 // good thing is player or penguin that does not move away
13397 if (IS_PLAYER(test_x, test_y))
13399 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13401 if (bad_element == EL_ROBOT && player->is_moving)
13402 continue; // robot does not kill player if he is moving
13404 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13406 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13407 continue; // center and border element do not touch
13415 else if (test_element == EL_PENGUIN)
13425 if (kill_x != -1 || kill_y != -1)
13427 if (IS_PLAYER(kill_x, kill_y))
13429 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13431 if (player->shield_deadly_time_left > 0 &&
13432 !IS_INDESTRUCTIBLE(bad_element))
13433 Bang(bad_x, bad_y);
13434 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13435 KillPlayer(player);
13438 Bang(kill_x, kill_y);
13442 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13444 int bad_element = Tile[bad_x][bad_y];
13445 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13446 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13447 int test_x = bad_x + dx, test_y = bad_y + dy;
13448 int test_move_dir, test_element;
13449 int kill_x = -1, kill_y = -1;
13451 if (!IN_LEV_FIELD(test_x, test_y))
13455 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13457 test_element = Tile[test_x][test_y];
13459 if (test_move_dir != bad_move_dir)
13461 // good thing can be player or penguin that does not move away
13462 if (IS_PLAYER(test_x, test_y))
13464 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13466 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13467 player as being hit when he is moving towards the bad thing, because
13468 the "get hit by" condition would be lost after the player stops) */
13469 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13470 return; // player moves away from bad thing
13475 else if (test_element == EL_PENGUIN)
13482 if (kill_x != -1 || kill_y != -1)
13484 if (IS_PLAYER(kill_x, kill_y))
13486 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13488 if (player->shield_deadly_time_left > 0 &&
13489 !IS_INDESTRUCTIBLE(bad_element))
13490 Bang(bad_x, bad_y);
13491 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13492 KillPlayer(player);
13495 Bang(kill_x, kill_y);
13499 void TestIfPlayerTouchesBadThing(int x, int y)
13501 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13504 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13506 TestIfGoodThingHitsBadThing(x, y, move_dir);
13509 void TestIfBadThingTouchesPlayer(int x, int y)
13511 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13514 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13516 TestIfBadThingHitsGoodThing(x, y, move_dir);
13519 void TestIfFriendTouchesBadThing(int x, int y)
13521 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13524 void TestIfBadThingTouchesFriend(int x, int y)
13526 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13529 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13531 int i, kill_x = bad_x, kill_y = bad_y;
13532 static int xy[4][2] =
13540 for (i = 0; i < NUM_DIRECTIONS; i++)
13544 x = bad_x + xy[i][0];
13545 y = bad_y + xy[i][1];
13546 if (!IN_LEV_FIELD(x, y))
13549 element = Tile[x][y];
13550 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13551 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13559 if (kill_x != bad_x || kill_y != bad_y)
13560 Bang(bad_x, bad_y);
13563 void KillPlayer(struct PlayerInfo *player)
13565 int jx = player->jx, jy = player->jy;
13567 if (!player->active)
13571 Debug("game:playing:KillPlayer",
13572 "0: killed == %d, active == %d, reanimated == %d",
13573 player->killed, player->active, player->reanimated);
13576 /* the following code was introduced to prevent an infinite loop when calling
13578 -> CheckTriggeredElementChangeExt()
13579 -> ExecuteCustomElementAction()
13581 -> (infinitely repeating the above sequence of function calls)
13582 which occurs when killing the player while having a CE with the setting
13583 "kill player X when explosion of <player X>"; the solution using a new
13584 field "player->killed" was chosen for backwards compatibility, although
13585 clever use of the fields "player->active" etc. would probably also work */
13587 if (player->killed)
13591 player->killed = TRUE;
13593 // remove accessible field at the player's position
13594 Tile[jx][jy] = EL_EMPTY;
13596 // deactivate shield (else Bang()/Explode() would not work right)
13597 player->shield_normal_time_left = 0;
13598 player->shield_deadly_time_left = 0;
13601 Debug("game:playing:KillPlayer",
13602 "1: killed == %d, active == %d, reanimated == %d",
13603 player->killed, player->active, player->reanimated);
13609 Debug("game:playing:KillPlayer",
13610 "2: killed == %d, active == %d, reanimated == %d",
13611 player->killed, player->active, player->reanimated);
13614 if (player->reanimated) // killed player may have been reanimated
13615 player->killed = player->reanimated = FALSE;
13617 BuryPlayer(player);
13620 static void KillPlayerUnlessEnemyProtected(int x, int y)
13622 if (!PLAYER_ENEMY_PROTECTED(x, y))
13623 KillPlayer(PLAYERINFO(x, y));
13626 static void KillPlayerUnlessExplosionProtected(int x, int y)
13628 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13629 KillPlayer(PLAYERINFO(x, y));
13632 void BuryPlayer(struct PlayerInfo *player)
13634 int jx = player->jx, jy = player->jy;
13636 if (!player->active)
13639 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13640 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13642 RemovePlayer(player);
13644 player->buried = TRUE;
13646 if (game.all_players_gone)
13647 game.GameOver = TRUE;
13650 void RemovePlayer(struct PlayerInfo *player)
13652 int jx = player->jx, jy = player->jy;
13653 int i, found = FALSE;
13655 player->present = FALSE;
13656 player->active = FALSE;
13658 // required for some CE actions (even if the player is not active anymore)
13659 player->MovPos = 0;
13661 if (!ExplodeField[jx][jy])
13662 StorePlayer[jx][jy] = 0;
13664 if (player->is_moving)
13665 TEST_DrawLevelField(player->last_jx, player->last_jy);
13667 for (i = 0; i < MAX_PLAYERS; i++)
13668 if (stored_player[i].active)
13673 game.all_players_gone = TRUE;
13674 game.GameOver = TRUE;
13677 game.exit_x = game.robot_wheel_x = jx;
13678 game.exit_y = game.robot_wheel_y = jy;
13681 void ExitPlayer(struct PlayerInfo *player)
13683 DrawPlayer(player); // needed here only to cleanup last field
13684 RemovePlayer(player);
13686 if (game.players_still_needed > 0)
13687 game.players_still_needed--;
13690 static void setFieldForSnapping(int x, int y, int element, int direction)
13692 struct ElementInfo *ei = &element_info[element];
13693 int direction_bit = MV_DIR_TO_BIT(direction);
13694 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13695 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13696 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13698 Tile[x][y] = EL_ELEMENT_SNAPPING;
13699 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13701 ResetGfxAnimation(x, y);
13703 GfxElement[x][y] = element;
13704 GfxAction[x][y] = action;
13705 GfxDir[x][y] = direction;
13706 GfxFrame[x][y] = -1;
13710 =============================================================================
13711 checkDiagonalPushing()
13712 -----------------------------------------------------------------------------
13713 check if diagonal input device direction results in pushing of object
13714 (by checking if the alternative direction is walkable, diggable, ...)
13715 =============================================================================
13718 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13719 int x, int y, int real_dx, int real_dy)
13721 int jx, jy, dx, dy, xx, yy;
13723 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13726 // diagonal direction: check alternative direction
13731 xx = jx + (dx == 0 ? real_dx : 0);
13732 yy = jy + (dy == 0 ? real_dy : 0);
13734 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13738 =============================================================================
13740 -----------------------------------------------------------------------------
13741 x, y: field next to player (non-diagonal) to try to dig to
13742 real_dx, real_dy: direction as read from input device (can be diagonal)
13743 =============================================================================
13746 static int DigField(struct PlayerInfo *player,
13747 int oldx, int oldy, int x, int y,
13748 int real_dx, int real_dy, int mode)
13750 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13751 boolean player_was_pushing = player->is_pushing;
13752 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13753 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13754 int jx = oldx, jy = oldy;
13755 int dx = x - jx, dy = y - jy;
13756 int nextx = x + dx, nexty = y + dy;
13757 int move_direction = (dx == -1 ? MV_LEFT :
13758 dx == +1 ? MV_RIGHT :
13760 dy == +1 ? MV_DOWN : MV_NONE);
13761 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13762 int dig_side = MV_DIR_OPPOSITE(move_direction);
13763 int old_element = Tile[jx][jy];
13764 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13767 if (is_player) // function can also be called by EL_PENGUIN
13769 if (player->MovPos == 0)
13771 player->is_digging = FALSE;
13772 player->is_collecting = FALSE;
13775 if (player->MovPos == 0) // last pushing move finished
13776 player->is_pushing = FALSE;
13778 if (mode == DF_NO_PUSH) // player just stopped pushing
13780 player->is_switching = FALSE;
13781 player->push_delay = -1;
13783 return MP_NO_ACTION;
13787 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13788 old_element = Back[jx][jy];
13790 // in case of element dropped at player position, check background
13791 else if (Back[jx][jy] != EL_EMPTY &&
13792 game.engine_version >= VERSION_IDENT(2,2,0,0))
13793 old_element = Back[jx][jy];
13795 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13796 return MP_NO_ACTION; // field has no opening in this direction
13798 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13799 return MP_NO_ACTION; // field has no opening in this direction
13801 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13805 Tile[jx][jy] = player->artwork_element;
13806 InitMovingField(jx, jy, MV_DOWN);
13807 Store[jx][jy] = EL_ACID;
13808 ContinueMoving(jx, jy);
13809 BuryPlayer(player);
13811 return MP_DONT_RUN_INTO;
13814 if (player_can_move && DONT_RUN_INTO(element))
13816 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13818 return MP_DONT_RUN_INTO;
13821 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13822 return MP_NO_ACTION;
13824 collect_count = element_info[element].collect_count_initial;
13826 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13827 return MP_NO_ACTION;
13829 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13830 player_can_move = player_can_move_or_snap;
13832 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13833 game.engine_version >= VERSION_IDENT(2,2,0,0))
13835 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13836 player->index_bit, dig_side);
13837 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13838 player->index_bit, dig_side);
13840 if (element == EL_DC_LANDMINE)
13843 if (Tile[x][y] != element) // field changed by snapping
13846 return MP_NO_ACTION;
13849 if (player->gravity && is_player && !player->is_auto_moving &&
13850 canFallDown(player) && move_direction != MV_DOWN &&
13851 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13852 return MP_NO_ACTION; // player cannot walk here due to gravity
13854 if (player_can_move &&
13855 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13857 int sound_element = SND_ELEMENT(element);
13858 int sound_action = ACTION_WALKING;
13860 if (IS_RND_GATE(element))
13862 if (!player->key[RND_GATE_NR(element)])
13863 return MP_NO_ACTION;
13865 else if (IS_RND_GATE_GRAY(element))
13867 if (!player->key[RND_GATE_GRAY_NR(element)])
13868 return MP_NO_ACTION;
13870 else if (IS_RND_GATE_GRAY_ACTIVE(element))
13872 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
13873 return MP_NO_ACTION;
13875 else if (element == EL_EXIT_OPEN ||
13876 element == EL_EM_EXIT_OPEN ||
13877 element == EL_EM_EXIT_OPENING ||
13878 element == EL_STEEL_EXIT_OPEN ||
13879 element == EL_EM_STEEL_EXIT_OPEN ||
13880 element == EL_EM_STEEL_EXIT_OPENING ||
13881 element == EL_SP_EXIT_OPEN ||
13882 element == EL_SP_EXIT_OPENING)
13884 sound_action = ACTION_PASSING; // player is passing exit
13886 else if (element == EL_EMPTY)
13888 sound_action = ACTION_MOVING; // nothing to walk on
13891 // play sound from background or player, whatever is available
13892 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
13893 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
13895 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
13897 else if (player_can_move &&
13898 IS_PASSABLE(element) && canPassField(x, y, move_direction))
13900 if (!ACCESS_FROM(element, opposite_direction))
13901 return MP_NO_ACTION; // field not accessible from this direction
13903 if (CAN_MOVE(element)) // only fixed elements can be passed!
13904 return MP_NO_ACTION;
13906 if (IS_EM_GATE(element))
13908 if (!player->key[EM_GATE_NR(element)])
13909 return MP_NO_ACTION;
13911 else if (IS_EM_GATE_GRAY(element))
13913 if (!player->key[EM_GATE_GRAY_NR(element)])
13914 return MP_NO_ACTION;
13916 else if (IS_EM_GATE_GRAY_ACTIVE(element))
13918 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
13919 return MP_NO_ACTION;
13921 else if (IS_EMC_GATE(element))
13923 if (!player->key[EMC_GATE_NR(element)])
13924 return MP_NO_ACTION;
13926 else if (IS_EMC_GATE_GRAY(element))
13928 if (!player->key[EMC_GATE_GRAY_NR(element)])
13929 return MP_NO_ACTION;
13931 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
13933 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
13934 return MP_NO_ACTION;
13936 else if (element == EL_DC_GATE_WHITE ||
13937 element == EL_DC_GATE_WHITE_GRAY ||
13938 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
13940 if (player->num_white_keys == 0)
13941 return MP_NO_ACTION;
13943 player->num_white_keys--;
13945 else if (IS_SP_PORT(element))
13947 if (element == EL_SP_GRAVITY_PORT_LEFT ||
13948 element == EL_SP_GRAVITY_PORT_RIGHT ||
13949 element == EL_SP_GRAVITY_PORT_UP ||
13950 element == EL_SP_GRAVITY_PORT_DOWN)
13951 player->gravity = !player->gravity;
13952 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
13953 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
13954 element == EL_SP_GRAVITY_ON_PORT_UP ||
13955 element == EL_SP_GRAVITY_ON_PORT_DOWN)
13956 player->gravity = TRUE;
13957 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
13958 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
13959 element == EL_SP_GRAVITY_OFF_PORT_UP ||
13960 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
13961 player->gravity = FALSE;
13964 // automatically move to the next field with double speed
13965 player->programmed_action = move_direction;
13967 if (player->move_delay_reset_counter == 0)
13969 player->move_delay_reset_counter = 2; // two double speed steps
13971 DOUBLE_PLAYER_SPEED(player);
13974 PlayLevelSoundAction(x, y, ACTION_PASSING);
13976 else if (player_can_move_or_snap && IS_DIGGABLE(element))
13980 if (mode != DF_SNAP)
13982 GfxElement[x][y] = GFX_ELEMENT(element);
13983 player->is_digging = TRUE;
13986 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
13988 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
13989 player->index_bit, dig_side);
13991 if (mode == DF_SNAP)
13993 if (level.block_snap_field)
13994 setFieldForSnapping(x, y, element, move_direction);
13996 TestIfElementTouchesCustomElement(x, y); // for empty space
13998 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13999 player->index_bit, dig_side);
14002 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14006 if (is_player && mode != DF_SNAP)
14008 GfxElement[x][y] = element;
14009 player->is_collecting = TRUE;
14012 if (element == EL_SPEED_PILL)
14014 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14016 else if (element == EL_EXTRA_TIME && level.time > 0)
14018 TimeLeft += level.extra_time;
14020 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14022 DisplayGameControlValues();
14024 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14026 player->shield_normal_time_left += level.shield_normal_time;
14027 if (element == EL_SHIELD_DEADLY)
14028 player->shield_deadly_time_left += level.shield_deadly_time;
14030 else if (element == EL_DYNAMITE ||
14031 element == EL_EM_DYNAMITE ||
14032 element == EL_SP_DISK_RED)
14034 if (player->inventory_size < MAX_INVENTORY_SIZE)
14035 player->inventory_element[player->inventory_size++] = element;
14037 DrawGameDoorValues();
14039 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14041 player->dynabomb_count++;
14042 player->dynabombs_left++;
14044 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14046 player->dynabomb_size++;
14048 else if (element == EL_DYNABOMB_INCREASE_POWER)
14050 player->dynabomb_xl = TRUE;
14052 else if (IS_KEY(element))
14054 player->key[KEY_NR(element)] = TRUE;
14056 DrawGameDoorValues();
14058 else if (element == EL_DC_KEY_WHITE)
14060 player->num_white_keys++;
14062 // display white keys?
14063 // DrawGameDoorValues();
14065 else if (IS_ENVELOPE(element))
14067 player->show_envelope = element;
14069 else if (element == EL_EMC_LENSES)
14071 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14073 RedrawAllInvisibleElementsForLenses();
14075 else if (element == EL_EMC_MAGNIFIER)
14077 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14079 RedrawAllInvisibleElementsForMagnifier();
14081 else if (IS_DROPPABLE(element) ||
14082 IS_THROWABLE(element)) // can be collected and dropped
14086 if (collect_count == 0)
14087 player->inventory_infinite_element = element;
14089 for (i = 0; i < collect_count; i++)
14090 if (player->inventory_size < MAX_INVENTORY_SIZE)
14091 player->inventory_element[player->inventory_size++] = element;
14093 DrawGameDoorValues();
14095 else if (collect_count > 0)
14097 game.gems_still_needed -= collect_count;
14098 if (game.gems_still_needed < 0)
14099 game.gems_still_needed = 0;
14101 game.snapshot.collected_item = TRUE;
14103 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14105 DisplayGameControlValues();
14108 RaiseScoreElement(element);
14109 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14112 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14113 player->index_bit, dig_side);
14115 if (mode == DF_SNAP)
14117 if (level.block_snap_field)
14118 setFieldForSnapping(x, y, element, move_direction);
14120 TestIfElementTouchesCustomElement(x, y); // for empty space
14122 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14123 player->index_bit, dig_side);
14126 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14128 if (mode == DF_SNAP && element != EL_BD_ROCK)
14129 return MP_NO_ACTION;
14131 if (CAN_FALL(element) && dy)
14132 return MP_NO_ACTION;
14134 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14135 !(element == EL_SPRING && level.use_spring_bug))
14136 return MP_NO_ACTION;
14138 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14139 ((move_direction & MV_VERTICAL &&
14140 ((element_info[element].move_pattern & MV_LEFT &&
14141 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14142 (element_info[element].move_pattern & MV_RIGHT &&
14143 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14144 (move_direction & MV_HORIZONTAL &&
14145 ((element_info[element].move_pattern & MV_UP &&
14146 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14147 (element_info[element].move_pattern & MV_DOWN &&
14148 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14149 return MP_NO_ACTION;
14151 // do not push elements already moving away faster than player
14152 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14153 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14154 return MP_NO_ACTION;
14156 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14158 if (player->push_delay_value == -1 || !player_was_pushing)
14159 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14161 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14163 if (player->push_delay_value == -1)
14164 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14166 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14168 if (!player->is_pushing)
14169 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14172 player->is_pushing = TRUE;
14173 player->is_active = TRUE;
14175 if (!(IN_LEV_FIELD(nextx, nexty) &&
14176 (IS_FREE(nextx, nexty) ||
14177 (IS_SB_ELEMENT(element) &&
14178 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14179 (IS_CUSTOM_ELEMENT(element) &&
14180 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14181 return MP_NO_ACTION;
14183 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14184 return MP_NO_ACTION;
14186 if (player->push_delay == -1) // new pushing; restart delay
14187 player->push_delay = 0;
14189 if (player->push_delay < player->push_delay_value &&
14190 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14191 element != EL_SPRING && element != EL_BALLOON)
14193 // make sure that there is no move delay before next try to push
14194 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14195 player->move_delay = 0;
14197 return MP_NO_ACTION;
14200 if (IS_CUSTOM_ELEMENT(element) &&
14201 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14203 if (!DigFieldByCE(nextx, nexty, element))
14204 return MP_NO_ACTION;
14207 if (IS_SB_ELEMENT(element))
14209 boolean sokoban_task_solved = FALSE;
14211 if (element == EL_SOKOBAN_FIELD_FULL)
14213 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14215 IncrementSokobanFieldsNeeded();
14216 IncrementSokobanObjectsNeeded();
14219 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14221 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14223 DecrementSokobanFieldsNeeded();
14224 DecrementSokobanObjectsNeeded();
14226 // sokoban object was pushed from empty field to sokoban field
14227 if (Back[x][y] == EL_EMPTY)
14228 sokoban_task_solved = TRUE;
14231 Tile[x][y] = EL_SOKOBAN_OBJECT;
14233 if (Back[x][y] == Back[nextx][nexty])
14234 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14235 else if (Back[x][y] != 0)
14236 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14239 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14242 if (sokoban_task_solved &&
14243 game.sokoban_fields_still_needed == 0 &&
14244 game.sokoban_objects_still_needed == 0 &&
14245 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14247 game.players_still_needed = 0;
14251 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14255 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14257 InitMovingField(x, y, move_direction);
14258 GfxAction[x][y] = ACTION_PUSHING;
14260 if (mode == DF_SNAP)
14261 ContinueMoving(x, y);
14263 MovPos[x][y] = (dx != 0 ? dx : dy);
14265 Pushed[x][y] = TRUE;
14266 Pushed[nextx][nexty] = TRUE;
14268 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14269 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14271 player->push_delay_value = -1; // get new value later
14273 // check for element change _after_ element has been pushed
14274 if (game.use_change_when_pushing_bug)
14276 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14277 player->index_bit, dig_side);
14278 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14279 player->index_bit, dig_side);
14282 else if (IS_SWITCHABLE(element))
14284 if (PLAYER_SWITCHING(player, x, y))
14286 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14287 player->index_bit, dig_side);
14292 player->is_switching = TRUE;
14293 player->switch_x = x;
14294 player->switch_y = y;
14296 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14298 if (element == EL_ROBOT_WHEEL)
14300 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14302 game.robot_wheel_x = x;
14303 game.robot_wheel_y = y;
14304 game.robot_wheel_active = TRUE;
14306 TEST_DrawLevelField(x, y);
14308 else if (element == EL_SP_TERMINAL)
14312 SCAN_PLAYFIELD(xx, yy)
14314 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14318 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14320 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14322 ResetGfxAnimation(xx, yy);
14323 TEST_DrawLevelField(xx, yy);
14327 else if (IS_BELT_SWITCH(element))
14329 ToggleBeltSwitch(x, y);
14331 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14332 element == EL_SWITCHGATE_SWITCH_DOWN ||
14333 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14334 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14336 ToggleSwitchgateSwitch(x, y);
14338 else if (element == EL_LIGHT_SWITCH ||
14339 element == EL_LIGHT_SWITCH_ACTIVE)
14341 ToggleLightSwitch(x, y);
14343 else if (element == EL_TIMEGATE_SWITCH ||
14344 element == EL_DC_TIMEGATE_SWITCH)
14346 ActivateTimegateSwitch(x, y);
14348 else if (element == EL_BALLOON_SWITCH_LEFT ||
14349 element == EL_BALLOON_SWITCH_RIGHT ||
14350 element == EL_BALLOON_SWITCH_UP ||
14351 element == EL_BALLOON_SWITCH_DOWN ||
14352 element == EL_BALLOON_SWITCH_NONE ||
14353 element == EL_BALLOON_SWITCH_ANY)
14355 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14356 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14357 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14358 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14359 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14362 else if (element == EL_LAMP)
14364 Tile[x][y] = EL_LAMP_ACTIVE;
14365 game.lights_still_needed--;
14367 ResetGfxAnimation(x, y);
14368 TEST_DrawLevelField(x, y);
14370 else if (element == EL_TIME_ORB_FULL)
14372 Tile[x][y] = EL_TIME_ORB_EMPTY;
14374 if (level.time > 0 || level.use_time_orb_bug)
14376 TimeLeft += level.time_orb_time;
14377 game.no_time_limit = FALSE;
14379 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14381 DisplayGameControlValues();
14384 ResetGfxAnimation(x, y);
14385 TEST_DrawLevelField(x, y);
14387 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14388 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14392 game.ball_active = !game.ball_active;
14394 SCAN_PLAYFIELD(xx, yy)
14396 int e = Tile[xx][yy];
14398 if (game.ball_active)
14400 if (e == EL_EMC_MAGIC_BALL)
14401 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14402 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14403 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14407 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14408 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14409 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14410 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14415 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14416 player->index_bit, dig_side);
14418 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14419 player->index_bit, dig_side);
14421 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14422 player->index_bit, dig_side);
14428 if (!PLAYER_SWITCHING(player, x, y))
14430 player->is_switching = TRUE;
14431 player->switch_x = x;
14432 player->switch_y = y;
14434 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14435 player->index_bit, dig_side);
14436 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14437 player->index_bit, dig_side);
14439 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14440 player->index_bit, dig_side);
14441 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14442 player->index_bit, dig_side);
14445 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14446 player->index_bit, dig_side);
14447 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14448 player->index_bit, dig_side);
14450 return MP_NO_ACTION;
14453 player->push_delay = -1;
14455 if (is_player) // function can also be called by EL_PENGUIN
14457 if (Tile[x][y] != element) // really digged/collected something
14459 player->is_collecting = !player->is_digging;
14460 player->is_active = TRUE;
14467 static boolean DigFieldByCE(int x, int y, int digging_element)
14469 int element = Tile[x][y];
14471 if (!IS_FREE(x, y))
14473 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14474 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14477 // no element can dig solid indestructible elements
14478 if (IS_INDESTRUCTIBLE(element) &&
14479 !IS_DIGGABLE(element) &&
14480 !IS_COLLECTIBLE(element))
14483 if (AmoebaNr[x][y] &&
14484 (element == EL_AMOEBA_FULL ||
14485 element == EL_BD_AMOEBA ||
14486 element == EL_AMOEBA_GROWING))
14488 AmoebaCnt[AmoebaNr[x][y]]--;
14489 AmoebaCnt2[AmoebaNr[x][y]]--;
14492 if (IS_MOVING(x, y))
14493 RemoveMovingField(x, y);
14497 TEST_DrawLevelField(x, y);
14500 // if digged element was about to explode, prevent the explosion
14501 ExplodeField[x][y] = EX_TYPE_NONE;
14503 PlayLevelSoundAction(x, y, action);
14506 Store[x][y] = EL_EMPTY;
14508 // this makes it possible to leave the removed element again
14509 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14510 Store[x][y] = element;
14515 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14517 int jx = player->jx, jy = player->jy;
14518 int x = jx + dx, y = jy + dy;
14519 int snap_direction = (dx == -1 ? MV_LEFT :
14520 dx == +1 ? MV_RIGHT :
14522 dy == +1 ? MV_DOWN : MV_NONE);
14523 boolean can_continue_snapping = (level.continuous_snapping &&
14524 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14526 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14529 if (!player->active || !IN_LEV_FIELD(x, y))
14537 if (player->MovPos == 0)
14538 player->is_pushing = FALSE;
14540 player->is_snapping = FALSE;
14542 if (player->MovPos == 0)
14544 player->is_moving = FALSE;
14545 player->is_digging = FALSE;
14546 player->is_collecting = FALSE;
14552 // prevent snapping with already pressed snap key when not allowed
14553 if (player->is_snapping && !can_continue_snapping)
14556 player->MovDir = snap_direction;
14558 if (player->MovPos == 0)
14560 player->is_moving = FALSE;
14561 player->is_digging = FALSE;
14562 player->is_collecting = FALSE;
14565 player->is_dropping = FALSE;
14566 player->is_dropping_pressed = FALSE;
14567 player->drop_pressed_delay = 0;
14569 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14572 player->is_snapping = TRUE;
14573 player->is_active = TRUE;
14575 if (player->MovPos == 0)
14577 player->is_moving = FALSE;
14578 player->is_digging = FALSE;
14579 player->is_collecting = FALSE;
14582 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14583 TEST_DrawLevelField(player->last_jx, player->last_jy);
14585 TEST_DrawLevelField(x, y);
14590 static boolean DropElement(struct PlayerInfo *player)
14592 int old_element, new_element;
14593 int dropx = player->jx, dropy = player->jy;
14594 int drop_direction = player->MovDir;
14595 int drop_side = drop_direction;
14596 int drop_element = get_next_dropped_element(player);
14598 /* do not drop an element on top of another element; when holding drop key
14599 pressed without moving, dropped element must move away before the next
14600 element can be dropped (this is especially important if the next element
14601 is dynamite, which can be placed on background for historical reasons) */
14602 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14605 if (IS_THROWABLE(drop_element))
14607 dropx += GET_DX_FROM_DIR(drop_direction);
14608 dropy += GET_DY_FROM_DIR(drop_direction);
14610 if (!IN_LEV_FIELD(dropx, dropy))
14614 old_element = Tile[dropx][dropy]; // old element at dropping position
14615 new_element = drop_element; // default: no change when dropping
14617 // check if player is active, not moving and ready to drop
14618 if (!player->active || player->MovPos || player->drop_delay > 0)
14621 // check if player has anything that can be dropped
14622 if (new_element == EL_UNDEFINED)
14625 // only set if player has anything that can be dropped
14626 player->is_dropping_pressed = TRUE;
14628 // check if drop key was pressed long enough for EM style dynamite
14629 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14632 // check if anything can be dropped at the current position
14633 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14636 // collected custom elements can only be dropped on empty fields
14637 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14640 if (old_element != EL_EMPTY)
14641 Back[dropx][dropy] = old_element; // store old element on this field
14643 ResetGfxAnimation(dropx, dropy);
14644 ResetRandomAnimationValue(dropx, dropy);
14646 if (player->inventory_size > 0 ||
14647 player->inventory_infinite_element != EL_UNDEFINED)
14649 if (player->inventory_size > 0)
14651 player->inventory_size--;
14653 DrawGameDoorValues();
14655 if (new_element == EL_DYNAMITE)
14656 new_element = EL_DYNAMITE_ACTIVE;
14657 else if (new_element == EL_EM_DYNAMITE)
14658 new_element = EL_EM_DYNAMITE_ACTIVE;
14659 else if (new_element == EL_SP_DISK_RED)
14660 new_element = EL_SP_DISK_RED_ACTIVE;
14663 Tile[dropx][dropy] = new_element;
14665 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14666 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14667 el2img(Tile[dropx][dropy]), 0);
14669 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14671 // needed if previous element just changed to "empty" in the last frame
14672 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14674 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14675 player->index_bit, drop_side);
14676 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14678 player->index_bit, drop_side);
14680 TestIfElementTouchesCustomElement(dropx, dropy);
14682 else // player is dropping a dyna bomb
14684 player->dynabombs_left--;
14686 Tile[dropx][dropy] = new_element;
14688 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14689 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14690 el2img(Tile[dropx][dropy]), 0);
14692 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14695 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14696 InitField_WithBug1(dropx, dropy, FALSE);
14698 new_element = Tile[dropx][dropy]; // element might have changed
14700 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14701 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14703 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14704 MovDir[dropx][dropy] = drop_direction;
14706 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14708 // do not cause impact style collision by dropping elements that can fall
14709 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14712 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14713 player->is_dropping = TRUE;
14715 player->drop_pressed_delay = 0;
14716 player->is_dropping_pressed = FALSE;
14718 player->drop_x = dropx;
14719 player->drop_y = dropy;
14724 // ----------------------------------------------------------------------------
14725 // game sound playing functions
14726 // ----------------------------------------------------------------------------
14728 static int *loop_sound_frame = NULL;
14729 static int *loop_sound_volume = NULL;
14731 void InitPlayLevelSound(void)
14733 int num_sounds = getSoundListSize();
14735 checked_free(loop_sound_frame);
14736 checked_free(loop_sound_volume);
14738 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14739 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14742 static void PlayLevelSound(int x, int y, int nr)
14744 int sx = SCREENX(x), sy = SCREENY(y);
14745 int volume, stereo_position;
14746 int max_distance = 8;
14747 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14749 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14750 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14753 if (!IN_LEV_FIELD(x, y) ||
14754 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14755 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14758 volume = SOUND_MAX_VOLUME;
14760 if (!IN_SCR_FIELD(sx, sy))
14762 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14763 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14765 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14768 stereo_position = (SOUND_MAX_LEFT +
14769 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14770 (SCR_FIELDX + 2 * max_distance));
14772 if (IS_LOOP_SOUND(nr))
14774 /* This assures that quieter loop sounds do not overwrite louder ones,
14775 while restarting sound volume comparison with each new game frame. */
14777 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14780 loop_sound_volume[nr] = volume;
14781 loop_sound_frame[nr] = FrameCounter;
14784 PlaySoundExt(nr, volume, stereo_position, type);
14787 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14789 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14790 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14791 y < LEVELY(BY1) ? LEVELY(BY1) :
14792 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14796 static void PlayLevelSoundAction(int x, int y, int action)
14798 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14801 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14803 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14805 if (sound_effect != SND_UNDEFINED)
14806 PlayLevelSound(x, y, sound_effect);
14809 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14812 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14814 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14815 PlayLevelSound(x, y, sound_effect);
14818 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14820 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14822 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14823 PlayLevelSound(x, y, sound_effect);
14826 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14828 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14830 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14831 StopSound(sound_effect);
14834 static int getLevelMusicNr(void)
14836 if (levelset.music[level_nr] != MUS_UNDEFINED)
14837 return levelset.music[level_nr]; // from config file
14839 return MAP_NOCONF_MUSIC(level_nr); // from music dir
14842 static void FadeLevelSounds(void)
14847 static void FadeLevelMusic(void)
14849 int music_nr = getLevelMusicNr();
14850 char *curr_music = getCurrentlyPlayingMusicFilename();
14851 char *next_music = getMusicInfoEntryFilename(music_nr);
14853 if (!strEqual(curr_music, next_music))
14857 void FadeLevelSoundsAndMusic(void)
14863 static void PlayLevelMusic(void)
14865 int music_nr = getLevelMusicNr();
14866 char *curr_music = getCurrentlyPlayingMusicFilename();
14867 char *next_music = getMusicInfoEntryFilename(music_nr);
14869 if (!strEqual(curr_music, next_music))
14870 PlayMusicLoop(music_nr);
14873 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
14875 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
14877 int x = xx - offset;
14878 int y = yy - offset;
14883 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
14887 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14891 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14895 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14899 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14903 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14907 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14910 case SOUND_android_clone:
14911 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14914 case SOUND_android_move:
14915 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14919 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14923 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
14927 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
14930 case SOUND_eater_eat:
14931 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14935 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
14938 case SOUND_collect:
14939 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14942 case SOUND_diamond:
14943 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14947 // !!! CHECK THIS !!!
14949 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
14951 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
14955 case SOUND_wonderfall:
14956 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
14960 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
14964 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14968 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14972 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
14976 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
14980 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
14984 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
14988 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14991 case SOUND_exit_open:
14992 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
14995 case SOUND_exit_leave:
14996 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
14999 case SOUND_dynamite:
15000 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15004 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15008 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15012 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15016 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15020 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15024 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15028 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15033 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15035 int element = map_element_SP_to_RND(element_sp);
15036 int action = map_action_SP_to_RND(action_sp);
15037 int offset = (setup.sp_show_border_elements ? 0 : 1);
15038 int x = xx - offset;
15039 int y = yy - offset;
15041 PlayLevelSoundElementAction(x, y, element, action);
15044 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15046 int element = map_element_MM_to_RND(element_mm);
15047 int action = map_action_MM_to_RND(action_mm);
15049 int x = xx - offset;
15050 int y = yy - offset;
15052 if (!IS_MM_ELEMENT(element))
15053 element = EL_MM_DEFAULT;
15055 PlayLevelSoundElementAction(x, y, element, action);
15058 void PlaySound_MM(int sound_mm)
15060 int sound = map_sound_MM_to_RND(sound_mm);
15062 if (sound == SND_UNDEFINED)
15068 void PlaySoundLoop_MM(int sound_mm)
15070 int sound = map_sound_MM_to_RND(sound_mm);
15072 if (sound == SND_UNDEFINED)
15075 PlaySoundLoop(sound);
15078 void StopSound_MM(int sound_mm)
15080 int sound = map_sound_MM_to_RND(sound_mm);
15082 if (sound == SND_UNDEFINED)
15088 void RaiseScore(int value)
15090 game.score += value;
15092 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15094 DisplayGameControlValues();
15097 void RaiseScoreElement(int element)
15102 case EL_BD_DIAMOND:
15103 case EL_EMERALD_YELLOW:
15104 case EL_EMERALD_RED:
15105 case EL_EMERALD_PURPLE:
15106 case EL_SP_INFOTRON:
15107 RaiseScore(level.score[SC_EMERALD]);
15110 RaiseScore(level.score[SC_DIAMOND]);
15113 RaiseScore(level.score[SC_CRYSTAL]);
15116 RaiseScore(level.score[SC_PEARL]);
15119 case EL_BD_BUTTERFLY:
15120 case EL_SP_ELECTRON:
15121 RaiseScore(level.score[SC_BUG]);
15124 case EL_BD_FIREFLY:
15125 case EL_SP_SNIKSNAK:
15126 RaiseScore(level.score[SC_SPACESHIP]);
15129 case EL_DARK_YAMYAM:
15130 RaiseScore(level.score[SC_YAMYAM]);
15133 RaiseScore(level.score[SC_ROBOT]);
15136 RaiseScore(level.score[SC_PACMAN]);
15139 RaiseScore(level.score[SC_NUT]);
15142 case EL_EM_DYNAMITE:
15143 case EL_SP_DISK_RED:
15144 case EL_DYNABOMB_INCREASE_NUMBER:
15145 case EL_DYNABOMB_INCREASE_SIZE:
15146 case EL_DYNABOMB_INCREASE_POWER:
15147 RaiseScore(level.score[SC_DYNAMITE]);
15149 case EL_SHIELD_NORMAL:
15150 case EL_SHIELD_DEADLY:
15151 RaiseScore(level.score[SC_SHIELD]);
15153 case EL_EXTRA_TIME:
15154 RaiseScore(level.extra_time_score);
15168 case EL_DC_KEY_WHITE:
15169 RaiseScore(level.score[SC_KEY]);
15172 RaiseScore(element_info[element].collect_score);
15177 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15179 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15181 // closing door required in case of envelope style request dialogs
15184 // prevent short reactivation of overlay buttons while closing door
15185 SetOverlayActive(FALSE);
15187 CloseDoor(DOOR_CLOSE_1);
15190 if (network.enabled)
15191 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15195 FadeSkipNextFadeIn();
15197 SetGameStatus(GAME_MODE_MAIN);
15202 else // continue playing the game
15204 if (tape.playing && tape.deactivate_display)
15205 TapeDeactivateDisplayOff(TRUE);
15207 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15209 if (tape.playing && tape.deactivate_display)
15210 TapeDeactivateDisplayOn();
15214 void RequestQuitGame(boolean ask_if_really_quit)
15216 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15217 boolean skip_request = game.all_players_gone || quick_quit;
15219 RequestQuitGameExt(skip_request, quick_quit,
15220 "Do you really want to quit the game?");
15223 void RequestRestartGame(char *message)
15225 game.restart_game_message = NULL;
15227 boolean has_started_game = hasStartedNetworkGame();
15228 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15230 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15232 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15236 SetGameStatus(GAME_MODE_MAIN);
15242 void CheckGameOver(void)
15244 static boolean last_game_over = FALSE;
15245 static int game_over_delay = 0;
15246 int game_over_delay_value = 50;
15247 boolean game_over = checkGameFailed();
15249 // do not handle game over if request dialog is already active
15250 if (game.request_active)
15253 // do not ask to play again if game was never actually played
15254 if (!game.GamePlayed)
15259 last_game_over = FALSE;
15260 game_over_delay = game_over_delay_value;
15265 if (game_over_delay > 0)
15272 if (last_game_over != game_over)
15273 game.restart_game_message = (hasStartedNetworkGame() ?
15274 "Game over! Play it again?" :
15277 last_game_over = game_over;
15280 boolean checkGameSolved(void)
15282 // set for all game engines if level was solved
15283 return game.LevelSolved_GameEnd;
15286 boolean checkGameFailed(void)
15288 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15289 return (game_em.game_over && !game_em.level_solved);
15290 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15291 return (game_sp.game_over && !game_sp.level_solved);
15292 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15293 return (game_mm.game_over && !game_mm.level_solved);
15294 else // GAME_ENGINE_TYPE_RND
15295 return (game.GameOver && !game.LevelSolved);
15298 boolean checkGameEnded(void)
15300 return (checkGameSolved() || checkGameFailed());
15304 // ----------------------------------------------------------------------------
15305 // random generator functions
15306 // ----------------------------------------------------------------------------
15308 unsigned int InitEngineRandom_RND(int seed)
15310 game.num_random_calls = 0;
15312 return InitEngineRandom(seed);
15315 unsigned int RND(int max)
15319 game.num_random_calls++;
15321 return GetEngineRandom(max);
15328 // ----------------------------------------------------------------------------
15329 // game engine snapshot handling functions
15330 // ----------------------------------------------------------------------------
15332 struct EngineSnapshotInfo
15334 // runtime values for custom element collect score
15335 int collect_score[NUM_CUSTOM_ELEMENTS];
15337 // runtime values for group element choice position
15338 int choice_pos[NUM_GROUP_ELEMENTS];
15340 // runtime values for belt position animations
15341 int belt_graphic[4][NUM_BELT_PARTS];
15342 int belt_anim_mode[4][NUM_BELT_PARTS];
15345 static struct EngineSnapshotInfo engine_snapshot_rnd;
15346 static char *snapshot_level_identifier = NULL;
15347 static int snapshot_level_nr = -1;
15349 static void SaveEngineSnapshotValues_RND(void)
15351 static int belt_base_active_element[4] =
15353 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15354 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15355 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15356 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15360 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15362 int element = EL_CUSTOM_START + i;
15364 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15367 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15369 int element = EL_GROUP_START + i;
15371 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15374 for (i = 0; i < 4; i++)
15376 for (j = 0; j < NUM_BELT_PARTS; j++)
15378 int element = belt_base_active_element[i] + j;
15379 int graphic = el2img(element);
15380 int anim_mode = graphic_info[graphic].anim_mode;
15382 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15383 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15388 static void LoadEngineSnapshotValues_RND(void)
15390 unsigned int num_random_calls = game.num_random_calls;
15393 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15395 int element = EL_CUSTOM_START + i;
15397 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15400 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15402 int element = EL_GROUP_START + i;
15404 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15407 for (i = 0; i < 4; i++)
15409 for (j = 0; j < NUM_BELT_PARTS; j++)
15411 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15412 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15414 graphic_info[graphic].anim_mode = anim_mode;
15418 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15420 InitRND(tape.random_seed);
15421 for (i = 0; i < num_random_calls; i++)
15425 if (game.num_random_calls != num_random_calls)
15427 Error("number of random calls out of sync");
15428 Error("number of random calls should be %d", num_random_calls);
15429 Error("number of random calls is %d", game.num_random_calls);
15431 Fail("this should not happen -- please debug");
15435 void FreeEngineSnapshotSingle(void)
15437 FreeSnapshotSingle();
15439 setString(&snapshot_level_identifier, NULL);
15440 snapshot_level_nr = -1;
15443 void FreeEngineSnapshotList(void)
15445 FreeSnapshotList();
15448 static ListNode *SaveEngineSnapshotBuffers(void)
15450 ListNode *buffers = NULL;
15452 // copy some special values to a structure better suited for the snapshot
15454 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15455 SaveEngineSnapshotValues_RND();
15456 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15457 SaveEngineSnapshotValues_EM();
15458 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15459 SaveEngineSnapshotValues_SP(&buffers);
15460 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15461 SaveEngineSnapshotValues_MM(&buffers);
15463 // save values stored in special snapshot structure
15465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15466 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15467 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15468 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15469 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15470 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15471 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15472 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15474 // save further RND engine values
15476 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15477 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15478 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15480 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15481 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15482 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15483 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15484 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15486 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15487 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15488 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15490 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15492 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15493 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15495 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15496 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15497 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15498 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15499 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15500 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15501 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15502 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15503 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15504 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15505 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15506 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15507 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15508 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15509 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15510 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15511 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15512 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15514 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15515 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15517 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15518 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15519 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15521 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15522 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15524 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15525 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15526 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15527 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15528 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15530 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15531 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15534 ListNode *node = engine_snapshot_list_rnd;
15537 while (node != NULL)
15539 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15544 Debug("game:playing:SaveEngineSnapshotBuffers",
15545 "size of engine snapshot: %d bytes", num_bytes);
15551 void SaveEngineSnapshotSingle(void)
15553 ListNode *buffers = SaveEngineSnapshotBuffers();
15555 // finally save all snapshot buffers to single snapshot
15556 SaveSnapshotSingle(buffers);
15558 // save level identification information
15559 setString(&snapshot_level_identifier, leveldir_current->identifier);
15560 snapshot_level_nr = level_nr;
15563 boolean CheckSaveEngineSnapshotToList(void)
15565 boolean save_snapshot =
15566 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15567 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15568 game.snapshot.changed_action) ||
15569 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15570 game.snapshot.collected_item));
15572 game.snapshot.changed_action = FALSE;
15573 game.snapshot.collected_item = FALSE;
15574 game.snapshot.save_snapshot = save_snapshot;
15576 return save_snapshot;
15579 void SaveEngineSnapshotToList(void)
15581 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15585 ListNode *buffers = SaveEngineSnapshotBuffers();
15587 // finally save all snapshot buffers to snapshot list
15588 SaveSnapshotToList(buffers);
15591 void SaveEngineSnapshotToListInitial(void)
15593 FreeEngineSnapshotList();
15595 SaveEngineSnapshotToList();
15598 static void LoadEngineSnapshotValues(void)
15600 // restore special values from snapshot structure
15602 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15603 LoadEngineSnapshotValues_RND();
15604 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15605 LoadEngineSnapshotValues_EM();
15606 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15607 LoadEngineSnapshotValues_SP();
15608 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15609 LoadEngineSnapshotValues_MM();
15612 void LoadEngineSnapshotSingle(void)
15614 LoadSnapshotSingle();
15616 LoadEngineSnapshotValues();
15619 static void LoadEngineSnapshot_Undo(int steps)
15621 LoadSnapshotFromList_Older(steps);
15623 LoadEngineSnapshotValues();
15626 static void LoadEngineSnapshot_Redo(int steps)
15628 LoadSnapshotFromList_Newer(steps);
15630 LoadEngineSnapshotValues();
15633 boolean CheckEngineSnapshotSingle(void)
15635 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15636 snapshot_level_nr == level_nr);
15639 boolean CheckEngineSnapshotList(void)
15641 return CheckSnapshotList();
15645 // ---------- new game button stuff -------------------------------------------
15652 boolean *setup_value;
15653 boolean allowed_on_tape;
15654 boolean is_touch_button;
15656 } gamebutton_info[NUM_GAME_BUTTONS] =
15659 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15660 GAME_CTRL_ID_STOP, NULL,
15661 TRUE, FALSE, "stop game"
15664 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15665 GAME_CTRL_ID_PAUSE, NULL,
15666 TRUE, FALSE, "pause game"
15669 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15670 GAME_CTRL_ID_PLAY, NULL,
15671 TRUE, FALSE, "play game"
15674 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15675 GAME_CTRL_ID_UNDO, NULL,
15676 TRUE, FALSE, "undo step"
15679 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15680 GAME_CTRL_ID_REDO, NULL,
15681 TRUE, FALSE, "redo step"
15684 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15685 GAME_CTRL_ID_SAVE, NULL,
15686 TRUE, FALSE, "save game"
15689 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15690 GAME_CTRL_ID_PAUSE2, NULL,
15691 TRUE, FALSE, "pause game"
15694 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15695 GAME_CTRL_ID_LOAD, NULL,
15696 TRUE, FALSE, "load game"
15699 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15700 GAME_CTRL_ID_PANEL_STOP, NULL,
15701 FALSE, FALSE, "stop game"
15704 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15705 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15706 FALSE, FALSE, "pause game"
15709 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15710 GAME_CTRL_ID_PANEL_PLAY, NULL,
15711 FALSE, FALSE, "play game"
15714 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15715 GAME_CTRL_ID_TOUCH_STOP, NULL,
15716 FALSE, TRUE, "stop game"
15719 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15720 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15721 FALSE, TRUE, "pause game"
15724 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15725 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15726 TRUE, FALSE, "background music on/off"
15729 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15730 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15731 TRUE, FALSE, "sound loops on/off"
15734 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15735 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15736 TRUE, FALSE, "normal sounds on/off"
15739 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15740 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15741 FALSE, FALSE, "background music on/off"
15744 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15745 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15746 FALSE, FALSE, "sound loops on/off"
15749 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15750 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15751 FALSE, FALSE, "normal sounds on/off"
15755 void CreateGameButtons(void)
15759 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15761 int graphic = gamebutton_info[i].graphic;
15762 struct GraphicInfo *gfx = &graphic_info[graphic];
15763 struct XY *pos = gamebutton_info[i].pos;
15764 struct GadgetInfo *gi;
15767 unsigned int event_mask;
15768 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15769 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15770 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15771 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15772 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15773 int gd_x = gfx->src_x;
15774 int gd_y = gfx->src_y;
15775 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15776 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15777 int gd_xa = gfx->src_x + gfx->active_xoffset;
15778 int gd_ya = gfx->src_y + gfx->active_yoffset;
15779 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15780 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15781 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15782 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15785 if (gfx->bitmap == NULL)
15787 game_gadget[id] = NULL;
15792 if (id == GAME_CTRL_ID_STOP ||
15793 id == GAME_CTRL_ID_PANEL_STOP ||
15794 id == GAME_CTRL_ID_TOUCH_STOP ||
15795 id == GAME_CTRL_ID_PLAY ||
15796 id == GAME_CTRL_ID_PANEL_PLAY ||
15797 id == GAME_CTRL_ID_SAVE ||
15798 id == GAME_CTRL_ID_LOAD)
15800 button_type = GD_TYPE_NORMAL_BUTTON;
15802 event_mask = GD_EVENT_RELEASED;
15804 else if (id == GAME_CTRL_ID_UNDO ||
15805 id == GAME_CTRL_ID_REDO)
15807 button_type = GD_TYPE_NORMAL_BUTTON;
15809 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15813 button_type = GD_TYPE_CHECK_BUTTON;
15814 checked = (gamebutton_info[i].setup_value != NULL ?
15815 *gamebutton_info[i].setup_value : FALSE);
15816 event_mask = GD_EVENT_PRESSED;
15819 gi = CreateGadget(GDI_CUSTOM_ID, id,
15820 GDI_IMAGE_ID, graphic,
15821 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15824 GDI_WIDTH, gfx->width,
15825 GDI_HEIGHT, gfx->height,
15826 GDI_TYPE, button_type,
15827 GDI_STATE, GD_BUTTON_UNPRESSED,
15828 GDI_CHECKED, checked,
15829 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15830 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15831 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15832 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15833 GDI_DIRECT_DRAW, FALSE,
15834 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15835 GDI_EVENT_MASK, event_mask,
15836 GDI_CALLBACK_ACTION, HandleGameButtons,
15840 Fail("cannot create gadget");
15842 game_gadget[id] = gi;
15846 void FreeGameButtons(void)
15850 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15851 FreeGadget(game_gadget[i]);
15854 static void UnmapGameButtonsAtSamePosition(int id)
15858 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15860 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15861 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15862 UnmapGadget(game_gadget[i]);
15865 static void UnmapGameButtonsAtSamePosition_All(void)
15867 if (setup.show_snapshot_buttons)
15869 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
15870 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
15871 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
15875 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
15876 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
15877 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
15879 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
15880 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
15881 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
15885 static void MapGameButtonsAtSamePosition(int id)
15889 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15891 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
15892 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
15893 MapGadget(game_gadget[i]);
15895 UnmapGameButtonsAtSamePosition_All();
15898 void MapUndoRedoButtons(void)
15900 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15901 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15903 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15904 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15907 void UnmapUndoRedoButtons(void)
15909 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
15910 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
15912 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
15913 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
15916 void ModifyPauseButtons(void)
15920 GAME_CTRL_ID_PAUSE,
15921 GAME_CTRL_ID_PAUSE2,
15922 GAME_CTRL_ID_PANEL_PAUSE,
15923 GAME_CTRL_ID_TOUCH_PAUSE,
15928 for (i = 0; ids[i] > -1; i++)
15929 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
15932 static void MapGameButtonsExt(boolean on_tape)
15936 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15937 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
15938 i != GAME_CTRL_ID_UNDO &&
15939 i != GAME_CTRL_ID_REDO)
15940 MapGadget(game_gadget[i]);
15942 UnmapGameButtonsAtSamePosition_All();
15944 RedrawGameButtons();
15947 static void UnmapGameButtonsExt(boolean on_tape)
15951 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15952 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15953 UnmapGadget(game_gadget[i]);
15956 static void RedrawGameButtonsExt(boolean on_tape)
15960 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15961 if (!on_tape || gamebutton_info[i].allowed_on_tape)
15962 RedrawGadget(game_gadget[i]);
15965 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
15970 gi->checked = state;
15973 static void RedrawSoundButtonGadget(int id)
15975 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
15976 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
15977 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
15978 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
15979 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
15980 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
15983 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
15984 RedrawGadget(game_gadget[id2]);
15987 void MapGameButtons(void)
15989 MapGameButtonsExt(FALSE);
15992 void UnmapGameButtons(void)
15994 UnmapGameButtonsExt(FALSE);
15997 void RedrawGameButtons(void)
15999 RedrawGameButtonsExt(FALSE);
16002 void MapGameButtonsOnTape(void)
16004 MapGameButtonsExt(TRUE);
16007 void UnmapGameButtonsOnTape(void)
16009 UnmapGameButtonsExt(TRUE);
16012 void RedrawGameButtonsOnTape(void)
16014 RedrawGameButtonsExt(TRUE);
16017 static void GameUndoRedoExt(void)
16019 ClearPlayerAction();
16021 tape.pausing = TRUE;
16024 UpdateAndDisplayGameControlValues();
16026 DrawCompleteVideoDisplay();
16027 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16028 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16029 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16034 static void GameUndo(int steps)
16036 if (!CheckEngineSnapshotList())
16039 LoadEngineSnapshot_Undo(steps);
16044 static void GameRedo(int steps)
16046 if (!CheckEngineSnapshotList())
16049 LoadEngineSnapshot_Redo(steps);
16054 static void HandleGameButtonsExt(int id, int button)
16056 static boolean game_undo_executed = FALSE;
16057 int steps = BUTTON_STEPSIZE(button);
16058 boolean handle_game_buttons =
16059 (game_status == GAME_MODE_PLAYING ||
16060 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16062 if (!handle_game_buttons)
16067 case GAME_CTRL_ID_STOP:
16068 case GAME_CTRL_ID_PANEL_STOP:
16069 case GAME_CTRL_ID_TOUCH_STOP:
16070 if (game_status == GAME_MODE_MAIN)
16076 RequestQuitGame(TRUE);
16080 case GAME_CTRL_ID_PAUSE:
16081 case GAME_CTRL_ID_PAUSE2:
16082 case GAME_CTRL_ID_PANEL_PAUSE:
16083 case GAME_CTRL_ID_TOUCH_PAUSE:
16084 if (network.enabled && game_status == GAME_MODE_PLAYING)
16087 SendToServer_ContinuePlaying();
16089 SendToServer_PausePlaying();
16092 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16094 game_undo_executed = FALSE;
16098 case GAME_CTRL_ID_PLAY:
16099 case GAME_CTRL_ID_PANEL_PLAY:
16100 if (game_status == GAME_MODE_MAIN)
16102 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16104 else if (tape.pausing)
16106 if (network.enabled)
16107 SendToServer_ContinuePlaying();
16109 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16113 case GAME_CTRL_ID_UNDO:
16114 // Important: When using "save snapshot when collecting an item" mode,
16115 // load last (current) snapshot for first "undo" after pressing "pause"
16116 // (else the last-but-one snapshot would be loaded, because the snapshot
16117 // pointer already points to the last snapshot when pressing "pause",
16118 // which is fine for "every step/move" mode, but not for "every collect")
16119 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16120 !game_undo_executed)
16123 game_undo_executed = TRUE;
16128 case GAME_CTRL_ID_REDO:
16132 case GAME_CTRL_ID_SAVE:
16136 case GAME_CTRL_ID_LOAD:
16140 case SOUND_CTRL_ID_MUSIC:
16141 case SOUND_CTRL_ID_PANEL_MUSIC:
16142 if (setup.sound_music)
16144 setup.sound_music = FALSE;
16148 else if (audio.music_available)
16150 setup.sound = setup.sound_music = TRUE;
16152 SetAudioMode(setup.sound);
16154 if (game_status == GAME_MODE_PLAYING)
16158 RedrawSoundButtonGadget(id);
16162 case SOUND_CTRL_ID_LOOPS:
16163 case SOUND_CTRL_ID_PANEL_LOOPS:
16164 if (setup.sound_loops)
16165 setup.sound_loops = FALSE;
16166 else if (audio.loops_available)
16168 setup.sound = setup.sound_loops = TRUE;
16170 SetAudioMode(setup.sound);
16173 RedrawSoundButtonGadget(id);
16177 case SOUND_CTRL_ID_SIMPLE:
16178 case SOUND_CTRL_ID_PANEL_SIMPLE:
16179 if (setup.sound_simple)
16180 setup.sound_simple = FALSE;
16181 else if (audio.sound_available)
16183 setup.sound = setup.sound_simple = TRUE;
16185 SetAudioMode(setup.sound);
16188 RedrawSoundButtonGadget(id);
16197 static void HandleGameButtons(struct GadgetInfo *gi)
16199 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16202 void HandleSoundButtonKeys(Key key)
16204 if (key == setup.shortcut.sound_simple)
16205 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16206 else if (key == setup.shortcut.sound_loops)
16207 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16208 else if (key == setup.shortcut.sound_music)
16209 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);