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 void TestFieldAfterSnapping(int, int, int, int, int);
1124 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1126 // for detection of endless loops, caused by custom element programming
1127 // (using maximal playfield width x 10 is just a rough approximation)
1128 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1130 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1132 if (recursion_loop_detected) \
1135 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1137 recursion_loop_detected = TRUE; \
1138 recursion_loop_element = (e); \
1141 recursion_loop_depth++; \
1144 #define RECURSION_LOOP_DETECTION_END() \
1146 recursion_loop_depth--; \
1149 static int recursion_loop_depth;
1150 static boolean recursion_loop_detected;
1151 static boolean recursion_loop_element;
1153 static int map_player_action[MAX_PLAYERS];
1156 // ----------------------------------------------------------------------------
1157 // definition of elements that automatically change to other elements after
1158 // a specified time, eventually calling a function when changing
1159 // ----------------------------------------------------------------------------
1161 // forward declaration for changer functions
1162 static void InitBuggyBase(int, int);
1163 static void WarnBuggyBase(int, int);
1165 static void InitTrap(int, int);
1166 static void ActivateTrap(int, int);
1167 static void ChangeActiveTrap(int, int);
1169 static void InitRobotWheel(int, int);
1170 static void RunRobotWheel(int, int);
1171 static void StopRobotWheel(int, int);
1173 static void InitTimegateWheel(int, int);
1174 static void RunTimegateWheel(int, int);
1176 static void InitMagicBallDelay(int, int);
1177 static void ActivateMagicBall(int, int);
1179 struct ChangingElementInfo
1184 void (*pre_change_function)(int x, int y);
1185 void (*change_function)(int x, int y);
1186 void (*post_change_function)(int x, int y);
1189 static struct ChangingElementInfo change_delay_list[] =
1224 EL_STEEL_EXIT_OPENING,
1232 EL_STEEL_EXIT_CLOSING,
1233 EL_STEEL_EXIT_CLOSED,
1256 EL_EM_STEEL_EXIT_OPENING,
1257 EL_EM_STEEL_EXIT_OPEN,
1264 EL_EM_STEEL_EXIT_CLOSING,
1288 EL_SWITCHGATE_OPENING,
1296 EL_SWITCHGATE_CLOSING,
1297 EL_SWITCHGATE_CLOSED,
1304 EL_TIMEGATE_OPENING,
1312 EL_TIMEGATE_CLOSING,
1321 EL_ACID_SPLASH_LEFT,
1329 EL_ACID_SPLASH_RIGHT,
1338 EL_SP_BUGGY_BASE_ACTIVATING,
1345 EL_SP_BUGGY_BASE_ACTIVATING,
1346 EL_SP_BUGGY_BASE_ACTIVE,
1353 EL_SP_BUGGY_BASE_ACTIVE,
1377 EL_ROBOT_WHEEL_ACTIVE,
1385 EL_TIMEGATE_SWITCH_ACTIVE,
1393 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1394 EL_DC_TIMEGATE_SWITCH,
1401 EL_EMC_MAGIC_BALL_ACTIVE,
1402 EL_EMC_MAGIC_BALL_ACTIVE,
1409 EL_EMC_SPRING_BUMPER_ACTIVE,
1410 EL_EMC_SPRING_BUMPER,
1417 EL_DIAGONAL_SHRINKING,
1425 EL_DIAGONAL_GROWING,
1446 int push_delay_fixed, push_delay_random;
1450 { EL_SPRING, 0, 0 },
1451 { EL_BALLOON, 0, 0 },
1453 { EL_SOKOBAN_OBJECT, 2, 0 },
1454 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1455 { EL_SATELLITE, 2, 0 },
1456 { EL_SP_DISK_YELLOW, 2, 0 },
1458 { EL_UNDEFINED, 0, 0 },
1466 move_stepsize_list[] =
1468 { EL_AMOEBA_DROP, 2 },
1469 { EL_AMOEBA_DROPPING, 2 },
1470 { EL_QUICKSAND_FILLING, 1 },
1471 { EL_QUICKSAND_EMPTYING, 1 },
1472 { EL_QUICKSAND_FAST_FILLING, 2 },
1473 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1474 { EL_MAGIC_WALL_FILLING, 2 },
1475 { EL_MAGIC_WALL_EMPTYING, 2 },
1476 { EL_BD_MAGIC_WALL_FILLING, 2 },
1477 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1478 { EL_DC_MAGIC_WALL_FILLING, 2 },
1479 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1481 { EL_UNDEFINED, 0 },
1489 collect_count_list[] =
1492 { EL_BD_DIAMOND, 1 },
1493 { EL_EMERALD_YELLOW, 1 },
1494 { EL_EMERALD_RED, 1 },
1495 { EL_EMERALD_PURPLE, 1 },
1497 { EL_SP_INFOTRON, 1 },
1501 { EL_UNDEFINED, 0 },
1509 access_direction_list[] =
1511 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1512 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1513 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1514 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1515 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1516 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1517 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1518 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1519 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1520 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1521 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1523 { EL_SP_PORT_LEFT, MV_RIGHT },
1524 { EL_SP_PORT_RIGHT, MV_LEFT },
1525 { EL_SP_PORT_UP, MV_DOWN },
1526 { EL_SP_PORT_DOWN, MV_UP },
1527 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1528 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1529 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1530 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1531 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1532 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1533 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1534 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1537 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1538 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1543 { EL_UNDEFINED, MV_NONE }
1546 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1548 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1549 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1550 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1551 IS_JUST_CHANGING(x, y))
1553 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1555 // static variables for playfield scan mode (scanning forward or backward)
1556 static int playfield_scan_start_x = 0;
1557 static int playfield_scan_start_y = 0;
1558 static int playfield_scan_delta_x = 1;
1559 static int playfield_scan_delta_y = 1;
1561 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1562 (y) >= 0 && (y) <= lev_fieldy - 1; \
1563 (y) += playfield_scan_delta_y) \
1564 for ((x) = playfield_scan_start_x; \
1565 (x) >= 0 && (x) <= lev_fieldx - 1; \
1566 (x) += playfield_scan_delta_x)
1569 void DEBUG_SetMaximumDynamite(void)
1573 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1574 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1575 local_player->inventory_element[local_player->inventory_size++] =
1580 static void InitPlayfieldScanModeVars(void)
1582 if (game.use_reverse_scan_direction)
1584 playfield_scan_start_x = lev_fieldx - 1;
1585 playfield_scan_start_y = lev_fieldy - 1;
1587 playfield_scan_delta_x = -1;
1588 playfield_scan_delta_y = -1;
1592 playfield_scan_start_x = 0;
1593 playfield_scan_start_y = 0;
1595 playfield_scan_delta_x = 1;
1596 playfield_scan_delta_y = 1;
1600 static void InitPlayfieldScanMode(int mode)
1602 game.use_reverse_scan_direction =
1603 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1605 InitPlayfieldScanModeVars();
1608 static int get_move_delay_from_stepsize(int move_stepsize)
1611 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1613 // make sure that stepsize value is always a power of 2
1614 move_stepsize = (1 << log_2(move_stepsize));
1616 return TILEX / move_stepsize;
1619 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1622 int player_nr = player->index_nr;
1623 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1624 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1626 // do no immediately change move delay -- the player might just be moving
1627 player->move_delay_value_next = move_delay;
1629 // information if player can move must be set separately
1630 player->cannot_move = cannot_move;
1634 player->move_delay = game.initial_move_delay[player_nr];
1635 player->move_delay_value = game.initial_move_delay_value[player_nr];
1637 player->move_delay_value_next = -1;
1639 player->move_delay_reset_counter = 0;
1643 void GetPlayerConfig(void)
1645 GameFrameDelay = setup.game_frame_delay;
1647 if (!audio.sound_available)
1648 setup.sound_simple = FALSE;
1650 if (!audio.loops_available)
1651 setup.sound_loops = FALSE;
1653 if (!audio.music_available)
1654 setup.sound_music = FALSE;
1656 if (!video.fullscreen_available)
1657 setup.fullscreen = FALSE;
1659 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1661 SetAudioMode(setup.sound);
1664 int GetElementFromGroupElement(int element)
1666 if (IS_GROUP_ELEMENT(element))
1668 struct ElementGroupInfo *group = element_info[element].group;
1669 int last_anim_random_frame = gfx.anim_random_frame;
1672 if (group->choice_mode == ANIM_RANDOM)
1673 gfx.anim_random_frame = RND(group->num_elements_resolved);
1675 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1676 group->choice_mode, 0,
1679 if (group->choice_mode == ANIM_RANDOM)
1680 gfx.anim_random_frame = last_anim_random_frame;
1682 group->choice_pos++;
1684 element = group->element_resolved[element_pos];
1690 static void IncrementSokobanFieldsNeeded(void)
1692 if (level.sb_fields_needed)
1693 game.sokoban_fields_still_needed++;
1696 static void IncrementSokobanObjectsNeeded(void)
1698 if (level.sb_objects_needed)
1699 game.sokoban_objects_still_needed++;
1702 static void DecrementSokobanFieldsNeeded(void)
1704 if (game.sokoban_fields_still_needed > 0)
1705 game.sokoban_fields_still_needed--;
1708 static void DecrementSokobanObjectsNeeded(void)
1710 if (game.sokoban_objects_still_needed > 0)
1711 game.sokoban_objects_still_needed--;
1714 static void InitPlayerField(int x, int y, int element, boolean init_game)
1716 if (element == EL_SP_MURPHY)
1720 if (stored_player[0].present)
1722 Tile[x][y] = EL_SP_MURPHY_CLONE;
1728 stored_player[0].initial_element = element;
1729 stored_player[0].use_murphy = TRUE;
1731 if (!level.use_artwork_element[0])
1732 stored_player[0].artwork_element = EL_SP_MURPHY;
1735 Tile[x][y] = EL_PLAYER_1;
1741 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1742 int jx = player->jx, jy = player->jy;
1744 player->present = TRUE;
1746 player->block_last_field = (element == EL_SP_MURPHY ?
1747 level.sp_block_last_field :
1748 level.block_last_field);
1750 // ---------- initialize player's last field block delay ------------------
1752 // always start with reliable default value (no adjustment needed)
1753 player->block_delay_adjustment = 0;
1755 // special case 1: in Supaplex, Murphy blocks last field one more frame
1756 if (player->block_last_field && element == EL_SP_MURPHY)
1757 player->block_delay_adjustment = 1;
1759 // special case 2: in game engines before 3.1.1, blocking was different
1760 if (game.use_block_last_field_bug)
1761 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1763 if (!network.enabled || player->connected_network)
1765 player->active = TRUE;
1767 // remove potentially duplicate players
1768 if (StorePlayer[jx][jy] == Tile[x][y])
1769 StorePlayer[jx][jy] = 0;
1771 StorePlayer[x][y] = Tile[x][y];
1773 #if DEBUG_INIT_PLAYER
1774 Debug("game:init:player", "- player element %d activated",
1775 player->element_nr);
1776 Debug("game:init:player", " (local player is %d and currently %s)",
1777 local_player->element_nr,
1778 local_player->active ? "active" : "not active");
1782 Tile[x][y] = EL_EMPTY;
1784 player->jx = player->last_jx = x;
1785 player->jy = player->last_jy = y;
1788 // always check if player was just killed and should be reanimated
1790 int player_nr = GET_PLAYER_NR(element);
1791 struct PlayerInfo *player = &stored_player[player_nr];
1793 if (player->active && player->killed)
1794 player->reanimated = TRUE; // if player was just killed, reanimate him
1798 static void InitField(int x, int y, boolean init_game)
1800 int element = Tile[x][y];
1809 InitPlayerField(x, y, element, init_game);
1812 case EL_SOKOBAN_FIELD_PLAYER:
1813 element = Tile[x][y] = EL_PLAYER_1;
1814 InitField(x, y, init_game);
1816 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1817 InitField(x, y, init_game);
1820 case EL_SOKOBAN_FIELD_EMPTY:
1821 IncrementSokobanFieldsNeeded();
1824 case EL_SOKOBAN_OBJECT:
1825 IncrementSokobanObjectsNeeded();
1829 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1830 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1831 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1832 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1833 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1834 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1835 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1836 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1837 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1838 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1847 case EL_SPACESHIP_RIGHT:
1848 case EL_SPACESHIP_UP:
1849 case EL_SPACESHIP_LEFT:
1850 case EL_SPACESHIP_DOWN:
1851 case EL_BD_BUTTERFLY:
1852 case EL_BD_BUTTERFLY_RIGHT:
1853 case EL_BD_BUTTERFLY_UP:
1854 case EL_BD_BUTTERFLY_LEFT:
1855 case EL_BD_BUTTERFLY_DOWN:
1857 case EL_BD_FIREFLY_RIGHT:
1858 case EL_BD_FIREFLY_UP:
1859 case EL_BD_FIREFLY_LEFT:
1860 case EL_BD_FIREFLY_DOWN:
1861 case EL_PACMAN_RIGHT:
1863 case EL_PACMAN_LEFT:
1864 case EL_PACMAN_DOWN:
1866 case EL_YAMYAM_LEFT:
1867 case EL_YAMYAM_RIGHT:
1869 case EL_YAMYAM_DOWN:
1870 case EL_DARK_YAMYAM:
1873 case EL_SP_SNIKSNAK:
1874 case EL_SP_ELECTRON:
1880 case EL_SPRING_LEFT:
1881 case EL_SPRING_RIGHT:
1885 case EL_AMOEBA_FULL:
1890 case EL_AMOEBA_DROP:
1891 if (y == lev_fieldy - 1)
1893 Tile[x][y] = EL_AMOEBA_GROWING;
1894 Store[x][y] = EL_AMOEBA_WET;
1898 case EL_DYNAMITE_ACTIVE:
1899 case EL_SP_DISK_RED_ACTIVE:
1900 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1901 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1902 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1903 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1904 MovDelay[x][y] = 96;
1907 case EL_EM_DYNAMITE_ACTIVE:
1908 MovDelay[x][y] = 32;
1912 game.lights_still_needed++;
1916 game.friends_still_needed++;
1921 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1924 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1925 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1926 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1927 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1928 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1929 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1930 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1931 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1932 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1933 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1934 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1935 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1938 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1939 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1940 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1942 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1944 game.belt_dir[belt_nr] = belt_dir;
1945 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1947 else // more than one switch -- set it like the first switch
1949 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1954 case EL_LIGHT_SWITCH_ACTIVE:
1956 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1959 case EL_INVISIBLE_STEELWALL:
1960 case EL_INVISIBLE_WALL:
1961 case EL_INVISIBLE_SAND:
1962 if (game.light_time_left > 0 ||
1963 game.lenses_time_left > 0)
1964 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1967 case EL_EMC_MAGIC_BALL:
1968 if (game.ball_active)
1969 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1972 case EL_EMC_MAGIC_BALL_SWITCH:
1973 if (game.ball_active)
1974 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1977 case EL_TRIGGER_PLAYER:
1978 case EL_TRIGGER_ELEMENT:
1979 case EL_TRIGGER_CE_VALUE:
1980 case EL_TRIGGER_CE_SCORE:
1982 case EL_ANY_ELEMENT:
1983 case EL_CURRENT_CE_VALUE:
1984 case EL_CURRENT_CE_SCORE:
2001 // reference elements should not be used on the playfield
2002 Tile[x][y] = EL_EMPTY;
2006 if (IS_CUSTOM_ELEMENT(element))
2008 if (CAN_MOVE(element))
2011 if (!element_info[element].use_last_ce_value || init_game)
2012 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2014 else if (IS_GROUP_ELEMENT(element))
2016 Tile[x][y] = GetElementFromGroupElement(element);
2018 InitField(x, y, init_game);
2025 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2028 static void InitField_WithBug1(int x, int y, boolean init_game)
2030 InitField(x, y, init_game);
2032 // not needed to call InitMovDir() -- already done by InitField()!
2033 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2034 CAN_MOVE(Tile[x][y]))
2038 static void InitField_WithBug2(int x, int y, boolean init_game)
2040 int old_element = Tile[x][y];
2042 InitField(x, y, init_game);
2044 // not needed to call InitMovDir() -- already done by InitField()!
2045 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2046 CAN_MOVE(old_element) &&
2047 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2050 /* this case is in fact a combination of not less than three bugs:
2051 first, it calls InitMovDir() for elements that can move, although this is
2052 already done by InitField(); then, it checks the element that was at this
2053 field _before_ the call to InitField() (which can change it); lastly, it
2054 was not called for "mole with direction" elements, which were treated as
2055 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2059 static int get_key_element_from_nr(int key_nr)
2061 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2062 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2063 EL_EM_KEY_1 : EL_KEY_1);
2065 return key_base_element + key_nr;
2068 static int get_next_dropped_element(struct PlayerInfo *player)
2070 return (player->inventory_size > 0 ?
2071 player->inventory_element[player->inventory_size - 1] :
2072 player->inventory_infinite_element != EL_UNDEFINED ?
2073 player->inventory_infinite_element :
2074 player->dynabombs_left > 0 ?
2075 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2079 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2081 // pos >= 0: get element from bottom of the stack;
2082 // pos < 0: get element from top of the stack
2086 int min_inventory_size = -pos;
2087 int inventory_pos = player->inventory_size - min_inventory_size;
2088 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2090 return (player->inventory_size >= min_inventory_size ?
2091 player->inventory_element[inventory_pos] :
2092 player->inventory_infinite_element != EL_UNDEFINED ?
2093 player->inventory_infinite_element :
2094 player->dynabombs_left >= min_dynabombs_left ?
2095 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2100 int min_dynabombs_left = pos + 1;
2101 int min_inventory_size = pos + 1 - player->dynabombs_left;
2102 int inventory_pos = pos - player->dynabombs_left;
2104 return (player->inventory_infinite_element != EL_UNDEFINED ?
2105 player->inventory_infinite_element :
2106 player->dynabombs_left >= min_dynabombs_left ?
2107 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 player->inventory_size >= min_inventory_size ?
2109 player->inventory_element[inventory_pos] :
2114 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2116 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2117 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2120 if (gpo1->sort_priority != gpo2->sort_priority)
2121 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2123 compare_result = gpo1->nr - gpo2->nr;
2125 return compare_result;
2128 int getPlayerInventorySize(int player_nr)
2130 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2131 return game_em.ply[player_nr]->dynamite;
2132 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2133 return game_sp.red_disk_count;
2135 return stored_player[player_nr].inventory_size;
2138 static void InitGameControlValues(void)
2142 for (i = 0; game_panel_controls[i].nr != -1; i++)
2144 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2145 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2146 struct TextPosInfo *pos = gpc->pos;
2148 int type = gpc->type;
2152 Error("'game_panel_controls' structure corrupted at %d", i);
2154 Fail("this should not happen -- please debug");
2157 // force update of game controls after initialization
2158 gpc->value = gpc->last_value = -1;
2159 gpc->frame = gpc->last_frame = -1;
2160 gpc->gfx_frame = -1;
2162 // determine panel value width for later calculation of alignment
2163 if (type == TYPE_INTEGER || type == TYPE_STRING)
2165 pos->width = pos->size * getFontWidth(pos->font);
2166 pos->height = getFontHeight(pos->font);
2168 else if (type == TYPE_ELEMENT)
2170 pos->width = pos->size;
2171 pos->height = pos->size;
2174 // fill structure for game panel draw order
2176 gpo->sort_priority = pos->sort_priority;
2179 // sort game panel controls according to sort_priority and control number
2180 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2181 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2184 static void UpdatePlayfieldElementCount(void)
2186 boolean use_element_count = FALSE;
2189 // first check if it is needed at all to calculate playfield element count
2190 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2191 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2192 use_element_count = TRUE;
2194 if (!use_element_count)
2197 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2198 element_info[i].element_count = 0;
2200 SCAN_PLAYFIELD(x, y)
2202 element_info[Tile[x][y]].element_count++;
2205 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2206 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2207 if (IS_IN_GROUP(j, i))
2208 element_info[EL_GROUP_START + i].element_count +=
2209 element_info[j].element_count;
2212 static void UpdateGameControlValues(void)
2215 int time = (game.LevelSolved ?
2216 game.LevelSolved_CountingTime :
2217 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2219 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2220 game_sp.time_played :
2221 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2222 game_mm.energy_left :
2223 game.no_time_limit ? TimePlayed : TimeLeft);
2224 int score = (game.LevelSolved ?
2225 game.LevelSolved_CountingScore :
2226 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 game_em.lev->score :
2228 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2230 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2233 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2234 game_em.lev->gems_needed :
2235 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2236 game_sp.infotrons_still_needed :
2237 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2238 game_mm.kettles_still_needed :
2239 game.gems_still_needed);
2240 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2241 game_em.lev->gems_needed > 0 :
2242 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2243 game_sp.infotrons_still_needed > 0 :
2244 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2245 game_mm.kettles_still_needed > 0 ||
2246 game_mm.lights_still_needed > 0 :
2247 game.gems_still_needed > 0 ||
2248 game.sokoban_fields_still_needed > 0 ||
2249 game.sokoban_objects_still_needed > 0 ||
2250 game.lights_still_needed > 0);
2251 int health = (game.LevelSolved ?
2252 game.LevelSolved_CountingHealth :
2253 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2254 MM_HEALTH(game_mm.laser_overload_value) :
2256 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2258 UpdatePlayfieldElementCount();
2260 // update game panel control values
2262 // used instead of "level_nr" (for network games)
2263 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2264 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2266 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2267 for (i = 0; i < MAX_NUM_KEYS; i++)
2268 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2269 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2270 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2272 if (game.centered_player_nr == -1)
2274 for (i = 0; i < MAX_PLAYERS; i++)
2276 // only one player in Supaplex game engine
2277 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2280 for (k = 0; k < MAX_NUM_KEYS; k++)
2282 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2284 if (game_em.ply[i]->keys & (1 << k))
2285 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2286 get_key_element_from_nr(k);
2288 else if (stored_player[i].key[k])
2289 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2290 get_key_element_from_nr(k);
2293 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2294 getPlayerInventorySize(i);
2296 if (stored_player[i].num_white_keys > 0)
2297 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2300 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2301 stored_player[i].num_white_keys;
2306 int player_nr = game.centered_player_nr;
2308 for (k = 0; k < MAX_NUM_KEYS; k++)
2310 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2312 if (game_em.ply[player_nr]->keys & (1 << k))
2313 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2314 get_key_element_from_nr(k);
2316 else if (stored_player[player_nr].key[k])
2317 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2318 get_key_element_from_nr(k);
2321 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2322 getPlayerInventorySize(player_nr);
2324 if (stored_player[player_nr].num_white_keys > 0)
2325 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2327 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2328 stored_player[player_nr].num_white_keys;
2331 // re-arrange keys on game panel, if needed or if defined by style settings
2332 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2334 int nr = GAME_PANEL_KEY_1 + i;
2335 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2336 struct TextPosInfo *pos = gpc->pos;
2338 // skip check if key is not in the player's inventory
2339 if (gpc->value == EL_EMPTY)
2342 // check if keys should be arranged on panel from left to right
2343 if (pos->style == STYLE_LEFTMOST_POSITION)
2345 // check previous key positions (left from current key)
2346 for (k = 0; k < i; k++)
2348 int nr_new = GAME_PANEL_KEY_1 + k;
2350 if (game_panel_controls[nr_new].value == EL_EMPTY)
2352 game_panel_controls[nr_new].value = gpc->value;
2353 gpc->value = EL_EMPTY;
2360 // check if "undefined" keys can be placed at some other position
2361 if (pos->x == -1 && pos->y == -1)
2363 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2365 // 1st try: display key at the same position as normal or EM keys
2366 if (game_panel_controls[nr_new].value == EL_EMPTY)
2368 game_panel_controls[nr_new].value = gpc->value;
2372 // 2nd try: display key at the next free position in the key panel
2373 for (k = 0; k < STD_NUM_KEYS; k++)
2375 nr_new = GAME_PANEL_KEY_1 + k;
2377 if (game_panel_controls[nr_new].value == EL_EMPTY)
2379 game_panel_controls[nr_new].value = gpc->value;
2388 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2390 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2391 get_inventory_element_from_pos(local_player, i);
2392 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2393 get_inventory_element_from_pos(local_player, -i - 1);
2396 game_panel_controls[GAME_PANEL_SCORE].value = score;
2397 game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score;
2399 game_panel_controls[GAME_PANEL_TIME].value = time;
2401 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2402 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2403 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2405 if (level.time == 0)
2406 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2408 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2410 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2411 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2413 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2415 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2416 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2418 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2419 local_player->shield_normal_time_left;
2420 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2421 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2423 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2424 local_player->shield_deadly_time_left;
2426 game_panel_controls[GAME_PANEL_EXIT].value =
2427 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2429 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2430 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2431 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2432 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2433 EL_EMC_MAGIC_BALL_SWITCH);
2435 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2436 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2437 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2438 game.light_time_left;
2440 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2441 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2442 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2443 game.timegate_time_left;
2445 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2446 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2448 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2449 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2450 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2451 game.lenses_time_left;
2453 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2454 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2455 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2456 game.magnify_time_left;
2458 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2459 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2460 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2461 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2462 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2463 EL_BALLOON_SWITCH_NONE);
2465 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2466 local_player->dynabomb_count;
2467 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2468 local_player->dynabomb_size;
2469 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2470 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2472 game_panel_controls[GAME_PANEL_PENGUINS].value =
2473 game.friends_still_needed;
2475 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2476 game.sokoban_objects_still_needed;
2477 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2478 game.sokoban_fields_still_needed;
2480 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2481 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2483 for (i = 0; i < NUM_BELTS; i++)
2485 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2486 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2487 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2488 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2489 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2492 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2493 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2494 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2495 game.magic_wall_time_left;
2497 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2498 local_player->gravity;
2500 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2501 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2503 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2504 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2505 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2506 game.panel.element[i].id : EL_UNDEFINED);
2508 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2509 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2510 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2511 element_info[game.panel.element_count[i].id].element_count : 0);
2513 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2514 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2515 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2516 element_info[game.panel.ce_score[i].id].collect_score : 0);
2518 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2519 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2520 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2521 element_info[game.panel.ce_score_element[i].id].collect_score :
2524 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2525 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2526 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2528 // update game panel control frames
2530 for (i = 0; game_panel_controls[i].nr != -1; i++)
2532 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2534 if (gpc->type == TYPE_ELEMENT)
2536 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2538 int last_anim_random_frame = gfx.anim_random_frame;
2539 int element = gpc->value;
2540 int graphic = el2panelimg(element);
2541 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2542 sync_random_frame : INIT_GFX_RANDOM());
2544 if (gpc->value != gpc->last_value)
2547 gpc->gfx_random = init_gfx_random;
2553 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2554 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2555 gpc->gfx_random = init_gfx_random;
2558 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2559 gfx.anim_random_frame = gpc->gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2562 gpc->gfx_frame = element_info[element].collect_score;
2564 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = last_anim_random_frame;
2570 else if (gpc->type == TYPE_GRAPHIC)
2572 if (gpc->graphic != IMG_UNDEFINED)
2574 int last_anim_random_frame = gfx.anim_random_frame;
2575 int graphic = gpc->graphic;
2576 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2577 sync_random_frame : INIT_GFX_RANDOM());
2579 if (gpc->value != gpc->last_value)
2582 gpc->gfx_random = init_gfx_random;
2588 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2589 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2590 gpc->gfx_random = init_gfx_random;
2593 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2594 gfx.anim_random_frame = gpc->gfx_random;
2596 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2598 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2599 gfx.anim_random_frame = last_anim_random_frame;
2605 static void DisplayGameControlValues(void)
2607 boolean redraw_panel = FALSE;
2610 for (i = 0; game_panel_controls[i].nr != -1; i++)
2612 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2614 if (PANEL_DEACTIVATED(gpc->pos))
2617 if (gpc->value == gpc->last_value &&
2618 gpc->frame == gpc->last_frame)
2621 redraw_panel = TRUE;
2627 // copy default game door content to main double buffer
2629 // !!! CHECK AGAIN !!!
2630 SetPanelBackground();
2631 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2632 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2634 // redraw game control buttons
2635 RedrawGameButtons();
2637 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2639 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2641 int nr = game_panel_order[i].nr;
2642 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2643 struct TextPosInfo *pos = gpc->pos;
2644 int type = gpc->type;
2645 int value = gpc->value;
2646 int frame = gpc->frame;
2647 int size = pos->size;
2648 int font = pos->font;
2649 boolean draw_masked = pos->draw_masked;
2650 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2652 if (PANEL_DEACTIVATED(pos))
2655 if (pos->class == get_hash_from_key("extra_panel_items") &&
2656 !setup.prefer_extra_panel_items)
2659 gpc->last_value = value;
2660 gpc->last_frame = frame;
2662 if (type == TYPE_INTEGER)
2664 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2665 nr == GAME_PANEL_TIME)
2667 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2669 if (use_dynamic_size) // use dynamic number of digits
2671 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2672 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2673 int size2 = size1 + 1;
2674 int font1 = pos->font;
2675 int font2 = pos->font_alt;
2677 size = (value < value_change ? size1 : size2);
2678 font = (value < value_change ? font1 : font2);
2682 // correct text size if "digits" is zero or less
2684 size = strlen(int2str(value, size));
2686 // dynamically correct text alignment
2687 pos->width = size * getFontWidth(font);
2689 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2690 int2str(value, size), font, mask_mode);
2692 else if (type == TYPE_ELEMENT)
2694 int element, graphic;
2698 int dst_x = PANEL_XPOS(pos);
2699 int dst_y = PANEL_YPOS(pos);
2701 if (value != EL_UNDEFINED && value != EL_EMPTY)
2704 graphic = el2panelimg(value);
2707 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2708 element, EL_NAME(element), size);
2711 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2714 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2717 width = graphic_info[graphic].width * size / TILESIZE;
2718 height = graphic_info[graphic].height * size / TILESIZE;
2721 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2724 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2728 else if (type == TYPE_GRAPHIC)
2730 int graphic = gpc->graphic;
2731 int graphic_active = gpc->graphic_active;
2735 int dst_x = PANEL_XPOS(pos);
2736 int dst_y = PANEL_YPOS(pos);
2737 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2738 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2740 if (graphic != IMG_UNDEFINED && !skip)
2742 if (pos->style == STYLE_REVERSE)
2743 value = 100 - value;
2745 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2747 if (pos->direction & MV_HORIZONTAL)
2749 width = graphic_info[graphic_active].width * value / 100;
2750 height = graphic_info[graphic_active].height;
2752 if (pos->direction == MV_LEFT)
2754 src_x += graphic_info[graphic_active].width - width;
2755 dst_x += graphic_info[graphic_active].width - width;
2760 width = graphic_info[graphic_active].width;
2761 height = graphic_info[graphic_active].height * value / 100;
2763 if (pos->direction == MV_UP)
2765 src_y += graphic_info[graphic_active].height - height;
2766 dst_y += graphic_info[graphic_active].height - height;
2771 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2774 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2777 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2779 if (pos->direction & MV_HORIZONTAL)
2781 if (pos->direction == MV_RIGHT)
2788 dst_x = PANEL_XPOS(pos);
2791 width = graphic_info[graphic].width - width;
2795 if (pos->direction == MV_DOWN)
2802 dst_y = PANEL_YPOS(pos);
2805 height = graphic_info[graphic].height - height;
2809 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2812 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2816 else if (type == TYPE_STRING)
2818 boolean active = (value != 0);
2819 char *state_normal = "off";
2820 char *state_active = "on";
2821 char *state = (active ? state_active : state_normal);
2822 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2823 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2824 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2825 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2827 if (nr == GAME_PANEL_GRAVITY_STATE)
2829 int font1 = pos->font; // (used for normal state)
2830 int font2 = pos->font_alt; // (used for active state)
2832 font = (active ? font2 : font1);
2841 // don't truncate output if "chars" is zero or less
2844 // dynamically correct text alignment
2845 pos->width = size * getFontWidth(font);
2848 s_cut = getStringCopyN(s, size);
2850 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2851 s_cut, font, mask_mode);
2857 redraw_mask |= REDRAW_DOOR_1;
2860 SetGameStatus(GAME_MODE_PLAYING);
2863 void UpdateAndDisplayGameControlValues(void)
2865 if (tape.deactivate_display)
2868 UpdateGameControlValues();
2869 DisplayGameControlValues();
2872 void UpdateGameDoorValues(void)
2874 UpdateGameControlValues();
2877 void DrawGameDoorValues(void)
2879 DisplayGameControlValues();
2883 // ============================================================================
2885 // ----------------------------------------------------------------------------
2886 // initialize game engine due to level / tape version number
2887 // ============================================================================
2889 static void InitGameEngine(void)
2891 int i, j, k, l, x, y;
2893 // set game engine from tape file when re-playing, else from level file
2894 game.engine_version = (tape.playing ? tape.engine_version :
2895 level.game_version);
2897 // set single or multi-player game mode (needed for re-playing tapes)
2898 game.team_mode = setup.team_mode;
2902 int num_players = 0;
2904 for (i = 0; i < MAX_PLAYERS; i++)
2905 if (tape.player_participates[i])
2908 // multi-player tapes contain input data for more than one player
2909 game.team_mode = (num_players > 1);
2913 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2914 level.game_version);
2915 Debug("game:init:level", " tape.file_version == %06d",
2917 Debug("game:init:level", " tape.game_version == %06d",
2919 Debug("game:init:level", " tape.engine_version == %06d",
2920 tape.engine_version);
2921 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2922 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2925 // --------------------------------------------------------------------------
2926 // set flags for bugs and changes according to active game engine version
2927 // --------------------------------------------------------------------------
2931 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2933 Bug was introduced in version:
2936 Bug was fixed in version:
2940 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2941 but the property "can fall" was missing, which caused some levels to be
2942 unsolvable. This was fixed in version 4.2.0.0.
2944 Affected levels/tapes:
2945 An example for a tape that was fixed by this bugfix is tape 029 from the
2946 level set "rnd_sam_bateman".
2947 The wrong behaviour will still be used for all levels or tapes that were
2948 created/recorded with it. An example for this is tape 023 from the level
2949 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2952 boolean use_amoeba_dropping_cannot_fall_bug =
2953 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2954 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2956 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2957 tape.game_version < VERSION_IDENT(4,2,0,0)));
2960 Summary of bugfix/change:
2961 Fixed move speed of elements entering or leaving magic wall.
2963 Fixed/changed in version:
2967 Before 2.0.1, move speed of elements entering or leaving magic wall was
2968 twice as fast as it is now.
2969 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2971 Affected levels/tapes:
2972 The first condition is generally needed for all levels/tapes before version
2973 2.0.1, which might use the old behaviour before it was changed; known tapes
2974 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2975 The second condition is an exception from the above case and is needed for
2976 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2977 above, but before it was known that this change would break tapes like the
2978 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2979 although the engine version while recording maybe was before 2.0.1. There
2980 are a lot of tapes that are affected by this exception, like tape 006 from
2981 the level set "rnd_conor_mancone".
2984 boolean use_old_move_stepsize_for_magic_wall =
2985 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2987 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2988 tape.game_version < VERSION_IDENT(4,2,0,0)));
2991 Summary of bugfix/change:
2992 Fixed handling for custom elements that change when pushed by the player.
2994 Fixed/changed in version:
2998 Before 3.1.0, custom elements that "change when pushing" changed directly
2999 after the player started pushing them (until then handled in "DigField()").
3000 Since 3.1.0, these custom elements are not changed until the "pushing"
3001 move of the element is finished (now handled in "ContinueMoving()").
3003 Affected levels/tapes:
3004 The first condition is generally needed for all levels/tapes before version
3005 3.1.0, which might use the old behaviour before it was changed; known tapes
3006 that are affected are some tapes from the level set "Walpurgis Gardens" by
3008 The second condition is an exception from the above case and is needed for
3009 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3010 above (including some development versions of 3.1.0), but before it was
3011 known that this change would break tapes like the above and was fixed in
3012 3.1.1, so that the changed behaviour was active although the engine version
3013 while recording maybe was before 3.1.0. There is at least one tape that is
3014 affected by this exception, which is the tape for the one-level set "Bug
3015 Machine" by Juergen Bonhagen.
3018 game.use_change_when_pushing_bug =
3019 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3021 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3022 tape.game_version < VERSION_IDENT(3,1,1,0)));
3025 Summary of bugfix/change:
3026 Fixed handling for blocking the field the player leaves when moving.
3028 Fixed/changed in version:
3032 Before 3.1.1, when "block last field when moving" was enabled, the field
3033 the player is leaving when moving was blocked for the time of the move,
3034 and was directly unblocked afterwards. This resulted in the last field
3035 being blocked for exactly one less than the number of frames of one player
3036 move. Additionally, even when blocking was disabled, the last field was
3037 blocked for exactly one frame.
3038 Since 3.1.1, due to changes in player movement handling, the last field
3039 is not blocked at all when blocking is disabled. When blocking is enabled,
3040 the last field is blocked for exactly the number of frames of one player
3041 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3042 last field is blocked for exactly one more than the number of frames of
3045 Affected levels/tapes:
3046 (!!! yet to be determined -- probably many !!!)
3049 game.use_block_last_field_bug =
3050 (game.engine_version < VERSION_IDENT(3,1,1,0));
3052 /* various special flags and settings for native Emerald Mine game engine */
3054 game_em.use_single_button =
3055 (game.engine_version > VERSION_IDENT(4,0,0,2));
3057 game_em.use_snap_key_bug =
3058 (game.engine_version < VERSION_IDENT(4,0,1,0));
3060 game_em.use_random_bug =
3061 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3063 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3065 game_em.use_old_explosions = use_old_em_engine;
3066 game_em.use_old_android = use_old_em_engine;
3067 game_em.use_old_push_elements = use_old_em_engine;
3068 game_em.use_old_push_into_acid = use_old_em_engine;
3070 game_em.use_wrap_around = !use_old_em_engine;
3072 // --------------------------------------------------------------------------
3074 // set maximal allowed number of custom element changes per game frame
3075 game.max_num_changes_per_frame = 1;
3077 // default scan direction: scan playfield from top/left to bottom/right
3078 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3080 // dynamically adjust element properties according to game engine version
3081 InitElementPropertiesEngine(game.engine_version);
3083 // ---------- initialize special element properties -------------------------
3085 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3086 if (use_amoeba_dropping_cannot_fall_bug)
3087 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3089 // ---------- initialize player's initial move delay ------------------------
3091 // dynamically adjust player properties according to level information
3092 for (i = 0; i < MAX_PLAYERS; i++)
3093 game.initial_move_delay_value[i] =
3094 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3096 // dynamically adjust player properties according to game engine version
3097 for (i = 0; i < MAX_PLAYERS; i++)
3098 game.initial_move_delay[i] =
3099 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3100 game.initial_move_delay_value[i] : 0);
3102 // ---------- initialize player's initial push delay ------------------------
3104 // dynamically adjust player properties according to game engine version
3105 game.initial_push_delay_value =
3106 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3108 // ---------- initialize changing elements ----------------------------------
3110 // initialize changing elements information
3111 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3113 struct ElementInfo *ei = &element_info[i];
3115 // this pointer might have been changed in the level editor
3116 ei->change = &ei->change_page[0];
3118 if (!IS_CUSTOM_ELEMENT(i))
3120 ei->change->target_element = EL_EMPTY_SPACE;
3121 ei->change->delay_fixed = 0;
3122 ei->change->delay_random = 0;
3123 ei->change->delay_frames = 1;
3126 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3128 ei->has_change_event[j] = FALSE;
3130 ei->event_page_nr[j] = 0;
3131 ei->event_page[j] = &ei->change_page[0];
3135 // add changing elements from pre-defined list
3136 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3138 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3139 struct ElementInfo *ei = &element_info[ch_delay->element];
3141 ei->change->target_element = ch_delay->target_element;
3142 ei->change->delay_fixed = ch_delay->change_delay;
3144 ei->change->pre_change_function = ch_delay->pre_change_function;
3145 ei->change->change_function = ch_delay->change_function;
3146 ei->change->post_change_function = ch_delay->post_change_function;
3148 ei->change->can_change = TRUE;
3149 ei->change->can_change_or_has_action = TRUE;
3151 ei->has_change_event[CE_DELAY] = TRUE;
3153 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3154 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3157 // ---------- initialize internal run-time variables ------------------------
3159 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3161 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3163 for (j = 0; j < ei->num_change_pages; j++)
3165 ei->change_page[j].can_change_or_has_action =
3166 (ei->change_page[j].can_change |
3167 ei->change_page[j].has_action);
3171 // add change events from custom element configuration
3172 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3174 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3176 for (j = 0; j < ei->num_change_pages; j++)
3178 if (!ei->change_page[j].can_change_or_has_action)
3181 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3183 // only add event page for the first page found with this event
3184 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3186 ei->has_change_event[k] = TRUE;
3188 ei->event_page_nr[k] = j;
3189 ei->event_page[k] = &ei->change_page[j];
3195 // ---------- initialize reference elements in change conditions ------------
3197 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3199 int element = EL_CUSTOM_START + i;
3200 struct ElementInfo *ei = &element_info[element];
3202 for (j = 0; j < ei->num_change_pages; j++)
3204 int trigger_element = ei->change_page[j].initial_trigger_element;
3206 if (trigger_element >= EL_PREV_CE_8 &&
3207 trigger_element <= EL_NEXT_CE_8)
3208 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3210 ei->change_page[j].trigger_element = trigger_element;
3214 // ---------- initialize run-time trigger player and element ----------------
3216 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3218 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3220 for (j = 0; j < ei->num_change_pages; j++)
3222 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3223 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3224 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3225 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3226 ei->change_page[j].actual_trigger_ce_value = 0;
3227 ei->change_page[j].actual_trigger_ce_score = 0;
3231 // ---------- initialize trigger events -------------------------------------
3233 // initialize trigger events information
3234 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3235 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3236 trigger_events[i][j] = FALSE;
3238 // add trigger events from element change event properties
3239 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3241 struct ElementInfo *ei = &element_info[i];
3243 for (j = 0; j < ei->num_change_pages; j++)
3245 if (!ei->change_page[j].can_change_or_has_action)
3248 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3250 int trigger_element = ei->change_page[j].trigger_element;
3252 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3254 if (ei->change_page[j].has_event[k])
3256 if (IS_GROUP_ELEMENT(trigger_element))
3258 struct ElementGroupInfo *group =
3259 element_info[trigger_element].group;
3261 for (l = 0; l < group->num_elements_resolved; l++)
3262 trigger_events[group->element_resolved[l]][k] = TRUE;
3264 else if (trigger_element == EL_ANY_ELEMENT)
3265 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3266 trigger_events[l][k] = TRUE;
3268 trigger_events[trigger_element][k] = TRUE;
3275 // ---------- initialize push delay -----------------------------------------
3277 // initialize push delay values to default
3278 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3280 if (!IS_CUSTOM_ELEMENT(i))
3282 // set default push delay values (corrected since version 3.0.7-1)
3283 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3285 element_info[i].push_delay_fixed = 2;
3286 element_info[i].push_delay_random = 8;
3290 element_info[i].push_delay_fixed = 8;
3291 element_info[i].push_delay_random = 8;
3296 // set push delay value for certain elements from pre-defined list
3297 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3299 int e = push_delay_list[i].element;
3301 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3302 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3305 // set push delay value for Supaplex elements for newer engine versions
3306 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3308 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3310 if (IS_SP_ELEMENT(i))
3312 // set SP push delay to just enough to push under a falling zonk
3313 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3315 element_info[i].push_delay_fixed = delay;
3316 element_info[i].push_delay_random = 0;
3321 // ---------- initialize move stepsize --------------------------------------
3323 // initialize move stepsize values to default
3324 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3325 if (!IS_CUSTOM_ELEMENT(i))
3326 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3328 // set move stepsize value for certain elements from pre-defined list
3329 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3331 int e = move_stepsize_list[i].element;
3333 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3335 // set move stepsize value for certain elements for older engine versions
3336 if (use_old_move_stepsize_for_magic_wall)
3338 if (e == EL_MAGIC_WALL_FILLING ||
3339 e == EL_MAGIC_WALL_EMPTYING ||
3340 e == EL_BD_MAGIC_WALL_FILLING ||
3341 e == EL_BD_MAGIC_WALL_EMPTYING)
3342 element_info[e].move_stepsize *= 2;
3346 // ---------- initialize collect score --------------------------------------
3348 // initialize collect score values for custom elements from initial value
3349 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3350 if (IS_CUSTOM_ELEMENT(i))
3351 element_info[i].collect_score = element_info[i].collect_score_initial;
3353 // ---------- initialize collect count --------------------------------------
3355 // initialize collect count values for non-custom elements
3356 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3357 if (!IS_CUSTOM_ELEMENT(i))
3358 element_info[i].collect_count_initial = 0;
3360 // add collect count values for all elements from pre-defined list
3361 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3362 element_info[collect_count_list[i].element].collect_count_initial =
3363 collect_count_list[i].count;
3365 // ---------- initialize access direction -----------------------------------
3367 // initialize access direction values to default (access from every side)
3368 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3369 if (!IS_CUSTOM_ELEMENT(i))
3370 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3372 // set access direction value for certain elements from pre-defined list
3373 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3374 element_info[access_direction_list[i].element].access_direction =
3375 access_direction_list[i].direction;
3377 // ---------- initialize explosion content ----------------------------------
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3380 if (IS_CUSTOM_ELEMENT(i))
3383 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3385 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3387 element_info[i].content.e[x][y] =
3388 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3389 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3390 i == EL_PLAYER_3 ? EL_EMERALD :
3391 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3392 i == EL_MOLE ? EL_EMERALD_RED :
3393 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3394 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3395 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3396 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3397 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3398 i == EL_WALL_EMERALD ? EL_EMERALD :
3399 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3400 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3401 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3402 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3403 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3404 i == EL_WALL_PEARL ? EL_PEARL :
3405 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3410 // ---------- initialize recursion detection --------------------------------
3411 recursion_loop_depth = 0;
3412 recursion_loop_detected = FALSE;
3413 recursion_loop_element = EL_UNDEFINED;
3415 // ---------- initialize graphics engine ------------------------------------
3416 game.scroll_delay_value =
3417 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3418 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3419 !setup.forced_scroll_delay ? 0 :
3420 setup.scroll_delay ? setup.scroll_delay_value : 0);
3421 game.scroll_delay_value =
3422 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3424 // ---------- initialize game engine snapshots ------------------------------
3425 for (i = 0; i < MAX_PLAYERS; i++)
3426 game.snapshot.last_action[i] = 0;
3427 game.snapshot.changed_action = FALSE;
3428 game.snapshot.collected_item = FALSE;
3429 game.snapshot.mode =
3430 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3431 SNAPSHOT_MODE_EVERY_STEP :
3432 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3433 SNAPSHOT_MODE_EVERY_MOVE :
3434 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3435 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3436 game.snapshot.save_snapshot = FALSE;
3438 // ---------- initialize level time for Supaplex engine ---------------------
3439 // Supaplex levels with time limit currently unsupported -- should be added
3440 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3443 // ---------- initialize flags for handling game actions --------------------
3445 // set flags for game actions to default values
3446 game.use_key_actions = TRUE;
3447 game.use_mouse_actions = FALSE;
3449 // when using Mirror Magic game engine, handle mouse events only
3450 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3452 game.use_key_actions = FALSE;
3453 game.use_mouse_actions = TRUE;
3456 // check for custom elements with mouse click events
3457 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3459 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3461 int element = EL_CUSTOM_START + i;
3463 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3464 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3465 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3466 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3467 game.use_mouse_actions = TRUE;
3472 static int get_num_special_action(int element, int action_first,
3475 int num_special_action = 0;
3478 for (i = action_first; i <= action_last; i++)
3480 boolean found = FALSE;
3482 for (j = 0; j < NUM_DIRECTIONS; j++)
3483 if (el_act_dir2img(element, i, j) !=
3484 el_act_dir2img(element, ACTION_DEFAULT, j))
3488 num_special_action++;
3493 return num_special_action;
3497 // ============================================================================
3499 // ----------------------------------------------------------------------------
3500 // initialize and start new game
3501 // ============================================================================
3503 #if DEBUG_INIT_PLAYER
3504 static void DebugPrintPlayerStatus(char *message)
3511 Debug("game:init:player", "%s:", message);
3513 for (i = 0; i < MAX_PLAYERS; i++)
3515 struct PlayerInfo *player = &stored_player[i];
3517 Debug("game:init:player",
3518 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3522 player->connected_locally,
3523 player->connected_network,
3525 (local_player == player ? " (local player)" : ""));
3532 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3533 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3534 int fade_mask = REDRAW_FIELD;
3536 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3537 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3538 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3539 int initial_move_dir = MV_DOWN;
3542 // required here to update video display before fading (FIX THIS)
3543 DrawMaskedBorder(REDRAW_DOOR_2);
3545 if (!game.restart_level)
3546 CloseDoor(DOOR_CLOSE_1);
3548 SetGameStatus(GAME_MODE_PLAYING);
3550 if (level_editor_test_game)
3551 FadeSkipNextFadeOut();
3553 FadeSetEnterScreen();
3556 fade_mask = REDRAW_ALL;
3558 FadeLevelSoundsAndMusic();
3560 ExpireSoundLoops(TRUE);
3564 if (level_editor_test_game)
3565 FadeSkipNextFadeIn();
3567 // needed if different viewport properties defined for playing
3568 ChangeViewportPropertiesIfNeeded();
3572 DrawCompleteVideoDisplay();
3574 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3577 InitGameControlValues();
3581 // initialize tape actions from game when recording tape
3582 tape.use_key_actions = game.use_key_actions;
3583 tape.use_mouse_actions = game.use_mouse_actions;
3585 // initialize visible playfield size when recording tape (for team mode)
3586 tape.scr_fieldx = SCR_FIELDX;
3587 tape.scr_fieldy = SCR_FIELDY;
3590 // don't play tapes over network
3591 network_playing = (network.enabled && !tape.playing);
3593 for (i = 0; i < MAX_PLAYERS; i++)
3595 struct PlayerInfo *player = &stored_player[i];
3597 player->index_nr = i;
3598 player->index_bit = (1 << i);
3599 player->element_nr = EL_PLAYER_1 + i;
3601 player->present = FALSE;
3602 player->active = FALSE;
3603 player->mapped = FALSE;
3605 player->killed = FALSE;
3606 player->reanimated = FALSE;
3607 player->buried = FALSE;
3610 player->effective_action = 0;
3611 player->programmed_action = 0;
3612 player->snap_action = 0;
3614 player->mouse_action.lx = 0;
3615 player->mouse_action.ly = 0;
3616 player->mouse_action.button = 0;
3617 player->mouse_action.button_hint = 0;
3619 player->effective_mouse_action.lx = 0;
3620 player->effective_mouse_action.ly = 0;
3621 player->effective_mouse_action.button = 0;
3622 player->effective_mouse_action.button_hint = 0;
3624 for (j = 0; j < MAX_NUM_KEYS; j++)
3625 player->key[j] = FALSE;
3627 player->num_white_keys = 0;
3629 player->dynabomb_count = 0;
3630 player->dynabomb_size = 1;
3631 player->dynabombs_left = 0;
3632 player->dynabomb_xl = FALSE;
3634 player->MovDir = initial_move_dir;
3637 player->GfxDir = initial_move_dir;
3638 player->GfxAction = ACTION_DEFAULT;
3640 player->StepFrame = 0;
3642 player->initial_element = player->element_nr;
3643 player->artwork_element =
3644 (level.use_artwork_element[i] ? level.artwork_element[i] :
3645 player->element_nr);
3646 player->use_murphy = FALSE;
3648 player->block_last_field = FALSE; // initialized in InitPlayerField()
3649 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3651 player->gravity = level.initial_player_gravity[i];
3653 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3655 player->actual_frame_counter = 0;
3657 player->step_counter = 0;
3659 player->last_move_dir = initial_move_dir;
3661 player->is_active = FALSE;
3663 player->is_waiting = FALSE;
3664 player->is_moving = FALSE;
3665 player->is_auto_moving = FALSE;
3666 player->is_digging = FALSE;
3667 player->is_snapping = FALSE;
3668 player->is_collecting = FALSE;
3669 player->is_pushing = FALSE;
3670 player->is_switching = FALSE;
3671 player->is_dropping = FALSE;
3672 player->is_dropping_pressed = FALSE;
3674 player->is_bored = FALSE;
3675 player->is_sleeping = FALSE;
3677 player->was_waiting = TRUE;
3678 player->was_moving = FALSE;
3679 player->was_snapping = FALSE;
3680 player->was_dropping = FALSE;
3682 player->force_dropping = FALSE;
3684 player->frame_counter_bored = -1;
3685 player->frame_counter_sleeping = -1;
3687 player->anim_delay_counter = 0;
3688 player->post_delay_counter = 0;
3690 player->dir_waiting = initial_move_dir;
3691 player->action_waiting = ACTION_DEFAULT;
3692 player->last_action_waiting = ACTION_DEFAULT;
3693 player->special_action_bored = ACTION_DEFAULT;
3694 player->special_action_sleeping = ACTION_DEFAULT;
3696 player->switch_x = -1;
3697 player->switch_y = -1;
3699 player->drop_x = -1;
3700 player->drop_y = -1;
3702 player->show_envelope = 0;
3704 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3706 player->push_delay = -1; // initialized when pushing starts
3707 player->push_delay_value = game.initial_push_delay_value;
3709 player->drop_delay = 0;
3710 player->drop_pressed_delay = 0;
3712 player->last_jx = -1;
3713 player->last_jy = -1;
3717 player->shield_normal_time_left = 0;
3718 player->shield_deadly_time_left = 0;
3720 player->last_removed_element = EL_UNDEFINED;
3722 player->inventory_infinite_element = EL_UNDEFINED;
3723 player->inventory_size = 0;
3725 if (level.use_initial_inventory[i])
3727 for (j = 0; j < level.initial_inventory_size[i]; j++)
3729 int element = level.initial_inventory_content[i][j];
3730 int collect_count = element_info[element].collect_count_initial;
3733 if (!IS_CUSTOM_ELEMENT(element))
3736 if (collect_count == 0)
3737 player->inventory_infinite_element = element;
3739 for (k = 0; k < collect_count; k++)
3740 if (player->inventory_size < MAX_INVENTORY_SIZE)
3741 player->inventory_element[player->inventory_size++] = element;
3745 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3746 SnapField(player, 0, 0);
3748 map_player_action[i] = i;
3751 network_player_action_received = FALSE;
3753 // initial null action
3754 if (network_playing)
3755 SendToServer_MovePlayer(MV_NONE);
3760 TimeLeft = level.time;
3763 ScreenMovDir = MV_NONE;
3767 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3769 game.robot_wheel_x = -1;
3770 game.robot_wheel_y = -1;
3775 game.all_players_gone = FALSE;
3777 game.LevelSolved = FALSE;
3778 game.GameOver = FALSE;
3780 game.GamePlayed = !tape.playing;
3782 game.LevelSolved_GameWon = FALSE;
3783 game.LevelSolved_GameEnd = FALSE;
3784 game.LevelSolved_SaveTape = FALSE;
3785 game.LevelSolved_SaveScore = FALSE;
3787 game.LevelSolved_CountingTime = 0;
3788 game.LevelSolved_CountingScore = 0;
3789 game.LevelSolved_CountingHealth = 0;
3791 game.panel.active = TRUE;
3793 game.no_time_limit = (level.time == 0);
3795 game.yamyam_content_nr = 0;
3796 game.robot_wheel_active = FALSE;
3797 game.magic_wall_active = FALSE;
3798 game.magic_wall_time_left = 0;
3799 game.light_time_left = 0;
3800 game.timegate_time_left = 0;
3801 game.switchgate_pos = 0;
3802 game.wind_direction = level.wind_direction_initial;
3805 game.score_final = 0;
3807 game.health = MAX_HEALTH;
3808 game.health_final = MAX_HEALTH;
3810 game.gems_still_needed = level.gems_needed;
3811 game.sokoban_fields_still_needed = 0;
3812 game.sokoban_objects_still_needed = 0;
3813 game.lights_still_needed = 0;
3814 game.players_still_needed = 0;
3815 game.friends_still_needed = 0;
3817 game.lenses_time_left = 0;
3818 game.magnify_time_left = 0;
3820 game.ball_active = level.ball_active_initial;
3821 game.ball_content_nr = 0;
3823 game.explosions_delayed = TRUE;
3825 game.envelope_active = FALSE;
3827 for (i = 0; i < NUM_BELTS; i++)
3829 game.belt_dir[i] = MV_NONE;
3830 game.belt_dir_nr[i] = 3; // not moving, next moving left
3833 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3834 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3836 #if DEBUG_INIT_PLAYER
3837 DebugPrintPlayerStatus("Player status at level initialization");
3840 SCAN_PLAYFIELD(x, y)
3842 Tile[x][y] = Last[x][y] = level.field[x][y];
3843 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3844 ChangeDelay[x][y] = 0;
3845 ChangePage[x][y] = -1;
3846 CustomValue[x][y] = 0; // initialized in InitField()
3847 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3849 WasJustMoving[x][y] = 0;
3850 WasJustFalling[x][y] = 0;
3851 CheckCollision[x][y] = 0;
3852 CheckImpact[x][y] = 0;
3854 Pushed[x][y] = FALSE;
3856 ChangeCount[x][y] = 0;
3857 ChangeEvent[x][y] = -1;
3859 ExplodePhase[x][y] = 0;
3860 ExplodeDelay[x][y] = 0;
3861 ExplodeField[x][y] = EX_TYPE_NONE;
3863 RunnerVisit[x][y] = 0;
3864 PlayerVisit[x][y] = 0;
3867 GfxRandom[x][y] = INIT_GFX_RANDOM();
3868 GfxElement[x][y] = EL_UNDEFINED;
3869 GfxAction[x][y] = ACTION_DEFAULT;
3870 GfxDir[x][y] = MV_NONE;
3871 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3874 SCAN_PLAYFIELD(x, y)
3876 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3878 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3880 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3883 InitField(x, y, TRUE);
3885 ResetGfxAnimation(x, y);
3890 for (i = 0; i < MAX_PLAYERS; i++)
3892 struct PlayerInfo *player = &stored_player[i];
3894 // set number of special actions for bored and sleeping animation
3895 player->num_special_action_bored =
3896 get_num_special_action(player->artwork_element,
3897 ACTION_BORING_1, ACTION_BORING_LAST);
3898 player->num_special_action_sleeping =
3899 get_num_special_action(player->artwork_element,
3900 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3903 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3904 emulate_sb ? EMU_SOKOBAN :
3905 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3907 // initialize type of slippery elements
3908 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3910 if (!IS_CUSTOM_ELEMENT(i))
3912 // default: elements slip down either to the left or right randomly
3913 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3915 // SP style elements prefer to slip down on the left side
3916 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3917 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3919 // BD style elements prefer to slip down on the left side
3920 if (game.emulation == EMU_BOULDERDASH)
3921 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3925 // initialize explosion and ignition delay
3926 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3928 if (!IS_CUSTOM_ELEMENT(i))
3931 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3932 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3933 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3934 int last_phase = (num_phase + 1) * delay;
3935 int half_phase = (num_phase / 2) * delay;
3937 element_info[i].explosion_delay = last_phase - 1;
3938 element_info[i].ignition_delay = half_phase;
3940 if (i == EL_BLACK_ORB)
3941 element_info[i].ignition_delay = 1;
3945 // correct non-moving belts to start moving left
3946 for (i = 0; i < NUM_BELTS; i++)
3947 if (game.belt_dir[i] == MV_NONE)
3948 game.belt_dir_nr[i] = 3; // not moving, next moving left
3950 #if USE_NEW_PLAYER_ASSIGNMENTS
3951 // use preferred player also in local single-player mode
3952 if (!network.enabled && !game.team_mode)
3954 int new_index_nr = setup.network_player_nr;
3956 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3958 for (i = 0; i < MAX_PLAYERS; i++)
3959 stored_player[i].connected_locally = FALSE;
3961 stored_player[new_index_nr].connected_locally = TRUE;
3965 for (i = 0; i < MAX_PLAYERS; i++)
3967 stored_player[i].connected = FALSE;
3969 // in network game mode, the local player might not be the first player
3970 if (stored_player[i].connected_locally)
3971 local_player = &stored_player[i];
3974 if (!network.enabled)
3975 local_player->connected = TRUE;
3979 for (i = 0; i < MAX_PLAYERS; i++)
3980 stored_player[i].connected = tape.player_participates[i];
3982 else if (network.enabled)
3984 // add team mode players connected over the network (needed for correct
3985 // assignment of player figures from level to locally playing players)
3987 for (i = 0; i < MAX_PLAYERS; i++)
3988 if (stored_player[i].connected_network)
3989 stored_player[i].connected = TRUE;
3991 else if (game.team_mode)
3993 // try to guess locally connected team mode players (needed for correct
3994 // assignment of player figures from level to locally playing players)
3996 for (i = 0; i < MAX_PLAYERS; i++)
3997 if (setup.input[i].use_joystick ||
3998 setup.input[i].key.left != KSYM_UNDEFINED)
3999 stored_player[i].connected = TRUE;
4002 #if DEBUG_INIT_PLAYER
4003 DebugPrintPlayerStatus("Player status after level initialization");
4006 #if DEBUG_INIT_PLAYER
4007 Debug("game:init:player", "Reassigning players ...");
4010 // check if any connected player was not found in playfield
4011 for (i = 0; i < MAX_PLAYERS; i++)
4013 struct PlayerInfo *player = &stored_player[i];
4015 if (player->connected && !player->present)
4017 struct PlayerInfo *field_player = NULL;
4019 #if DEBUG_INIT_PLAYER
4020 Debug("game:init:player",
4021 "- looking for field player for player %d ...", i + 1);
4024 // assign first free player found that is present in the playfield
4026 // first try: look for unmapped playfield player that is not connected
4027 for (j = 0; j < MAX_PLAYERS; j++)
4028 if (field_player == NULL &&
4029 stored_player[j].present &&
4030 !stored_player[j].mapped &&
4031 !stored_player[j].connected)
4032 field_player = &stored_player[j];
4034 // second try: look for *any* unmapped playfield player
4035 for (j = 0; j < MAX_PLAYERS; j++)
4036 if (field_player == NULL &&
4037 stored_player[j].present &&
4038 !stored_player[j].mapped)
4039 field_player = &stored_player[j];
4041 if (field_player != NULL)
4043 int jx = field_player->jx, jy = field_player->jy;
4045 #if DEBUG_INIT_PLAYER
4046 Debug("game:init:player", "- found player %d",
4047 field_player->index_nr + 1);
4050 player->present = FALSE;
4051 player->active = FALSE;
4053 field_player->present = TRUE;
4054 field_player->active = TRUE;
4057 player->initial_element = field_player->initial_element;
4058 player->artwork_element = field_player->artwork_element;
4060 player->block_last_field = field_player->block_last_field;
4061 player->block_delay_adjustment = field_player->block_delay_adjustment;
4064 StorePlayer[jx][jy] = field_player->element_nr;
4066 field_player->jx = field_player->last_jx = jx;
4067 field_player->jy = field_player->last_jy = jy;
4069 if (local_player == player)
4070 local_player = field_player;
4072 map_player_action[field_player->index_nr] = i;
4074 field_player->mapped = TRUE;
4076 #if DEBUG_INIT_PLAYER
4077 Debug("game:init:player", "- map_player_action[%d] == %d",
4078 field_player->index_nr + 1, i + 1);
4083 if (player->connected && player->present)
4084 player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4093 // check if any connected player was not found in playfield
4094 for (i = 0; i < MAX_PLAYERS; i++)
4096 struct PlayerInfo *player = &stored_player[i];
4098 if (player->connected && !player->present)
4100 for (j = 0; j < MAX_PLAYERS; j++)
4102 struct PlayerInfo *field_player = &stored_player[j];
4103 int jx = field_player->jx, jy = field_player->jy;
4105 // assign first free player found that is present in the playfield
4106 if (field_player->present && !field_player->connected)
4108 player->present = TRUE;
4109 player->active = TRUE;
4111 field_player->present = FALSE;
4112 field_player->active = FALSE;
4114 player->initial_element = field_player->initial_element;
4115 player->artwork_element = field_player->artwork_element;
4117 player->block_last_field = field_player->block_last_field;
4118 player->block_delay_adjustment = field_player->block_delay_adjustment;
4120 StorePlayer[jx][jy] = player->element_nr;
4122 player->jx = player->last_jx = jx;
4123 player->jy = player->last_jy = jy;
4133 Debug("game:init:player", "local_player->present == %d",
4134 local_player->present);
4137 // set focus to local player for network games, else to all players
4138 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4139 game.centered_player_nr_next = game.centered_player_nr;
4140 game.set_centered_player = FALSE;
4141 game.set_centered_player_wrap = FALSE;
4143 if (network_playing && tape.recording)
4145 // store client dependent player focus when recording network games
4146 tape.centered_player_nr_next = game.centered_player_nr_next;
4147 tape.set_centered_player = TRUE;
4152 // when playing a tape, eliminate all players who do not participate
4154 #if USE_NEW_PLAYER_ASSIGNMENTS
4156 if (!game.team_mode)
4158 for (i = 0; i < MAX_PLAYERS; i++)
4160 if (stored_player[i].active &&
4161 !tape.player_participates[map_player_action[i]])
4163 struct PlayerInfo *player = &stored_player[i];
4164 int jx = player->jx, jy = player->jy;
4166 #if DEBUG_INIT_PLAYER
4167 Debug("game:init:player", "Removing player %d at (%d, %d)",
4171 player->active = FALSE;
4172 StorePlayer[jx][jy] = 0;
4173 Tile[jx][jy] = EL_EMPTY;
4180 for (i = 0; i < MAX_PLAYERS; i++)
4182 if (stored_player[i].active &&
4183 !tape.player_participates[i])
4185 struct PlayerInfo *player = &stored_player[i];
4186 int jx = player->jx, jy = player->jy;
4188 player->active = FALSE;
4189 StorePlayer[jx][jy] = 0;
4190 Tile[jx][jy] = EL_EMPTY;
4195 else if (!network.enabled && !game.team_mode) // && !tape.playing
4197 // when in single player mode, eliminate all but the local player
4199 for (i = 0; i < MAX_PLAYERS; i++)
4201 struct PlayerInfo *player = &stored_player[i];
4203 if (player->active && player != local_player)
4205 int jx = player->jx, jy = player->jy;
4207 player->active = FALSE;
4208 player->present = FALSE;
4210 StorePlayer[jx][jy] = 0;
4211 Tile[jx][jy] = EL_EMPTY;
4216 for (i = 0; i < MAX_PLAYERS; i++)
4217 if (stored_player[i].active)
4218 game.players_still_needed++;
4220 if (level.solved_by_one_player)
4221 game.players_still_needed = 1;
4223 // when recording the game, store which players take part in the game
4226 #if USE_NEW_PLAYER_ASSIGNMENTS
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].connected)
4229 tape.player_participates[i] = TRUE;
4231 for (i = 0; i < MAX_PLAYERS; i++)
4232 if (stored_player[i].active)
4233 tape.player_participates[i] = TRUE;
4237 #if DEBUG_INIT_PLAYER
4238 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4241 if (BorderElement == EL_EMPTY)
4244 SBX_Right = lev_fieldx - SCR_FIELDX;
4246 SBY_Lower = lev_fieldy - SCR_FIELDY;
4251 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4253 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4256 if (full_lev_fieldx <= SCR_FIELDX)
4257 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4258 if (full_lev_fieldy <= SCR_FIELDY)
4259 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4261 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4263 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4266 // if local player not found, look for custom element that might create
4267 // the player (make some assumptions about the right custom element)
4268 if (!local_player->present)
4270 int start_x = 0, start_y = 0;
4271 int found_rating = 0;
4272 int found_element = EL_UNDEFINED;
4273 int player_nr = local_player->index_nr;
4275 SCAN_PLAYFIELD(x, y)
4277 int element = Tile[x][y];
4282 if (level.use_start_element[player_nr] &&
4283 level.start_element[player_nr] == element &&
4290 found_element = element;
4293 if (!IS_CUSTOM_ELEMENT(element))
4296 if (CAN_CHANGE(element))
4298 for (i = 0; i < element_info[element].num_change_pages; i++)
4300 // check for player created from custom element as single target
4301 content = element_info[element].change_page[i].target_element;
4302 is_player = ELEM_IS_PLAYER(content);
4304 if (is_player && (found_rating < 3 ||
4305 (found_rating == 3 && element < found_element)))
4311 found_element = element;
4316 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4318 // check for player created from custom element as explosion content
4319 content = element_info[element].content.e[xx][yy];
4320 is_player = ELEM_IS_PLAYER(content);
4322 if (is_player && (found_rating < 2 ||
4323 (found_rating == 2 && element < found_element)))
4325 start_x = x + xx - 1;
4326 start_y = y + yy - 1;
4329 found_element = element;
4332 if (!CAN_CHANGE(element))
4335 for (i = 0; i < element_info[element].num_change_pages; i++)
4337 // check for player created from custom element as extended target
4339 element_info[element].change_page[i].target_content.e[xx][yy];
4341 is_player = ELEM_IS_PLAYER(content);
4343 if (is_player && (found_rating < 1 ||
4344 (found_rating == 1 && element < found_element)))
4346 start_x = x + xx - 1;
4347 start_y = y + yy - 1;
4350 found_element = element;
4356 scroll_x = SCROLL_POSITION_X(start_x);
4357 scroll_y = SCROLL_POSITION_Y(start_y);
4361 scroll_x = SCROLL_POSITION_X(local_player->jx);
4362 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4365 // !!! FIX THIS (START) !!!
4366 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4368 InitGameEngine_EM();
4370 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4372 InitGameEngine_SP();
4374 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4376 InitGameEngine_MM();
4380 DrawLevel(REDRAW_FIELD);
4383 // after drawing the level, correct some elements
4384 if (game.timegate_time_left == 0)
4385 CloseAllOpenTimegates();
4388 // blit playfield from scroll buffer to normal back buffer for fading in
4389 BlitScreenToBitmap(backbuffer);
4390 // !!! FIX THIS (END) !!!
4392 DrawMaskedBorder(fade_mask);
4397 // full screen redraw is required at this point in the following cases:
4398 // - special editor door undrawn when game was started from level editor
4399 // - drawing area (playfield) was changed and has to be removed completely
4400 redraw_mask = REDRAW_ALL;
4404 if (!game.restart_level)
4406 // copy default game door content to main double buffer
4408 // !!! CHECK AGAIN !!!
4409 SetPanelBackground();
4410 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4411 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4414 SetPanelBackground();
4415 SetDrawBackgroundMask(REDRAW_DOOR_1);
4417 UpdateAndDisplayGameControlValues();
4419 if (!game.restart_level)
4425 CreateGameButtons();
4430 // copy actual game door content to door double buffer for OpenDoor()
4431 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4433 OpenDoor(DOOR_OPEN_ALL);
4435 KeyboardAutoRepeatOffUnlessAutoplay();
4437 #if DEBUG_INIT_PLAYER
4438 DebugPrintPlayerStatus("Player status (final)");
4447 if (!game.restart_level && !tape.playing)
4449 LevelStats_incPlayed(level_nr);
4451 SaveLevelSetup_SeriesInfo();
4454 game.restart_level = FALSE;
4455 game.restart_game_message = NULL;
4457 game.request_active = FALSE;
4458 game.request_active_or_moving = FALSE;
4460 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4461 InitGameActions_MM();
4463 SaveEngineSnapshotToListInitial();
4465 if (!game.restart_level)
4467 PlaySound(SND_GAME_STARTING);
4469 if (setup.sound_music)
4474 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4475 int actual_player_x, int actual_player_y)
4477 // this is used for non-R'n'D game engines to update certain engine values
4479 // needed to determine if sounds are played within the visible screen area
4480 scroll_x = actual_scroll_x;
4481 scroll_y = actual_scroll_y;
4483 // needed to get player position for "follow finger" playing input method
4484 local_player->jx = actual_player_x;
4485 local_player->jy = actual_player_y;
4488 void InitMovDir(int x, int y)
4490 int i, element = Tile[x][y];
4491 static int xy[4][2] =
4498 static int direction[3][4] =
4500 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4501 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4502 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4511 Tile[x][y] = EL_BUG;
4512 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4515 case EL_SPACESHIP_RIGHT:
4516 case EL_SPACESHIP_UP:
4517 case EL_SPACESHIP_LEFT:
4518 case EL_SPACESHIP_DOWN:
4519 Tile[x][y] = EL_SPACESHIP;
4520 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4523 case EL_BD_BUTTERFLY_RIGHT:
4524 case EL_BD_BUTTERFLY_UP:
4525 case EL_BD_BUTTERFLY_LEFT:
4526 case EL_BD_BUTTERFLY_DOWN:
4527 Tile[x][y] = EL_BD_BUTTERFLY;
4528 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4531 case EL_BD_FIREFLY_RIGHT:
4532 case EL_BD_FIREFLY_UP:
4533 case EL_BD_FIREFLY_LEFT:
4534 case EL_BD_FIREFLY_DOWN:
4535 Tile[x][y] = EL_BD_FIREFLY;
4536 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4539 case EL_PACMAN_RIGHT:
4541 case EL_PACMAN_LEFT:
4542 case EL_PACMAN_DOWN:
4543 Tile[x][y] = EL_PACMAN;
4544 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4547 case EL_YAMYAM_LEFT:
4548 case EL_YAMYAM_RIGHT:
4550 case EL_YAMYAM_DOWN:
4551 Tile[x][y] = EL_YAMYAM;
4552 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4555 case EL_SP_SNIKSNAK:
4556 MovDir[x][y] = MV_UP;
4559 case EL_SP_ELECTRON:
4560 MovDir[x][y] = MV_LEFT;
4567 Tile[x][y] = EL_MOLE;
4568 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4571 case EL_SPRING_LEFT:
4572 case EL_SPRING_RIGHT:
4573 Tile[x][y] = EL_SPRING;
4574 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4578 if (IS_CUSTOM_ELEMENT(element))
4580 struct ElementInfo *ei = &element_info[element];
4581 int move_direction_initial = ei->move_direction_initial;
4582 int move_pattern = ei->move_pattern;
4584 if (move_direction_initial == MV_START_PREVIOUS)
4586 if (MovDir[x][y] != MV_NONE)
4589 move_direction_initial = MV_START_AUTOMATIC;
4592 if (move_direction_initial == MV_START_RANDOM)
4593 MovDir[x][y] = 1 << RND(4);
4594 else if (move_direction_initial & MV_ANY_DIRECTION)
4595 MovDir[x][y] = move_direction_initial;
4596 else if (move_pattern == MV_ALL_DIRECTIONS ||
4597 move_pattern == MV_TURNING_LEFT ||
4598 move_pattern == MV_TURNING_RIGHT ||
4599 move_pattern == MV_TURNING_LEFT_RIGHT ||
4600 move_pattern == MV_TURNING_RIGHT_LEFT ||
4601 move_pattern == MV_TURNING_RANDOM)
4602 MovDir[x][y] = 1 << RND(4);
4603 else if (move_pattern == MV_HORIZONTAL)
4604 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4605 else if (move_pattern == MV_VERTICAL)
4606 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4607 else if (move_pattern & MV_ANY_DIRECTION)
4608 MovDir[x][y] = element_info[element].move_pattern;
4609 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4610 move_pattern == MV_ALONG_RIGHT_SIDE)
4612 // use random direction as default start direction
4613 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4614 MovDir[x][y] = 1 << RND(4);
4616 for (i = 0; i < NUM_DIRECTIONS; i++)
4618 int x1 = x + xy[i][0];
4619 int y1 = y + xy[i][1];
4621 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4623 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4624 MovDir[x][y] = direction[0][i];
4626 MovDir[x][y] = direction[1][i];
4635 MovDir[x][y] = 1 << RND(4);
4637 if (element != EL_BUG &&
4638 element != EL_SPACESHIP &&
4639 element != EL_BD_BUTTERFLY &&
4640 element != EL_BD_FIREFLY)
4643 for (i = 0; i < NUM_DIRECTIONS; i++)
4645 int x1 = x + xy[i][0];
4646 int y1 = y + xy[i][1];
4648 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4650 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4652 MovDir[x][y] = direction[0][i];
4655 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4656 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4658 MovDir[x][y] = direction[1][i];
4667 GfxDir[x][y] = MovDir[x][y];
4670 void InitAmoebaNr(int x, int y)
4673 int group_nr = AmoebaNeighbourNr(x, y);
4677 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4679 if (AmoebaCnt[i] == 0)
4687 AmoebaNr[x][y] = group_nr;
4688 AmoebaCnt[group_nr]++;
4689 AmoebaCnt2[group_nr]++;
4692 static void LevelSolved(void)
4694 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4695 game.players_still_needed > 0)
4698 game.LevelSolved = TRUE;
4699 game.GameOver = TRUE;
4701 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4702 game_em.lev->score :
4703 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4706 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4707 MM_HEALTH(game_mm.laser_overload_value) :
4710 game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft);
4711 game.LevelSolved_CountingScore = game.score_final;
4712 game.LevelSolved_CountingHealth = game.health_final;
4717 static int time_count_steps;
4718 static int time, time_final;
4719 static float score, score_final; // needed for time score < 10 for 10 seconds
4720 static int health, health_final;
4721 static int game_over_delay_1 = 0;
4722 static int game_over_delay_2 = 0;
4723 static int game_over_delay_3 = 0;
4724 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4725 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4727 if (!game.LevelSolved_GameWon)
4731 // do not start end game actions before the player stops moving (to exit)
4732 if (local_player->active && local_player->MovPos)
4735 game.LevelSolved_GameWon = TRUE;
4736 game.LevelSolved_SaveTape = tape.recording;
4737 game.LevelSolved_SaveScore = !tape.playing;
4741 LevelStats_incSolved(level_nr);
4743 SaveLevelSetup_SeriesInfo();
4746 if (tape.auto_play) // tape might already be stopped here
4747 tape.auto_play_level_solved = TRUE;
4751 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4752 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4753 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4755 time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4756 score = score_final = game.score_final;
4757 health = health_final = game.health_final;
4761 int time_frames = 0;
4766 time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4768 else if (game.no_time_limit && TimePlayed < 999)
4771 time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
4774 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4776 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4778 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4781 score_final += health * time_score;
4784 game.score_final = score_final;
4785 game.health_final = health_final;
4788 if (level_editor_test_game || !setup.count_score_after_game)
4791 score = score_final;
4793 game.LevelSolved_CountingTime = time;
4794 game.LevelSolved_CountingScore = score;
4796 game_panel_controls[GAME_PANEL_TIME].value = time;
4797 game_panel_controls[GAME_PANEL_SCORE].value = score;
4799 DisplayGameControlValues();
4802 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4804 // check if last player has left the level
4805 if (game.exit_x >= 0 &&
4808 int x = game.exit_x;
4809 int y = game.exit_y;
4810 int element = Tile[x][y];
4812 // close exit door after last player
4813 if ((game.all_players_gone &&
4814 (element == EL_EXIT_OPEN ||
4815 element == EL_SP_EXIT_OPEN ||
4816 element == EL_STEEL_EXIT_OPEN)) ||
4817 element == EL_EM_EXIT_OPEN ||
4818 element == EL_EM_STEEL_EXIT_OPEN)
4822 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4823 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4824 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4825 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4826 EL_EM_STEEL_EXIT_CLOSING);
4828 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4831 // player disappears
4832 DrawLevelField(x, y);
4835 for (i = 0; i < MAX_PLAYERS; i++)
4837 struct PlayerInfo *player = &stored_player[i];
4839 if (player->present)
4841 RemovePlayer(player);
4843 // player disappears
4844 DrawLevelField(player->jx, player->jy);
4849 PlaySound(SND_GAME_WINNING);
4852 if (setup.count_score_after_game)
4854 if (time != time_final)
4856 if (game_over_delay_1 > 0)
4858 game_over_delay_1--;
4863 int time_to_go = ABS(time_final - time);
4864 int time_count_dir = (time < time_final ? +1 : -1);
4866 if (time_to_go < time_count_steps)
4867 time_count_steps = 1;
4869 time += time_count_steps * time_count_dir;
4870 score += time_count_steps * time_score;
4872 // set final score to correct rounding differences after counting score
4873 if (time == time_final)
4874 score = score_final;
4876 game.LevelSolved_CountingTime = time;
4877 game.LevelSolved_CountingScore = score;
4879 game_panel_controls[GAME_PANEL_TIME].value = time;
4880 game_panel_controls[GAME_PANEL_SCORE].value = score;
4882 DisplayGameControlValues();
4884 if (time == time_final)
4885 StopSound(SND_GAME_LEVELTIME_BONUS);
4886 else if (setup.sound_loops)
4887 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4889 PlaySound(SND_GAME_LEVELTIME_BONUS);
4894 if (health != health_final)
4896 if (game_over_delay_2 > 0)
4898 game_over_delay_2--;
4903 int health_count_dir = (health < health_final ? +1 : -1);
4905 health += health_count_dir;
4906 score += time_score;
4908 game.LevelSolved_CountingHealth = health;
4909 game.LevelSolved_CountingScore = score;
4911 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4912 game_panel_controls[GAME_PANEL_SCORE].value = score;
4914 DisplayGameControlValues();
4916 if (health == health_final)
4917 StopSound(SND_GAME_LEVELTIME_BONUS);
4918 else if (setup.sound_loops)
4919 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4921 PlaySound(SND_GAME_LEVELTIME_BONUS);
4927 game.panel.active = FALSE;
4929 if (game_over_delay_3 > 0)
4931 game_over_delay_3--;
4941 // used instead of "level_nr" (needed for network games)
4942 int last_level_nr = levelset.level_nr;
4945 game.LevelSolved_GameEnd = TRUE;
4947 if (game.LevelSolved_SaveTape)
4949 // make sure that request dialog to save tape does not open door again
4950 if (!global.use_envelope_request)
4951 CloseDoor(DOOR_CLOSE_1);
4953 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4956 // if no tape is to be saved, close both doors simultaneously
4957 CloseDoor(DOOR_CLOSE_ALL);
4959 if (level_editor_test_game)
4961 SetGameStatus(GAME_MODE_MAIN);
4968 if (!game.LevelSolved_SaveScore)
4970 SetGameStatus(GAME_MODE_MAIN);
4977 if (level_nr == leveldir_current->handicap_level)
4979 leveldir_current->handicap_level++;
4981 SaveLevelSetup_SeriesInfo();
4984 if (setup.increment_levels &&
4985 level_nr < leveldir_current->last_level &&
4988 level_nr++; // advance to next level
4989 TapeErase(); // start with empty tape
4991 if (setup.auto_play_next_level)
4993 LoadLevel(level_nr);
4995 SaveLevelSetup_SeriesInfo();
4999 hi_pos = NewHiScore(last_level_nr);
5001 if (hi_pos >= 0 && setup.show_scores_after_game)
5003 SetGameStatus(GAME_MODE_SCORES);
5005 DrawHallOfFame(last_level_nr, hi_pos);
5007 else if (setup.auto_play_next_level && setup.increment_levels &&
5008 last_level_nr < leveldir_current->last_level &&
5011 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5015 SetGameStatus(GAME_MODE_MAIN);
5021 int NewHiScore(int level_nr)
5025 boolean one_score_entry_per_name = !program.many_scores_per_name;
5027 LoadScore(level_nr);
5029 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
5030 game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score)
5033 for (k = 0; k < MAX_SCORE_ENTRIES; k++)
5035 if (game.score_final > highscore[k].Score)
5037 // player has made it to the hall of fame
5039 if (k < MAX_SCORE_ENTRIES - 1)
5041 int m = MAX_SCORE_ENTRIES - 1;
5043 if (one_score_entry_per_name)
5045 for (l = k; l < MAX_SCORE_ENTRIES; l++)
5046 if (strEqual(setup.player_name, highscore[l].Name))
5049 if (m == k) // player's new highscore overwrites his old one
5053 for (l = m; l > k; l--)
5055 strcpy(highscore[l].Name, highscore[l - 1].Name);
5056 highscore[l].Score = highscore[l - 1].Score;
5062 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
5063 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
5064 highscore[k].Score = game.score_final;
5069 else if (one_score_entry_per_name &&
5070 !strncmp(setup.player_name, highscore[k].Name,
5071 MAX_PLAYER_NAME_LEN))
5072 break; // player already there with a higher score
5076 SaveScore(level_nr);
5081 static int getElementMoveStepsizeExt(int x, int y, int direction)
5083 int element = Tile[x][y];
5084 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5085 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5086 int horiz_move = (dx != 0);
5087 int sign = (horiz_move ? dx : dy);
5088 int step = sign * element_info[element].move_stepsize;
5090 // special values for move stepsize for spring and things on conveyor belt
5093 if (CAN_FALL(element) &&
5094 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5095 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5096 else if (element == EL_SPRING)
5097 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5103 static int getElementMoveStepsize(int x, int y)
5105 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5108 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5110 if (player->GfxAction != action || player->GfxDir != dir)
5112 player->GfxAction = action;
5113 player->GfxDir = dir;
5115 player->StepFrame = 0;
5119 static void ResetGfxFrame(int x, int y)
5121 // profiling showed that "autotest" spends 10~20% of its time in this function
5122 if (DrawingDeactivatedField())
5125 int element = Tile[x][y];
5126 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5128 if (graphic_info[graphic].anim_global_sync)
5129 GfxFrame[x][y] = FrameCounter;
5130 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5131 GfxFrame[x][y] = CustomValue[x][y];
5132 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5133 GfxFrame[x][y] = element_info[element].collect_score;
5134 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5135 GfxFrame[x][y] = ChangeDelay[x][y];
5138 static void ResetGfxAnimation(int x, int y)
5140 GfxAction[x][y] = ACTION_DEFAULT;
5141 GfxDir[x][y] = MovDir[x][y];
5144 ResetGfxFrame(x, y);
5147 static void ResetRandomAnimationValue(int x, int y)
5149 GfxRandom[x][y] = INIT_GFX_RANDOM();
5152 static void InitMovingField(int x, int y, int direction)
5154 int element = Tile[x][y];
5155 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5156 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5159 boolean is_moving_before, is_moving_after;
5161 // check if element was/is moving or being moved before/after mode change
5162 is_moving_before = (WasJustMoving[x][y] != 0);
5163 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5165 // reset animation only for moving elements which change direction of moving
5166 // or which just started or stopped moving
5167 // (else CEs with property "can move" / "not moving" are reset each frame)
5168 if (is_moving_before != is_moving_after ||
5169 direction != MovDir[x][y])
5170 ResetGfxAnimation(x, y);
5172 MovDir[x][y] = direction;
5173 GfxDir[x][y] = direction;
5175 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5176 direction == MV_DOWN && CAN_FALL(element) ?
5177 ACTION_FALLING : ACTION_MOVING);
5179 // this is needed for CEs with property "can move" / "not moving"
5181 if (is_moving_after)
5183 if (Tile[newx][newy] == EL_EMPTY)
5184 Tile[newx][newy] = EL_BLOCKED;
5186 MovDir[newx][newy] = MovDir[x][y];
5188 CustomValue[newx][newy] = CustomValue[x][y];
5190 GfxFrame[newx][newy] = GfxFrame[x][y];
5191 GfxRandom[newx][newy] = GfxRandom[x][y];
5192 GfxAction[newx][newy] = GfxAction[x][y];
5193 GfxDir[newx][newy] = GfxDir[x][y];
5197 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5199 int direction = MovDir[x][y];
5200 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5201 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5207 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5209 int oldx = x, oldy = y;
5210 int direction = MovDir[x][y];
5212 if (direction == MV_LEFT)
5214 else if (direction == MV_RIGHT)
5216 else if (direction == MV_UP)
5218 else if (direction == MV_DOWN)
5221 *comes_from_x = oldx;
5222 *comes_from_y = oldy;
5225 static int MovingOrBlocked2Element(int x, int y)
5227 int element = Tile[x][y];
5229 if (element == EL_BLOCKED)
5233 Blocked2Moving(x, y, &oldx, &oldy);
5234 return Tile[oldx][oldy];
5240 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5242 // like MovingOrBlocked2Element(), but if element is moving
5243 // and (x,y) is the field the moving element is just leaving,
5244 // return EL_BLOCKED instead of the element value
5245 int element = Tile[x][y];
5247 if (IS_MOVING(x, y))
5249 if (element == EL_BLOCKED)
5253 Blocked2Moving(x, y, &oldx, &oldy);
5254 return Tile[oldx][oldy];
5263 static void RemoveField(int x, int y)
5265 Tile[x][y] = EL_EMPTY;
5271 CustomValue[x][y] = 0;
5274 ChangeDelay[x][y] = 0;
5275 ChangePage[x][y] = -1;
5276 Pushed[x][y] = FALSE;
5278 GfxElement[x][y] = EL_UNDEFINED;
5279 GfxAction[x][y] = ACTION_DEFAULT;
5280 GfxDir[x][y] = MV_NONE;
5283 static void RemoveMovingField(int x, int y)
5285 int oldx = x, oldy = y, newx = x, newy = y;
5286 int element = Tile[x][y];
5287 int next_element = EL_UNDEFINED;
5289 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5292 if (IS_MOVING(x, y))
5294 Moving2Blocked(x, y, &newx, &newy);
5296 if (Tile[newx][newy] != EL_BLOCKED)
5298 // element is moving, but target field is not free (blocked), but
5299 // already occupied by something different (example: acid pool);
5300 // in this case, only remove the moving field, but not the target
5302 RemoveField(oldx, oldy);
5304 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5306 TEST_DrawLevelField(oldx, oldy);
5311 else if (element == EL_BLOCKED)
5313 Blocked2Moving(x, y, &oldx, &oldy);
5314 if (!IS_MOVING(oldx, oldy))
5318 if (element == EL_BLOCKED &&
5319 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5320 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5321 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5322 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5323 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5324 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5325 next_element = get_next_element(Tile[oldx][oldy]);
5327 RemoveField(oldx, oldy);
5328 RemoveField(newx, newy);
5330 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5332 if (next_element != EL_UNDEFINED)
5333 Tile[oldx][oldy] = next_element;
5335 TEST_DrawLevelField(oldx, oldy);
5336 TEST_DrawLevelField(newx, newy);
5339 void DrawDynamite(int x, int y)
5341 int sx = SCREENX(x), sy = SCREENY(y);
5342 int graphic = el2img(Tile[x][y]);
5345 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5348 if (IS_WALKABLE_INSIDE(Back[x][y]))
5352 DrawGraphic(sx, sy, el2img(Back[x][y]), 0);
5353 else if (Store[x][y])
5354 DrawGraphic(sx, sy, el2img(Store[x][y]), 0);
5356 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5358 if (Back[x][y] || Store[x][y])
5359 DrawGraphicThruMask(sx, sy, graphic, frame);
5361 DrawGraphic(sx, sy, graphic, frame);
5364 static void CheckDynamite(int x, int y)
5366 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5370 if (MovDelay[x][y] != 0)
5373 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5379 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5384 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5386 boolean num_checked_players = 0;
5389 for (i = 0; i < MAX_PLAYERS; i++)
5391 if (stored_player[i].active)
5393 int sx = stored_player[i].jx;
5394 int sy = stored_player[i].jy;
5396 if (num_checked_players == 0)
5403 *sx1 = MIN(*sx1, sx);
5404 *sy1 = MIN(*sy1, sy);
5405 *sx2 = MAX(*sx2, sx);
5406 *sy2 = MAX(*sy2, sy);
5409 num_checked_players++;
5414 static boolean checkIfAllPlayersFitToScreen_RND(void)
5416 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5418 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5420 return (sx2 - sx1 < SCR_FIELDX &&
5421 sy2 - sy1 < SCR_FIELDY);
5424 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5426 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5428 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5430 *sx = (sx1 + sx2) / 2;
5431 *sy = (sy1 + sy2) / 2;
5434 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5435 boolean center_screen, boolean quick_relocation)
5437 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5438 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5439 boolean no_delay = (tape.warp_forward);
5440 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5441 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5442 int new_scroll_x, new_scroll_y;
5444 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5446 // case 1: quick relocation inside visible screen (without scrolling)
5453 if (!level.shifted_relocation || center_screen)
5455 // relocation _with_ centering of screen
5457 new_scroll_x = SCROLL_POSITION_X(x);
5458 new_scroll_y = SCROLL_POSITION_Y(y);
5462 // relocation _without_ centering of screen
5464 int center_scroll_x = SCROLL_POSITION_X(old_x);
5465 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5466 int offset_x = x + (scroll_x - center_scroll_x);
5467 int offset_y = y + (scroll_y - center_scroll_y);
5469 // for new screen position, apply previous offset to center position
5470 new_scroll_x = SCROLL_POSITION_X(offset_x);
5471 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5474 if (quick_relocation)
5476 // case 2: quick relocation (redraw without visible scrolling)
5478 scroll_x = new_scroll_x;
5479 scroll_y = new_scroll_y;
5486 // case 3: visible relocation (with scrolling to new position)
5488 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5490 SetVideoFrameDelay(wait_delay_value);
5492 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5494 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5495 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5497 if (dx == 0 && dy == 0) // no scrolling needed at all
5503 // set values for horizontal/vertical screen scrolling (half tile size)
5504 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5505 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5506 int pos_x = dx * TILEX / 2;
5507 int pos_y = dy * TILEY / 2;
5508 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5509 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5511 ScrollLevel(dx, dy);
5514 // scroll in two steps of half tile size to make things smoother
5515 BlitScreenToBitmapExt_RND(window, fx, fy);
5517 // scroll second step to align at full tile size
5518 BlitScreenToBitmap(window);
5524 SetVideoFrameDelay(frame_delay_value_old);
5527 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5529 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5530 int player_nr = GET_PLAYER_NR(el_player);
5531 struct PlayerInfo *player = &stored_player[player_nr];
5532 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5533 boolean no_delay = (tape.warp_forward);
5534 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5535 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5536 int old_jx = player->jx;
5537 int old_jy = player->jy;
5538 int old_element = Tile[old_jx][old_jy];
5539 int element = Tile[jx][jy];
5540 boolean player_relocated = (old_jx != jx || old_jy != jy);
5542 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5543 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5544 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5545 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5546 int leave_side_horiz = move_dir_horiz;
5547 int leave_side_vert = move_dir_vert;
5548 int enter_side = enter_side_horiz | enter_side_vert;
5549 int leave_side = leave_side_horiz | leave_side_vert;
5551 if (player->buried) // do not reanimate dead player
5554 if (!player_relocated) // no need to relocate the player
5557 if (IS_PLAYER(jx, jy)) // player already placed at new position
5559 RemoveField(jx, jy); // temporarily remove newly placed player
5560 DrawLevelField(jx, jy);
5563 if (player->present)
5565 while (player->MovPos)
5567 ScrollPlayer(player, SCROLL_GO_ON);
5568 ScrollScreen(NULL, SCROLL_GO_ON);
5570 AdvanceFrameAndPlayerCounters(player->index_nr);
5574 BackToFront_WithFrameDelay(wait_delay_value);
5577 DrawPlayer(player); // needed here only to cleanup last field
5578 DrawLevelField(player->jx, player->jy); // remove player graphic
5580 player->is_moving = FALSE;
5583 if (IS_CUSTOM_ELEMENT(old_element))
5584 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5586 player->index_bit, leave_side);
5588 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5590 player->index_bit, leave_side);
5592 Tile[jx][jy] = el_player;
5593 InitPlayerField(jx, jy, el_player, TRUE);
5595 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5596 possible that the relocation target field did not contain a player element,
5597 but a walkable element, to which the new player was relocated -- in this
5598 case, restore that (already initialized!) element on the player field */
5599 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5601 Tile[jx][jy] = element; // restore previously existing element
5604 // only visually relocate centered player
5605 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5606 FALSE, level.instant_relocation);
5608 TestIfPlayerTouchesBadThing(jx, jy);
5609 TestIfPlayerTouchesCustomElement(jx, jy);
5611 if (IS_CUSTOM_ELEMENT(element))
5612 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5613 player->index_bit, enter_side);
5615 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5616 player->index_bit, enter_side);
5618 if (player->is_switching)
5620 /* ensure that relocation while still switching an element does not cause
5621 a new element to be treated as also switched directly after relocation
5622 (this is important for teleporter switches that teleport the player to
5623 a place where another teleporter switch is in the same direction, which
5624 would then incorrectly be treated as immediately switched before the
5625 direction key that caused the switch was released) */
5627 player->switch_x += jx - old_jx;
5628 player->switch_y += jy - old_jy;
5632 static void Explode(int ex, int ey, int phase, int mode)
5638 // !!! eliminate this variable !!!
5639 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5641 if (game.explosions_delayed)
5643 ExplodeField[ex][ey] = mode;
5647 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5649 int center_element = Tile[ex][ey];
5650 int artwork_element, explosion_element; // set these values later
5652 // remove things displayed in background while burning dynamite
5653 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5656 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5658 // put moving element to center field (and let it explode there)
5659 center_element = MovingOrBlocked2Element(ex, ey);
5660 RemoveMovingField(ex, ey);
5661 Tile[ex][ey] = center_element;
5664 // now "center_element" is finally determined -- set related values now
5665 artwork_element = center_element; // for custom player artwork
5666 explosion_element = center_element; // for custom player artwork
5668 if (IS_PLAYER(ex, ey))
5670 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5672 artwork_element = stored_player[player_nr].artwork_element;
5674 if (level.use_explosion_element[player_nr])
5676 explosion_element = level.explosion_element[player_nr];
5677 artwork_element = explosion_element;
5681 if (mode == EX_TYPE_NORMAL ||
5682 mode == EX_TYPE_CENTER ||
5683 mode == EX_TYPE_CROSS)
5684 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5686 last_phase = element_info[explosion_element].explosion_delay + 1;
5688 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5690 int xx = x - ex + 1;
5691 int yy = y - ey + 1;
5694 if (!IN_LEV_FIELD(x, y) ||
5695 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5696 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5699 element = Tile[x][y];
5701 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5703 element = MovingOrBlocked2Element(x, y);
5705 if (!IS_EXPLOSION_PROOF(element))
5706 RemoveMovingField(x, y);
5709 // indestructible elements can only explode in center (but not flames)
5710 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5711 mode == EX_TYPE_BORDER)) ||
5712 element == EL_FLAMES)
5715 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5716 behaviour, for example when touching a yamyam that explodes to rocks
5717 with active deadly shield, a rock is created under the player !!! */
5718 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5720 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5721 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5722 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5724 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5727 if (IS_ACTIVE_BOMB(element))
5729 // re-activate things under the bomb like gate or penguin
5730 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5737 // save walkable background elements while explosion on same tile
5738 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5739 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5740 Back[x][y] = element;
5742 // ignite explodable elements reached by other explosion
5743 if (element == EL_EXPLOSION)
5744 element = Store2[x][y];
5746 if (AmoebaNr[x][y] &&
5747 (element == EL_AMOEBA_FULL ||
5748 element == EL_BD_AMOEBA ||
5749 element == EL_AMOEBA_GROWING))
5751 AmoebaCnt[AmoebaNr[x][y]]--;
5752 AmoebaCnt2[AmoebaNr[x][y]]--;
5757 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5759 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5761 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5763 if (PLAYERINFO(ex, ey)->use_murphy)
5764 Store[x][y] = EL_EMPTY;
5767 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5768 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5769 else if (ELEM_IS_PLAYER(center_element))
5770 Store[x][y] = EL_EMPTY;
5771 else if (center_element == EL_YAMYAM)
5772 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5773 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5774 Store[x][y] = element_info[center_element].content.e[xx][yy];
5776 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5777 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5778 // otherwise) -- FIX THIS !!!
5779 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5780 Store[x][y] = element_info[element].content.e[1][1];
5782 else if (!CAN_EXPLODE(element))
5783 Store[x][y] = element_info[element].content.e[1][1];
5786 Store[x][y] = EL_EMPTY;
5788 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5789 center_element == EL_AMOEBA_TO_DIAMOND)
5790 Store2[x][y] = element;
5792 Tile[x][y] = EL_EXPLOSION;
5793 GfxElement[x][y] = artwork_element;
5795 ExplodePhase[x][y] = 1;
5796 ExplodeDelay[x][y] = last_phase;
5801 if (center_element == EL_YAMYAM)
5802 game.yamyam_content_nr =
5803 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5815 GfxFrame[x][y] = 0; // restart explosion animation
5817 last_phase = ExplodeDelay[x][y];
5819 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5821 // this can happen if the player leaves an explosion just in time
5822 if (GfxElement[x][y] == EL_UNDEFINED)
5823 GfxElement[x][y] = EL_EMPTY;
5825 border_element = Store2[x][y];
5826 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5827 border_element = StorePlayer[x][y];
5829 if (phase == element_info[border_element].ignition_delay ||
5830 phase == last_phase)
5832 boolean border_explosion = FALSE;
5834 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5835 !PLAYER_EXPLOSION_PROTECTED(x, y))
5837 KillPlayerUnlessExplosionProtected(x, y);
5838 border_explosion = TRUE;
5840 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5842 Tile[x][y] = Store2[x][y];
5845 border_explosion = TRUE;
5847 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5849 AmoebaToDiamond(x, y);
5851 border_explosion = TRUE;
5854 // if an element just explodes due to another explosion (chain-reaction),
5855 // do not immediately end the new explosion when it was the last frame of
5856 // the explosion (as it would be done in the following "if"-statement!)
5857 if (border_explosion && phase == last_phase)
5861 if (phase == last_phase)
5865 element = Tile[x][y] = Store[x][y];
5866 Store[x][y] = Store2[x][y] = 0;
5867 GfxElement[x][y] = EL_UNDEFINED;
5869 // player can escape from explosions and might therefore be still alive
5870 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5871 element <= EL_PLAYER_IS_EXPLODING_4)
5873 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5874 int explosion_element = EL_PLAYER_1 + player_nr;
5875 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5876 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5878 if (level.use_explosion_element[player_nr])
5879 explosion_element = level.explosion_element[player_nr];
5881 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5882 element_info[explosion_element].content.e[xx][yy]);
5885 // restore probably existing indestructible background element
5886 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5887 element = Tile[x][y] = Back[x][y];
5890 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5891 GfxDir[x][y] = MV_NONE;
5892 ChangeDelay[x][y] = 0;
5893 ChangePage[x][y] = -1;
5895 CustomValue[x][y] = 0;
5897 InitField_WithBug2(x, y, FALSE);
5899 TEST_DrawLevelField(x, y);
5901 TestIfElementTouchesCustomElement(x, y);
5903 if (GFX_CRUMBLED(element))
5904 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5906 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5907 StorePlayer[x][y] = 0;
5909 if (ELEM_IS_PLAYER(element))
5910 RelocatePlayer(x, y, element);
5912 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5914 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5915 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5918 TEST_DrawLevelFieldCrumbled(x, y);
5920 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5922 DrawLevelElement(x, y, Back[x][y]);
5923 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5925 else if (IS_WALKABLE_UNDER(Back[x][y]))
5927 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5928 DrawLevelElementThruMask(x, y, Back[x][y]);
5930 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5931 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5935 static void DynaExplode(int ex, int ey)
5938 int dynabomb_element = Tile[ex][ey];
5939 int dynabomb_size = 1;
5940 boolean dynabomb_xl = FALSE;
5941 struct PlayerInfo *player;
5942 static int xy[4][2] =
5950 if (IS_ACTIVE_BOMB(dynabomb_element))
5952 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
5953 dynabomb_size = player->dynabomb_size;
5954 dynabomb_xl = player->dynabomb_xl;
5955 player->dynabombs_left++;
5958 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
5960 for (i = 0; i < NUM_DIRECTIONS; i++)
5962 for (j = 1; j <= dynabomb_size; j++)
5964 int x = ex + j * xy[i][0];
5965 int y = ey + j * xy[i][1];
5968 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
5971 element = Tile[x][y];
5973 // do not restart explosions of fields with active bombs
5974 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
5977 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
5979 if (element != EL_EMPTY && element != EL_EXPLOSION &&
5980 !IS_DIGGABLE(element) && !dynabomb_xl)
5986 void Bang(int x, int y)
5988 int element = MovingOrBlocked2Element(x, y);
5989 int explosion_type = EX_TYPE_NORMAL;
5991 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5993 struct PlayerInfo *player = PLAYERINFO(x, y);
5995 element = Tile[x][y] = player->initial_element;
5997 if (level.use_explosion_element[player->index_nr])
5999 int explosion_element = level.explosion_element[player->index_nr];
6001 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6002 explosion_type = EX_TYPE_CROSS;
6003 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6004 explosion_type = EX_TYPE_CENTER;
6012 case EL_BD_BUTTERFLY:
6015 case EL_DARK_YAMYAM:
6019 RaiseScoreElement(element);
6022 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6023 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6024 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6025 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6026 case EL_DYNABOMB_INCREASE_NUMBER:
6027 case EL_DYNABOMB_INCREASE_SIZE:
6028 case EL_DYNABOMB_INCREASE_POWER:
6029 explosion_type = EX_TYPE_DYNA;
6032 case EL_DC_LANDMINE:
6033 explosion_type = EX_TYPE_CENTER;
6038 case EL_LAMP_ACTIVE:
6039 case EL_AMOEBA_TO_DIAMOND:
6040 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6041 explosion_type = EX_TYPE_CENTER;
6045 if (element_info[element].explosion_type == EXPLODES_CROSS)
6046 explosion_type = EX_TYPE_CROSS;
6047 else if (element_info[element].explosion_type == EXPLODES_1X1)
6048 explosion_type = EX_TYPE_CENTER;
6052 if (explosion_type == EX_TYPE_DYNA)
6055 Explode(x, y, EX_PHASE_START, explosion_type);
6057 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6060 static void SplashAcid(int x, int y)
6062 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6063 (!IN_LEV_FIELD(x - 1, y - 2) ||
6064 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6065 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6067 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6068 (!IN_LEV_FIELD(x + 1, y - 2) ||
6069 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6070 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6072 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6075 static void InitBeltMovement(void)
6077 static int belt_base_element[4] =
6079 EL_CONVEYOR_BELT_1_LEFT,
6080 EL_CONVEYOR_BELT_2_LEFT,
6081 EL_CONVEYOR_BELT_3_LEFT,
6082 EL_CONVEYOR_BELT_4_LEFT
6084 static int belt_base_active_element[4] =
6086 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6087 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6088 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6089 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6094 // set frame order for belt animation graphic according to belt direction
6095 for (i = 0; i < NUM_BELTS; i++)
6099 for (j = 0; j < NUM_BELT_PARTS; j++)
6101 int element = belt_base_active_element[belt_nr] + j;
6102 int graphic_1 = el2img(element);
6103 int graphic_2 = el2panelimg(element);
6105 if (game.belt_dir[i] == MV_LEFT)
6107 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6108 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6112 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6113 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6118 SCAN_PLAYFIELD(x, y)
6120 int element = Tile[x][y];
6122 for (i = 0; i < NUM_BELTS; i++)
6124 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6126 int e_belt_nr = getBeltNrFromBeltElement(element);
6129 if (e_belt_nr == belt_nr)
6131 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6133 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6140 static void ToggleBeltSwitch(int x, int y)
6142 static int belt_base_element[4] =
6144 EL_CONVEYOR_BELT_1_LEFT,
6145 EL_CONVEYOR_BELT_2_LEFT,
6146 EL_CONVEYOR_BELT_3_LEFT,
6147 EL_CONVEYOR_BELT_4_LEFT
6149 static int belt_base_active_element[4] =
6151 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6152 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6153 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6154 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6156 static int belt_base_switch_element[4] =
6158 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6159 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6160 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6161 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6163 static int belt_move_dir[4] =
6171 int element = Tile[x][y];
6172 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6173 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6174 int belt_dir = belt_move_dir[belt_dir_nr];
6177 if (!IS_BELT_SWITCH(element))
6180 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6181 game.belt_dir[belt_nr] = belt_dir;
6183 if (belt_dir_nr == 3)
6186 // set frame order for belt animation graphic according to belt direction
6187 for (i = 0; i < NUM_BELT_PARTS; i++)
6189 int element = belt_base_active_element[belt_nr] + i;
6190 int graphic_1 = el2img(element);
6191 int graphic_2 = el2panelimg(element);
6193 if (belt_dir == MV_LEFT)
6195 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6196 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6200 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6201 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6205 SCAN_PLAYFIELD(xx, yy)
6207 int element = Tile[xx][yy];
6209 if (IS_BELT_SWITCH(element))
6211 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6213 if (e_belt_nr == belt_nr)
6215 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6216 TEST_DrawLevelField(xx, yy);
6219 else if (IS_BELT(element) && belt_dir != MV_NONE)
6221 int e_belt_nr = getBeltNrFromBeltElement(element);
6223 if (e_belt_nr == belt_nr)
6225 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6227 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6228 TEST_DrawLevelField(xx, yy);
6231 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6233 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6235 if (e_belt_nr == belt_nr)
6237 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6239 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6240 TEST_DrawLevelField(xx, yy);
6246 static void ToggleSwitchgateSwitch(int x, int y)
6250 game.switchgate_pos = !game.switchgate_pos;
6252 SCAN_PLAYFIELD(xx, yy)
6254 int element = Tile[xx][yy];
6256 if (element == EL_SWITCHGATE_SWITCH_UP)
6258 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6259 TEST_DrawLevelField(xx, yy);
6261 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6263 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6264 TEST_DrawLevelField(xx, yy);
6266 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6268 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6269 TEST_DrawLevelField(xx, yy);
6271 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6273 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6274 TEST_DrawLevelField(xx, yy);
6276 else if (element == EL_SWITCHGATE_OPEN ||
6277 element == EL_SWITCHGATE_OPENING)
6279 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6281 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6283 else if (element == EL_SWITCHGATE_CLOSED ||
6284 element == EL_SWITCHGATE_CLOSING)
6286 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6288 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6293 static int getInvisibleActiveFromInvisibleElement(int element)
6295 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6296 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6297 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6301 static int getInvisibleFromInvisibleActiveElement(int element)
6303 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6304 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6305 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6309 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6313 SCAN_PLAYFIELD(x, y)
6315 int element = Tile[x][y];
6317 if (element == EL_LIGHT_SWITCH &&
6318 game.light_time_left > 0)
6320 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6321 TEST_DrawLevelField(x, y);
6323 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6324 game.light_time_left == 0)
6326 Tile[x][y] = EL_LIGHT_SWITCH;
6327 TEST_DrawLevelField(x, y);
6329 else if (element == EL_EMC_DRIPPER &&
6330 game.light_time_left > 0)
6332 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6333 TEST_DrawLevelField(x, y);
6335 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6336 game.light_time_left == 0)
6338 Tile[x][y] = EL_EMC_DRIPPER;
6339 TEST_DrawLevelField(x, y);
6341 else if (element == EL_INVISIBLE_STEELWALL ||
6342 element == EL_INVISIBLE_WALL ||
6343 element == EL_INVISIBLE_SAND)
6345 if (game.light_time_left > 0)
6346 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6348 TEST_DrawLevelField(x, y);
6350 // uncrumble neighbour fields, if needed
6351 if (element == EL_INVISIBLE_SAND)
6352 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6354 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6355 element == EL_INVISIBLE_WALL_ACTIVE ||
6356 element == EL_INVISIBLE_SAND_ACTIVE)
6358 if (game.light_time_left == 0)
6359 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6361 TEST_DrawLevelField(x, y);
6363 // re-crumble neighbour fields, if needed
6364 if (element == EL_INVISIBLE_SAND)
6365 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6370 static void RedrawAllInvisibleElementsForLenses(void)
6374 SCAN_PLAYFIELD(x, y)
6376 int element = Tile[x][y];
6378 if (element == EL_EMC_DRIPPER &&
6379 game.lenses_time_left > 0)
6381 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6382 TEST_DrawLevelField(x, y);
6384 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6385 game.lenses_time_left == 0)
6387 Tile[x][y] = EL_EMC_DRIPPER;
6388 TEST_DrawLevelField(x, y);
6390 else if (element == EL_INVISIBLE_STEELWALL ||
6391 element == EL_INVISIBLE_WALL ||
6392 element == EL_INVISIBLE_SAND)
6394 if (game.lenses_time_left > 0)
6395 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6397 TEST_DrawLevelField(x, y);
6399 // uncrumble neighbour fields, if needed
6400 if (element == EL_INVISIBLE_SAND)
6401 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6403 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6404 element == EL_INVISIBLE_WALL_ACTIVE ||
6405 element == EL_INVISIBLE_SAND_ACTIVE)
6407 if (game.lenses_time_left == 0)
6408 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6410 TEST_DrawLevelField(x, y);
6412 // re-crumble neighbour fields, if needed
6413 if (element == EL_INVISIBLE_SAND)
6414 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6419 static void RedrawAllInvisibleElementsForMagnifier(void)
6423 SCAN_PLAYFIELD(x, y)
6425 int element = Tile[x][y];
6427 if (element == EL_EMC_FAKE_GRASS &&
6428 game.magnify_time_left > 0)
6430 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6431 TEST_DrawLevelField(x, y);
6433 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6434 game.magnify_time_left == 0)
6436 Tile[x][y] = EL_EMC_FAKE_GRASS;
6437 TEST_DrawLevelField(x, y);
6439 else if (IS_GATE_GRAY(element) &&
6440 game.magnify_time_left > 0)
6442 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6443 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6444 IS_EM_GATE_GRAY(element) ?
6445 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6446 IS_EMC_GATE_GRAY(element) ?
6447 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6448 IS_DC_GATE_GRAY(element) ?
6449 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6451 TEST_DrawLevelField(x, y);
6453 else if (IS_GATE_GRAY_ACTIVE(element) &&
6454 game.magnify_time_left == 0)
6456 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6457 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6458 IS_EM_GATE_GRAY_ACTIVE(element) ?
6459 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6460 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6461 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6462 IS_DC_GATE_GRAY_ACTIVE(element) ?
6463 EL_DC_GATE_WHITE_GRAY :
6465 TEST_DrawLevelField(x, y);
6470 static void ToggleLightSwitch(int x, int y)
6472 int element = Tile[x][y];
6474 game.light_time_left =
6475 (element == EL_LIGHT_SWITCH ?
6476 level.time_light * FRAMES_PER_SECOND : 0);
6478 RedrawAllLightSwitchesAndInvisibleElements();
6481 static void ActivateTimegateSwitch(int x, int y)
6485 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6487 SCAN_PLAYFIELD(xx, yy)
6489 int element = Tile[xx][yy];
6491 if (element == EL_TIMEGATE_CLOSED ||
6492 element == EL_TIMEGATE_CLOSING)
6494 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6495 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6499 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6501 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6502 TEST_DrawLevelField(xx, yy);
6508 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6509 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6512 static void Impact(int x, int y)
6514 boolean last_line = (y == lev_fieldy - 1);
6515 boolean object_hit = FALSE;
6516 boolean impact = (last_line || object_hit);
6517 int element = Tile[x][y];
6518 int smashed = EL_STEELWALL;
6520 if (!last_line) // check if element below was hit
6522 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6525 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6526 MovDir[x][y + 1] != MV_DOWN ||
6527 MovPos[x][y + 1] <= TILEY / 2));
6529 // do not smash moving elements that left the smashed field in time
6530 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6531 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6534 #if USE_QUICKSAND_IMPACT_BUGFIX
6535 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6537 RemoveMovingField(x, y + 1);
6538 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6539 Tile[x][y + 2] = EL_ROCK;
6540 TEST_DrawLevelField(x, y + 2);
6545 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6547 RemoveMovingField(x, y + 1);
6548 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6549 Tile[x][y + 2] = EL_ROCK;
6550 TEST_DrawLevelField(x, y + 2);
6557 smashed = MovingOrBlocked2Element(x, y + 1);
6559 impact = (last_line || object_hit);
6562 if (!last_line && smashed == EL_ACID) // element falls into acid
6564 SplashAcid(x, y + 1);
6568 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6569 // only reset graphic animation if graphic really changes after impact
6571 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6573 ResetGfxAnimation(x, y);
6574 TEST_DrawLevelField(x, y);
6577 if (impact && CAN_EXPLODE_IMPACT(element))
6582 else if (impact && element == EL_PEARL &&
6583 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6585 ResetGfxAnimation(x, y);
6587 Tile[x][y] = EL_PEARL_BREAKING;
6588 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6591 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6593 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6598 if (impact && element == EL_AMOEBA_DROP)
6600 if (object_hit && IS_PLAYER(x, y + 1))
6601 KillPlayerUnlessEnemyProtected(x, y + 1);
6602 else if (object_hit && smashed == EL_PENGUIN)
6606 Tile[x][y] = EL_AMOEBA_GROWING;
6607 Store[x][y] = EL_AMOEBA_WET;
6609 ResetRandomAnimationValue(x, y);
6614 if (object_hit) // check which object was hit
6616 if ((CAN_PASS_MAGIC_WALL(element) &&
6617 (smashed == EL_MAGIC_WALL ||
6618 smashed == EL_BD_MAGIC_WALL)) ||
6619 (CAN_PASS_DC_MAGIC_WALL(element) &&
6620 smashed == EL_DC_MAGIC_WALL))
6623 int activated_magic_wall =
6624 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6625 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6626 EL_DC_MAGIC_WALL_ACTIVE);
6628 // activate magic wall / mill
6629 SCAN_PLAYFIELD(xx, yy)
6631 if (Tile[xx][yy] == smashed)
6632 Tile[xx][yy] = activated_magic_wall;
6635 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6636 game.magic_wall_active = TRUE;
6638 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6639 SND_MAGIC_WALL_ACTIVATING :
6640 smashed == EL_BD_MAGIC_WALL ?
6641 SND_BD_MAGIC_WALL_ACTIVATING :
6642 SND_DC_MAGIC_WALL_ACTIVATING));
6645 if (IS_PLAYER(x, y + 1))
6647 if (CAN_SMASH_PLAYER(element))
6649 KillPlayerUnlessEnemyProtected(x, y + 1);
6653 else if (smashed == EL_PENGUIN)
6655 if (CAN_SMASH_PLAYER(element))
6661 else if (element == EL_BD_DIAMOND)
6663 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6669 else if (((element == EL_SP_INFOTRON ||
6670 element == EL_SP_ZONK) &&
6671 (smashed == EL_SP_SNIKSNAK ||
6672 smashed == EL_SP_ELECTRON ||
6673 smashed == EL_SP_DISK_ORANGE)) ||
6674 (element == EL_SP_INFOTRON &&
6675 smashed == EL_SP_DISK_YELLOW))
6680 else if (CAN_SMASH_EVERYTHING(element))
6682 if (IS_CLASSIC_ENEMY(smashed) ||
6683 CAN_EXPLODE_SMASHED(smashed))
6688 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6690 if (smashed == EL_LAMP ||
6691 smashed == EL_LAMP_ACTIVE)
6696 else if (smashed == EL_NUT)
6698 Tile[x][y + 1] = EL_NUT_BREAKING;
6699 PlayLevelSound(x, y, SND_NUT_BREAKING);
6700 RaiseScoreElement(EL_NUT);
6703 else if (smashed == EL_PEARL)
6705 ResetGfxAnimation(x, y);
6707 Tile[x][y + 1] = EL_PEARL_BREAKING;
6708 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6711 else if (smashed == EL_DIAMOND)
6713 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6714 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6717 else if (IS_BELT_SWITCH(smashed))
6719 ToggleBeltSwitch(x, y + 1);
6721 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6722 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6723 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6724 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6726 ToggleSwitchgateSwitch(x, y + 1);
6728 else if (smashed == EL_LIGHT_SWITCH ||
6729 smashed == EL_LIGHT_SWITCH_ACTIVE)
6731 ToggleLightSwitch(x, y + 1);
6735 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6737 CheckElementChangeBySide(x, y + 1, smashed, element,
6738 CE_SWITCHED, CH_SIDE_TOP);
6739 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6745 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6750 // play sound of magic wall / mill
6752 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6753 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6754 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6756 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6757 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6758 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6759 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6760 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6761 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6766 // play sound of object that hits the ground
6767 if (last_line || object_hit)
6768 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6771 static void TurnRoundExt(int x, int y)
6783 { 0, 0 }, { 0, 0 }, { 0, 0 },
6788 int left, right, back;
6792 { MV_DOWN, MV_UP, MV_RIGHT },
6793 { MV_UP, MV_DOWN, MV_LEFT },
6795 { MV_LEFT, MV_RIGHT, MV_DOWN },
6799 { MV_RIGHT, MV_LEFT, MV_UP }
6802 int element = Tile[x][y];
6803 int move_pattern = element_info[element].move_pattern;
6805 int old_move_dir = MovDir[x][y];
6806 int left_dir = turn[old_move_dir].left;
6807 int right_dir = turn[old_move_dir].right;
6808 int back_dir = turn[old_move_dir].back;
6810 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6811 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6812 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6813 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6815 int left_x = x + left_dx, left_y = y + left_dy;
6816 int right_x = x + right_dx, right_y = y + right_dy;
6817 int move_x = x + move_dx, move_y = y + move_dy;
6821 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6823 TestIfBadThingTouchesOtherBadThing(x, y);
6825 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6826 MovDir[x][y] = right_dir;
6827 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6828 MovDir[x][y] = left_dir;
6830 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6832 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6835 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6837 TestIfBadThingTouchesOtherBadThing(x, y);
6839 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6840 MovDir[x][y] = left_dir;
6841 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6842 MovDir[x][y] = right_dir;
6844 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6846 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6849 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6851 TestIfBadThingTouchesOtherBadThing(x, y);
6853 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6854 MovDir[x][y] = left_dir;
6855 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6856 MovDir[x][y] = right_dir;
6858 if (MovDir[x][y] != old_move_dir)
6861 else if (element == EL_YAMYAM)
6863 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6864 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6866 if (can_turn_left && can_turn_right)
6867 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6868 else if (can_turn_left)
6869 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6870 else if (can_turn_right)
6871 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6873 MovDir[x][y] = back_dir;
6875 MovDelay[x][y] = 16 + 16 * RND(3);
6877 else if (element == EL_DARK_YAMYAM)
6879 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6881 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6884 if (can_turn_left && can_turn_right)
6885 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6886 else if (can_turn_left)
6887 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6888 else if (can_turn_right)
6889 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6891 MovDir[x][y] = back_dir;
6893 MovDelay[x][y] = 16 + 16 * RND(3);
6895 else if (element == EL_PACMAN)
6897 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6898 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6900 if (can_turn_left && can_turn_right)
6901 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6902 else if (can_turn_left)
6903 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6904 else if (can_turn_right)
6905 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6907 MovDir[x][y] = back_dir;
6909 MovDelay[x][y] = 6 + RND(40);
6911 else if (element == EL_PIG)
6913 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6914 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6915 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6916 boolean should_turn_left, should_turn_right, should_move_on;
6918 int rnd = RND(rnd_value);
6920 should_turn_left = (can_turn_left &&
6922 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6923 y + back_dy + left_dy)));
6924 should_turn_right = (can_turn_right &&
6926 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6927 y + back_dy + right_dy)));
6928 should_move_on = (can_move_on &&
6931 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6932 y + move_dy + left_dy) ||
6933 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6934 y + move_dy + right_dy)));
6936 if (should_turn_left || should_turn_right || should_move_on)
6938 if (should_turn_left && should_turn_right && should_move_on)
6939 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6940 rnd < 2 * rnd_value / 3 ? right_dir :
6942 else if (should_turn_left && should_turn_right)
6943 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6944 else if (should_turn_left && should_move_on)
6945 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
6946 else if (should_turn_right && should_move_on)
6947 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
6948 else if (should_turn_left)
6949 MovDir[x][y] = left_dir;
6950 else if (should_turn_right)
6951 MovDir[x][y] = right_dir;
6952 else if (should_move_on)
6953 MovDir[x][y] = old_move_dir;
6955 else if (can_move_on && rnd > rnd_value / 8)
6956 MovDir[x][y] = old_move_dir;
6957 else if (can_turn_left && can_turn_right)
6958 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6959 else if (can_turn_left && rnd > rnd_value / 8)
6960 MovDir[x][y] = left_dir;
6961 else if (can_turn_right && rnd > rnd_value/8)
6962 MovDir[x][y] = right_dir;
6964 MovDir[x][y] = back_dir;
6966 xx = x + move_xy[MovDir[x][y]].dx;
6967 yy = y + move_xy[MovDir[x][y]].dy;
6969 if (!IN_LEV_FIELD(xx, yy) ||
6970 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
6971 MovDir[x][y] = old_move_dir;
6975 else if (element == EL_DRAGON)
6977 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
6978 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
6979 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
6981 int rnd = RND(rnd_value);
6983 if (can_move_on && rnd > rnd_value / 8)
6984 MovDir[x][y] = old_move_dir;
6985 else if (can_turn_left && can_turn_right)
6986 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
6987 else if (can_turn_left && rnd > rnd_value / 8)
6988 MovDir[x][y] = left_dir;
6989 else if (can_turn_right && rnd > rnd_value / 8)
6990 MovDir[x][y] = right_dir;
6992 MovDir[x][y] = back_dir;
6994 xx = x + move_xy[MovDir[x][y]].dx;
6995 yy = y + move_xy[MovDir[x][y]].dy;
6997 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
6998 MovDir[x][y] = old_move_dir;
7002 else if (element == EL_MOLE)
7004 boolean can_move_on =
7005 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7006 IS_AMOEBOID(Tile[move_x][move_y]) ||
7007 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7010 boolean can_turn_left =
7011 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7012 IS_AMOEBOID(Tile[left_x][left_y])));
7014 boolean can_turn_right =
7015 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7016 IS_AMOEBOID(Tile[right_x][right_y])));
7018 if (can_turn_left && can_turn_right)
7019 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7020 else if (can_turn_left)
7021 MovDir[x][y] = left_dir;
7023 MovDir[x][y] = right_dir;
7026 if (MovDir[x][y] != old_move_dir)
7029 else if (element == EL_BALLOON)
7031 MovDir[x][y] = game.wind_direction;
7034 else if (element == EL_SPRING)
7036 if (MovDir[x][y] & MV_HORIZONTAL)
7038 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7039 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7041 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7042 ResetGfxAnimation(move_x, move_y);
7043 TEST_DrawLevelField(move_x, move_y);
7045 MovDir[x][y] = back_dir;
7047 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7048 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7049 MovDir[x][y] = MV_NONE;
7054 else if (element == EL_ROBOT ||
7055 element == EL_SATELLITE ||
7056 element == EL_PENGUIN ||
7057 element == EL_EMC_ANDROID)
7059 int attr_x = -1, attr_y = -1;
7061 if (game.all_players_gone)
7063 attr_x = game.exit_x;
7064 attr_y = game.exit_y;
7070 for (i = 0; i < MAX_PLAYERS; i++)
7072 struct PlayerInfo *player = &stored_player[i];
7073 int jx = player->jx, jy = player->jy;
7075 if (!player->active)
7079 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7087 if (element == EL_ROBOT &&
7088 game.robot_wheel_x >= 0 &&
7089 game.robot_wheel_y >= 0 &&
7090 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7091 game.engine_version < VERSION_IDENT(3,1,0,0)))
7093 attr_x = game.robot_wheel_x;
7094 attr_y = game.robot_wheel_y;
7097 if (element == EL_PENGUIN)
7100 static int xy[4][2] =
7108 for (i = 0; i < NUM_DIRECTIONS; i++)
7110 int ex = x + xy[i][0];
7111 int ey = y + xy[i][1];
7113 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7114 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7115 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7116 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7125 MovDir[x][y] = MV_NONE;
7127 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7128 else if (attr_x > x)
7129 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7131 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7132 else if (attr_y > y)
7133 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7135 if (element == EL_ROBOT)
7139 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7140 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7141 Moving2Blocked(x, y, &newx, &newy);
7143 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7144 MovDelay[x][y] = 8 + 8 * !RND(3);
7146 MovDelay[x][y] = 16;
7148 else if (element == EL_PENGUIN)
7154 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7156 boolean first_horiz = RND(2);
7157 int new_move_dir = MovDir[x][y];
7160 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7161 Moving2Blocked(x, y, &newx, &newy);
7163 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7167 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7168 Moving2Blocked(x, y, &newx, &newy);
7170 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7173 MovDir[x][y] = old_move_dir;
7177 else if (element == EL_SATELLITE)
7183 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7185 boolean first_horiz = RND(2);
7186 int new_move_dir = MovDir[x][y];
7189 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7190 Moving2Blocked(x, y, &newx, &newy);
7192 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7196 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7197 Moving2Blocked(x, y, &newx, &newy);
7199 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7202 MovDir[x][y] = old_move_dir;
7206 else if (element == EL_EMC_ANDROID)
7208 static int check_pos[16] =
7210 -1, // 0 => (invalid)
7213 -1, // 3 => (invalid)
7215 0, // 5 => MV_LEFT | MV_UP
7216 2, // 6 => MV_RIGHT | MV_UP
7217 -1, // 7 => (invalid)
7219 6, // 9 => MV_LEFT | MV_DOWN
7220 4, // 10 => MV_RIGHT | MV_DOWN
7221 -1, // 11 => (invalid)
7222 -1, // 12 => (invalid)
7223 -1, // 13 => (invalid)
7224 -1, // 14 => (invalid)
7225 -1, // 15 => (invalid)
7233 { -1, -1, MV_LEFT | MV_UP },
7235 { +1, -1, MV_RIGHT | MV_UP },
7236 { +1, 0, MV_RIGHT },
7237 { +1, +1, MV_RIGHT | MV_DOWN },
7239 { -1, +1, MV_LEFT | MV_DOWN },
7242 int start_pos, check_order;
7243 boolean can_clone = FALSE;
7246 // check if there is any free field around current position
7247 for (i = 0; i < 8; i++)
7249 int newx = x + check_xy[i].dx;
7250 int newy = y + check_xy[i].dy;
7252 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7260 if (can_clone) // randomly find an element to clone
7264 start_pos = check_pos[RND(8)];
7265 check_order = (RND(2) ? -1 : +1);
7267 for (i = 0; i < 8; i++)
7269 int pos_raw = start_pos + i * check_order;
7270 int pos = (pos_raw + 8) % 8;
7271 int newx = x + check_xy[pos].dx;
7272 int newy = y + check_xy[pos].dy;
7274 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7276 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7277 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7279 Store[x][y] = Tile[newx][newy];
7288 if (can_clone) // randomly find a direction to move
7292 start_pos = check_pos[RND(8)];
7293 check_order = (RND(2) ? -1 : +1);
7295 for (i = 0; i < 8; i++)
7297 int pos_raw = start_pos + i * check_order;
7298 int pos = (pos_raw + 8) % 8;
7299 int newx = x + check_xy[pos].dx;
7300 int newy = y + check_xy[pos].dy;
7301 int new_move_dir = check_xy[pos].dir;
7303 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7305 MovDir[x][y] = new_move_dir;
7306 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7315 if (can_clone) // cloning and moving successful
7318 // cannot clone -- try to move towards player
7320 start_pos = check_pos[MovDir[x][y] & 0x0f];
7321 check_order = (RND(2) ? -1 : +1);
7323 for (i = 0; i < 3; i++)
7325 // first check start_pos, then previous/next or (next/previous) pos
7326 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7327 int pos = (pos_raw + 8) % 8;
7328 int newx = x + check_xy[pos].dx;
7329 int newy = y + check_xy[pos].dy;
7330 int new_move_dir = check_xy[pos].dir;
7332 if (IS_PLAYER(newx, newy))
7335 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7337 MovDir[x][y] = new_move_dir;
7338 MovDelay[x][y] = level.android_move_time * 8 + 1;
7345 else if (move_pattern == MV_TURNING_LEFT ||
7346 move_pattern == MV_TURNING_RIGHT ||
7347 move_pattern == MV_TURNING_LEFT_RIGHT ||
7348 move_pattern == MV_TURNING_RIGHT_LEFT ||
7349 move_pattern == MV_TURNING_RANDOM ||
7350 move_pattern == MV_ALL_DIRECTIONS)
7352 boolean can_turn_left =
7353 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7354 boolean can_turn_right =
7355 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7357 if (element_info[element].move_stepsize == 0) // "not moving"
7360 if (move_pattern == MV_TURNING_LEFT)
7361 MovDir[x][y] = left_dir;
7362 else if (move_pattern == MV_TURNING_RIGHT)
7363 MovDir[x][y] = right_dir;
7364 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7365 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7366 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7367 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7368 else if (move_pattern == MV_TURNING_RANDOM)
7369 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7370 can_turn_right && !can_turn_left ? right_dir :
7371 RND(2) ? left_dir : right_dir);
7372 else if (can_turn_left && can_turn_right)
7373 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7374 else if (can_turn_left)
7375 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7376 else if (can_turn_right)
7377 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7379 MovDir[x][y] = back_dir;
7381 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7383 else if (move_pattern == MV_HORIZONTAL ||
7384 move_pattern == MV_VERTICAL)
7386 if (move_pattern & old_move_dir)
7387 MovDir[x][y] = back_dir;
7388 else if (move_pattern == MV_HORIZONTAL)
7389 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7390 else if (move_pattern == MV_VERTICAL)
7391 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7393 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7395 else if (move_pattern & MV_ANY_DIRECTION)
7397 MovDir[x][y] = move_pattern;
7398 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7400 else if (move_pattern & MV_WIND_DIRECTION)
7402 MovDir[x][y] = game.wind_direction;
7403 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7405 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7407 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7408 MovDir[x][y] = left_dir;
7409 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7410 MovDir[x][y] = right_dir;
7412 if (MovDir[x][y] != old_move_dir)
7413 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7415 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7417 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7418 MovDir[x][y] = right_dir;
7419 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7420 MovDir[x][y] = left_dir;
7422 if (MovDir[x][y] != old_move_dir)
7423 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7425 else if (move_pattern == MV_TOWARDS_PLAYER ||
7426 move_pattern == MV_AWAY_FROM_PLAYER)
7428 int attr_x = -1, attr_y = -1;
7430 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7432 if (game.all_players_gone)
7434 attr_x = game.exit_x;
7435 attr_y = game.exit_y;
7441 for (i = 0; i < MAX_PLAYERS; i++)
7443 struct PlayerInfo *player = &stored_player[i];
7444 int jx = player->jx, jy = player->jy;
7446 if (!player->active)
7450 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7458 MovDir[x][y] = MV_NONE;
7460 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7461 else if (attr_x > x)
7462 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7464 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7465 else if (attr_y > y)
7466 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7468 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7470 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7472 boolean first_horiz = RND(2);
7473 int new_move_dir = MovDir[x][y];
7475 if (element_info[element].move_stepsize == 0) // "not moving"
7477 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7478 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7484 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7485 Moving2Blocked(x, y, &newx, &newy);
7487 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7491 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7492 Moving2Blocked(x, y, &newx, &newy);
7494 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7497 MovDir[x][y] = old_move_dir;
7500 else if (move_pattern == MV_WHEN_PUSHED ||
7501 move_pattern == MV_WHEN_DROPPED)
7503 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7504 MovDir[x][y] = MV_NONE;
7508 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7510 static int test_xy[7][2] =
7520 static int test_dir[7] =
7530 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7531 int move_preference = -1000000; // start with very low preference
7532 int new_move_dir = MV_NONE;
7533 int start_test = RND(4);
7536 for (i = 0; i < NUM_DIRECTIONS; i++)
7538 int move_dir = test_dir[start_test + i];
7539 int move_dir_preference;
7541 xx = x + test_xy[start_test + i][0];
7542 yy = y + test_xy[start_test + i][1];
7544 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7545 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7547 new_move_dir = move_dir;
7552 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7555 move_dir_preference = -1 * RunnerVisit[xx][yy];
7556 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7557 move_dir_preference = PlayerVisit[xx][yy];
7559 if (move_dir_preference > move_preference)
7561 // prefer field that has not been visited for the longest time
7562 move_preference = move_dir_preference;
7563 new_move_dir = move_dir;
7565 else if (move_dir_preference == move_preference &&
7566 move_dir == old_move_dir)
7568 // prefer last direction when all directions are preferred equally
7569 move_preference = move_dir_preference;
7570 new_move_dir = move_dir;
7574 MovDir[x][y] = new_move_dir;
7575 if (old_move_dir != new_move_dir)
7576 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7580 static void TurnRound(int x, int y)
7582 int direction = MovDir[x][y];
7586 GfxDir[x][y] = MovDir[x][y];
7588 if (direction != MovDir[x][y])
7592 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7594 ResetGfxFrame(x, y);
7597 static boolean JustBeingPushed(int x, int y)
7601 for (i = 0; i < MAX_PLAYERS; i++)
7603 struct PlayerInfo *player = &stored_player[i];
7605 if (player->active && player->is_pushing && player->MovPos)
7607 int next_jx = player->jx + (player->jx - player->last_jx);
7608 int next_jy = player->jy + (player->jy - player->last_jy);
7610 if (x == next_jx && y == next_jy)
7618 static void StartMoving(int x, int y)
7620 boolean started_moving = FALSE; // some elements can fall _and_ move
7621 int element = Tile[x][y];
7626 if (MovDelay[x][y] == 0)
7627 GfxAction[x][y] = ACTION_DEFAULT;
7629 if (CAN_FALL(element) && y < lev_fieldy - 1)
7631 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7632 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7633 if (JustBeingPushed(x, y))
7636 if (element == EL_QUICKSAND_FULL)
7638 if (IS_FREE(x, y + 1))
7640 InitMovingField(x, y, MV_DOWN);
7641 started_moving = TRUE;
7643 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7644 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7645 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7646 Store[x][y] = EL_ROCK;
7648 Store[x][y] = EL_ROCK;
7651 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7653 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7655 if (!MovDelay[x][y])
7657 MovDelay[x][y] = TILEY + 1;
7659 ResetGfxAnimation(x, y);
7660 ResetGfxAnimation(x, y + 1);
7665 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7666 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7673 Tile[x][y] = EL_QUICKSAND_EMPTY;
7674 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7675 Store[x][y + 1] = Store[x][y];
7678 PlayLevelSoundAction(x, y, ACTION_FILLING);
7680 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7682 if (!MovDelay[x][y])
7684 MovDelay[x][y] = TILEY + 1;
7686 ResetGfxAnimation(x, y);
7687 ResetGfxAnimation(x, y + 1);
7692 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7693 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7700 Tile[x][y] = EL_QUICKSAND_EMPTY;
7701 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7702 Store[x][y + 1] = Store[x][y];
7705 PlayLevelSoundAction(x, y, ACTION_FILLING);
7708 else if (element == EL_QUICKSAND_FAST_FULL)
7710 if (IS_FREE(x, y + 1))
7712 InitMovingField(x, y, MV_DOWN);
7713 started_moving = TRUE;
7715 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7716 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7717 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7718 Store[x][y] = EL_ROCK;
7720 Store[x][y] = EL_ROCK;
7723 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7725 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7727 if (!MovDelay[x][y])
7729 MovDelay[x][y] = TILEY + 1;
7731 ResetGfxAnimation(x, y);
7732 ResetGfxAnimation(x, y + 1);
7737 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7738 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7745 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7746 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7747 Store[x][y + 1] = Store[x][y];
7750 PlayLevelSoundAction(x, y, ACTION_FILLING);
7752 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7754 if (!MovDelay[x][y])
7756 MovDelay[x][y] = TILEY + 1;
7758 ResetGfxAnimation(x, y);
7759 ResetGfxAnimation(x, y + 1);
7764 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7765 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7772 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7773 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7774 Store[x][y + 1] = Store[x][y];
7777 PlayLevelSoundAction(x, y, ACTION_FILLING);
7780 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7781 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7783 InitMovingField(x, y, MV_DOWN);
7784 started_moving = TRUE;
7786 Tile[x][y] = EL_QUICKSAND_FILLING;
7787 Store[x][y] = element;
7789 PlayLevelSoundAction(x, y, ACTION_FILLING);
7791 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7792 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7794 InitMovingField(x, y, MV_DOWN);
7795 started_moving = TRUE;
7797 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7798 Store[x][y] = element;
7800 PlayLevelSoundAction(x, y, ACTION_FILLING);
7802 else if (element == EL_MAGIC_WALL_FULL)
7804 if (IS_FREE(x, y + 1))
7806 InitMovingField(x, y, MV_DOWN);
7807 started_moving = TRUE;
7809 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7810 Store[x][y] = EL_CHANGED(Store[x][y]);
7812 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7814 if (!MovDelay[x][y])
7815 MovDelay[x][y] = TILEY / 4 + 1;
7824 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7825 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7826 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7830 else if (element == EL_BD_MAGIC_WALL_FULL)
7832 if (IS_FREE(x, y + 1))
7834 InitMovingField(x, y, MV_DOWN);
7835 started_moving = TRUE;
7837 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7838 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7840 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7842 if (!MovDelay[x][y])
7843 MovDelay[x][y] = TILEY / 4 + 1;
7852 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7853 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7854 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7858 else if (element == EL_DC_MAGIC_WALL_FULL)
7860 if (IS_FREE(x, y + 1))
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7865 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7866 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7868 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7870 if (!MovDelay[x][y])
7871 MovDelay[x][y] = TILEY / 4 + 1;
7880 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7881 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7882 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7886 else if ((CAN_PASS_MAGIC_WALL(element) &&
7887 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7888 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7889 (CAN_PASS_DC_MAGIC_WALL(element) &&
7890 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7893 InitMovingField(x, y, MV_DOWN);
7894 started_moving = TRUE;
7897 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7898 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7899 EL_DC_MAGIC_WALL_FILLING);
7900 Store[x][y] = element;
7902 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7904 SplashAcid(x, y + 1);
7906 InitMovingField(x, y, MV_DOWN);
7907 started_moving = TRUE;
7909 Store[x][y] = EL_ACID;
7912 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7913 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7914 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7915 CAN_FALL(element) && WasJustFalling[x][y] &&
7916 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7918 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7919 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7920 (Tile[x][y + 1] == EL_BLOCKED)))
7922 /* this is needed for a special case not covered by calling "Impact()"
7923 from "ContinueMoving()": if an element moves to a tile directly below
7924 another element which was just falling on that tile (which was empty
7925 in the previous frame), the falling element above would just stop
7926 instead of smashing the element below (in previous version, the above
7927 element was just checked for "moving" instead of "falling", resulting
7928 in incorrect smashes caused by horizontal movement of the above
7929 element; also, the case of the player being the element to smash was
7930 simply not covered here... :-/ ) */
7932 CheckCollision[x][y] = 0;
7933 CheckImpact[x][y] = 0;
7937 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7939 if (MovDir[x][y] == MV_NONE)
7941 InitMovingField(x, y, MV_DOWN);
7942 started_moving = TRUE;
7945 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
7947 if (WasJustFalling[x][y]) // prevent animation from being restarted
7948 MovDir[x][y] = MV_DOWN;
7950 InitMovingField(x, y, MV_DOWN);
7951 started_moving = TRUE;
7953 else if (element == EL_AMOEBA_DROP)
7955 Tile[x][y] = EL_AMOEBA_GROWING;
7956 Store[x][y] = EL_AMOEBA_WET;
7958 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
7959 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
7960 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
7961 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
7963 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
7964 (IS_FREE(x - 1, y + 1) ||
7965 Tile[x - 1][y + 1] == EL_ACID));
7966 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
7967 (IS_FREE(x + 1, y + 1) ||
7968 Tile[x + 1][y + 1] == EL_ACID));
7969 boolean can_fall_any = (can_fall_left || can_fall_right);
7970 boolean can_fall_both = (can_fall_left && can_fall_right);
7971 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
7973 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
7975 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
7976 can_fall_right = FALSE;
7977 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
7978 can_fall_left = FALSE;
7979 else if (slippery_type == SLIPPERY_ONLY_LEFT)
7980 can_fall_right = FALSE;
7981 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
7982 can_fall_left = FALSE;
7984 can_fall_any = (can_fall_left || can_fall_right);
7985 can_fall_both = FALSE;
7990 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
7991 can_fall_right = FALSE; // slip down on left side
7993 can_fall_left = !(can_fall_right = RND(2));
7995 can_fall_both = FALSE;
8000 // if not determined otherwise, prefer left side for slipping down
8001 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8002 started_moving = TRUE;
8005 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8007 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8008 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8009 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8010 int belt_dir = game.belt_dir[belt_nr];
8012 if ((belt_dir == MV_LEFT && left_is_free) ||
8013 (belt_dir == MV_RIGHT && right_is_free))
8015 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8017 InitMovingField(x, y, belt_dir);
8018 started_moving = TRUE;
8020 Pushed[x][y] = TRUE;
8021 Pushed[nextx][y] = TRUE;
8023 GfxAction[x][y] = ACTION_DEFAULT;
8027 MovDir[x][y] = 0; // if element was moving, stop it
8032 // not "else if" because of elements that can fall and move (EL_SPRING)
8033 if (CAN_MOVE(element) && !started_moving)
8035 int move_pattern = element_info[element].move_pattern;
8038 Moving2Blocked(x, y, &newx, &newy);
8040 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8043 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8044 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8046 WasJustMoving[x][y] = 0;
8047 CheckCollision[x][y] = 0;
8049 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8051 if (Tile[x][y] != element) // element has changed
8055 if (!MovDelay[x][y]) // start new movement phase
8057 // all objects that can change their move direction after each step
8058 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8060 if (element != EL_YAMYAM &&
8061 element != EL_DARK_YAMYAM &&
8062 element != EL_PACMAN &&
8063 !(move_pattern & MV_ANY_DIRECTION) &&
8064 move_pattern != MV_TURNING_LEFT &&
8065 move_pattern != MV_TURNING_RIGHT &&
8066 move_pattern != MV_TURNING_LEFT_RIGHT &&
8067 move_pattern != MV_TURNING_RIGHT_LEFT &&
8068 move_pattern != MV_TURNING_RANDOM)
8072 if (MovDelay[x][y] && (element == EL_BUG ||
8073 element == EL_SPACESHIP ||
8074 element == EL_SP_SNIKSNAK ||
8075 element == EL_SP_ELECTRON ||
8076 element == EL_MOLE))
8077 TEST_DrawLevelField(x, y);
8081 if (MovDelay[x][y]) // wait some time before next movement
8085 if (element == EL_ROBOT ||
8086 element == EL_YAMYAM ||
8087 element == EL_DARK_YAMYAM)
8089 DrawLevelElementAnimationIfNeeded(x, y, element);
8090 PlayLevelSoundAction(x, y, ACTION_WAITING);
8092 else if (element == EL_SP_ELECTRON)
8093 DrawLevelElementAnimationIfNeeded(x, y, element);
8094 else if (element == EL_DRAGON)
8097 int dir = MovDir[x][y];
8098 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8099 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8100 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8101 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8102 dir == MV_UP ? IMG_FLAMES_1_UP :
8103 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8104 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8106 GfxAction[x][y] = ACTION_ATTACKING;
8108 if (IS_PLAYER(x, y))
8109 DrawPlayerField(x, y);
8111 TEST_DrawLevelField(x, y);
8113 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8115 for (i = 1; i <= 3; i++)
8117 int xx = x + i * dx;
8118 int yy = y + i * dy;
8119 int sx = SCREENX(xx);
8120 int sy = SCREENY(yy);
8121 int flame_graphic = graphic + (i - 1);
8123 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8128 int flamed = MovingOrBlocked2Element(xx, yy);
8130 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8133 RemoveMovingField(xx, yy);
8135 ChangeDelay[xx][yy] = 0;
8137 Tile[xx][yy] = EL_FLAMES;
8139 if (IN_SCR_FIELD(sx, sy))
8141 TEST_DrawLevelFieldCrumbled(xx, yy);
8142 DrawGraphic(sx, sy, flame_graphic, frame);
8147 if (Tile[xx][yy] == EL_FLAMES)
8148 Tile[xx][yy] = EL_EMPTY;
8149 TEST_DrawLevelField(xx, yy);
8154 if (MovDelay[x][y]) // element still has to wait some time
8156 PlayLevelSoundAction(x, y, ACTION_WAITING);
8162 // now make next step
8164 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8166 if (DONT_COLLIDE_WITH(element) &&
8167 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8168 !PLAYER_ENEMY_PROTECTED(newx, newy))
8170 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8175 else if (CAN_MOVE_INTO_ACID(element) &&
8176 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8177 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8178 (MovDir[x][y] == MV_DOWN ||
8179 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8181 SplashAcid(newx, newy);
8182 Store[x][y] = EL_ACID;
8184 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8186 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8187 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8188 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8189 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8192 TEST_DrawLevelField(x, y);
8194 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8195 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8196 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8198 game.friends_still_needed--;
8199 if (!game.friends_still_needed &&
8201 game.all_players_gone)
8206 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8208 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8209 TEST_DrawLevelField(newx, newy);
8211 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8213 else if (!IS_FREE(newx, newy))
8215 GfxAction[x][y] = ACTION_WAITING;
8217 if (IS_PLAYER(x, y))
8218 DrawPlayerField(x, y);
8220 TEST_DrawLevelField(x, y);
8225 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8227 if (IS_FOOD_PIG(Tile[newx][newy]))
8229 if (IS_MOVING(newx, newy))
8230 RemoveMovingField(newx, newy);
8233 Tile[newx][newy] = EL_EMPTY;
8234 TEST_DrawLevelField(newx, newy);
8237 PlayLevelSound(x, y, SND_PIG_DIGGING);
8239 else if (!IS_FREE(newx, newy))
8241 if (IS_PLAYER(x, y))
8242 DrawPlayerField(x, y);
8244 TEST_DrawLevelField(x, y);
8249 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8251 if (Store[x][y] != EL_EMPTY)
8253 boolean can_clone = FALSE;
8256 // check if element to clone is still there
8257 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8259 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8267 // cannot clone or target field not free anymore -- do not clone
8268 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8269 Store[x][y] = EL_EMPTY;
8272 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8274 if (IS_MV_DIAGONAL(MovDir[x][y]))
8276 int diagonal_move_dir = MovDir[x][y];
8277 int stored = Store[x][y];
8278 int change_delay = 8;
8281 // android is moving diagonally
8283 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8285 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8286 GfxElement[x][y] = EL_EMC_ANDROID;
8287 GfxAction[x][y] = ACTION_SHRINKING;
8288 GfxDir[x][y] = diagonal_move_dir;
8289 ChangeDelay[x][y] = change_delay;
8291 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8294 DrawLevelGraphicAnimation(x, y, graphic);
8295 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8297 if (Tile[newx][newy] == EL_ACID)
8299 SplashAcid(newx, newy);
8304 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8306 Store[newx][newy] = EL_EMC_ANDROID;
8307 GfxElement[newx][newy] = EL_EMC_ANDROID;
8308 GfxAction[newx][newy] = ACTION_GROWING;
8309 GfxDir[newx][newy] = diagonal_move_dir;
8310 ChangeDelay[newx][newy] = change_delay;
8312 graphic = el_act_dir2img(GfxElement[newx][newy],
8313 GfxAction[newx][newy], GfxDir[newx][newy]);
8315 DrawLevelGraphicAnimation(newx, newy, graphic);
8316 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8322 Tile[newx][newy] = EL_EMPTY;
8323 TEST_DrawLevelField(newx, newy);
8325 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8328 else if (!IS_FREE(newx, newy))
8333 else if (IS_CUSTOM_ELEMENT(element) &&
8334 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8336 if (!DigFieldByCE(newx, newy, element))
8339 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8341 RunnerVisit[x][y] = FrameCounter;
8342 PlayerVisit[x][y] /= 8; // expire player visit path
8345 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8347 if (!IS_FREE(newx, newy))
8349 if (IS_PLAYER(x, y))
8350 DrawPlayerField(x, y);
8352 TEST_DrawLevelField(x, y);
8358 boolean wanna_flame = !RND(10);
8359 int dx = newx - x, dy = newy - y;
8360 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8361 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8362 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8363 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8364 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8365 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8368 IS_CLASSIC_ENEMY(element1) ||
8369 IS_CLASSIC_ENEMY(element2)) &&
8370 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8371 element1 != EL_FLAMES && element2 != EL_FLAMES)
8373 ResetGfxAnimation(x, y);
8374 GfxAction[x][y] = ACTION_ATTACKING;
8376 if (IS_PLAYER(x, y))
8377 DrawPlayerField(x, y);
8379 TEST_DrawLevelField(x, y);
8381 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8383 MovDelay[x][y] = 50;
8385 Tile[newx][newy] = EL_FLAMES;
8386 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8387 Tile[newx1][newy1] = EL_FLAMES;
8388 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8389 Tile[newx2][newy2] = EL_FLAMES;
8395 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8396 Tile[newx][newy] == EL_DIAMOND)
8398 if (IS_MOVING(newx, newy))
8399 RemoveMovingField(newx, newy);
8402 Tile[newx][newy] = EL_EMPTY;
8403 TEST_DrawLevelField(newx, newy);
8406 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8408 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8409 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8411 if (AmoebaNr[newx][newy])
8413 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8414 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8415 Tile[newx][newy] == EL_BD_AMOEBA)
8416 AmoebaCnt[AmoebaNr[newx][newy]]--;
8419 if (IS_MOVING(newx, newy))
8421 RemoveMovingField(newx, newy);
8425 Tile[newx][newy] = EL_EMPTY;
8426 TEST_DrawLevelField(newx, newy);
8429 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8431 else if ((element == EL_PACMAN || element == EL_MOLE)
8432 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8434 if (AmoebaNr[newx][newy])
8436 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8437 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8438 Tile[newx][newy] == EL_BD_AMOEBA)
8439 AmoebaCnt[AmoebaNr[newx][newy]]--;
8442 if (element == EL_MOLE)
8444 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8445 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8447 ResetGfxAnimation(x, y);
8448 GfxAction[x][y] = ACTION_DIGGING;
8449 TEST_DrawLevelField(x, y);
8451 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8453 return; // wait for shrinking amoeba
8455 else // element == EL_PACMAN
8457 Tile[newx][newy] = EL_EMPTY;
8458 TEST_DrawLevelField(newx, newy);
8459 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8462 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8463 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8464 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8466 // wait for shrinking amoeba to completely disappear
8469 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8471 // object was running against a wall
8475 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8476 DrawLevelElementAnimation(x, y, element);
8478 if (DONT_TOUCH(element))
8479 TestIfBadThingTouchesPlayer(x, y);
8484 InitMovingField(x, y, MovDir[x][y]);
8486 PlayLevelSoundAction(x, y, ACTION_MOVING);
8490 ContinueMoving(x, y);
8493 void ContinueMoving(int x, int y)
8495 int element = Tile[x][y];
8496 struct ElementInfo *ei = &element_info[element];
8497 int direction = MovDir[x][y];
8498 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8499 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8500 int newx = x + dx, newy = y + dy;
8501 int stored = Store[x][y];
8502 int stored_new = Store[newx][newy];
8503 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8504 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8505 boolean last_line = (newy == lev_fieldy - 1);
8507 MovPos[x][y] += getElementMoveStepsize(x, y);
8509 if (pushed_by_player) // special case: moving object pushed by player
8510 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8512 if (ABS(MovPos[x][y]) < TILEX)
8514 TEST_DrawLevelField(x, y);
8516 return; // element is still moving
8519 // element reached destination field
8521 Tile[x][y] = EL_EMPTY;
8522 Tile[newx][newy] = element;
8523 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8525 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8527 element = Tile[newx][newy] = EL_ACID;
8529 else if (element == EL_MOLE)
8531 Tile[x][y] = EL_SAND;
8533 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8535 else if (element == EL_QUICKSAND_FILLING)
8537 element = Tile[newx][newy] = get_next_element(element);
8538 Store[newx][newy] = Store[x][y];
8540 else if (element == EL_QUICKSAND_EMPTYING)
8542 Tile[x][y] = get_next_element(element);
8543 element = Tile[newx][newy] = Store[x][y];
8545 else if (element == EL_QUICKSAND_FAST_FILLING)
8547 element = Tile[newx][newy] = get_next_element(element);
8548 Store[newx][newy] = Store[x][y];
8550 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8552 Tile[x][y] = get_next_element(element);
8553 element = Tile[newx][newy] = Store[x][y];
8555 else if (element == EL_MAGIC_WALL_FILLING)
8557 element = Tile[newx][newy] = get_next_element(element);
8558 if (!game.magic_wall_active)
8559 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8560 Store[newx][newy] = Store[x][y];
8562 else if (element == EL_MAGIC_WALL_EMPTYING)
8564 Tile[x][y] = get_next_element(element);
8565 if (!game.magic_wall_active)
8566 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8567 element = Tile[newx][newy] = Store[x][y];
8569 InitField(newx, newy, FALSE);
8571 else if (element == EL_BD_MAGIC_WALL_FILLING)
8573 element = Tile[newx][newy] = get_next_element(element);
8574 if (!game.magic_wall_active)
8575 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8576 Store[newx][newy] = Store[x][y];
8578 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8580 Tile[x][y] = get_next_element(element);
8581 if (!game.magic_wall_active)
8582 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8583 element = Tile[newx][newy] = Store[x][y];
8585 InitField(newx, newy, FALSE);
8587 else if (element == EL_DC_MAGIC_WALL_FILLING)
8589 element = Tile[newx][newy] = get_next_element(element);
8590 if (!game.magic_wall_active)
8591 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8592 Store[newx][newy] = Store[x][y];
8594 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8596 Tile[x][y] = get_next_element(element);
8597 if (!game.magic_wall_active)
8598 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8599 element = Tile[newx][newy] = Store[x][y];
8601 InitField(newx, newy, FALSE);
8603 else if (element == EL_AMOEBA_DROPPING)
8605 Tile[x][y] = get_next_element(element);
8606 element = Tile[newx][newy] = Store[x][y];
8608 else if (element == EL_SOKOBAN_OBJECT)
8611 Tile[x][y] = Back[x][y];
8613 if (Back[newx][newy])
8614 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8616 Back[x][y] = Back[newx][newy] = 0;
8619 Store[x][y] = EL_EMPTY;
8624 MovDelay[newx][newy] = 0;
8626 if (CAN_CHANGE_OR_HAS_ACTION(element))
8628 // copy element change control values to new field
8629 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8630 ChangePage[newx][newy] = ChangePage[x][y];
8631 ChangeCount[newx][newy] = ChangeCount[x][y];
8632 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8635 CustomValue[newx][newy] = CustomValue[x][y];
8637 ChangeDelay[x][y] = 0;
8638 ChangePage[x][y] = -1;
8639 ChangeCount[x][y] = 0;
8640 ChangeEvent[x][y] = -1;
8642 CustomValue[x][y] = 0;
8644 // copy animation control values to new field
8645 GfxFrame[newx][newy] = GfxFrame[x][y];
8646 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8647 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8648 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8650 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8652 // some elements can leave other elements behind after moving
8653 if (ei->move_leave_element != EL_EMPTY &&
8654 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8655 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8657 int move_leave_element = ei->move_leave_element;
8659 // this makes it possible to leave the removed element again
8660 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8661 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8663 Tile[x][y] = move_leave_element;
8665 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8666 MovDir[x][y] = direction;
8668 InitField(x, y, FALSE);
8670 if (GFX_CRUMBLED(Tile[x][y]))
8671 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8673 if (ELEM_IS_PLAYER(move_leave_element))
8674 RelocatePlayer(x, y, move_leave_element);
8677 // do this after checking for left-behind element
8678 ResetGfxAnimation(x, y); // reset animation values for old field
8680 if (!CAN_MOVE(element) ||
8681 (CAN_FALL(element) && direction == MV_DOWN &&
8682 (element == EL_SPRING ||
8683 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8684 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8685 GfxDir[x][y] = MovDir[newx][newy] = 0;
8687 TEST_DrawLevelField(x, y);
8688 TEST_DrawLevelField(newx, newy);
8690 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8692 // prevent pushed element from moving on in pushed direction
8693 if (pushed_by_player && CAN_MOVE(element) &&
8694 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8695 !(element_info[element].move_pattern & direction))
8696 TurnRound(newx, newy);
8698 // prevent elements on conveyor belt from moving on in last direction
8699 if (pushed_by_conveyor && CAN_FALL(element) &&
8700 direction & MV_HORIZONTAL)
8701 MovDir[newx][newy] = 0;
8703 if (!pushed_by_player)
8705 int nextx = newx + dx, nexty = newy + dy;
8706 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8708 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8710 if (CAN_FALL(element) && direction == MV_DOWN)
8711 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8713 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8714 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8716 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8717 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8720 if (DONT_TOUCH(element)) // object may be nasty to player or others
8722 TestIfBadThingTouchesPlayer(newx, newy);
8723 TestIfBadThingTouchesFriend(newx, newy);
8725 if (!IS_CUSTOM_ELEMENT(element))
8726 TestIfBadThingTouchesOtherBadThing(newx, newy);
8728 else if (element == EL_PENGUIN)
8729 TestIfFriendTouchesBadThing(newx, newy);
8731 if (DONT_GET_HIT_BY(element))
8733 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8736 // give the player one last chance (one more frame) to move away
8737 if (CAN_FALL(element) && direction == MV_DOWN &&
8738 (last_line || (!IS_FREE(x, newy + 1) &&
8739 (!IS_PLAYER(x, newy + 1) ||
8740 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8743 if (pushed_by_player && !game.use_change_when_pushing_bug)
8745 int push_side = MV_DIR_OPPOSITE(direction);
8746 struct PlayerInfo *player = PLAYERINFO(x, y);
8748 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8749 player->index_bit, push_side);
8750 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8751 player->index_bit, push_side);
8754 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8755 MovDelay[newx][newy] = 1;
8757 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8759 TestIfElementTouchesCustomElement(x, y); // empty or new element
8760 TestIfElementHitsCustomElement(newx, newy, direction);
8761 TestIfPlayerTouchesCustomElement(newx, newy);
8762 TestIfElementTouchesCustomElement(newx, newy);
8764 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8765 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8766 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8767 MV_DIR_OPPOSITE(direction));
8770 int AmoebaNeighbourNr(int ax, int ay)
8773 int element = Tile[ax][ay];
8775 static int xy[4][2] =
8783 for (i = 0; i < NUM_DIRECTIONS; i++)
8785 int x = ax + xy[i][0];
8786 int y = ay + xy[i][1];
8788 if (!IN_LEV_FIELD(x, y))
8791 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8792 group_nr = AmoebaNr[x][y];
8798 static void AmoebaMerge(int ax, int ay)
8800 int i, x, y, xx, yy;
8801 int new_group_nr = AmoebaNr[ax][ay];
8802 static int xy[4][2] =
8810 if (new_group_nr == 0)
8813 for (i = 0; i < NUM_DIRECTIONS; i++)
8818 if (!IN_LEV_FIELD(x, y))
8821 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8822 Tile[x][y] == EL_BD_AMOEBA ||
8823 Tile[x][y] == EL_AMOEBA_DEAD) &&
8824 AmoebaNr[x][y] != new_group_nr)
8826 int old_group_nr = AmoebaNr[x][y];
8828 if (old_group_nr == 0)
8831 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8832 AmoebaCnt[old_group_nr] = 0;
8833 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8834 AmoebaCnt2[old_group_nr] = 0;
8836 SCAN_PLAYFIELD(xx, yy)
8838 if (AmoebaNr[xx][yy] == old_group_nr)
8839 AmoebaNr[xx][yy] = new_group_nr;
8845 void AmoebaToDiamond(int ax, int ay)
8849 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8851 int group_nr = AmoebaNr[ax][ay];
8856 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8857 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8863 SCAN_PLAYFIELD(x, y)
8865 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8868 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8872 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8873 SND_AMOEBA_TURNING_TO_GEM :
8874 SND_AMOEBA_TURNING_TO_ROCK));
8879 static int xy[4][2] =
8887 for (i = 0; i < NUM_DIRECTIONS; i++)
8892 if (!IN_LEV_FIELD(x, y))
8895 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8897 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8898 SND_AMOEBA_TURNING_TO_GEM :
8899 SND_AMOEBA_TURNING_TO_ROCK));
8906 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8909 int group_nr = AmoebaNr[ax][ay];
8910 boolean done = FALSE;
8915 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8916 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
8922 SCAN_PLAYFIELD(x, y)
8924 if (AmoebaNr[x][y] == group_nr &&
8925 (Tile[x][y] == EL_AMOEBA_DEAD ||
8926 Tile[x][y] == EL_BD_AMOEBA ||
8927 Tile[x][y] == EL_AMOEBA_GROWING))
8930 Tile[x][y] = new_element;
8931 InitField(x, y, FALSE);
8932 TEST_DrawLevelField(x, y);
8938 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
8939 SND_BD_AMOEBA_TURNING_TO_ROCK :
8940 SND_BD_AMOEBA_TURNING_TO_GEM));
8943 static void AmoebaGrowing(int x, int y)
8945 static unsigned int sound_delay = 0;
8946 static unsigned int sound_delay_value = 0;
8948 if (!MovDelay[x][y]) // start new growing cycle
8952 if (DelayReached(&sound_delay, sound_delay_value))
8954 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
8955 sound_delay_value = 30;
8959 if (MovDelay[x][y]) // wait some time before growing bigger
8962 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8964 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
8965 6 - MovDelay[x][y]);
8967 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
8970 if (!MovDelay[x][y])
8972 Tile[x][y] = Store[x][y];
8974 TEST_DrawLevelField(x, y);
8979 static void AmoebaShrinking(int x, int y)
8981 static unsigned int sound_delay = 0;
8982 static unsigned int sound_delay_value = 0;
8984 if (!MovDelay[x][y]) // start new shrinking cycle
8988 if (DelayReached(&sound_delay, sound_delay_value))
8989 sound_delay_value = 30;
8992 if (MovDelay[x][y]) // wait some time before shrinking
8995 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
8997 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
8998 6 - MovDelay[x][y]);
9000 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9003 if (!MovDelay[x][y])
9005 Tile[x][y] = EL_EMPTY;
9006 TEST_DrawLevelField(x, y);
9008 // don't let mole enter this field in this cycle;
9009 // (give priority to objects falling to this field from above)
9015 static void AmoebaReproduce(int ax, int ay)
9018 int element = Tile[ax][ay];
9019 int graphic = el2img(element);
9020 int newax = ax, neway = ay;
9021 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9022 static int xy[4][2] =
9030 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9032 Tile[ax][ay] = EL_AMOEBA_DEAD;
9033 TEST_DrawLevelField(ax, ay);
9037 if (IS_ANIMATED(graphic))
9038 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9040 if (!MovDelay[ax][ay]) // start making new amoeba field
9041 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9043 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9046 if (MovDelay[ax][ay])
9050 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9053 int x = ax + xy[start][0];
9054 int y = ay + xy[start][1];
9056 if (!IN_LEV_FIELD(x, y))
9059 if (IS_FREE(x, y) ||
9060 CAN_GROW_INTO(Tile[x][y]) ||
9061 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9062 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9068 if (newax == ax && neway == ay)
9071 else // normal or "filled" (BD style) amoeba
9074 boolean waiting_for_player = FALSE;
9076 for (i = 0; i < NUM_DIRECTIONS; i++)
9078 int j = (start + i) % 4;
9079 int x = ax + xy[j][0];
9080 int y = ay + xy[j][1];
9082 if (!IN_LEV_FIELD(x, y))
9085 if (IS_FREE(x, y) ||
9086 CAN_GROW_INTO(Tile[x][y]) ||
9087 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9088 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9094 else if (IS_PLAYER(x, y))
9095 waiting_for_player = TRUE;
9098 if (newax == ax && neway == ay) // amoeba cannot grow
9100 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9102 Tile[ax][ay] = EL_AMOEBA_DEAD;
9103 TEST_DrawLevelField(ax, ay);
9104 AmoebaCnt[AmoebaNr[ax][ay]]--;
9106 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9108 if (element == EL_AMOEBA_FULL)
9109 AmoebaToDiamond(ax, ay);
9110 else if (element == EL_BD_AMOEBA)
9111 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9116 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9118 // amoeba gets larger by growing in some direction
9120 int new_group_nr = AmoebaNr[ax][ay];
9123 if (new_group_nr == 0)
9125 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9127 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9133 AmoebaNr[newax][neway] = new_group_nr;
9134 AmoebaCnt[new_group_nr]++;
9135 AmoebaCnt2[new_group_nr]++;
9137 // if amoeba touches other amoeba(s) after growing, unify them
9138 AmoebaMerge(newax, neway);
9140 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9142 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9148 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9149 (neway == lev_fieldy - 1 && newax != ax))
9151 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9152 Store[newax][neway] = element;
9154 else if (neway == ay || element == EL_EMC_DRIPPER)
9156 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9158 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9162 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9163 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9164 Store[ax][ay] = EL_AMOEBA_DROP;
9165 ContinueMoving(ax, ay);
9169 TEST_DrawLevelField(newax, neway);
9172 static void Life(int ax, int ay)
9176 int element = Tile[ax][ay];
9177 int graphic = el2img(element);
9178 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9180 boolean changed = FALSE;
9182 if (IS_ANIMATED(graphic))
9183 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9188 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9189 MovDelay[ax][ay] = life_time;
9191 if (MovDelay[ax][ay]) // wait some time before next cycle
9194 if (MovDelay[ax][ay])
9198 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9200 int xx = ax+x1, yy = ay+y1;
9201 int old_element = Tile[xx][yy];
9202 int num_neighbours = 0;
9204 if (!IN_LEV_FIELD(xx, yy))
9207 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9209 int x = xx+x2, y = yy+y2;
9211 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9214 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9215 boolean is_neighbour = FALSE;
9217 if (level.use_life_bugs)
9219 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9220 (IS_FREE(x, y) && Stop[x][y]));
9223 (Last[x][y] == element || is_player_cell);
9229 boolean is_free = FALSE;
9231 if (level.use_life_bugs)
9232 is_free = (IS_FREE(xx, yy));
9234 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9236 if (xx == ax && yy == ay) // field in the middle
9238 if (num_neighbours < life_parameter[0] ||
9239 num_neighbours > life_parameter[1])
9241 Tile[xx][yy] = EL_EMPTY;
9242 if (Tile[xx][yy] != old_element)
9243 TEST_DrawLevelField(xx, yy);
9244 Stop[xx][yy] = TRUE;
9248 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9249 { // free border field
9250 if (num_neighbours >= life_parameter[2] &&
9251 num_neighbours <= life_parameter[3])
9253 Tile[xx][yy] = element;
9254 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9255 if (Tile[xx][yy] != old_element)
9256 TEST_DrawLevelField(xx, yy);
9257 Stop[xx][yy] = TRUE;
9264 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9265 SND_GAME_OF_LIFE_GROWING);
9268 static void InitRobotWheel(int x, int y)
9270 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9273 static void RunRobotWheel(int x, int y)
9275 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9278 static void StopRobotWheel(int x, int y)
9280 if (game.robot_wheel_x == x &&
9281 game.robot_wheel_y == y)
9283 game.robot_wheel_x = -1;
9284 game.robot_wheel_y = -1;
9285 game.robot_wheel_active = FALSE;
9289 static void InitTimegateWheel(int x, int y)
9291 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9294 static void RunTimegateWheel(int x, int y)
9296 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9299 static void InitMagicBallDelay(int x, int y)
9301 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9304 static void ActivateMagicBall(int bx, int by)
9308 if (level.ball_random)
9310 int pos_border = RND(8); // select one of the eight border elements
9311 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9312 int xx = pos_content % 3;
9313 int yy = pos_content / 3;
9318 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9319 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9323 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9325 int xx = x - bx + 1;
9326 int yy = y - by + 1;
9328 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9329 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9333 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9336 static void CheckExit(int x, int y)
9338 if (game.gems_still_needed > 0 ||
9339 game.sokoban_fields_still_needed > 0 ||
9340 game.sokoban_objects_still_needed > 0 ||
9341 game.lights_still_needed > 0)
9343 int element = Tile[x][y];
9344 int graphic = el2img(element);
9346 if (IS_ANIMATED(graphic))
9347 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9352 // do not re-open exit door closed after last player
9353 if (game.all_players_gone)
9356 Tile[x][y] = EL_EXIT_OPENING;
9358 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9361 static void CheckExitEM(int x, int y)
9363 if (game.gems_still_needed > 0 ||
9364 game.sokoban_fields_still_needed > 0 ||
9365 game.sokoban_objects_still_needed > 0 ||
9366 game.lights_still_needed > 0)
9368 int element = Tile[x][y];
9369 int graphic = el2img(element);
9371 if (IS_ANIMATED(graphic))
9372 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9377 // do not re-open exit door closed after last player
9378 if (game.all_players_gone)
9381 Tile[x][y] = EL_EM_EXIT_OPENING;
9383 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9386 static void CheckExitSteel(int x, int y)
9388 if (game.gems_still_needed > 0 ||
9389 game.sokoban_fields_still_needed > 0 ||
9390 game.sokoban_objects_still_needed > 0 ||
9391 game.lights_still_needed > 0)
9393 int element = Tile[x][y];
9394 int graphic = el2img(element);
9396 if (IS_ANIMATED(graphic))
9397 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9402 // do not re-open exit door closed after last player
9403 if (game.all_players_gone)
9406 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9408 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9411 static void CheckExitSteelEM(int x, int y)
9413 if (game.gems_still_needed > 0 ||
9414 game.sokoban_fields_still_needed > 0 ||
9415 game.sokoban_objects_still_needed > 0 ||
9416 game.lights_still_needed > 0)
9418 int element = Tile[x][y];
9419 int graphic = el2img(element);
9421 if (IS_ANIMATED(graphic))
9422 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9427 // do not re-open exit door closed after last player
9428 if (game.all_players_gone)
9431 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9433 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9436 static void CheckExitSP(int x, int y)
9438 if (game.gems_still_needed > 0)
9440 int element = Tile[x][y];
9441 int graphic = el2img(element);
9443 if (IS_ANIMATED(graphic))
9444 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9449 // do not re-open exit door closed after last player
9450 if (game.all_players_gone)
9453 Tile[x][y] = EL_SP_EXIT_OPENING;
9455 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9458 static void CloseAllOpenTimegates(void)
9462 SCAN_PLAYFIELD(x, y)
9464 int element = Tile[x][y];
9466 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9468 Tile[x][y] = EL_TIMEGATE_CLOSING;
9470 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9475 static void DrawTwinkleOnField(int x, int y)
9477 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9480 if (Tile[x][y] == EL_BD_DIAMOND)
9483 if (MovDelay[x][y] == 0) // next animation frame
9484 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9486 if (MovDelay[x][y] != 0) // wait some time before next frame
9490 DrawLevelElementAnimation(x, y, Tile[x][y]);
9492 if (MovDelay[x][y] != 0)
9494 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9495 10 - MovDelay[x][y]);
9497 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9502 static void MauerWaechst(int x, int y)
9506 if (!MovDelay[x][y]) // next animation frame
9507 MovDelay[x][y] = 3 * delay;
9509 if (MovDelay[x][y]) // wait some time before next frame
9513 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9515 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9516 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9518 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9521 if (!MovDelay[x][y])
9523 if (MovDir[x][y] == MV_LEFT)
9525 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9526 TEST_DrawLevelField(x - 1, y);
9528 else if (MovDir[x][y] == MV_RIGHT)
9530 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9531 TEST_DrawLevelField(x + 1, y);
9533 else if (MovDir[x][y] == MV_UP)
9535 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9536 TEST_DrawLevelField(x, y - 1);
9540 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9541 TEST_DrawLevelField(x, y + 1);
9544 Tile[x][y] = Store[x][y];
9546 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9547 TEST_DrawLevelField(x, y);
9552 static void MauerAbleger(int ax, int ay)
9554 int element = Tile[ax][ay];
9555 int graphic = el2img(element);
9556 boolean oben_frei = FALSE, unten_frei = FALSE;
9557 boolean links_frei = FALSE, rechts_frei = FALSE;
9558 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9559 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9560 boolean new_wall = FALSE;
9562 if (IS_ANIMATED(graphic))
9563 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9565 if (!MovDelay[ax][ay]) // start building new wall
9566 MovDelay[ax][ay] = 6;
9568 if (MovDelay[ax][ay]) // wait some time before building new wall
9571 if (MovDelay[ax][ay])
9575 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9577 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9579 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9581 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9584 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9585 element == EL_EXPANDABLE_WALL_ANY)
9589 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9590 Store[ax][ay-1] = element;
9591 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9592 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9593 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9594 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9599 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9600 Store[ax][ay+1] = element;
9601 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9602 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9603 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9604 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9609 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9610 element == EL_EXPANDABLE_WALL_ANY ||
9611 element == EL_EXPANDABLE_WALL ||
9612 element == EL_BD_EXPANDABLE_WALL)
9616 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9617 Store[ax-1][ay] = element;
9618 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9619 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9620 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9621 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9627 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9628 Store[ax+1][ay] = element;
9629 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9630 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9631 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9632 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9637 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9638 TEST_DrawLevelField(ax, ay);
9640 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9642 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9643 unten_massiv = TRUE;
9644 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9645 links_massiv = TRUE;
9646 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9647 rechts_massiv = TRUE;
9649 if (((oben_massiv && unten_massiv) ||
9650 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9651 element == EL_EXPANDABLE_WALL) &&
9652 ((links_massiv && rechts_massiv) ||
9653 element == EL_EXPANDABLE_WALL_VERTICAL))
9654 Tile[ax][ay] = EL_WALL;
9657 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9660 static void MauerAblegerStahl(int ax, int ay)
9662 int element = Tile[ax][ay];
9663 int graphic = el2img(element);
9664 boolean oben_frei = FALSE, unten_frei = FALSE;
9665 boolean links_frei = FALSE, rechts_frei = FALSE;
9666 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9667 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9668 boolean new_wall = FALSE;
9670 if (IS_ANIMATED(graphic))
9671 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9673 if (!MovDelay[ax][ay]) // start building new wall
9674 MovDelay[ax][ay] = 6;
9676 if (MovDelay[ax][ay]) // wait some time before building new wall
9679 if (MovDelay[ax][ay])
9683 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9685 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9687 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9689 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9692 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9693 element == EL_EXPANDABLE_STEELWALL_ANY)
9697 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9698 Store[ax][ay-1] = element;
9699 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9700 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9701 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9702 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9707 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9708 Store[ax][ay+1] = element;
9709 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9710 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9711 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9712 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9717 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9718 element == EL_EXPANDABLE_STEELWALL_ANY)
9722 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9723 Store[ax-1][ay] = element;
9724 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9725 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9726 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9727 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9733 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9734 Store[ax+1][ay] = element;
9735 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9736 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9737 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9738 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9743 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9745 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9746 unten_massiv = TRUE;
9747 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9748 links_massiv = TRUE;
9749 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9750 rechts_massiv = TRUE;
9752 if (((oben_massiv && unten_massiv) ||
9753 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9754 ((links_massiv && rechts_massiv) ||
9755 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9756 Tile[ax][ay] = EL_STEELWALL;
9759 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9762 static void CheckForDragon(int x, int y)
9765 boolean dragon_found = FALSE;
9766 static int xy[4][2] =
9774 for (i = 0; i < NUM_DIRECTIONS; i++)
9776 for (j = 0; j < 4; j++)
9778 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9780 if (IN_LEV_FIELD(xx, yy) &&
9781 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9783 if (Tile[xx][yy] == EL_DRAGON)
9784 dragon_found = TRUE;
9793 for (i = 0; i < NUM_DIRECTIONS; i++)
9795 for (j = 0; j < 3; j++)
9797 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9799 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9801 Tile[xx][yy] = EL_EMPTY;
9802 TEST_DrawLevelField(xx, yy);
9811 static void InitBuggyBase(int x, int y)
9813 int element = Tile[x][y];
9814 int activating_delay = FRAMES_PER_SECOND / 4;
9817 (element == EL_SP_BUGGY_BASE ?
9818 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9819 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9821 element == EL_SP_BUGGY_BASE_ACTIVE ?
9822 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9825 static void WarnBuggyBase(int x, int y)
9828 static int xy[4][2] =
9836 for (i = 0; i < NUM_DIRECTIONS; i++)
9838 int xx = x + xy[i][0];
9839 int yy = y + xy[i][1];
9841 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9843 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9850 static void InitTrap(int x, int y)
9852 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9855 static void ActivateTrap(int x, int y)
9857 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9860 static void ChangeActiveTrap(int x, int y)
9862 int graphic = IMG_TRAP_ACTIVE;
9864 // if new animation frame was drawn, correct crumbled sand border
9865 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9866 TEST_DrawLevelFieldCrumbled(x, y);
9869 static int getSpecialActionElement(int element, int number, int base_element)
9871 return (element != EL_EMPTY ? element :
9872 number != -1 ? base_element + number - 1 :
9876 static int getModifiedActionNumber(int value_old, int operator, int operand,
9877 int value_min, int value_max)
9879 int value_new = (operator == CA_MODE_SET ? operand :
9880 operator == CA_MODE_ADD ? value_old + operand :
9881 operator == CA_MODE_SUBTRACT ? value_old - operand :
9882 operator == CA_MODE_MULTIPLY ? value_old * operand :
9883 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9884 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9887 return (value_new < value_min ? value_min :
9888 value_new > value_max ? value_max :
9892 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9894 struct ElementInfo *ei = &element_info[element];
9895 struct ElementChangeInfo *change = &ei->change_page[page];
9896 int target_element = change->target_element;
9897 int action_type = change->action_type;
9898 int action_mode = change->action_mode;
9899 int action_arg = change->action_arg;
9900 int action_element = change->action_element;
9903 if (!change->has_action)
9906 // ---------- determine action paramater values -----------------------------
9908 int level_time_value =
9909 (level.time > 0 ? TimeLeft :
9912 int action_arg_element_raw =
9913 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9914 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9915 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9916 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9917 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9918 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9919 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9921 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
9923 int action_arg_direction =
9924 (action_arg >= CA_ARG_DIRECTION_LEFT &&
9925 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
9926 action_arg == CA_ARG_DIRECTION_TRIGGER ?
9927 change->actual_trigger_side :
9928 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
9929 MV_DIR_OPPOSITE(change->actual_trigger_side) :
9932 int action_arg_number_min =
9933 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
9936 int action_arg_number_max =
9937 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
9938 action_type == CA_SET_LEVEL_GEMS ? 999 :
9939 action_type == CA_SET_LEVEL_TIME ? 9999 :
9940 action_type == CA_SET_LEVEL_SCORE ? 99999 :
9941 action_type == CA_SET_CE_VALUE ? 9999 :
9942 action_type == CA_SET_CE_SCORE ? 9999 :
9945 int action_arg_number_reset =
9946 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
9947 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
9948 action_type == CA_SET_LEVEL_TIME ? level.time :
9949 action_type == CA_SET_LEVEL_SCORE ? 0 :
9950 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
9951 action_type == CA_SET_CE_SCORE ? 0 :
9954 int action_arg_number =
9955 (action_arg <= CA_ARG_MAX ? action_arg :
9956 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
9957 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
9958 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
9959 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
9960 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
9961 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
9962 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
9963 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
9964 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
9965 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
9966 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
9967 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
9968 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
9969 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
9970 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
9971 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
9972 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
9973 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
9974 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
9975 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
9976 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
9979 int action_arg_number_old =
9980 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
9981 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
9982 action_type == CA_SET_LEVEL_SCORE ? game.score :
9983 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
9984 action_type == CA_SET_CE_SCORE ? ei->collect_score :
9987 int action_arg_number_new =
9988 getModifiedActionNumber(action_arg_number_old,
9989 action_mode, action_arg_number,
9990 action_arg_number_min, action_arg_number_max);
9992 int trigger_player_bits =
9993 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
9994 change->actual_trigger_player_bits : change->trigger_player);
9996 int action_arg_player_bits =
9997 (action_arg >= CA_ARG_PLAYER_1 &&
9998 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
9999 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10000 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10003 // ---------- execute action -----------------------------------------------
10005 switch (action_type)
10012 // ---------- level actions ----------------------------------------------
10014 case CA_RESTART_LEVEL:
10016 game.restart_level = TRUE;
10021 case CA_SHOW_ENVELOPE:
10023 int element = getSpecialActionElement(action_arg_element,
10024 action_arg_number, EL_ENVELOPE_1);
10026 if (IS_ENVELOPE(element))
10027 local_player->show_envelope = element;
10032 case CA_SET_LEVEL_TIME:
10034 if (level.time > 0) // only modify limited time value
10036 TimeLeft = action_arg_number_new;
10038 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10040 DisplayGameControlValues();
10042 if (!TimeLeft && setup.time_limit)
10043 for (i = 0; i < MAX_PLAYERS; i++)
10044 KillPlayer(&stored_player[i]);
10050 case CA_SET_LEVEL_SCORE:
10052 game.score = action_arg_number_new;
10054 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10056 DisplayGameControlValues();
10061 case CA_SET_LEVEL_GEMS:
10063 game.gems_still_needed = action_arg_number_new;
10065 game.snapshot.collected_item = TRUE;
10067 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10069 DisplayGameControlValues();
10074 case CA_SET_LEVEL_WIND:
10076 game.wind_direction = action_arg_direction;
10081 case CA_SET_LEVEL_RANDOM_SEED:
10083 // ensure that setting a new random seed while playing is predictable
10084 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10089 // ---------- player actions ---------------------------------------------
10091 case CA_MOVE_PLAYER:
10092 case CA_MOVE_PLAYER_NEW:
10094 // automatically move to the next field in specified direction
10095 for (i = 0; i < MAX_PLAYERS; i++)
10096 if (trigger_player_bits & (1 << i))
10097 if (action_type == CA_MOVE_PLAYER ||
10098 stored_player[i].MovPos == 0)
10099 stored_player[i].programmed_action = action_arg_direction;
10104 case CA_EXIT_PLAYER:
10106 for (i = 0; i < MAX_PLAYERS; i++)
10107 if (action_arg_player_bits & (1 << i))
10108 ExitPlayer(&stored_player[i]);
10110 if (game.players_still_needed == 0)
10116 case CA_KILL_PLAYER:
10118 for (i = 0; i < MAX_PLAYERS; i++)
10119 if (action_arg_player_bits & (1 << i))
10120 KillPlayer(&stored_player[i]);
10125 case CA_SET_PLAYER_KEYS:
10127 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10128 int element = getSpecialActionElement(action_arg_element,
10129 action_arg_number, EL_KEY_1);
10131 if (IS_KEY(element))
10133 for (i = 0; i < MAX_PLAYERS; i++)
10135 if (trigger_player_bits & (1 << i))
10137 stored_player[i].key[KEY_NR(element)] = key_state;
10139 DrawGameDoorValues();
10147 case CA_SET_PLAYER_SPEED:
10149 for (i = 0; i < MAX_PLAYERS; i++)
10151 if (trigger_player_bits & (1 << i))
10153 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10155 if (action_arg == CA_ARG_SPEED_FASTER &&
10156 stored_player[i].cannot_move)
10158 action_arg_number = STEPSIZE_VERY_SLOW;
10160 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10161 action_arg == CA_ARG_SPEED_FASTER)
10163 action_arg_number = 2;
10164 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10167 else if (action_arg == CA_ARG_NUMBER_RESET)
10169 action_arg_number = level.initial_player_stepsize[i];
10173 getModifiedActionNumber(move_stepsize,
10176 action_arg_number_min,
10177 action_arg_number_max);
10179 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10186 case CA_SET_PLAYER_SHIELD:
10188 for (i = 0; i < MAX_PLAYERS; i++)
10190 if (trigger_player_bits & (1 << i))
10192 if (action_arg == CA_ARG_SHIELD_OFF)
10194 stored_player[i].shield_normal_time_left = 0;
10195 stored_player[i].shield_deadly_time_left = 0;
10197 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10199 stored_player[i].shield_normal_time_left = 999999;
10201 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10203 stored_player[i].shield_normal_time_left = 999999;
10204 stored_player[i].shield_deadly_time_left = 999999;
10212 case CA_SET_PLAYER_GRAVITY:
10214 for (i = 0; i < MAX_PLAYERS; i++)
10216 if (trigger_player_bits & (1 << i))
10218 stored_player[i].gravity =
10219 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10220 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10221 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10222 stored_player[i].gravity);
10229 case CA_SET_PLAYER_ARTWORK:
10231 for (i = 0; i < MAX_PLAYERS; i++)
10233 if (trigger_player_bits & (1 << i))
10235 int artwork_element = action_arg_element;
10237 if (action_arg == CA_ARG_ELEMENT_RESET)
10239 (level.use_artwork_element[i] ? level.artwork_element[i] :
10240 stored_player[i].element_nr);
10242 if (stored_player[i].artwork_element != artwork_element)
10243 stored_player[i].Frame = 0;
10245 stored_player[i].artwork_element = artwork_element;
10247 SetPlayerWaiting(&stored_player[i], FALSE);
10249 // set number of special actions for bored and sleeping animation
10250 stored_player[i].num_special_action_bored =
10251 get_num_special_action(artwork_element,
10252 ACTION_BORING_1, ACTION_BORING_LAST);
10253 stored_player[i].num_special_action_sleeping =
10254 get_num_special_action(artwork_element,
10255 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10262 case CA_SET_PLAYER_INVENTORY:
10264 for (i = 0; i < MAX_PLAYERS; i++)
10266 struct PlayerInfo *player = &stored_player[i];
10269 if (trigger_player_bits & (1 << i))
10271 int inventory_element = action_arg_element;
10273 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10274 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10275 action_arg == CA_ARG_ELEMENT_ACTION)
10277 int element = inventory_element;
10278 int collect_count = element_info[element].collect_count_initial;
10280 if (!IS_CUSTOM_ELEMENT(element))
10283 if (collect_count == 0)
10284 player->inventory_infinite_element = element;
10286 for (k = 0; k < collect_count; k++)
10287 if (player->inventory_size < MAX_INVENTORY_SIZE)
10288 player->inventory_element[player->inventory_size++] =
10291 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10292 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10293 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10295 if (player->inventory_infinite_element != EL_UNDEFINED &&
10296 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10297 action_arg_element_raw))
10298 player->inventory_infinite_element = EL_UNDEFINED;
10300 for (k = 0, j = 0; j < player->inventory_size; j++)
10302 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10303 action_arg_element_raw))
10304 player->inventory_element[k++] = player->inventory_element[j];
10307 player->inventory_size = k;
10309 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10311 if (player->inventory_size > 0)
10313 for (j = 0; j < player->inventory_size - 1; j++)
10314 player->inventory_element[j] = player->inventory_element[j + 1];
10316 player->inventory_size--;
10319 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10321 if (player->inventory_size > 0)
10322 player->inventory_size--;
10324 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10326 player->inventory_infinite_element = EL_UNDEFINED;
10327 player->inventory_size = 0;
10329 else if (action_arg == CA_ARG_INVENTORY_RESET)
10331 player->inventory_infinite_element = EL_UNDEFINED;
10332 player->inventory_size = 0;
10334 if (level.use_initial_inventory[i])
10336 for (j = 0; j < level.initial_inventory_size[i]; j++)
10338 int element = level.initial_inventory_content[i][j];
10339 int collect_count = element_info[element].collect_count_initial;
10341 if (!IS_CUSTOM_ELEMENT(element))
10344 if (collect_count == 0)
10345 player->inventory_infinite_element = element;
10347 for (k = 0; k < collect_count; k++)
10348 if (player->inventory_size < MAX_INVENTORY_SIZE)
10349 player->inventory_element[player->inventory_size++] =
10360 // ---------- CE actions -------------------------------------------------
10362 case CA_SET_CE_VALUE:
10364 int last_ce_value = CustomValue[x][y];
10366 CustomValue[x][y] = action_arg_number_new;
10368 if (CustomValue[x][y] != last_ce_value)
10370 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10371 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10373 if (CustomValue[x][y] == 0)
10375 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10376 ChangeCount[x][y] = 0; // allow at least one more change
10378 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10379 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10386 case CA_SET_CE_SCORE:
10388 int last_ce_score = ei->collect_score;
10390 ei->collect_score = action_arg_number_new;
10392 if (ei->collect_score != last_ce_score)
10394 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10395 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10397 if (ei->collect_score == 0)
10401 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10402 ChangeCount[x][y] = 0; // allow at least one more change
10404 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10405 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10408 This is a very special case that seems to be a mixture between
10409 CheckElementChange() and CheckTriggeredElementChange(): while
10410 the first one only affects single elements that are triggered
10411 directly, the second one affects multiple elements in the playfield
10412 that are triggered indirectly by another element. This is a third
10413 case: Changing the CE score always affects multiple identical CEs,
10414 so every affected CE must be checked, not only the single CE for
10415 which the CE score was changed in the first place (as every instance
10416 of that CE shares the same CE score, and therefore also can change)!
10418 SCAN_PLAYFIELD(xx, yy)
10420 if (Tile[xx][yy] == element)
10421 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10422 CE_SCORE_GETS_ZERO);
10430 case CA_SET_CE_ARTWORK:
10432 int artwork_element = action_arg_element;
10433 boolean reset_frame = FALSE;
10436 if (action_arg == CA_ARG_ELEMENT_RESET)
10437 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10440 if (ei->gfx_element != artwork_element)
10441 reset_frame = TRUE;
10443 ei->gfx_element = artwork_element;
10445 SCAN_PLAYFIELD(xx, yy)
10447 if (Tile[xx][yy] == element)
10451 ResetGfxAnimation(xx, yy);
10452 ResetRandomAnimationValue(xx, yy);
10455 TEST_DrawLevelField(xx, yy);
10462 // ---------- engine actions ---------------------------------------------
10464 case CA_SET_ENGINE_SCAN_MODE:
10466 InitPlayfieldScanMode(action_arg);
10476 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10478 int old_element = Tile[x][y];
10479 int new_element = GetElementFromGroupElement(element);
10480 int previous_move_direction = MovDir[x][y];
10481 int last_ce_value = CustomValue[x][y];
10482 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10483 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10484 boolean add_player_onto_element = (new_element_is_player &&
10485 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10486 IS_WALKABLE(old_element));
10488 if (!add_player_onto_element)
10490 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10491 RemoveMovingField(x, y);
10495 Tile[x][y] = new_element;
10497 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10498 MovDir[x][y] = previous_move_direction;
10500 if (element_info[new_element].use_last_ce_value)
10501 CustomValue[x][y] = last_ce_value;
10503 InitField_WithBug1(x, y, FALSE);
10505 new_element = Tile[x][y]; // element may have changed
10507 ResetGfxAnimation(x, y);
10508 ResetRandomAnimationValue(x, y);
10510 TEST_DrawLevelField(x, y);
10512 if (GFX_CRUMBLED(new_element))
10513 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10516 // check if element under the player changes from accessible to unaccessible
10517 // (needed for special case of dropping element which then changes)
10518 // (must be checked after creating new element for walkable group elements)
10519 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10520 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10527 // "ChangeCount" not set yet to allow "entered by player" change one time
10528 if (new_element_is_player)
10529 RelocatePlayer(x, y, new_element);
10532 ChangeCount[x][y]++; // count number of changes in the same frame
10534 TestIfBadThingTouchesPlayer(x, y);
10535 TestIfPlayerTouchesCustomElement(x, y);
10536 TestIfElementTouchesCustomElement(x, y);
10539 static void CreateField(int x, int y, int element)
10541 CreateFieldExt(x, y, element, FALSE);
10544 static void CreateElementFromChange(int x, int y, int element)
10546 element = GET_VALID_RUNTIME_ELEMENT(element);
10548 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10550 int old_element = Tile[x][y];
10552 // prevent changed element from moving in same engine frame
10553 // unless both old and new element can either fall or move
10554 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10555 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10559 CreateFieldExt(x, y, element, TRUE);
10562 static boolean ChangeElement(int x, int y, int element, int page)
10564 struct ElementInfo *ei = &element_info[element];
10565 struct ElementChangeInfo *change = &ei->change_page[page];
10566 int ce_value = CustomValue[x][y];
10567 int ce_score = ei->collect_score;
10568 int target_element;
10569 int old_element = Tile[x][y];
10571 // always use default change event to prevent running into a loop
10572 if (ChangeEvent[x][y] == -1)
10573 ChangeEvent[x][y] = CE_DELAY;
10575 if (ChangeEvent[x][y] == CE_DELAY)
10577 // reset actual trigger element, trigger player and action element
10578 change->actual_trigger_element = EL_EMPTY;
10579 change->actual_trigger_player = EL_EMPTY;
10580 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10581 change->actual_trigger_side = CH_SIDE_NONE;
10582 change->actual_trigger_ce_value = 0;
10583 change->actual_trigger_ce_score = 0;
10586 // do not change elements more than a specified maximum number of changes
10587 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10590 ChangeCount[x][y]++; // count number of changes in the same frame
10592 if (change->explode)
10599 if (change->use_target_content)
10601 boolean complete_replace = TRUE;
10602 boolean can_replace[3][3];
10605 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10608 boolean is_walkable;
10609 boolean is_diggable;
10610 boolean is_collectible;
10611 boolean is_removable;
10612 boolean is_destructible;
10613 int ex = x + xx - 1;
10614 int ey = y + yy - 1;
10615 int content_element = change->target_content.e[xx][yy];
10618 can_replace[xx][yy] = TRUE;
10620 if (ex == x && ey == y) // do not check changing element itself
10623 if (content_element == EL_EMPTY_SPACE)
10625 can_replace[xx][yy] = FALSE; // do not replace border with space
10630 if (!IN_LEV_FIELD(ex, ey))
10632 can_replace[xx][yy] = FALSE;
10633 complete_replace = FALSE;
10640 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10641 e = MovingOrBlocked2Element(ex, ey);
10643 is_empty = (IS_FREE(ex, ey) ||
10644 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10646 is_walkable = (is_empty || IS_WALKABLE(e));
10647 is_diggable = (is_empty || IS_DIGGABLE(e));
10648 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10649 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10650 is_removable = (is_diggable || is_collectible);
10652 can_replace[xx][yy] =
10653 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10654 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10655 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10656 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10657 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10658 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10659 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10661 if (!can_replace[xx][yy])
10662 complete_replace = FALSE;
10665 if (!change->only_if_complete || complete_replace)
10667 boolean something_has_changed = FALSE;
10669 if (change->only_if_complete && change->use_random_replace &&
10670 RND(100) < change->random_percentage)
10673 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10675 int ex = x + xx - 1;
10676 int ey = y + yy - 1;
10677 int content_element;
10679 if (can_replace[xx][yy] && (!change->use_random_replace ||
10680 RND(100) < change->random_percentage))
10682 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10683 RemoveMovingField(ex, ey);
10685 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10687 content_element = change->target_content.e[xx][yy];
10688 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10689 ce_value, ce_score);
10691 CreateElementFromChange(ex, ey, target_element);
10693 something_has_changed = TRUE;
10695 // for symmetry reasons, freeze newly created border elements
10696 if (ex != x || ey != y)
10697 Stop[ex][ey] = TRUE; // no more moving in this frame
10701 if (something_has_changed)
10703 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10704 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10710 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10711 ce_value, ce_score);
10713 if (element == EL_DIAGONAL_GROWING ||
10714 element == EL_DIAGONAL_SHRINKING)
10716 target_element = Store[x][y];
10718 Store[x][y] = EL_EMPTY;
10721 CreateElementFromChange(x, y, target_element);
10723 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10724 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10727 // this uses direct change before indirect change
10728 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10733 static void HandleElementChange(int x, int y, int page)
10735 int element = MovingOrBlocked2Element(x, y);
10736 struct ElementInfo *ei = &element_info[element];
10737 struct ElementChangeInfo *change = &ei->change_page[page];
10738 boolean handle_action_before_change = FALSE;
10741 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10742 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10744 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10745 x, y, element, element_info[element].token_name);
10746 Debug("game:playing:HandleElementChange", "This should never happen!");
10750 // this can happen with classic bombs on walkable, changing elements
10751 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10756 if (ChangeDelay[x][y] == 0) // initialize element change
10758 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10760 if (change->can_change)
10762 // !!! not clear why graphic animation should be reset at all here !!!
10763 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10764 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10767 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10769 When using an animation frame delay of 1 (this only happens with
10770 "sp_zonk.moving.left/right" in the classic graphics), the default
10771 (non-moving) animation shows wrong animation frames (while the
10772 moving animation, like "sp_zonk.moving.left/right", is correct,
10773 so this graphical bug never shows up with the classic graphics).
10774 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10775 be drawn instead of the correct frames 0,1,2,3. This is caused by
10776 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10777 an element change: First when the change delay ("ChangeDelay[][]")
10778 counter has reached zero after decrementing, then a second time in
10779 the next frame (after "GfxFrame[][]" was already incremented) when
10780 "ChangeDelay[][]" is reset to the initial delay value again.
10782 This causes frame 0 to be drawn twice, while the last frame won't
10783 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10785 As some animations may already be cleverly designed around this bug
10786 (at least the "Snake Bite" snake tail animation does this), it cannot
10787 simply be fixed here without breaking such existing animations.
10788 Unfortunately, it cannot easily be detected if a graphics set was
10789 designed "before" or "after" the bug was fixed. As a workaround,
10790 a new graphics set option "game.graphics_engine_version" was added
10791 to be able to specify the game's major release version for which the
10792 graphics set was designed, which can then be used to decide if the
10793 bugfix should be used (version 4 and above) or not (version 3 or
10794 below, or if no version was specified at all, as with old sets).
10796 (The wrong/fixed animation frames can be tested with the test level set
10797 "test_gfxframe" and level "000", which contains a specially prepared
10798 custom element at level position (x/y) == (11/9) which uses the zonk
10799 animation mentioned above. Using "game.graphics_engine_version: 4"
10800 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10801 This can also be seen from the debug output for this test element.)
10804 // when a custom element is about to change (for example by change delay),
10805 // do not reset graphic animation when the custom element is moving
10806 if (game.graphics_engine_version < 4 &&
10809 ResetGfxAnimation(x, y);
10810 ResetRandomAnimationValue(x, y);
10813 if (change->pre_change_function)
10814 change->pre_change_function(x, y);
10818 ChangeDelay[x][y]--;
10820 if (ChangeDelay[x][y] != 0) // continue element change
10822 if (change->can_change)
10824 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10826 if (IS_ANIMATED(graphic))
10827 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10829 if (change->change_function)
10830 change->change_function(x, y);
10833 else // finish element change
10835 if (ChangePage[x][y] != -1) // remember page from delayed change
10837 page = ChangePage[x][y];
10838 ChangePage[x][y] = -1;
10840 change = &ei->change_page[page];
10843 if (IS_MOVING(x, y)) // never change a running system ;-)
10845 ChangeDelay[x][y] = 1; // try change after next move step
10846 ChangePage[x][y] = page; // remember page to use for change
10851 // special case: set new level random seed before changing element
10852 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10853 handle_action_before_change = TRUE;
10855 if (change->has_action && handle_action_before_change)
10856 ExecuteCustomElementAction(x, y, element, page);
10858 if (change->can_change)
10860 if (ChangeElement(x, y, element, page))
10862 if (change->post_change_function)
10863 change->post_change_function(x, y);
10867 if (change->has_action && !handle_action_before_change)
10868 ExecuteCustomElementAction(x, y, element, page);
10872 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10873 int trigger_element,
10875 int trigger_player,
10879 boolean change_done_any = FALSE;
10880 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10883 if (!(trigger_events[trigger_element][trigger_event]))
10886 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10888 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10890 int element = EL_CUSTOM_START + i;
10891 boolean change_done = FALSE;
10894 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10895 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10898 for (p = 0; p < element_info[element].num_change_pages; p++)
10900 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10902 if (change->can_change_or_has_action &&
10903 change->has_event[trigger_event] &&
10904 change->trigger_side & trigger_side &&
10905 change->trigger_player & trigger_player &&
10906 change->trigger_page & trigger_page_bits &&
10907 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10909 change->actual_trigger_element = trigger_element;
10910 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10911 change->actual_trigger_player_bits = trigger_player;
10912 change->actual_trigger_side = trigger_side;
10913 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10914 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10916 if ((change->can_change && !change_done) || change->has_action)
10920 SCAN_PLAYFIELD(x, y)
10922 if (Tile[x][y] == element)
10924 if (change->can_change && !change_done)
10926 // if element already changed in this frame, not only prevent
10927 // another element change (checked in ChangeElement()), but
10928 // also prevent additional element actions for this element
10930 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10931 !level.use_action_after_change_bug)
10934 ChangeDelay[x][y] = 1;
10935 ChangeEvent[x][y] = trigger_event;
10937 HandleElementChange(x, y, p);
10939 else if (change->has_action)
10941 // if element already changed in this frame, not only prevent
10942 // another element change (checked in ChangeElement()), but
10943 // also prevent additional element actions for this element
10945 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
10946 !level.use_action_after_change_bug)
10949 ExecuteCustomElementAction(x, y, element, p);
10950 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
10955 if (change->can_change)
10957 change_done = TRUE;
10958 change_done_any = TRUE;
10965 RECURSION_LOOP_DETECTION_END();
10967 return change_done_any;
10970 static boolean CheckElementChangeExt(int x, int y,
10972 int trigger_element,
10974 int trigger_player,
10977 boolean change_done = FALSE;
10980 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10981 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10984 if (Tile[x][y] == EL_BLOCKED)
10986 Blocked2Moving(x, y, &x, &y);
10987 element = Tile[x][y];
10990 // check if element has already changed or is about to change after moving
10991 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
10992 Tile[x][y] != element) ||
10994 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
10995 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
10996 ChangePage[x][y] != -1)))
10999 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11001 for (p = 0; p < element_info[element].num_change_pages; p++)
11003 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11005 /* check trigger element for all events where the element that is checked
11006 for changing interacts with a directly adjacent element -- this is
11007 different to element changes that affect other elements to change on the
11008 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11009 boolean check_trigger_element =
11010 (trigger_event == CE_TOUCHING_X ||
11011 trigger_event == CE_HITTING_X ||
11012 trigger_event == CE_HIT_BY_X ||
11013 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11015 if (change->can_change_or_has_action &&
11016 change->has_event[trigger_event] &&
11017 change->trigger_side & trigger_side &&
11018 change->trigger_player & trigger_player &&
11019 (!check_trigger_element ||
11020 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11022 change->actual_trigger_element = trigger_element;
11023 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11024 change->actual_trigger_player_bits = trigger_player;
11025 change->actual_trigger_side = trigger_side;
11026 change->actual_trigger_ce_value = CustomValue[x][y];
11027 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11029 // special case: trigger element not at (x,y) position for some events
11030 if (check_trigger_element)
11042 { 0, 0 }, { 0, 0 }, { 0, 0 },
11046 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11047 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11049 change->actual_trigger_ce_value = CustomValue[xx][yy];
11050 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11053 if (change->can_change && !change_done)
11055 ChangeDelay[x][y] = 1;
11056 ChangeEvent[x][y] = trigger_event;
11058 HandleElementChange(x, y, p);
11060 change_done = TRUE;
11062 else if (change->has_action)
11064 ExecuteCustomElementAction(x, y, element, p);
11065 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11070 RECURSION_LOOP_DETECTION_END();
11072 return change_done;
11075 static void PlayPlayerSound(struct PlayerInfo *player)
11077 int jx = player->jx, jy = player->jy;
11078 int sound_element = player->artwork_element;
11079 int last_action = player->last_action_waiting;
11080 int action = player->action_waiting;
11082 if (player->is_waiting)
11084 if (action != last_action)
11085 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11087 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11091 if (action != last_action)
11092 StopSound(element_info[sound_element].sound[last_action]);
11094 if (last_action == ACTION_SLEEPING)
11095 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11099 static void PlayAllPlayersSound(void)
11103 for (i = 0; i < MAX_PLAYERS; i++)
11104 if (stored_player[i].active)
11105 PlayPlayerSound(&stored_player[i]);
11108 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11110 boolean last_waiting = player->is_waiting;
11111 int move_dir = player->MovDir;
11113 player->dir_waiting = move_dir;
11114 player->last_action_waiting = player->action_waiting;
11118 if (!last_waiting) // not waiting -> waiting
11120 player->is_waiting = TRUE;
11122 player->frame_counter_bored =
11124 game.player_boring_delay_fixed +
11125 GetSimpleRandom(game.player_boring_delay_random);
11126 player->frame_counter_sleeping =
11128 game.player_sleeping_delay_fixed +
11129 GetSimpleRandom(game.player_sleeping_delay_random);
11131 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11134 if (game.player_sleeping_delay_fixed +
11135 game.player_sleeping_delay_random > 0 &&
11136 player->anim_delay_counter == 0 &&
11137 player->post_delay_counter == 0 &&
11138 FrameCounter >= player->frame_counter_sleeping)
11139 player->is_sleeping = TRUE;
11140 else if (game.player_boring_delay_fixed +
11141 game.player_boring_delay_random > 0 &&
11142 FrameCounter >= player->frame_counter_bored)
11143 player->is_bored = TRUE;
11145 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11146 player->is_bored ? ACTION_BORING :
11149 if (player->is_sleeping && player->use_murphy)
11151 // special case for sleeping Murphy when leaning against non-free tile
11153 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11154 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11155 !IS_MOVING(player->jx - 1, player->jy)))
11156 move_dir = MV_LEFT;
11157 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11158 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11159 !IS_MOVING(player->jx + 1, player->jy)))
11160 move_dir = MV_RIGHT;
11162 player->is_sleeping = FALSE;
11164 player->dir_waiting = move_dir;
11167 if (player->is_sleeping)
11169 if (player->num_special_action_sleeping > 0)
11171 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11173 int last_special_action = player->special_action_sleeping;
11174 int num_special_action = player->num_special_action_sleeping;
11175 int special_action =
11176 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11177 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11178 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11179 last_special_action + 1 : ACTION_SLEEPING);
11180 int special_graphic =
11181 el_act_dir2img(player->artwork_element, special_action, move_dir);
11183 player->anim_delay_counter =
11184 graphic_info[special_graphic].anim_delay_fixed +
11185 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11186 player->post_delay_counter =
11187 graphic_info[special_graphic].post_delay_fixed +
11188 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11190 player->special_action_sleeping = special_action;
11193 if (player->anim_delay_counter > 0)
11195 player->action_waiting = player->special_action_sleeping;
11196 player->anim_delay_counter--;
11198 else if (player->post_delay_counter > 0)
11200 player->post_delay_counter--;
11204 else if (player->is_bored)
11206 if (player->num_special_action_bored > 0)
11208 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11210 int special_action =
11211 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11212 int special_graphic =
11213 el_act_dir2img(player->artwork_element, special_action, move_dir);
11215 player->anim_delay_counter =
11216 graphic_info[special_graphic].anim_delay_fixed +
11217 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11218 player->post_delay_counter =
11219 graphic_info[special_graphic].post_delay_fixed +
11220 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11222 player->special_action_bored = special_action;
11225 if (player->anim_delay_counter > 0)
11227 player->action_waiting = player->special_action_bored;
11228 player->anim_delay_counter--;
11230 else if (player->post_delay_counter > 0)
11232 player->post_delay_counter--;
11237 else if (last_waiting) // waiting -> not waiting
11239 player->is_waiting = FALSE;
11240 player->is_bored = FALSE;
11241 player->is_sleeping = FALSE;
11243 player->frame_counter_bored = -1;
11244 player->frame_counter_sleeping = -1;
11246 player->anim_delay_counter = 0;
11247 player->post_delay_counter = 0;
11249 player->dir_waiting = player->MovDir;
11250 player->action_waiting = ACTION_DEFAULT;
11252 player->special_action_bored = ACTION_DEFAULT;
11253 player->special_action_sleeping = ACTION_DEFAULT;
11257 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11259 if ((!player->is_moving && player->was_moving) ||
11260 (player->MovPos == 0 && player->was_moving) ||
11261 (player->is_snapping && !player->was_snapping) ||
11262 (player->is_dropping && !player->was_dropping))
11264 if (!CheckSaveEngineSnapshotToList())
11267 player->was_moving = FALSE;
11268 player->was_snapping = TRUE;
11269 player->was_dropping = TRUE;
11273 if (player->is_moving)
11274 player->was_moving = TRUE;
11276 if (!player->is_snapping)
11277 player->was_snapping = FALSE;
11279 if (!player->is_dropping)
11280 player->was_dropping = FALSE;
11283 static struct MouseActionInfo mouse_action_last = { 0 };
11284 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11285 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11288 CheckSaveEngineSnapshotToList();
11290 mouse_action_last = mouse_action;
11293 static void CheckSingleStepMode(struct PlayerInfo *player)
11295 if (tape.single_step && tape.recording && !tape.pausing)
11297 // as it is called "single step mode", just return to pause mode when the
11298 // player stopped moving after one tile (or never starts moving at all)
11299 // (reverse logic needed here in case single step mode used in team mode)
11300 if (player->is_moving ||
11301 player->is_pushing ||
11302 player->is_dropping_pressed ||
11303 player->effective_mouse_action.button)
11304 game.enter_single_step_mode = FALSE;
11307 CheckSaveEngineSnapshot(player);
11310 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11312 int left = player_action & JOY_LEFT;
11313 int right = player_action & JOY_RIGHT;
11314 int up = player_action & JOY_UP;
11315 int down = player_action & JOY_DOWN;
11316 int button1 = player_action & JOY_BUTTON_1;
11317 int button2 = player_action & JOY_BUTTON_2;
11318 int dx = (left ? -1 : right ? 1 : 0);
11319 int dy = (up ? -1 : down ? 1 : 0);
11321 if (!player->active || tape.pausing)
11327 SnapField(player, dx, dy);
11331 DropElement(player);
11333 MovePlayer(player, dx, dy);
11336 CheckSingleStepMode(player);
11338 SetPlayerWaiting(player, FALSE);
11340 return player_action;
11344 // no actions for this player (no input at player's configured device)
11346 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11347 SnapField(player, 0, 0);
11348 CheckGravityMovementWhenNotMoving(player);
11350 if (player->MovPos == 0)
11351 SetPlayerWaiting(player, TRUE);
11353 if (player->MovPos == 0) // needed for tape.playing
11354 player->is_moving = FALSE;
11356 player->is_dropping = FALSE;
11357 player->is_dropping_pressed = FALSE;
11358 player->drop_pressed_delay = 0;
11360 CheckSingleStepMode(player);
11366 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11369 if (!tape.use_mouse_actions)
11372 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11373 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11374 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11377 static void SetTapeActionFromMouseAction(byte *tape_action,
11378 struct MouseActionInfo *mouse_action)
11380 if (!tape.use_mouse_actions)
11383 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11384 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11385 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11388 static void CheckLevelSolved(void)
11390 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11392 if (game_em.level_solved &&
11393 !game_em.game_over) // game won
11397 game_em.game_over = TRUE;
11399 game.all_players_gone = TRUE;
11402 if (game_em.game_over) // game lost
11403 game.all_players_gone = TRUE;
11405 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11407 if (game_sp.level_solved &&
11408 !game_sp.game_over) // game won
11412 game_sp.game_over = TRUE;
11414 game.all_players_gone = TRUE;
11417 if (game_sp.game_over) // game lost
11418 game.all_players_gone = TRUE;
11420 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11422 if (game_mm.level_solved &&
11423 !game_mm.game_over) // game won
11427 game_mm.game_over = TRUE;
11429 game.all_players_gone = TRUE;
11432 if (game_mm.game_over) // game lost
11433 game.all_players_gone = TRUE;
11437 static void CheckLevelTime(void)
11441 if (TimeFrames >= FRAMES_PER_SECOND)
11446 for (i = 0; i < MAX_PLAYERS; i++)
11448 struct PlayerInfo *player = &stored_player[i];
11450 if (SHIELD_ON(player))
11452 player->shield_normal_time_left--;
11454 if (player->shield_deadly_time_left > 0)
11455 player->shield_deadly_time_left--;
11459 if (!game.LevelSolved && !level.use_step_counter)
11467 if (TimeLeft <= 10 && setup.time_limit)
11468 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11470 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11471 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11473 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11475 if (!TimeLeft && setup.time_limit)
11477 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11478 game_em.lev->killed_out_of_time = TRUE;
11480 for (i = 0; i < MAX_PLAYERS; i++)
11481 KillPlayer(&stored_player[i]);
11484 else if (game.no_time_limit && !game.all_players_gone)
11486 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11489 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11492 if (tape.recording || tape.playing)
11493 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11496 if (tape.recording || tape.playing)
11497 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11499 UpdateAndDisplayGameControlValues();
11502 void AdvanceFrameAndPlayerCounters(int player_nr)
11506 // advance frame counters (global frame counter and time frame counter)
11510 // advance player counters (counters for move delay, move animation etc.)
11511 for (i = 0; i < MAX_PLAYERS; i++)
11513 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11514 int move_delay_value = stored_player[i].move_delay_value;
11515 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11517 if (!advance_player_counters) // not all players may be affected
11520 if (move_frames == 0) // less than one move per game frame
11522 int stepsize = TILEX / move_delay_value;
11523 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11524 int count = (stored_player[i].is_moving ?
11525 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11527 if (count % delay == 0)
11531 stored_player[i].Frame += move_frames;
11533 if (stored_player[i].MovPos != 0)
11534 stored_player[i].StepFrame += move_frames;
11536 if (stored_player[i].move_delay > 0)
11537 stored_player[i].move_delay--;
11539 // due to bugs in previous versions, counter must count up, not down
11540 if (stored_player[i].push_delay != -1)
11541 stored_player[i].push_delay++;
11543 if (stored_player[i].drop_delay > 0)
11544 stored_player[i].drop_delay--;
11546 if (stored_player[i].is_dropping_pressed)
11547 stored_player[i].drop_pressed_delay++;
11551 void StartGameActions(boolean init_network_game, boolean record_tape,
11554 unsigned int new_random_seed = InitRND(random_seed);
11557 TapeStartRecording(new_random_seed);
11559 if (init_network_game)
11561 SendToServer_LevelFile();
11562 SendToServer_StartPlaying();
11570 static void GameActionsExt(void)
11573 static unsigned int game_frame_delay = 0;
11575 unsigned int game_frame_delay_value;
11576 byte *recorded_player_action;
11577 byte summarized_player_action = 0;
11578 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11581 // detect endless loops, caused by custom element programming
11582 if (recursion_loop_detected && recursion_loop_depth == 0)
11584 char *message = getStringCat3("Internal Error! Element ",
11585 EL_NAME(recursion_loop_element),
11586 " caused endless loop! Quit the game?");
11588 Warn("element '%s' caused endless loop in game engine",
11589 EL_NAME(recursion_loop_element));
11591 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11593 recursion_loop_detected = FALSE; // if game should be continued
11600 if (game.restart_level)
11601 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11603 CheckLevelSolved();
11605 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11608 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11611 if (game_status != GAME_MODE_PLAYING) // status might have changed
11614 game_frame_delay_value =
11615 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11617 if (tape.playing && tape.warp_forward && !tape.pausing)
11618 game_frame_delay_value = 0;
11620 SetVideoFrameDelay(game_frame_delay_value);
11622 // (de)activate virtual buttons depending on current game status
11623 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11625 if (game.all_players_gone) // if no players there to be controlled anymore
11626 SetOverlayActive(FALSE);
11627 else if (!tape.playing) // if game continues after tape stopped playing
11628 SetOverlayActive(TRUE);
11633 // ---------- main game synchronization point ----------
11635 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11637 Debug("game:playing:skip", "skip == %d", skip);
11640 // ---------- main game synchronization point ----------
11642 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11646 if (network_playing && !network_player_action_received)
11648 // try to get network player actions in time
11650 // last chance to get network player actions without main loop delay
11651 HandleNetworking();
11653 // game was quit by network peer
11654 if (game_status != GAME_MODE_PLAYING)
11657 // check if network player actions still missing and game still running
11658 if (!network_player_action_received && !checkGameEnded())
11659 return; // failed to get network player actions in time
11661 // do not yet reset "network_player_action_received" (for tape.pausing)
11667 // at this point we know that we really continue executing the game
11669 network_player_action_received = FALSE;
11671 // when playing tape, read previously recorded player input from tape data
11672 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11674 local_player->effective_mouse_action = local_player->mouse_action;
11676 if (recorded_player_action != NULL)
11677 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11678 recorded_player_action);
11680 // TapePlayAction() may return NULL when toggling to "pause before death"
11684 if (tape.set_centered_player)
11686 game.centered_player_nr_next = tape.centered_player_nr_next;
11687 game.set_centered_player = TRUE;
11690 for (i = 0; i < MAX_PLAYERS; i++)
11692 summarized_player_action |= stored_player[i].action;
11694 if (!network_playing && (game.team_mode || tape.playing))
11695 stored_player[i].effective_action = stored_player[i].action;
11698 if (network_playing && !checkGameEnded())
11699 SendToServer_MovePlayer(summarized_player_action);
11701 // summarize all actions at local players mapped input device position
11702 // (this allows using different input devices in single player mode)
11703 if (!network.enabled && !game.team_mode)
11704 stored_player[map_player_action[local_player->index_nr]].effective_action =
11705 summarized_player_action;
11707 // summarize all actions at centered player in local team mode
11708 if (tape.recording &&
11709 setup.team_mode && !network.enabled &&
11710 setup.input_on_focus &&
11711 game.centered_player_nr != -1)
11713 for (i = 0; i < MAX_PLAYERS; i++)
11714 stored_player[map_player_action[i]].effective_action =
11715 (i == game.centered_player_nr ? summarized_player_action : 0);
11718 if (recorded_player_action != NULL)
11719 for (i = 0; i < MAX_PLAYERS; i++)
11720 stored_player[i].effective_action = recorded_player_action[i];
11722 for (i = 0; i < MAX_PLAYERS; i++)
11724 tape_action[i] = stored_player[i].effective_action;
11726 /* (this may happen in the RND game engine if a player was not present on
11727 the playfield on level start, but appeared later from a custom element */
11728 if (setup.team_mode &&
11731 !tape.player_participates[i])
11732 tape.player_participates[i] = TRUE;
11735 SetTapeActionFromMouseAction(tape_action,
11736 &local_player->effective_mouse_action);
11738 // only record actions from input devices, but not programmed actions
11739 if (tape.recording)
11740 TapeRecordAction(tape_action);
11742 // remember if game was played (especially after tape stopped playing)
11743 if (!tape.playing && summarized_player_action)
11744 game.GamePlayed = TRUE;
11746 #if USE_NEW_PLAYER_ASSIGNMENTS
11747 // !!! also map player actions in single player mode !!!
11748 // if (game.team_mode)
11751 byte mapped_action[MAX_PLAYERS];
11753 #if DEBUG_PLAYER_ACTIONS
11754 for (i = 0; i < MAX_PLAYERS; i++)
11755 DebugContinued("", "%d, ", stored_player[i].effective_action);
11758 for (i = 0; i < MAX_PLAYERS; i++)
11759 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11761 for (i = 0; i < MAX_PLAYERS; i++)
11762 stored_player[i].effective_action = mapped_action[i];
11764 #if DEBUG_PLAYER_ACTIONS
11765 DebugContinued("", "=> ");
11766 for (i = 0; i < MAX_PLAYERS; i++)
11767 DebugContinued("", "%d, ", stored_player[i].effective_action);
11768 DebugContinued("game:playing:player", "\n");
11771 #if DEBUG_PLAYER_ACTIONS
11774 for (i = 0; i < MAX_PLAYERS; i++)
11775 DebugContinued("", "%d, ", stored_player[i].effective_action);
11776 DebugContinued("game:playing:player", "\n");
11781 for (i = 0; i < MAX_PLAYERS; i++)
11783 // allow engine snapshot in case of changed movement attempt
11784 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11785 (stored_player[i].effective_action & KEY_MOTION))
11786 game.snapshot.changed_action = TRUE;
11788 // allow engine snapshot in case of snapping/dropping attempt
11789 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11790 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11791 game.snapshot.changed_action = TRUE;
11793 game.snapshot.last_action[i] = stored_player[i].effective_action;
11796 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11798 GameActions_EM_Main();
11800 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11802 GameActions_SP_Main();
11804 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11806 GameActions_MM_Main();
11810 GameActions_RND_Main();
11813 BlitScreenToBitmap(backbuffer);
11815 CheckLevelSolved();
11818 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11820 if (global.show_frames_per_second)
11822 static unsigned int fps_counter = 0;
11823 static int fps_frames = 0;
11824 unsigned int fps_delay_ms = Counter() - fps_counter;
11828 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11830 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11833 fps_counter = Counter();
11835 // always draw FPS to screen after FPS value was updated
11836 redraw_mask |= REDRAW_FPS;
11839 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11840 if (GetDrawDeactivationMask() == REDRAW_NONE)
11841 redraw_mask |= REDRAW_FPS;
11845 static void GameActions_CheckSaveEngineSnapshot(void)
11847 if (!game.snapshot.save_snapshot)
11850 // clear flag for saving snapshot _before_ saving snapshot
11851 game.snapshot.save_snapshot = FALSE;
11853 SaveEngineSnapshotToList();
11856 void GameActions(void)
11860 GameActions_CheckSaveEngineSnapshot();
11863 void GameActions_EM_Main(void)
11865 byte effective_action[MAX_PLAYERS];
11866 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11869 for (i = 0; i < MAX_PLAYERS; i++)
11870 effective_action[i] = stored_player[i].effective_action;
11872 GameActions_EM(effective_action, warp_mode);
11875 void GameActions_SP_Main(void)
11877 byte effective_action[MAX_PLAYERS];
11878 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11881 for (i = 0; i < MAX_PLAYERS; i++)
11882 effective_action[i] = stored_player[i].effective_action;
11884 GameActions_SP(effective_action, warp_mode);
11886 for (i = 0; i < MAX_PLAYERS; i++)
11888 if (stored_player[i].force_dropping)
11889 stored_player[i].action |= KEY_BUTTON_DROP;
11891 stored_player[i].force_dropping = FALSE;
11895 void GameActions_MM_Main(void)
11897 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11899 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11902 void GameActions_RND_Main(void)
11907 void GameActions_RND(void)
11909 static struct MouseActionInfo mouse_action_last = { 0 };
11910 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11911 int magic_wall_x = 0, magic_wall_y = 0;
11912 int i, x, y, element, graphic, last_gfx_frame;
11914 InitPlayfieldScanModeVars();
11916 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11918 SCAN_PLAYFIELD(x, y)
11920 ChangeCount[x][y] = 0;
11921 ChangeEvent[x][y] = -1;
11925 if (game.set_centered_player)
11927 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
11929 // switching to "all players" only possible if all players fit to screen
11930 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
11932 game.centered_player_nr_next = game.centered_player_nr;
11933 game.set_centered_player = FALSE;
11936 // do not switch focus to non-existing (or non-active) player
11937 if (game.centered_player_nr_next >= 0 &&
11938 !stored_player[game.centered_player_nr_next].active)
11940 game.centered_player_nr_next = game.centered_player_nr;
11941 game.set_centered_player = FALSE;
11945 if (game.set_centered_player &&
11946 ScreenMovPos == 0) // screen currently aligned at tile position
11950 if (game.centered_player_nr_next == -1)
11952 setScreenCenteredToAllPlayers(&sx, &sy);
11956 sx = stored_player[game.centered_player_nr_next].jx;
11957 sy = stored_player[game.centered_player_nr_next].jy;
11960 game.centered_player_nr = game.centered_player_nr_next;
11961 game.set_centered_player = FALSE;
11963 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
11964 DrawGameDoorValues();
11967 // check single step mode (set flag and clear again if any player is active)
11968 game.enter_single_step_mode =
11969 (tape.single_step && tape.recording && !tape.pausing);
11971 for (i = 0; i < MAX_PLAYERS; i++)
11973 int actual_player_action = stored_player[i].effective_action;
11976 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
11977 - rnd_equinox_tetrachloride 048
11978 - rnd_equinox_tetrachloride_ii 096
11979 - rnd_emanuel_schmieg 002
11980 - doctor_sloan_ww 001, 020
11982 if (stored_player[i].MovPos == 0)
11983 CheckGravityMovement(&stored_player[i]);
11986 // overwrite programmed action with tape action
11987 if (stored_player[i].programmed_action)
11988 actual_player_action = stored_player[i].programmed_action;
11990 PlayerActions(&stored_player[i], actual_player_action);
11992 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
11995 // single step pause mode may already have been toggled by "ScrollPlayer()"
11996 if (game.enter_single_step_mode && !tape.pausing)
11997 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
11999 ScrollScreen(NULL, SCROLL_GO_ON);
12001 /* for backwards compatibility, the following code emulates a fixed bug that
12002 occured when pushing elements (causing elements that just made their last
12003 pushing step to already (if possible) make their first falling step in the
12004 same game frame, which is bad); this code is also needed to use the famous
12005 "spring push bug" which is used in older levels and might be wanted to be
12006 used also in newer levels, but in this case the buggy pushing code is only
12007 affecting the "spring" element and no other elements */
12009 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12011 for (i = 0; i < MAX_PLAYERS; i++)
12013 struct PlayerInfo *player = &stored_player[i];
12014 int x = player->jx;
12015 int y = player->jy;
12017 if (player->active && player->is_pushing && player->is_moving &&
12019 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12020 Tile[x][y] == EL_SPRING))
12022 ContinueMoving(x, y);
12024 // continue moving after pushing (this is actually a bug)
12025 if (!IS_MOVING(x, y))
12026 Stop[x][y] = FALSE;
12031 SCAN_PLAYFIELD(x, y)
12033 Last[x][y] = Tile[x][y];
12035 ChangeCount[x][y] = 0;
12036 ChangeEvent[x][y] = -1;
12038 // this must be handled before main playfield loop
12039 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12042 if (MovDelay[x][y] <= 0)
12046 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12049 if (MovDelay[x][y] <= 0)
12051 int element = Store[x][y];
12052 int move_direction = MovDir[x][y];
12053 int player_index_bit = Store2[x][y];
12059 TEST_DrawLevelField(x, y);
12061 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12066 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12068 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12070 Debug("game:playing:GameActions_RND", "This should never happen!");
12072 ChangePage[x][y] = -1;
12076 Stop[x][y] = FALSE;
12077 if (WasJustMoving[x][y] > 0)
12078 WasJustMoving[x][y]--;
12079 if (WasJustFalling[x][y] > 0)
12080 WasJustFalling[x][y]--;
12081 if (CheckCollision[x][y] > 0)
12082 CheckCollision[x][y]--;
12083 if (CheckImpact[x][y] > 0)
12084 CheckImpact[x][y]--;
12088 /* reset finished pushing action (not done in ContinueMoving() to allow
12089 continuous pushing animation for elements with zero push delay) */
12090 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12092 ResetGfxAnimation(x, y);
12093 TEST_DrawLevelField(x, y);
12097 if (IS_BLOCKED(x, y))
12101 Blocked2Moving(x, y, &oldx, &oldy);
12102 if (!IS_MOVING(oldx, oldy))
12104 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12105 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12106 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12107 Debug("game:playing:GameActions_RND", "This should never happen!");
12113 if (mouse_action.button)
12115 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12117 x = mouse_action.lx;
12118 y = mouse_action.ly;
12119 element = Tile[x][y];
12123 CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
12124 CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
12127 CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
12128 CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
12131 SCAN_PLAYFIELD(x, y)
12133 element = Tile[x][y];
12134 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12135 last_gfx_frame = GfxFrame[x][y];
12137 ResetGfxFrame(x, y);
12139 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12140 DrawLevelGraphicAnimation(x, y, graphic);
12142 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12143 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12144 ResetRandomAnimationValue(x, y);
12146 SetRandomAnimationValue(x, y);
12148 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12150 if (IS_INACTIVE(element))
12152 if (IS_ANIMATED(graphic))
12153 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12158 // this may take place after moving, so 'element' may have changed
12159 if (IS_CHANGING(x, y) &&
12160 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12162 int page = element_info[element].event_page_nr[CE_DELAY];
12164 HandleElementChange(x, y, page);
12166 element = Tile[x][y];
12167 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12170 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12174 element = Tile[x][y];
12175 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12177 if (IS_ANIMATED(graphic) &&
12178 !IS_MOVING(x, y) &&
12180 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12182 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12183 TEST_DrawTwinkleOnField(x, y);
12185 else if (element == EL_ACID)
12188 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12190 else if ((element == EL_EXIT_OPEN ||
12191 element == EL_EM_EXIT_OPEN ||
12192 element == EL_SP_EXIT_OPEN ||
12193 element == EL_STEEL_EXIT_OPEN ||
12194 element == EL_EM_STEEL_EXIT_OPEN ||
12195 element == EL_SP_TERMINAL ||
12196 element == EL_SP_TERMINAL_ACTIVE ||
12197 element == EL_EXTRA_TIME ||
12198 element == EL_SHIELD_NORMAL ||
12199 element == EL_SHIELD_DEADLY) &&
12200 IS_ANIMATED(graphic))
12201 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12202 else if (IS_MOVING(x, y))
12203 ContinueMoving(x, y);
12204 else if (IS_ACTIVE_BOMB(element))
12205 CheckDynamite(x, y);
12206 else if (element == EL_AMOEBA_GROWING)
12207 AmoebaGrowing(x, y);
12208 else if (element == EL_AMOEBA_SHRINKING)
12209 AmoebaShrinking(x, y);
12211 #if !USE_NEW_AMOEBA_CODE
12212 else if (IS_AMOEBALIVE(element))
12213 AmoebaReproduce(x, y);
12216 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12218 else if (element == EL_EXIT_CLOSED)
12220 else if (element == EL_EM_EXIT_CLOSED)
12222 else if (element == EL_STEEL_EXIT_CLOSED)
12223 CheckExitSteel(x, y);
12224 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12225 CheckExitSteelEM(x, y);
12226 else if (element == EL_SP_EXIT_CLOSED)
12228 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12229 element == EL_EXPANDABLE_STEELWALL_GROWING)
12230 MauerWaechst(x, y);
12231 else if (element == EL_EXPANDABLE_WALL ||
12232 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12233 element == EL_EXPANDABLE_WALL_VERTICAL ||
12234 element == EL_EXPANDABLE_WALL_ANY ||
12235 element == EL_BD_EXPANDABLE_WALL)
12236 MauerAbleger(x, y);
12237 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12238 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12239 element == EL_EXPANDABLE_STEELWALL_ANY)
12240 MauerAblegerStahl(x, y);
12241 else if (element == EL_FLAMES)
12242 CheckForDragon(x, y);
12243 else if (element == EL_EXPLOSION)
12244 ; // drawing of correct explosion animation is handled separately
12245 else if (element == EL_ELEMENT_SNAPPING ||
12246 element == EL_DIAGONAL_SHRINKING ||
12247 element == EL_DIAGONAL_GROWING)
12249 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12251 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12253 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12254 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12256 if (IS_BELT_ACTIVE(element))
12257 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12259 if (game.magic_wall_active)
12261 int jx = local_player->jx, jy = local_player->jy;
12263 // play the element sound at the position nearest to the player
12264 if ((element == EL_MAGIC_WALL_FULL ||
12265 element == EL_MAGIC_WALL_ACTIVE ||
12266 element == EL_MAGIC_WALL_EMPTYING ||
12267 element == EL_BD_MAGIC_WALL_FULL ||
12268 element == EL_BD_MAGIC_WALL_ACTIVE ||
12269 element == EL_BD_MAGIC_WALL_EMPTYING ||
12270 element == EL_DC_MAGIC_WALL_FULL ||
12271 element == EL_DC_MAGIC_WALL_ACTIVE ||
12272 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12273 ABS(x - jx) + ABS(y - jy) <
12274 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12282 #if USE_NEW_AMOEBA_CODE
12283 // new experimental amoeba growth stuff
12284 if (!(FrameCounter % 8))
12286 static unsigned int random = 1684108901;
12288 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12290 x = RND(lev_fieldx);
12291 y = RND(lev_fieldy);
12292 element = Tile[x][y];
12294 if (!IS_PLAYER(x,y) &&
12295 (element == EL_EMPTY ||
12296 CAN_GROW_INTO(element) ||
12297 element == EL_QUICKSAND_EMPTY ||
12298 element == EL_QUICKSAND_FAST_EMPTY ||
12299 element == EL_ACID_SPLASH_LEFT ||
12300 element == EL_ACID_SPLASH_RIGHT))
12302 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12303 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12304 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12305 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12306 Tile[x][y] = EL_AMOEBA_DROP;
12309 random = random * 129 + 1;
12314 game.explosions_delayed = FALSE;
12316 SCAN_PLAYFIELD(x, y)
12318 element = Tile[x][y];
12320 if (ExplodeField[x][y])
12321 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12322 else if (element == EL_EXPLOSION)
12323 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12325 ExplodeField[x][y] = EX_TYPE_NONE;
12328 game.explosions_delayed = TRUE;
12330 if (game.magic_wall_active)
12332 if (!(game.magic_wall_time_left % 4))
12334 int element = Tile[magic_wall_x][magic_wall_y];
12336 if (element == EL_BD_MAGIC_WALL_FULL ||
12337 element == EL_BD_MAGIC_WALL_ACTIVE ||
12338 element == EL_BD_MAGIC_WALL_EMPTYING)
12339 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12340 else if (element == EL_DC_MAGIC_WALL_FULL ||
12341 element == EL_DC_MAGIC_WALL_ACTIVE ||
12342 element == EL_DC_MAGIC_WALL_EMPTYING)
12343 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12345 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12348 if (game.magic_wall_time_left > 0)
12350 game.magic_wall_time_left--;
12352 if (!game.magic_wall_time_left)
12354 SCAN_PLAYFIELD(x, y)
12356 element = Tile[x][y];
12358 if (element == EL_MAGIC_WALL_ACTIVE ||
12359 element == EL_MAGIC_WALL_FULL)
12361 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12362 TEST_DrawLevelField(x, y);
12364 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12365 element == EL_BD_MAGIC_WALL_FULL)
12367 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12368 TEST_DrawLevelField(x, y);
12370 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12371 element == EL_DC_MAGIC_WALL_FULL)
12373 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12374 TEST_DrawLevelField(x, y);
12378 game.magic_wall_active = FALSE;
12383 if (game.light_time_left > 0)
12385 game.light_time_left--;
12387 if (game.light_time_left == 0)
12388 RedrawAllLightSwitchesAndInvisibleElements();
12391 if (game.timegate_time_left > 0)
12393 game.timegate_time_left--;
12395 if (game.timegate_time_left == 0)
12396 CloseAllOpenTimegates();
12399 if (game.lenses_time_left > 0)
12401 game.lenses_time_left--;
12403 if (game.lenses_time_left == 0)
12404 RedrawAllInvisibleElementsForLenses();
12407 if (game.magnify_time_left > 0)
12409 game.magnify_time_left--;
12411 if (game.magnify_time_left == 0)
12412 RedrawAllInvisibleElementsForMagnifier();
12415 for (i = 0; i < MAX_PLAYERS; i++)
12417 struct PlayerInfo *player = &stored_player[i];
12419 if (SHIELD_ON(player))
12421 if (player->shield_deadly_time_left)
12422 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12423 else if (player->shield_normal_time_left)
12424 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12428 #if USE_DELAYED_GFX_REDRAW
12429 SCAN_PLAYFIELD(x, y)
12431 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12433 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12434 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12436 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12437 DrawLevelField(x, y);
12439 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12440 DrawLevelFieldCrumbled(x, y);
12442 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12443 DrawLevelFieldCrumbledNeighbours(x, y);
12445 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12446 DrawTwinkleOnField(x, y);
12449 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12454 PlayAllPlayersSound();
12456 for (i = 0; i < MAX_PLAYERS; i++)
12458 struct PlayerInfo *player = &stored_player[i];
12460 if (player->show_envelope != 0 && (!player->active ||
12461 player->MovPos == 0))
12463 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12465 player->show_envelope = 0;
12469 // use random number generator in every frame to make it less predictable
12470 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12473 mouse_action_last = mouse_action;
12476 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12478 int min_x = x, min_y = y, max_x = x, max_y = y;
12479 int scr_fieldx = getScreenFieldSizeX();
12480 int scr_fieldy = getScreenFieldSizeY();
12483 for (i = 0; i < MAX_PLAYERS; i++)
12485 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12487 if (!stored_player[i].active || &stored_player[i] == player)
12490 min_x = MIN(min_x, jx);
12491 min_y = MIN(min_y, jy);
12492 max_x = MAX(max_x, jx);
12493 max_y = MAX(max_y, jy);
12496 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12499 static boolean AllPlayersInVisibleScreen(void)
12503 for (i = 0; i < MAX_PLAYERS; i++)
12505 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12507 if (!stored_player[i].active)
12510 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12517 void ScrollLevel(int dx, int dy)
12519 int scroll_offset = 2 * TILEX_VAR;
12522 BlitBitmap(drawto_field, drawto_field,
12523 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12524 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12525 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12526 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12527 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12528 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12532 x = (dx == 1 ? BX1 : BX2);
12533 for (y = BY1; y <= BY2; y++)
12534 DrawScreenField(x, y);
12539 y = (dy == 1 ? BY1 : BY2);
12540 for (x = BX1; x <= BX2; x++)
12541 DrawScreenField(x, y);
12544 redraw_mask |= REDRAW_FIELD;
12547 static boolean canFallDown(struct PlayerInfo *player)
12549 int jx = player->jx, jy = player->jy;
12551 return (IN_LEV_FIELD(jx, jy + 1) &&
12552 (IS_FREE(jx, jy + 1) ||
12553 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12554 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12555 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12558 static boolean canPassField(int x, int y, int move_dir)
12560 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12561 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12562 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12563 int nextx = x + dx;
12564 int nexty = y + dy;
12565 int element = Tile[x][y];
12567 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12568 !CAN_MOVE(element) &&
12569 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12570 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12571 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12574 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12576 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12577 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12578 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12582 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12583 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12584 (IS_DIGGABLE(Tile[newx][newy]) ||
12585 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12586 canPassField(newx, newy, move_dir)));
12589 static void CheckGravityMovement(struct PlayerInfo *player)
12591 if (player->gravity && !player->programmed_action)
12593 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12594 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12595 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12596 int jx = player->jx, jy = player->jy;
12597 boolean player_is_moving_to_valid_field =
12598 (!player_is_snapping &&
12599 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12600 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12601 boolean player_can_fall_down = canFallDown(player);
12603 if (player_can_fall_down &&
12604 !player_is_moving_to_valid_field)
12605 player->programmed_action = MV_DOWN;
12609 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12611 return CheckGravityMovement(player);
12613 if (player->gravity && !player->programmed_action)
12615 int jx = player->jx, jy = player->jy;
12616 boolean field_under_player_is_free =
12617 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12618 boolean player_is_standing_on_valid_field =
12619 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12620 (IS_WALKABLE(Tile[jx][jy]) &&
12621 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12623 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12624 player->programmed_action = MV_DOWN;
12629 MovePlayerOneStep()
12630 -----------------------------------------------------------------------------
12631 dx, dy: direction (non-diagonal) to try to move the player to
12632 real_dx, real_dy: direction as read from input device (can be diagonal)
12635 boolean MovePlayerOneStep(struct PlayerInfo *player,
12636 int dx, int dy, int real_dx, int real_dy)
12638 int jx = player->jx, jy = player->jy;
12639 int new_jx = jx + dx, new_jy = jy + dy;
12641 boolean player_can_move = !player->cannot_move;
12643 if (!player->active || (!dx && !dy))
12644 return MP_NO_ACTION;
12646 player->MovDir = (dx < 0 ? MV_LEFT :
12647 dx > 0 ? MV_RIGHT :
12649 dy > 0 ? MV_DOWN : MV_NONE);
12651 if (!IN_LEV_FIELD(new_jx, new_jy))
12652 return MP_NO_ACTION;
12654 if (!player_can_move)
12656 if (player->MovPos == 0)
12658 player->is_moving = FALSE;
12659 player->is_digging = FALSE;
12660 player->is_collecting = FALSE;
12661 player->is_snapping = FALSE;
12662 player->is_pushing = FALSE;
12666 if (!network.enabled && game.centered_player_nr == -1 &&
12667 !AllPlayersInSight(player, new_jx, new_jy))
12668 return MP_NO_ACTION;
12670 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12671 if (can_move != MP_MOVING)
12674 // check if DigField() has caused relocation of the player
12675 if (player->jx != jx || player->jy != jy)
12676 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12678 StorePlayer[jx][jy] = 0;
12679 player->last_jx = jx;
12680 player->last_jy = jy;
12681 player->jx = new_jx;
12682 player->jy = new_jy;
12683 StorePlayer[new_jx][new_jy] = player->element_nr;
12685 if (player->move_delay_value_next != -1)
12687 player->move_delay_value = player->move_delay_value_next;
12688 player->move_delay_value_next = -1;
12692 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12694 player->step_counter++;
12696 PlayerVisit[jx][jy] = FrameCounter;
12698 player->is_moving = TRUE;
12701 // should better be called in MovePlayer(), but this breaks some tapes
12702 ScrollPlayer(player, SCROLL_INIT);
12708 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12710 int jx = player->jx, jy = player->jy;
12711 int old_jx = jx, old_jy = jy;
12712 int moved = MP_NO_ACTION;
12714 if (!player->active)
12719 if (player->MovPos == 0)
12721 player->is_moving = FALSE;
12722 player->is_digging = FALSE;
12723 player->is_collecting = FALSE;
12724 player->is_snapping = FALSE;
12725 player->is_pushing = FALSE;
12731 if (player->move_delay > 0)
12734 player->move_delay = -1; // set to "uninitialized" value
12736 // store if player is automatically moved to next field
12737 player->is_auto_moving = (player->programmed_action != MV_NONE);
12739 // remove the last programmed player action
12740 player->programmed_action = 0;
12742 if (player->MovPos)
12744 // should only happen if pre-1.2 tape recordings are played
12745 // this is only for backward compatibility
12747 int original_move_delay_value = player->move_delay_value;
12750 Debug("game:playing:MovePlayer",
12751 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12755 // scroll remaining steps with finest movement resolution
12756 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12758 while (player->MovPos)
12760 ScrollPlayer(player, SCROLL_GO_ON);
12761 ScrollScreen(NULL, SCROLL_GO_ON);
12763 AdvanceFrameAndPlayerCounters(player->index_nr);
12766 BackToFront_WithFrameDelay(0);
12769 player->move_delay_value = original_move_delay_value;
12772 player->is_active = FALSE;
12774 if (player->last_move_dir & MV_HORIZONTAL)
12776 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12777 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12781 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12782 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12785 if (!moved && !player->is_active)
12787 player->is_moving = FALSE;
12788 player->is_digging = FALSE;
12789 player->is_collecting = FALSE;
12790 player->is_snapping = FALSE;
12791 player->is_pushing = FALSE;
12797 if (moved & MP_MOVING && !ScreenMovPos &&
12798 (player->index_nr == game.centered_player_nr ||
12799 game.centered_player_nr == -1))
12801 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12803 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12805 // actual player has left the screen -- scroll in that direction
12806 if (jx != old_jx) // player has moved horizontally
12807 scroll_x += (jx - old_jx);
12808 else // player has moved vertically
12809 scroll_y += (jy - old_jy);
12813 int offset_raw = game.scroll_delay_value;
12815 if (jx != old_jx) // player has moved horizontally
12817 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12818 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12819 int new_scroll_x = jx - MIDPOSX + offset_x;
12821 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12822 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12823 scroll_x = new_scroll_x;
12825 // don't scroll over playfield boundaries
12826 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12828 // don't scroll more than one field at a time
12829 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12831 // don't scroll against the player's moving direction
12832 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12833 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12834 scroll_x = old_scroll_x;
12836 else // player has moved vertically
12838 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12839 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12840 int new_scroll_y = jy - MIDPOSY + offset_y;
12842 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12843 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12844 scroll_y = new_scroll_y;
12846 // don't scroll over playfield boundaries
12847 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12849 // don't scroll more than one field at a time
12850 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12852 // don't scroll against the player's moving direction
12853 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12854 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12855 scroll_y = old_scroll_y;
12859 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12861 if (!network.enabled && game.centered_player_nr == -1 &&
12862 !AllPlayersInVisibleScreen())
12864 scroll_x = old_scroll_x;
12865 scroll_y = old_scroll_y;
12869 ScrollScreen(player, SCROLL_INIT);
12870 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12875 player->StepFrame = 0;
12877 if (moved & MP_MOVING)
12879 if (old_jx != jx && old_jy == jy)
12880 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12881 else if (old_jx == jx && old_jy != jy)
12882 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12884 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12886 player->last_move_dir = player->MovDir;
12887 player->is_moving = TRUE;
12888 player->is_snapping = FALSE;
12889 player->is_switching = FALSE;
12890 player->is_dropping = FALSE;
12891 player->is_dropping_pressed = FALSE;
12892 player->drop_pressed_delay = 0;
12895 // should better be called here than above, but this breaks some tapes
12896 ScrollPlayer(player, SCROLL_INIT);
12901 CheckGravityMovementWhenNotMoving(player);
12903 player->is_moving = FALSE;
12905 /* at this point, the player is allowed to move, but cannot move right now
12906 (e.g. because of something blocking the way) -- ensure that the player
12907 is also allowed to move in the next frame (in old versions before 3.1.1,
12908 the player was forced to wait again for eight frames before next try) */
12910 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12911 player->move_delay = 0; // allow direct movement in the next frame
12914 if (player->move_delay == -1) // not yet initialized by DigField()
12915 player->move_delay = player->move_delay_value;
12917 if (game.engine_version < VERSION_IDENT(3,0,7,0))
12919 TestIfPlayerTouchesBadThing(jx, jy);
12920 TestIfPlayerTouchesCustomElement(jx, jy);
12923 if (!player->active)
12924 RemovePlayer(player);
12929 void ScrollPlayer(struct PlayerInfo *player, int mode)
12931 int jx = player->jx, jy = player->jy;
12932 int last_jx = player->last_jx, last_jy = player->last_jy;
12933 int move_stepsize = TILEX / player->move_delay_value;
12935 if (!player->active)
12938 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
12941 if (mode == SCROLL_INIT)
12943 player->actual_frame_counter = FrameCounter;
12944 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12946 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
12947 Tile[last_jx][last_jy] == EL_EMPTY)
12949 int last_field_block_delay = 0; // start with no blocking at all
12950 int block_delay_adjustment = player->block_delay_adjustment;
12952 // if player blocks last field, add delay for exactly one move
12953 if (player->block_last_field)
12955 last_field_block_delay += player->move_delay_value;
12957 // when blocking enabled, prevent moving up despite gravity
12958 if (player->gravity && player->MovDir == MV_UP)
12959 block_delay_adjustment = -1;
12962 // add block delay adjustment (also possible when not blocking)
12963 last_field_block_delay += block_delay_adjustment;
12965 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
12966 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
12969 if (player->MovPos != 0) // player has not yet reached destination
12972 else if (!FrameReached(&player->actual_frame_counter, 1))
12975 if (player->MovPos != 0)
12977 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
12978 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
12980 // before DrawPlayer() to draw correct player graphic for this case
12981 if (player->MovPos == 0)
12982 CheckGravityMovement(player);
12985 if (player->MovPos == 0) // player reached destination field
12987 if (player->move_delay_reset_counter > 0)
12989 player->move_delay_reset_counter--;
12991 if (player->move_delay_reset_counter == 0)
12993 // continue with normal speed after quickly moving through gate
12994 HALVE_PLAYER_SPEED(player);
12996 // be able to make the next move without delay
12997 player->move_delay = 0;
13001 player->last_jx = jx;
13002 player->last_jy = jy;
13004 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13005 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13006 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13007 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13008 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13009 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13010 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13011 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13013 ExitPlayer(player);
13015 if (game.players_still_needed == 0 &&
13016 (game.friends_still_needed == 0 ||
13017 IS_SP_ELEMENT(Tile[jx][jy])))
13021 // this breaks one level: "machine", level 000
13023 int move_direction = player->MovDir;
13024 int enter_side = MV_DIR_OPPOSITE(move_direction);
13025 int leave_side = move_direction;
13026 int old_jx = last_jx;
13027 int old_jy = last_jy;
13028 int old_element = Tile[old_jx][old_jy];
13029 int new_element = Tile[jx][jy];
13031 if (IS_CUSTOM_ELEMENT(old_element))
13032 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13034 player->index_bit, leave_side);
13036 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13037 CE_PLAYER_LEAVES_X,
13038 player->index_bit, leave_side);
13040 if (IS_CUSTOM_ELEMENT(new_element))
13041 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13042 player->index_bit, enter_side);
13044 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13045 CE_PLAYER_ENTERS_X,
13046 player->index_bit, enter_side);
13048 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13049 CE_MOVE_OF_X, move_direction);
13052 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13054 TestIfPlayerTouchesBadThing(jx, jy);
13055 TestIfPlayerTouchesCustomElement(jx, jy);
13057 /* needed because pushed element has not yet reached its destination,
13058 so it would trigger a change event at its previous field location */
13059 if (!player->is_pushing)
13060 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13062 if (level.finish_dig_collect &&
13063 (player->is_digging || player->is_collecting))
13065 int last_element = player->last_removed_element;
13066 int move_direction = player->MovDir;
13067 int enter_side = MV_DIR_OPPOSITE(move_direction);
13068 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13069 CE_PLAYER_COLLECTS_X);
13071 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13072 player->index_bit, enter_side);
13074 player->last_removed_element = EL_UNDEFINED;
13077 if (!player->active)
13078 RemovePlayer(player);
13081 if (!game.LevelSolved && level.use_step_counter)
13091 if (TimeLeft <= 10 && setup.time_limit)
13092 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13094 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13096 DisplayGameControlValues();
13098 if (!TimeLeft && setup.time_limit)
13099 for (i = 0; i < MAX_PLAYERS; i++)
13100 KillPlayer(&stored_player[i]);
13102 else if (game.no_time_limit && !game.all_players_gone)
13104 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13106 DisplayGameControlValues();
13110 if (tape.single_step && tape.recording && !tape.pausing &&
13111 !player->programmed_action)
13112 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13114 if (!player->programmed_action)
13115 CheckSaveEngineSnapshot(player);
13119 void ScrollScreen(struct PlayerInfo *player, int mode)
13121 static unsigned int screen_frame_counter = 0;
13123 if (mode == SCROLL_INIT)
13125 // set scrolling step size according to actual player's moving speed
13126 ScrollStepSize = TILEX / player->move_delay_value;
13128 screen_frame_counter = FrameCounter;
13129 ScreenMovDir = player->MovDir;
13130 ScreenMovPos = player->MovPos;
13131 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13134 else if (!FrameReached(&screen_frame_counter, 1))
13139 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13140 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13141 redraw_mask |= REDRAW_FIELD;
13144 ScreenMovDir = MV_NONE;
13147 void TestIfPlayerTouchesCustomElement(int x, int y)
13149 static int xy[4][2] =
13156 static int trigger_sides[4][2] =
13158 // center side border side
13159 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13160 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13161 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13162 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13164 static int touch_dir[4] =
13166 MV_LEFT | MV_RIGHT,
13171 int center_element = Tile[x][y]; // should always be non-moving!
13174 for (i = 0; i < NUM_DIRECTIONS; i++)
13176 int xx = x + xy[i][0];
13177 int yy = y + xy[i][1];
13178 int center_side = trigger_sides[i][0];
13179 int border_side = trigger_sides[i][1];
13180 int border_element;
13182 if (!IN_LEV_FIELD(xx, yy))
13185 if (IS_PLAYER(x, y)) // player found at center element
13187 struct PlayerInfo *player = PLAYERINFO(x, y);
13189 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13190 border_element = Tile[xx][yy]; // may be moving!
13191 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13192 border_element = Tile[xx][yy];
13193 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13194 border_element = MovingOrBlocked2Element(xx, yy);
13196 continue; // center and border element do not touch
13198 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13199 player->index_bit, border_side);
13200 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13201 CE_PLAYER_TOUCHES_X,
13202 player->index_bit, border_side);
13205 /* use player element that is initially defined in the level playfield,
13206 not the player element that corresponds to the runtime player number
13207 (example: a level that contains EL_PLAYER_3 as the only player would
13208 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13209 int player_element = PLAYERINFO(x, y)->initial_element;
13211 CheckElementChangeBySide(xx, yy, border_element, player_element,
13212 CE_TOUCHING_X, border_side);
13215 else if (IS_PLAYER(xx, yy)) // player found at border element
13217 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13219 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13221 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13222 continue; // center and border element do not touch
13225 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13226 player->index_bit, center_side);
13227 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13228 CE_PLAYER_TOUCHES_X,
13229 player->index_bit, center_side);
13232 /* use player element that is initially defined in the level playfield,
13233 not the player element that corresponds to the runtime player number
13234 (example: a level that contains EL_PLAYER_3 as the only player would
13235 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13236 int player_element = PLAYERINFO(xx, yy)->initial_element;
13238 CheckElementChangeBySide(x, y, center_element, player_element,
13239 CE_TOUCHING_X, center_side);
13247 void TestIfElementTouchesCustomElement(int x, int y)
13249 static int xy[4][2] =
13256 static int trigger_sides[4][2] =
13258 // center side border side
13259 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13260 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13261 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13262 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13264 static int touch_dir[4] =
13266 MV_LEFT | MV_RIGHT,
13271 boolean change_center_element = FALSE;
13272 int center_element = Tile[x][y]; // should always be non-moving!
13273 int border_element_old[NUM_DIRECTIONS];
13276 for (i = 0; i < NUM_DIRECTIONS; i++)
13278 int xx = x + xy[i][0];
13279 int yy = y + xy[i][1];
13280 int border_element;
13282 border_element_old[i] = -1;
13284 if (!IN_LEV_FIELD(xx, yy))
13287 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13288 border_element = Tile[xx][yy]; // may be moving!
13289 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13290 border_element = Tile[xx][yy];
13291 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13292 border_element = MovingOrBlocked2Element(xx, yy);
13294 continue; // center and border element do not touch
13296 border_element_old[i] = border_element;
13299 for (i = 0; i < NUM_DIRECTIONS; i++)
13301 int xx = x + xy[i][0];
13302 int yy = y + xy[i][1];
13303 int center_side = trigger_sides[i][0];
13304 int border_element = border_element_old[i];
13306 if (border_element == -1)
13309 // check for change of border element
13310 CheckElementChangeBySide(xx, yy, border_element, center_element,
13311 CE_TOUCHING_X, center_side);
13313 // (center element cannot be player, so we dont have to check this here)
13316 for (i = 0; i < NUM_DIRECTIONS; i++)
13318 int xx = x + xy[i][0];
13319 int yy = y + xy[i][1];
13320 int border_side = trigger_sides[i][1];
13321 int border_element = border_element_old[i];
13323 if (border_element == -1)
13326 // check for change of center element (but change it only once)
13327 if (!change_center_element)
13328 change_center_element =
13329 CheckElementChangeBySide(x, y, center_element, border_element,
13330 CE_TOUCHING_X, border_side);
13332 if (IS_PLAYER(xx, yy))
13334 /* use player element that is initially defined in the level playfield,
13335 not the player element that corresponds to the runtime player number
13336 (example: a level that contains EL_PLAYER_3 as the only player would
13337 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13338 int player_element = PLAYERINFO(xx, yy)->initial_element;
13340 CheckElementChangeBySide(x, y, center_element, player_element,
13341 CE_TOUCHING_X, border_side);
13346 void TestIfElementHitsCustomElement(int x, int y, int direction)
13348 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13349 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13350 int hitx = x + dx, hity = y + dy;
13351 int hitting_element = Tile[x][y];
13352 int touched_element;
13354 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13357 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13358 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13360 if (IN_LEV_FIELD(hitx, hity))
13362 int opposite_direction = MV_DIR_OPPOSITE(direction);
13363 int hitting_side = direction;
13364 int touched_side = opposite_direction;
13365 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13366 MovDir[hitx][hity] != direction ||
13367 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13373 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13374 CE_HITTING_X, touched_side);
13376 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13377 CE_HIT_BY_X, hitting_side);
13379 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13380 CE_HIT_BY_SOMETHING, opposite_direction);
13382 if (IS_PLAYER(hitx, hity))
13384 /* use player element that is initially defined in the level playfield,
13385 not the player element that corresponds to the runtime player number
13386 (example: a level that contains EL_PLAYER_3 as the only player would
13387 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13388 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13390 CheckElementChangeBySide(x, y, hitting_element, player_element,
13391 CE_HITTING_X, touched_side);
13396 // "hitting something" is also true when hitting the playfield border
13397 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13398 CE_HITTING_SOMETHING, direction);
13401 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13403 int i, kill_x = -1, kill_y = -1;
13405 int bad_element = -1;
13406 static int test_xy[4][2] =
13413 static int test_dir[4] =
13421 for (i = 0; i < NUM_DIRECTIONS; i++)
13423 int test_x, test_y, test_move_dir, test_element;
13425 test_x = good_x + test_xy[i][0];
13426 test_y = good_y + test_xy[i][1];
13428 if (!IN_LEV_FIELD(test_x, test_y))
13432 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13434 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13436 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13437 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13439 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13440 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13444 bad_element = test_element;
13450 if (kill_x != -1 || kill_y != -1)
13452 if (IS_PLAYER(good_x, good_y))
13454 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13456 if (player->shield_deadly_time_left > 0 &&
13457 !IS_INDESTRUCTIBLE(bad_element))
13458 Bang(kill_x, kill_y);
13459 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13460 KillPlayer(player);
13463 Bang(good_x, good_y);
13467 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13469 int i, kill_x = -1, kill_y = -1;
13470 int bad_element = Tile[bad_x][bad_y];
13471 static int test_xy[4][2] =
13478 static int touch_dir[4] =
13480 MV_LEFT | MV_RIGHT,
13485 static int test_dir[4] =
13493 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13496 for (i = 0; i < NUM_DIRECTIONS; i++)
13498 int test_x, test_y, test_move_dir, test_element;
13500 test_x = bad_x + test_xy[i][0];
13501 test_y = bad_y + test_xy[i][1];
13503 if (!IN_LEV_FIELD(test_x, test_y))
13507 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13509 test_element = Tile[test_x][test_y];
13511 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13512 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13514 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13515 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13517 // good thing is player or penguin that does not move away
13518 if (IS_PLAYER(test_x, test_y))
13520 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13522 if (bad_element == EL_ROBOT && player->is_moving)
13523 continue; // robot does not kill player if he is moving
13525 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13527 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13528 continue; // center and border element do not touch
13536 else if (test_element == EL_PENGUIN)
13546 if (kill_x != -1 || kill_y != -1)
13548 if (IS_PLAYER(kill_x, kill_y))
13550 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13552 if (player->shield_deadly_time_left > 0 &&
13553 !IS_INDESTRUCTIBLE(bad_element))
13554 Bang(bad_x, bad_y);
13555 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13556 KillPlayer(player);
13559 Bang(kill_x, kill_y);
13563 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13565 int bad_element = Tile[bad_x][bad_y];
13566 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13567 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13568 int test_x = bad_x + dx, test_y = bad_y + dy;
13569 int test_move_dir, test_element;
13570 int kill_x = -1, kill_y = -1;
13572 if (!IN_LEV_FIELD(test_x, test_y))
13576 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13578 test_element = Tile[test_x][test_y];
13580 if (test_move_dir != bad_move_dir)
13582 // good thing can be player or penguin that does not move away
13583 if (IS_PLAYER(test_x, test_y))
13585 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13587 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13588 player as being hit when he is moving towards the bad thing, because
13589 the "get hit by" condition would be lost after the player stops) */
13590 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13591 return; // player moves away from bad thing
13596 else if (test_element == EL_PENGUIN)
13603 if (kill_x != -1 || kill_y != -1)
13605 if (IS_PLAYER(kill_x, kill_y))
13607 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13609 if (player->shield_deadly_time_left > 0 &&
13610 !IS_INDESTRUCTIBLE(bad_element))
13611 Bang(bad_x, bad_y);
13612 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13613 KillPlayer(player);
13616 Bang(kill_x, kill_y);
13620 void TestIfPlayerTouchesBadThing(int x, int y)
13622 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13625 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13627 TestIfGoodThingHitsBadThing(x, y, move_dir);
13630 void TestIfBadThingTouchesPlayer(int x, int y)
13632 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13635 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13637 TestIfBadThingHitsGoodThing(x, y, move_dir);
13640 void TestIfFriendTouchesBadThing(int x, int y)
13642 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13645 void TestIfBadThingTouchesFriend(int x, int y)
13647 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13650 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13652 int i, kill_x = bad_x, kill_y = bad_y;
13653 static int xy[4][2] =
13661 for (i = 0; i < NUM_DIRECTIONS; i++)
13665 x = bad_x + xy[i][0];
13666 y = bad_y + xy[i][1];
13667 if (!IN_LEV_FIELD(x, y))
13670 element = Tile[x][y];
13671 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13672 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13680 if (kill_x != bad_x || kill_y != bad_y)
13681 Bang(bad_x, bad_y);
13684 void KillPlayer(struct PlayerInfo *player)
13686 int jx = player->jx, jy = player->jy;
13688 if (!player->active)
13692 Debug("game:playing:KillPlayer",
13693 "0: killed == %d, active == %d, reanimated == %d",
13694 player->killed, player->active, player->reanimated);
13697 /* the following code was introduced to prevent an infinite loop when calling
13699 -> CheckTriggeredElementChangeExt()
13700 -> ExecuteCustomElementAction()
13702 -> (infinitely repeating the above sequence of function calls)
13703 which occurs when killing the player while having a CE with the setting
13704 "kill player X when explosion of <player X>"; the solution using a new
13705 field "player->killed" was chosen for backwards compatibility, although
13706 clever use of the fields "player->active" etc. would probably also work */
13708 if (player->killed)
13712 player->killed = TRUE;
13714 // remove accessible field at the player's position
13715 Tile[jx][jy] = EL_EMPTY;
13717 // deactivate shield (else Bang()/Explode() would not work right)
13718 player->shield_normal_time_left = 0;
13719 player->shield_deadly_time_left = 0;
13722 Debug("game:playing:KillPlayer",
13723 "1: killed == %d, active == %d, reanimated == %d",
13724 player->killed, player->active, player->reanimated);
13730 Debug("game:playing:KillPlayer",
13731 "2: killed == %d, active == %d, reanimated == %d",
13732 player->killed, player->active, player->reanimated);
13735 if (player->reanimated) // killed player may have been reanimated
13736 player->killed = player->reanimated = FALSE;
13738 BuryPlayer(player);
13741 static void KillPlayerUnlessEnemyProtected(int x, int y)
13743 if (!PLAYER_ENEMY_PROTECTED(x, y))
13744 KillPlayer(PLAYERINFO(x, y));
13747 static void KillPlayerUnlessExplosionProtected(int x, int y)
13749 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13750 KillPlayer(PLAYERINFO(x, y));
13753 void BuryPlayer(struct PlayerInfo *player)
13755 int jx = player->jx, jy = player->jy;
13757 if (!player->active)
13760 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13761 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13763 RemovePlayer(player);
13765 player->buried = TRUE;
13767 if (game.all_players_gone)
13768 game.GameOver = TRUE;
13771 void RemovePlayer(struct PlayerInfo *player)
13773 int jx = player->jx, jy = player->jy;
13774 int i, found = FALSE;
13776 player->present = FALSE;
13777 player->active = FALSE;
13779 // required for some CE actions (even if the player is not active anymore)
13780 player->MovPos = 0;
13782 if (!ExplodeField[jx][jy])
13783 StorePlayer[jx][jy] = 0;
13785 if (player->is_moving)
13786 TEST_DrawLevelField(player->last_jx, player->last_jy);
13788 for (i = 0; i < MAX_PLAYERS; i++)
13789 if (stored_player[i].active)
13794 game.all_players_gone = TRUE;
13795 game.GameOver = TRUE;
13798 game.exit_x = game.robot_wheel_x = jx;
13799 game.exit_y = game.robot_wheel_y = jy;
13802 void ExitPlayer(struct PlayerInfo *player)
13804 DrawPlayer(player); // needed here only to cleanup last field
13805 RemovePlayer(player);
13807 if (game.players_still_needed > 0)
13808 game.players_still_needed--;
13811 static void SetFieldForSnapping(int x, int y, int element, int direction,
13812 int player_index_bit)
13814 struct ElementInfo *ei = &element_info[element];
13815 int direction_bit = MV_DIR_TO_BIT(direction);
13816 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13817 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13818 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13820 Tile[x][y] = EL_ELEMENT_SNAPPING;
13821 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13822 MovDir[x][y] = direction;
13823 Store[x][y] = element;
13824 Store2[x][y] = player_index_bit;
13826 ResetGfxAnimation(x, y);
13828 GfxElement[x][y] = element;
13829 GfxAction[x][y] = action;
13830 GfxDir[x][y] = direction;
13831 GfxFrame[x][y] = -1;
13834 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13835 int player_index_bit)
13837 TestIfElementTouchesCustomElement(x, y); // for empty space
13839 if (level.finish_dig_collect)
13841 int dig_side = MV_DIR_OPPOSITE(direction);
13843 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13844 player_index_bit, dig_side);
13849 =============================================================================
13850 checkDiagonalPushing()
13851 -----------------------------------------------------------------------------
13852 check if diagonal input device direction results in pushing of object
13853 (by checking if the alternative direction is walkable, diggable, ...)
13854 =============================================================================
13857 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13858 int x, int y, int real_dx, int real_dy)
13860 int jx, jy, dx, dy, xx, yy;
13862 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13865 // diagonal direction: check alternative direction
13870 xx = jx + (dx == 0 ? real_dx : 0);
13871 yy = jy + (dy == 0 ? real_dy : 0);
13873 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13877 =============================================================================
13879 -----------------------------------------------------------------------------
13880 x, y: field next to player (non-diagonal) to try to dig to
13881 real_dx, real_dy: direction as read from input device (can be diagonal)
13882 =============================================================================
13885 static int DigField(struct PlayerInfo *player,
13886 int oldx, int oldy, int x, int y,
13887 int real_dx, int real_dy, int mode)
13889 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13890 boolean player_was_pushing = player->is_pushing;
13891 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13892 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13893 int jx = oldx, jy = oldy;
13894 int dx = x - jx, dy = y - jy;
13895 int nextx = x + dx, nexty = y + dy;
13896 int move_direction = (dx == -1 ? MV_LEFT :
13897 dx == +1 ? MV_RIGHT :
13899 dy == +1 ? MV_DOWN : MV_NONE);
13900 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13901 int dig_side = MV_DIR_OPPOSITE(move_direction);
13902 int old_element = Tile[jx][jy];
13903 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13906 if (is_player) // function can also be called by EL_PENGUIN
13908 if (player->MovPos == 0)
13910 player->is_digging = FALSE;
13911 player->is_collecting = FALSE;
13914 if (player->MovPos == 0) // last pushing move finished
13915 player->is_pushing = FALSE;
13917 if (mode == DF_NO_PUSH) // player just stopped pushing
13919 player->is_switching = FALSE;
13920 player->push_delay = -1;
13922 return MP_NO_ACTION;
13926 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
13927 old_element = Back[jx][jy];
13929 // in case of element dropped at player position, check background
13930 else if (Back[jx][jy] != EL_EMPTY &&
13931 game.engine_version >= VERSION_IDENT(2,2,0,0))
13932 old_element = Back[jx][jy];
13934 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
13935 return MP_NO_ACTION; // field has no opening in this direction
13937 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
13938 return MP_NO_ACTION; // field has no opening in this direction
13940 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
13944 Tile[jx][jy] = player->artwork_element;
13945 InitMovingField(jx, jy, MV_DOWN);
13946 Store[jx][jy] = EL_ACID;
13947 ContinueMoving(jx, jy);
13948 BuryPlayer(player);
13950 return MP_DONT_RUN_INTO;
13953 if (player_can_move && DONT_RUN_INTO(element))
13955 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
13957 return MP_DONT_RUN_INTO;
13960 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
13961 return MP_NO_ACTION;
13963 collect_count = element_info[element].collect_count_initial;
13965 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
13966 return MP_NO_ACTION;
13968 if (game.engine_version < VERSION_IDENT(2,2,0,0))
13969 player_can_move = player_can_move_or_snap;
13971 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
13972 game.engine_version >= VERSION_IDENT(2,2,0,0))
13974 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
13975 player->index_bit, dig_side);
13976 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13977 player->index_bit, dig_side);
13979 if (element == EL_DC_LANDMINE)
13982 if (Tile[x][y] != element) // field changed by snapping
13985 return MP_NO_ACTION;
13988 if (player->gravity && is_player && !player->is_auto_moving &&
13989 canFallDown(player) && move_direction != MV_DOWN &&
13990 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
13991 return MP_NO_ACTION; // player cannot walk here due to gravity
13993 if (player_can_move &&
13994 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
13996 int sound_element = SND_ELEMENT(element);
13997 int sound_action = ACTION_WALKING;
13999 if (IS_RND_GATE(element))
14001 if (!player->key[RND_GATE_NR(element)])
14002 return MP_NO_ACTION;
14004 else if (IS_RND_GATE_GRAY(element))
14006 if (!player->key[RND_GATE_GRAY_NR(element)])
14007 return MP_NO_ACTION;
14009 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14011 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14012 return MP_NO_ACTION;
14014 else if (element == EL_EXIT_OPEN ||
14015 element == EL_EM_EXIT_OPEN ||
14016 element == EL_EM_EXIT_OPENING ||
14017 element == EL_STEEL_EXIT_OPEN ||
14018 element == EL_EM_STEEL_EXIT_OPEN ||
14019 element == EL_EM_STEEL_EXIT_OPENING ||
14020 element == EL_SP_EXIT_OPEN ||
14021 element == EL_SP_EXIT_OPENING)
14023 sound_action = ACTION_PASSING; // player is passing exit
14025 else if (element == EL_EMPTY)
14027 sound_action = ACTION_MOVING; // nothing to walk on
14030 // play sound from background or player, whatever is available
14031 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14032 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14034 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14036 else if (player_can_move &&
14037 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14039 if (!ACCESS_FROM(element, opposite_direction))
14040 return MP_NO_ACTION; // field not accessible from this direction
14042 if (CAN_MOVE(element)) // only fixed elements can be passed!
14043 return MP_NO_ACTION;
14045 if (IS_EM_GATE(element))
14047 if (!player->key[EM_GATE_NR(element)])
14048 return MP_NO_ACTION;
14050 else if (IS_EM_GATE_GRAY(element))
14052 if (!player->key[EM_GATE_GRAY_NR(element)])
14053 return MP_NO_ACTION;
14055 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14057 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14058 return MP_NO_ACTION;
14060 else if (IS_EMC_GATE(element))
14062 if (!player->key[EMC_GATE_NR(element)])
14063 return MP_NO_ACTION;
14065 else if (IS_EMC_GATE_GRAY(element))
14067 if (!player->key[EMC_GATE_GRAY_NR(element)])
14068 return MP_NO_ACTION;
14070 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14072 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14073 return MP_NO_ACTION;
14075 else if (element == EL_DC_GATE_WHITE ||
14076 element == EL_DC_GATE_WHITE_GRAY ||
14077 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14079 if (player->num_white_keys == 0)
14080 return MP_NO_ACTION;
14082 player->num_white_keys--;
14084 else if (IS_SP_PORT(element))
14086 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14087 element == EL_SP_GRAVITY_PORT_RIGHT ||
14088 element == EL_SP_GRAVITY_PORT_UP ||
14089 element == EL_SP_GRAVITY_PORT_DOWN)
14090 player->gravity = !player->gravity;
14091 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14092 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14093 element == EL_SP_GRAVITY_ON_PORT_UP ||
14094 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14095 player->gravity = TRUE;
14096 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14097 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14098 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14099 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14100 player->gravity = FALSE;
14103 // automatically move to the next field with double speed
14104 player->programmed_action = move_direction;
14106 if (player->move_delay_reset_counter == 0)
14108 player->move_delay_reset_counter = 2; // two double speed steps
14110 DOUBLE_PLAYER_SPEED(player);
14113 PlayLevelSoundAction(x, y, ACTION_PASSING);
14115 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14119 if (mode != DF_SNAP)
14121 GfxElement[x][y] = GFX_ELEMENT(element);
14122 player->is_digging = TRUE;
14125 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14127 // use old behaviour for old levels (digging)
14128 if (!level.finish_dig_collect)
14130 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14131 player->index_bit, dig_side);
14133 // if digging triggered player relocation, finish digging tile
14134 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14135 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14138 if (mode == DF_SNAP)
14140 if (level.block_snap_field)
14141 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14143 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14145 // use old behaviour for old levels (snapping)
14146 if (!level.finish_dig_collect)
14147 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14148 player->index_bit, dig_side);
14151 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14155 if (is_player && mode != DF_SNAP)
14157 GfxElement[x][y] = element;
14158 player->is_collecting = TRUE;
14161 if (element == EL_SPEED_PILL)
14163 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14165 else if (element == EL_EXTRA_TIME && level.time > 0)
14167 TimeLeft += level.extra_time;
14169 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14171 DisplayGameControlValues();
14173 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14175 player->shield_normal_time_left += level.shield_normal_time;
14176 if (element == EL_SHIELD_DEADLY)
14177 player->shield_deadly_time_left += level.shield_deadly_time;
14179 else if (element == EL_DYNAMITE ||
14180 element == EL_EM_DYNAMITE ||
14181 element == EL_SP_DISK_RED)
14183 if (player->inventory_size < MAX_INVENTORY_SIZE)
14184 player->inventory_element[player->inventory_size++] = element;
14186 DrawGameDoorValues();
14188 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14190 player->dynabomb_count++;
14191 player->dynabombs_left++;
14193 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14195 player->dynabomb_size++;
14197 else if (element == EL_DYNABOMB_INCREASE_POWER)
14199 player->dynabomb_xl = TRUE;
14201 else if (IS_KEY(element))
14203 player->key[KEY_NR(element)] = TRUE;
14205 DrawGameDoorValues();
14207 else if (element == EL_DC_KEY_WHITE)
14209 player->num_white_keys++;
14211 // display white keys?
14212 // DrawGameDoorValues();
14214 else if (IS_ENVELOPE(element))
14216 player->show_envelope = element;
14218 else if (element == EL_EMC_LENSES)
14220 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14222 RedrawAllInvisibleElementsForLenses();
14224 else if (element == EL_EMC_MAGNIFIER)
14226 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14228 RedrawAllInvisibleElementsForMagnifier();
14230 else if (IS_DROPPABLE(element) ||
14231 IS_THROWABLE(element)) // can be collected and dropped
14235 if (collect_count == 0)
14236 player->inventory_infinite_element = element;
14238 for (i = 0; i < collect_count; i++)
14239 if (player->inventory_size < MAX_INVENTORY_SIZE)
14240 player->inventory_element[player->inventory_size++] = element;
14242 DrawGameDoorValues();
14244 else if (collect_count > 0)
14246 game.gems_still_needed -= collect_count;
14247 if (game.gems_still_needed < 0)
14248 game.gems_still_needed = 0;
14250 game.snapshot.collected_item = TRUE;
14252 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14254 DisplayGameControlValues();
14257 RaiseScoreElement(element);
14258 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14260 // use old behaviour for old levels (collecting)
14261 if (!level.finish_dig_collect && is_player)
14263 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14264 player->index_bit, dig_side);
14266 // if collecting triggered player relocation, finish collecting tile
14267 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14268 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14271 if (mode == DF_SNAP)
14273 if (level.block_snap_field)
14274 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14276 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14278 // use old behaviour for old levels (snapping)
14279 if (!level.finish_dig_collect)
14280 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14281 player->index_bit, dig_side);
14284 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14286 if (mode == DF_SNAP && element != EL_BD_ROCK)
14287 return MP_NO_ACTION;
14289 if (CAN_FALL(element) && dy)
14290 return MP_NO_ACTION;
14292 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14293 !(element == EL_SPRING && level.use_spring_bug))
14294 return MP_NO_ACTION;
14296 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14297 ((move_direction & MV_VERTICAL &&
14298 ((element_info[element].move_pattern & MV_LEFT &&
14299 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14300 (element_info[element].move_pattern & MV_RIGHT &&
14301 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14302 (move_direction & MV_HORIZONTAL &&
14303 ((element_info[element].move_pattern & MV_UP &&
14304 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14305 (element_info[element].move_pattern & MV_DOWN &&
14306 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14307 return MP_NO_ACTION;
14309 // do not push elements already moving away faster than player
14310 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14311 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14312 return MP_NO_ACTION;
14314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14316 if (player->push_delay_value == -1 || !player_was_pushing)
14317 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14319 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14321 if (player->push_delay_value == -1)
14322 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14324 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14326 if (!player->is_pushing)
14327 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14330 player->is_pushing = TRUE;
14331 player->is_active = TRUE;
14333 if (!(IN_LEV_FIELD(nextx, nexty) &&
14334 (IS_FREE(nextx, nexty) ||
14335 (IS_SB_ELEMENT(element) &&
14336 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14337 (IS_CUSTOM_ELEMENT(element) &&
14338 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14339 return MP_NO_ACTION;
14341 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14342 return MP_NO_ACTION;
14344 if (player->push_delay == -1) // new pushing; restart delay
14345 player->push_delay = 0;
14347 if (player->push_delay < player->push_delay_value &&
14348 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14349 element != EL_SPRING && element != EL_BALLOON)
14351 // make sure that there is no move delay before next try to push
14352 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14353 player->move_delay = 0;
14355 return MP_NO_ACTION;
14358 if (IS_CUSTOM_ELEMENT(element) &&
14359 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14361 if (!DigFieldByCE(nextx, nexty, element))
14362 return MP_NO_ACTION;
14365 if (IS_SB_ELEMENT(element))
14367 boolean sokoban_task_solved = FALSE;
14369 if (element == EL_SOKOBAN_FIELD_FULL)
14371 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14373 IncrementSokobanFieldsNeeded();
14374 IncrementSokobanObjectsNeeded();
14377 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14379 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14381 DecrementSokobanFieldsNeeded();
14382 DecrementSokobanObjectsNeeded();
14384 // sokoban object was pushed from empty field to sokoban field
14385 if (Back[x][y] == EL_EMPTY)
14386 sokoban_task_solved = TRUE;
14389 Tile[x][y] = EL_SOKOBAN_OBJECT;
14391 if (Back[x][y] == Back[nextx][nexty])
14392 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14393 else if (Back[x][y] != 0)
14394 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14397 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14400 if (sokoban_task_solved &&
14401 game.sokoban_fields_still_needed == 0 &&
14402 game.sokoban_objects_still_needed == 0 &&
14403 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14405 game.players_still_needed = 0;
14409 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14413 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14415 InitMovingField(x, y, move_direction);
14416 GfxAction[x][y] = ACTION_PUSHING;
14418 if (mode == DF_SNAP)
14419 ContinueMoving(x, y);
14421 MovPos[x][y] = (dx != 0 ? dx : dy);
14423 Pushed[x][y] = TRUE;
14424 Pushed[nextx][nexty] = TRUE;
14426 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14427 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14429 player->push_delay_value = -1; // get new value later
14431 // check for element change _after_ element has been pushed
14432 if (game.use_change_when_pushing_bug)
14434 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14435 player->index_bit, dig_side);
14436 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14437 player->index_bit, dig_side);
14440 else if (IS_SWITCHABLE(element))
14442 if (PLAYER_SWITCHING(player, x, y))
14444 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14445 player->index_bit, dig_side);
14450 player->is_switching = TRUE;
14451 player->switch_x = x;
14452 player->switch_y = y;
14454 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14456 if (element == EL_ROBOT_WHEEL)
14458 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14460 game.robot_wheel_x = x;
14461 game.robot_wheel_y = y;
14462 game.robot_wheel_active = TRUE;
14464 TEST_DrawLevelField(x, y);
14466 else if (element == EL_SP_TERMINAL)
14470 SCAN_PLAYFIELD(xx, yy)
14472 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14476 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14478 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14480 ResetGfxAnimation(xx, yy);
14481 TEST_DrawLevelField(xx, yy);
14485 else if (IS_BELT_SWITCH(element))
14487 ToggleBeltSwitch(x, y);
14489 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14490 element == EL_SWITCHGATE_SWITCH_DOWN ||
14491 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14492 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14494 ToggleSwitchgateSwitch(x, y);
14496 else if (element == EL_LIGHT_SWITCH ||
14497 element == EL_LIGHT_SWITCH_ACTIVE)
14499 ToggleLightSwitch(x, y);
14501 else if (element == EL_TIMEGATE_SWITCH ||
14502 element == EL_DC_TIMEGATE_SWITCH)
14504 ActivateTimegateSwitch(x, y);
14506 else if (element == EL_BALLOON_SWITCH_LEFT ||
14507 element == EL_BALLOON_SWITCH_RIGHT ||
14508 element == EL_BALLOON_SWITCH_UP ||
14509 element == EL_BALLOON_SWITCH_DOWN ||
14510 element == EL_BALLOON_SWITCH_NONE ||
14511 element == EL_BALLOON_SWITCH_ANY)
14513 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14514 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14515 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14516 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14517 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14520 else if (element == EL_LAMP)
14522 Tile[x][y] = EL_LAMP_ACTIVE;
14523 game.lights_still_needed--;
14525 ResetGfxAnimation(x, y);
14526 TEST_DrawLevelField(x, y);
14528 else if (element == EL_TIME_ORB_FULL)
14530 Tile[x][y] = EL_TIME_ORB_EMPTY;
14532 if (level.time > 0 || level.use_time_orb_bug)
14534 TimeLeft += level.time_orb_time;
14535 game.no_time_limit = FALSE;
14537 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14539 DisplayGameControlValues();
14542 ResetGfxAnimation(x, y);
14543 TEST_DrawLevelField(x, y);
14545 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14546 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14550 game.ball_active = !game.ball_active;
14552 SCAN_PLAYFIELD(xx, yy)
14554 int e = Tile[xx][yy];
14556 if (game.ball_active)
14558 if (e == EL_EMC_MAGIC_BALL)
14559 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14560 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14561 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14565 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14566 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14567 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14568 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14573 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14574 player->index_bit, dig_side);
14576 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14577 player->index_bit, dig_side);
14579 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14580 player->index_bit, dig_side);
14586 if (!PLAYER_SWITCHING(player, x, y))
14588 player->is_switching = TRUE;
14589 player->switch_x = x;
14590 player->switch_y = y;
14592 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14593 player->index_bit, dig_side);
14594 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14595 player->index_bit, dig_side);
14597 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14598 player->index_bit, dig_side);
14599 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14600 player->index_bit, dig_side);
14603 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14604 player->index_bit, dig_side);
14605 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14606 player->index_bit, dig_side);
14608 return MP_NO_ACTION;
14611 player->push_delay = -1;
14613 if (is_player) // function can also be called by EL_PENGUIN
14615 if (Tile[x][y] != element) // really digged/collected something
14617 player->is_collecting = !player->is_digging;
14618 player->is_active = TRUE;
14620 player->last_removed_element = element;
14627 static boolean DigFieldByCE(int x, int y, int digging_element)
14629 int element = Tile[x][y];
14631 if (!IS_FREE(x, y))
14633 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14634 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14637 // no element can dig solid indestructible elements
14638 if (IS_INDESTRUCTIBLE(element) &&
14639 !IS_DIGGABLE(element) &&
14640 !IS_COLLECTIBLE(element))
14643 if (AmoebaNr[x][y] &&
14644 (element == EL_AMOEBA_FULL ||
14645 element == EL_BD_AMOEBA ||
14646 element == EL_AMOEBA_GROWING))
14648 AmoebaCnt[AmoebaNr[x][y]]--;
14649 AmoebaCnt2[AmoebaNr[x][y]]--;
14652 if (IS_MOVING(x, y))
14653 RemoveMovingField(x, y);
14657 TEST_DrawLevelField(x, y);
14660 // if digged element was about to explode, prevent the explosion
14661 ExplodeField[x][y] = EX_TYPE_NONE;
14663 PlayLevelSoundAction(x, y, action);
14666 Store[x][y] = EL_EMPTY;
14668 // this makes it possible to leave the removed element again
14669 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14670 Store[x][y] = element;
14675 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14677 int jx = player->jx, jy = player->jy;
14678 int x = jx + dx, y = jy + dy;
14679 int snap_direction = (dx == -1 ? MV_LEFT :
14680 dx == +1 ? MV_RIGHT :
14682 dy == +1 ? MV_DOWN : MV_NONE);
14683 boolean can_continue_snapping = (level.continuous_snapping &&
14684 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14686 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14689 if (!player->active || !IN_LEV_FIELD(x, y))
14697 if (player->MovPos == 0)
14698 player->is_pushing = FALSE;
14700 player->is_snapping = FALSE;
14702 if (player->MovPos == 0)
14704 player->is_moving = FALSE;
14705 player->is_digging = FALSE;
14706 player->is_collecting = FALSE;
14712 // prevent snapping with already pressed snap key when not allowed
14713 if (player->is_snapping && !can_continue_snapping)
14716 player->MovDir = snap_direction;
14718 if (player->MovPos == 0)
14720 player->is_moving = FALSE;
14721 player->is_digging = FALSE;
14722 player->is_collecting = FALSE;
14725 player->is_dropping = FALSE;
14726 player->is_dropping_pressed = FALSE;
14727 player->drop_pressed_delay = 0;
14729 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14732 player->is_snapping = TRUE;
14733 player->is_active = TRUE;
14735 if (player->MovPos == 0)
14737 player->is_moving = FALSE;
14738 player->is_digging = FALSE;
14739 player->is_collecting = FALSE;
14742 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14743 TEST_DrawLevelField(player->last_jx, player->last_jy);
14745 TEST_DrawLevelField(x, y);
14750 static boolean DropElement(struct PlayerInfo *player)
14752 int old_element, new_element;
14753 int dropx = player->jx, dropy = player->jy;
14754 int drop_direction = player->MovDir;
14755 int drop_side = drop_direction;
14756 int drop_element = get_next_dropped_element(player);
14758 /* do not drop an element on top of another element; when holding drop key
14759 pressed without moving, dropped element must move away before the next
14760 element can be dropped (this is especially important if the next element
14761 is dynamite, which can be placed on background for historical reasons) */
14762 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14765 if (IS_THROWABLE(drop_element))
14767 dropx += GET_DX_FROM_DIR(drop_direction);
14768 dropy += GET_DY_FROM_DIR(drop_direction);
14770 if (!IN_LEV_FIELD(dropx, dropy))
14774 old_element = Tile[dropx][dropy]; // old element at dropping position
14775 new_element = drop_element; // default: no change when dropping
14777 // check if player is active, not moving and ready to drop
14778 if (!player->active || player->MovPos || player->drop_delay > 0)
14781 // check if player has anything that can be dropped
14782 if (new_element == EL_UNDEFINED)
14785 // only set if player has anything that can be dropped
14786 player->is_dropping_pressed = TRUE;
14788 // check if drop key was pressed long enough for EM style dynamite
14789 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14792 // check if anything can be dropped at the current position
14793 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14796 // collected custom elements can only be dropped on empty fields
14797 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14800 if (old_element != EL_EMPTY)
14801 Back[dropx][dropy] = old_element; // store old element on this field
14803 ResetGfxAnimation(dropx, dropy);
14804 ResetRandomAnimationValue(dropx, dropy);
14806 if (player->inventory_size > 0 ||
14807 player->inventory_infinite_element != EL_UNDEFINED)
14809 if (player->inventory_size > 0)
14811 player->inventory_size--;
14813 DrawGameDoorValues();
14815 if (new_element == EL_DYNAMITE)
14816 new_element = EL_DYNAMITE_ACTIVE;
14817 else if (new_element == EL_EM_DYNAMITE)
14818 new_element = EL_EM_DYNAMITE_ACTIVE;
14819 else if (new_element == EL_SP_DISK_RED)
14820 new_element = EL_SP_DISK_RED_ACTIVE;
14823 Tile[dropx][dropy] = new_element;
14825 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14826 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14827 el2img(Tile[dropx][dropy]), 0);
14829 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14831 // needed if previous element just changed to "empty" in the last frame
14832 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14834 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14835 player->index_bit, drop_side);
14836 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14838 player->index_bit, drop_side);
14840 TestIfElementTouchesCustomElement(dropx, dropy);
14842 else // player is dropping a dyna bomb
14844 player->dynabombs_left--;
14846 Tile[dropx][dropy] = new_element;
14848 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14849 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14850 el2img(Tile[dropx][dropy]), 0);
14852 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14855 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14856 InitField_WithBug1(dropx, dropy, FALSE);
14858 new_element = Tile[dropx][dropy]; // element might have changed
14860 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14861 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14863 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14864 MovDir[dropx][dropy] = drop_direction;
14866 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14868 // do not cause impact style collision by dropping elements that can fall
14869 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14872 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14873 player->is_dropping = TRUE;
14875 player->drop_pressed_delay = 0;
14876 player->is_dropping_pressed = FALSE;
14878 player->drop_x = dropx;
14879 player->drop_y = dropy;
14884 // ----------------------------------------------------------------------------
14885 // game sound playing functions
14886 // ----------------------------------------------------------------------------
14888 static int *loop_sound_frame = NULL;
14889 static int *loop_sound_volume = NULL;
14891 void InitPlayLevelSound(void)
14893 int num_sounds = getSoundListSize();
14895 checked_free(loop_sound_frame);
14896 checked_free(loop_sound_volume);
14898 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14899 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14902 static void PlayLevelSound(int x, int y, int nr)
14904 int sx = SCREENX(x), sy = SCREENY(y);
14905 int volume, stereo_position;
14906 int max_distance = 8;
14907 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14909 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14910 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14913 if (!IN_LEV_FIELD(x, y) ||
14914 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14915 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14918 volume = SOUND_MAX_VOLUME;
14920 if (!IN_SCR_FIELD(sx, sy))
14922 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
14923 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
14925 volume -= volume * (dx > dy ? dx : dy) / max_distance;
14928 stereo_position = (SOUND_MAX_LEFT +
14929 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
14930 (SCR_FIELDX + 2 * max_distance));
14932 if (IS_LOOP_SOUND(nr))
14934 /* This assures that quieter loop sounds do not overwrite louder ones,
14935 while restarting sound volume comparison with each new game frame. */
14937 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
14940 loop_sound_volume[nr] = volume;
14941 loop_sound_frame[nr] = FrameCounter;
14944 PlaySoundExt(nr, volume, stereo_position, type);
14947 static void PlayLevelSoundNearest(int x, int y, int sound_action)
14949 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
14950 x > LEVELX(BX2) ? LEVELX(BX2) : x,
14951 y < LEVELY(BY1) ? LEVELY(BY1) :
14952 y > LEVELY(BY2) ? LEVELY(BY2) : y,
14956 static void PlayLevelSoundAction(int x, int y, int action)
14958 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
14961 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
14963 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14965 if (sound_effect != SND_UNDEFINED)
14966 PlayLevelSound(x, y, sound_effect);
14969 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
14972 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
14974 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14975 PlayLevelSound(x, y, sound_effect);
14978 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
14980 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14982 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14983 PlayLevelSound(x, y, sound_effect);
14986 static void StopLevelSoundActionIfLoop(int x, int y, int action)
14988 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
14990 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
14991 StopSound(sound_effect);
14994 static int getLevelMusicNr(void)
14996 if (levelset.music[level_nr] != MUS_UNDEFINED)
14997 return levelset.music[level_nr]; // from config file
14999 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15002 static void FadeLevelSounds(void)
15007 static void FadeLevelMusic(void)
15009 int music_nr = getLevelMusicNr();
15010 char *curr_music = getCurrentlyPlayingMusicFilename();
15011 char *next_music = getMusicInfoEntryFilename(music_nr);
15013 if (!strEqual(curr_music, next_music))
15017 void FadeLevelSoundsAndMusic(void)
15023 static void PlayLevelMusic(void)
15025 int music_nr = getLevelMusicNr();
15026 char *curr_music = getCurrentlyPlayingMusicFilename();
15027 char *next_music = getMusicInfoEntryFilename(music_nr);
15029 if (!strEqual(curr_music, next_music))
15030 PlayMusicLoop(music_nr);
15033 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15035 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15037 int x = xx - offset;
15038 int y = yy - offset;
15043 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15047 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15051 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15055 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15059 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15063 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15067 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15070 case SOUND_android_clone:
15071 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15074 case SOUND_android_move:
15075 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15079 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15083 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15087 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15090 case SOUND_eater_eat:
15091 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15095 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15098 case SOUND_collect:
15099 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15102 case SOUND_diamond:
15103 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15107 // !!! CHECK THIS !!!
15109 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15111 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15115 case SOUND_wonderfall:
15116 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15120 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15124 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15128 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15132 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15136 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15140 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15144 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15148 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15151 case SOUND_exit_open:
15152 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15155 case SOUND_exit_leave:
15156 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15159 case SOUND_dynamite:
15160 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15164 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15168 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15172 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15180 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15184 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15188 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15193 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15195 int element = map_element_SP_to_RND(element_sp);
15196 int action = map_action_SP_to_RND(action_sp);
15197 int offset = (setup.sp_show_border_elements ? 0 : 1);
15198 int x = xx - offset;
15199 int y = yy - offset;
15201 PlayLevelSoundElementAction(x, y, element, action);
15204 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15206 int element = map_element_MM_to_RND(element_mm);
15207 int action = map_action_MM_to_RND(action_mm);
15209 int x = xx - offset;
15210 int y = yy - offset;
15212 if (!IS_MM_ELEMENT(element))
15213 element = EL_MM_DEFAULT;
15215 PlayLevelSoundElementAction(x, y, element, action);
15218 void PlaySound_MM(int sound_mm)
15220 int sound = map_sound_MM_to_RND(sound_mm);
15222 if (sound == SND_UNDEFINED)
15228 void PlaySoundLoop_MM(int sound_mm)
15230 int sound = map_sound_MM_to_RND(sound_mm);
15232 if (sound == SND_UNDEFINED)
15235 PlaySoundLoop(sound);
15238 void StopSound_MM(int sound_mm)
15240 int sound = map_sound_MM_to_RND(sound_mm);
15242 if (sound == SND_UNDEFINED)
15248 void RaiseScore(int value)
15250 game.score += value;
15252 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15254 DisplayGameControlValues();
15257 void RaiseScoreElement(int element)
15262 case EL_BD_DIAMOND:
15263 case EL_EMERALD_YELLOW:
15264 case EL_EMERALD_RED:
15265 case EL_EMERALD_PURPLE:
15266 case EL_SP_INFOTRON:
15267 RaiseScore(level.score[SC_EMERALD]);
15270 RaiseScore(level.score[SC_DIAMOND]);
15273 RaiseScore(level.score[SC_CRYSTAL]);
15276 RaiseScore(level.score[SC_PEARL]);
15279 case EL_BD_BUTTERFLY:
15280 case EL_SP_ELECTRON:
15281 RaiseScore(level.score[SC_BUG]);
15284 case EL_BD_FIREFLY:
15285 case EL_SP_SNIKSNAK:
15286 RaiseScore(level.score[SC_SPACESHIP]);
15289 case EL_DARK_YAMYAM:
15290 RaiseScore(level.score[SC_YAMYAM]);
15293 RaiseScore(level.score[SC_ROBOT]);
15296 RaiseScore(level.score[SC_PACMAN]);
15299 RaiseScore(level.score[SC_NUT]);
15302 case EL_EM_DYNAMITE:
15303 case EL_SP_DISK_RED:
15304 case EL_DYNABOMB_INCREASE_NUMBER:
15305 case EL_DYNABOMB_INCREASE_SIZE:
15306 case EL_DYNABOMB_INCREASE_POWER:
15307 RaiseScore(level.score[SC_DYNAMITE]);
15309 case EL_SHIELD_NORMAL:
15310 case EL_SHIELD_DEADLY:
15311 RaiseScore(level.score[SC_SHIELD]);
15313 case EL_EXTRA_TIME:
15314 RaiseScore(level.extra_time_score);
15328 case EL_DC_KEY_WHITE:
15329 RaiseScore(level.score[SC_KEY]);
15332 RaiseScore(element_info[element].collect_score);
15337 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15339 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15341 // closing door required in case of envelope style request dialogs
15344 // prevent short reactivation of overlay buttons while closing door
15345 SetOverlayActive(FALSE);
15347 CloseDoor(DOOR_CLOSE_1);
15350 if (network.enabled)
15351 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15355 FadeSkipNextFadeIn();
15357 SetGameStatus(GAME_MODE_MAIN);
15362 else // continue playing the game
15364 if (tape.playing && tape.deactivate_display)
15365 TapeDeactivateDisplayOff(TRUE);
15367 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15369 if (tape.playing && tape.deactivate_display)
15370 TapeDeactivateDisplayOn();
15374 void RequestQuitGame(boolean ask_if_really_quit)
15376 boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
15377 boolean skip_request = game.all_players_gone || quick_quit;
15379 RequestQuitGameExt(skip_request, quick_quit,
15380 "Do you really want to quit the game?");
15383 void RequestRestartGame(char *message)
15385 game.restart_game_message = NULL;
15387 boolean has_started_game = hasStartedNetworkGame();
15388 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15390 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15392 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15396 // needed in case of envelope request to close game panel
15397 CloseDoor(DOOR_CLOSE_1);
15399 SetGameStatus(GAME_MODE_MAIN);
15405 void CheckGameOver(void)
15407 static boolean last_game_over = FALSE;
15408 static int game_over_delay = 0;
15409 int game_over_delay_value = 50;
15410 boolean game_over = checkGameFailed();
15412 // do not handle game over if request dialog is already active
15413 if (game.request_active)
15416 // do not ask to play again if game was never actually played
15417 if (!game.GamePlayed)
15422 last_game_over = FALSE;
15423 game_over_delay = game_over_delay_value;
15428 if (game_over_delay > 0)
15435 if (last_game_over != game_over)
15436 game.restart_game_message = (hasStartedNetworkGame() ?
15437 "Game over! Play it again?" :
15440 last_game_over = game_over;
15443 boolean checkGameSolved(void)
15445 // set for all game engines if level was solved
15446 return game.LevelSolved_GameEnd;
15449 boolean checkGameFailed(void)
15451 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15452 return (game_em.game_over && !game_em.level_solved);
15453 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15454 return (game_sp.game_over && !game_sp.level_solved);
15455 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15456 return (game_mm.game_over && !game_mm.level_solved);
15457 else // GAME_ENGINE_TYPE_RND
15458 return (game.GameOver && !game.LevelSolved);
15461 boolean checkGameEnded(void)
15463 return (checkGameSolved() || checkGameFailed());
15467 // ----------------------------------------------------------------------------
15468 // random generator functions
15469 // ----------------------------------------------------------------------------
15471 unsigned int InitEngineRandom_RND(int seed)
15473 game.num_random_calls = 0;
15475 return InitEngineRandom(seed);
15478 unsigned int RND(int max)
15482 game.num_random_calls++;
15484 return GetEngineRandom(max);
15491 // ----------------------------------------------------------------------------
15492 // game engine snapshot handling functions
15493 // ----------------------------------------------------------------------------
15495 struct EngineSnapshotInfo
15497 // runtime values for custom element collect score
15498 int collect_score[NUM_CUSTOM_ELEMENTS];
15500 // runtime values for group element choice position
15501 int choice_pos[NUM_GROUP_ELEMENTS];
15503 // runtime values for belt position animations
15504 int belt_graphic[4][NUM_BELT_PARTS];
15505 int belt_anim_mode[4][NUM_BELT_PARTS];
15508 static struct EngineSnapshotInfo engine_snapshot_rnd;
15509 static char *snapshot_level_identifier = NULL;
15510 static int snapshot_level_nr = -1;
15512 static void SaveEngineSnapshotValues_RND(void)
15514 static int belt_base_active_element[4] =
15516 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15517 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15518 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15519 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15523 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15525 int element = EL_CUSTOM_START + i;
15527 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15530 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15532 int element = EL_GROUP_START + i;
15534 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15537 for (i = 0; i < 4; i++)
15539 for (j = 0; j < NUM_BELT_PARTS; j++)
15541 int element = belt_base_active_element[i] + j;
15542 int graphic = el2img(element);
15543 int anim_mode = graphic_info[graphic].anim_mode;
15545 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15546 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15551 static void LoadEngineSnapshotValues_RND(void)
15553 unsigned int num_random_calls = game.num_random_calls;
15556 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15558 int element = EL_CUSTOM_START + i;
15560 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15563 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15565 int element = EL_GROUP_START + i;
15567 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15570 for (i = 0; i < 4; i++)
15572 for (j = 0; j < NUM_BELT_PARTS; j++)
15574 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15575 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15577 graphic_info[graphic].anim_mode = anim_mode;
15581 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15583 InitRND(tape.random_seed);
15584 for (i = 0; i < num_random_calls; i++)
15588 if (game.num_random_calls != num_random_calls)
15590 Error("number of random calls out of sync");
15591 Error("number of random calls should be %d", num_random_calls);
15592 Error("number of random calls is %d", game.num_random_calls);
15594 Fail("this should not happen -- please debug");
15598 void FreeEngineSnapshotSingle(void)
15600 FreeSnapshotSingle();
15602 setString(&snapshot_level_identifier, NULL);
15603 snapshot_level_nr = -1;
15606 void FreeEngineSnapshotList(void)
15608 FreeSnapshotList();
15611 static ListNode *SaveEngineSnapshotBuffers(void)
15613 ListNode *buffers = NULL;
15615 // copy some special values to a structure better suited for the snapshot
15617 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15618 SaveEngineSnapshotValues_RND();
15619 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15620 SaveEngineSnapshotValues_EM();
15621 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15622 SaveEngineSnapshotValues_SP(&buffers);
15623 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15624 SaveEngineSnapshotValues_MM(&buffers);
15626 // save values stored in special snapshot structure
15628 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15629 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15630 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15631 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15632 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15633 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15634 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15635 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15637 // save further RND engine values
15639 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15640 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15641 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15643 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15644 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15645 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15646 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15647 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15649 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15650 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15651 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15653 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15655 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15656 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15658 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15659 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15660 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15661 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15662 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15663 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15664 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15665 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15666 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15667 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15668 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15669 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15670 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15671 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15672 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15673 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15674 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15675 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15677 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15678 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15680 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15681 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15682 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15684 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15685 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15687 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15688 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15689 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15690 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15691 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15693 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15694 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15697 ListNode *node = engine_snapshot_list_rnd;
15700 while (node != NULL)
15702 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15707 Debug("game:playing:SaveEngineSnapshotBuffers",
15708 "size of engine snapshot: %d bytes", num_bytes);
15714 void SaveEngineSnapshotSingle(void)
15716 ListNode *buffers = SaveEngineSnapshotBuffers();
15718 // finally save all snapshot buffers to single snapshot
15719 SaveSnapshotSingle(buffers);
15721 // save level identification information
15722 setString(&snapshot_level_identifier, leveldir_current->identifier);
15723 snapshot_level_nr = level_nr;
15726 boolean CheckSaveEngineSnapshotToList(void)
15728 boolean save_snapshot =
15729 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15730 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15731 game.snapshot.changed_action) ||
15732 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15733 game.snapshot.collected_item));
15735 game.snapshot.changed_action = FALSE;
15736 game.snapshot.collected_item = FALSE;
15737 game.snapshot.save_snapshot = save_snapshot;
15739 return save_snapshot;
15742 void SaveEngineSnapshotToList(void)
15744 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15748 ListNode *buffers = SaveEngineSnapshotBuffers();
15750 // finally save all snapshot buffers to snapshot list
15751 SaveSnapshotToList(buffers);
15754 void SaveEngineSnapshotToListInitial(void)
15756 FreeEngineSnapshotList();
15758 SaveEngineSnapshotToList();
15761 static void LoadEngineSnapshotValues(void)
15763 // restore special values from snapshot structure
15765 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15766 LoadEngineSnapshotValues_RND();
15767 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15768 LoadEngineSnapshotValues_EM();
15769 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15770 LoadEngineSnapshotValues_SP();
15771 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15772 LoadEngineSnapshotValues_MM();
15775 void LoadEngineSnapshotSingle(void)
15777 LoadSnapshotSingle();
15779 LoadEngineSnapshotValues();
15782 static void LoadEngineSnapshot_Undo(int steps)
15784 LoadSnapshotFromList_Older(steps);
15786 LoadEngineSnapshotValues();
15789 static void LoadEngineSnapshot_Redo(int steps)
15791 LoadSnapshotFromList_Newer(steps);
15793 LoadEngineSnapshotValues();
15796 boolean CheckEngineSnapshotSingle(void)
15798 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15799 snapshot_level_nr == level_nr);
15802 boolean CheckEngineSnapshotList(void)
15804 return CheckSnapshotList();
15808 // ---------- new game button stuff -------------------------------------------
15815 boolean *setup_value;
15816 boolean allowed_on_tape;
15817 boolean is_touch_button;
15819 } gamebutton_info[NUM_GAME_BUTTONS] =
15822 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15823 GAME_CTRL_ID_STOP, NULL,
15824 TRUE, FALSE, "stop game"
15827 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15828 GAME_CTRL_ID_PAUSE, NULL,
15829 TRUE, FALSE, "pause game"
15832 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15833 GAME_CTRL_ID_PLAY, NULL,
15834 TRUE, FALSE, "play game"
15837 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15838 GAME_CTRL_ID_UNDO, NULL,
15839 TRUE, FALSE, "undo step"
15842 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15843 GAME_CTRL_ID_REDO, NULL,
15844 TRUE, FALSE, "redo step"
15847 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15848 GAME_CTRL_ID_SAVE, NULL,
15849 TRUE, FALSE, "save game"
15852 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15853 GAME_CTRL_ID_PAUSE2, NULL,
15854 TRUE, FALSE, "pause game"
15857 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15858 GAME_CTRL_ID_LOAD, NULL,
15859 TRUE, FALSE, "load game"
15862 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15863 GAME_CTRL_ID_PANEL_STOP, NULL,
15864 FALSE, FALSE, "stop game"
15867 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15868 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15869 FALSE, FALSE, "pause game"
15872 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15873 GAME_CTRL_ID_PANEL_PLAY, NULL,
15874 FALSE, FALSE, "play game"
15877 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15878 GAME_CTRL_ID_TOUCH_STOP, NULL,
15879 FALSE, TRUE, "stop game"
15882 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15883 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15884 FALSE, TRUE, "pause game"
15887 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15888 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15889 TRUE, FALSE, "background music on/off"
15892 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15893 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15894 TRUE, FALSE, "sound loops on/off"
15897 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15898 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15899 TRUE, FALSE, "normal sounds on/off"
15902 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15903 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15904 FALSE, FALSE, "background music on/off"
15907 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15908 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15909 FALSE, FALSE, "sound loops on/off"
15912 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15913 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15914 FALSE, FALSE, "normal sounds on/off"
15918 void CreateGameButtons(void)
15922 for (i = 0; i < NUM_GAME_BUTTONS; i++)
15924 int graphic = gamebutton_info[i].graphic;
15925 struct GraphicInfo *gfx = &graphic_info[graphic];
15926 struct XY *pos = gamebutton_info[i].pos;
15927 struct GadgetInfo *gi;
15930 unsigned int event_mask;
15931 boolean is_touch_button = gamebutton_info[i].is_touch_button;
15932 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
15933 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
15934 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
15935 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
15936 int gd_x = gfx->src_x;
15937 int gd_y = gfx->src_y;
15938 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
15939 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
15940 int gd_xa = gfx->src_x + gfx->active_xoffset;
15941 int gd_ya = gfx->src_y + gfx->active_yoffset;
15942 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
15943 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
15944 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
15945 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
15948 if (gfx->bitmap == NULL)
15950 game_gadget[id] = NULL;
15955 if (id == GAME_CTRL_ID_STOP ||
15956 id == GAME_CTRL_ID_PANEL_STOP ||
15957 id == GAME_CTRL_ID_TOUCH_STOP ||
15958 id == GAME_CTRL_ID_PLAY ||
15959 id == GAME_CTRL_ID_PANEL_PLAY ||
15960 id == GAME_CTRL_ID_SAVE ||
15961 id == GAME_CTRL_ID_LOAD)
15963 button_type = GD_TYPE_NORMAL_BUTTON;
15965 event_mask = GD_EVENT_RELEASED;
15967 else if (id == GAME_CTRL_ID_UNDO ||
15968 id == GAME_CTRL_ID_REDO)
15970 button_type = GD_TYPE_NORMAL_BUTTON;
15972 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
15976 button_type = GD_TYPE_CHECK_BUTTON;
15977 checked = (gamebutton_info[i].setup_value != NULL ?
15978 *gamebutton_info[i].setup_value : FALSE);
15979 event_mask = GD_EVENT_PRESSED;
15982 gi = CreateGadget(GDI_CUSTOM_ID, id,
15983 GDI_IMAGE_ID, graphic,
15984 GDI_INFO_TEXT, gamebutton_info[i].infotext,
15987 GDI_WIDTH, gfx->width,
15988 GDI_HEIGHT, gfx->height,
15989 GDI_TYPE, button_type,
15990 GDI_STATE, GD_BUTTON_UNPRESSED,
15991 GDI_CHECKED, checked,
15992 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
15993 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
15994 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
15995 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
15996 GDI_DIRECT_DRAW, FALSE,
15997 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
15998 GDI_EVENT_MASK, event_mask,
15999 GDI_CALLBACK_ACTION, HandleGameButtons,
16003 Fail("cannot create gadget");
16005 game_gadget[id] = gi;
16009 void FreeGameButtons(void)
16013 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16014 FreeGadget(game_gadget[i]);
16017 static void UnmapGameButtonsAtSamePosition(int id)
16021 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16023 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16024 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16025 UnmapGadget(game_gadget[i]);
16028 static void UnmapGameButtonsAtSamePosition_All(void)
16030 if (setup.show_snapshot_buttons)
16032 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16033 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16034 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16038 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16039 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16040 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16042 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16043 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16044 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16048 static void MapGameButtonsAtSamePosition(int id)
16052 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16054 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16055 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16056 MapGadget(game_gadget[i]);
16058 UnmapGameButtonsAtSamePosition_All();
16061 void MapUndoRedoButtons(void)
16063 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16064 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16066 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16067 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16070 void UnmapUndoRedoButtons(void)
16072 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16073 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16075 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16076 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16079 void ModifyPauseButtons(void)
16083 GAME_CTRL_ID_PAUSE,
16084 GAME_CTRL_ID_PAUSE2,
16085 GAME_CTRL_ID_PANEL_PAUSE,
16086 GAME_CTRL_ID_TOUCH_PAUSE,
16091 for (i = 0; ids[i] > -1; i++)
16092 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16095 static void MapGameButtonsExt(boolean on_tape)
16099 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16100 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16101 i != GAME_CTRL_ID_UNDO &&
16102 i != GAME_CTRL_ID_REDO)
16103 MapGadget(game_gadget[i]);
16105 UnmapGameButtonsAtSamePosition_All();
16107 RedrawGameButtons();
16110 static void UnmapGameButtonsExt(boolean on_tape)
16114 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16115 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16116 UnmapGadget(game_gadget[i]);
16119 static void RedrawGameButtonsExt(boolean on_tape)
16123 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16124 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16125 RedrawGadget(game_gadget[i]);
16128 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16133 gi->checked = state;
16136 static void RedrawSoundButtonGadget(int id)
16138 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16139 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16140 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16141 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16142 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16143 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16146 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16147 RedrawGadget(game_gadget[id2]);
16150 void MapGameButtons(void)
16152 MapGameButtonsExt(FALSE);
16155 void UnmapGameButtons(void)
16157 UnmapGameButtonsExt(FALSE);
16160 void RedrawGameButtons(void)
16162 RedrawGameButtonsExt(FALSE);
16165 void MapGameButtonsOnTape(void)
16167 MapGameButtonsExt(TRUE);
16170 void UnmapGameButtonsOnTape(void)
16172 UnmapGameButtonsExt(TRUE);
16175 void RedrawGameButtonsOnTape(void)
16177 RedrawGameButtonsExt(TRUE);
16180 static void GameUndoRedoExt(void)
16182 ClearPlayerAction();
16184 tape.pausing = TRUE;
16187 UpdateAndDisplayGameControlValues();
16189 DrawCompleteVideoDisplay();
16190 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16191 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16192 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16197 static void GameUndo(int steps)
16199 if (!CheckEngineSnapshotList())
16202 LoadEngineSnapshot_Undo(steps);
16207 static void GameRedo(int steps)
16209 if (!CheckEngineSnapshotList())
16212 LoadEngineSnapshot_Redo(steps);
16217 static void HandleGameButtonsExt(int id, int button)
16219 static boolean game_undo_executed = FALSE;
16220 int steps = BUTTON_STEPSIZE(button);
16221 boolean handle_game_buttons =
16222 (game_status == GAME_MODE_PLAYING ||
16223 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16225 if (!handle_game_buttons)
16230 case GAME_CTRL_ID_STOP:
16231 case GAME_CTRL_ID_PANEL_STOP:
16232 case GAME_CTRL_ID_TOUCH_STOP:
16233 if (game_status == GAME_MODE_MAIN)
16239 RequestQuitGame(TRUE);
16243 case GAME_CTRL_ID_PAUSE:
16244 case GAME_CTRL_ID_PAUSE2:
16245 case GAME_CTRL_ID_PANEL_PAUSE:
16246 case GAME_CTRL_ID_TOUCH_PAUSE:
16247 if (network.enabled && game_status == GAME_MODE_PLAYING)
16250 SendToServer_ContinuePlaying();
16252 SendToServer_PausePlaying();
16255 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16257 game_undo_executed = FALSE;
16261 case GAME_CTRL_ID_PLAY:
16262 case GAME_CTRL_ID_PANEL_PLAY:
16263 if (game_status == GAME_MODE_MAIN)
16265 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16267 else if (tape.pausing)
16269 if (network.enabled)
16270 SendToServer_ContinuePlaying();
16272 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16276 case GAME_CTRL_ID_UNDO:
16277 // Important: When using "save snapshot when collecting an item" mode,
16278 // load last (current) snapshot for first "undo" after pressing "pause"
16279 // (else the last-but-one snapshot would be loaded, because the snapshot
16280 // pointer already points to the last snapshot when pressing "pause",
16281 // which is fine for "every step/move" mode, but not for "every collect")
16282 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16283 !game_undo_executed)
16286 game_undo_executed = TRUE;
16291 case GAME_CTRL_ID_REDO:
16295 case GAME_CTRL_ID_SAVE:
16299 case GAME_CTRL_ID_LOAD:
16303 case SOUND_CTRL_ID_MUSIC:
16304 case SOUND_CTRL_ID_PANEL_MUSIC:
16305 if (setup.sound_music)
16307 setup.sound_music = FALSE;
16311 else if (audio.music_available)
16313 setup.sound = setup.sound_music = TRUE;
16315 SetAudioMode(setup.sound);
16317 if (game_status == GAME_MODE_PLAYING)
16321 RedrawSoundButtonGadget(id);
16325 case SOUND_CTRL_ID_LOOPS:
16326 case SOUND_CTRL_ID_PANEL_LOOPS:
16327 if (setup.sound_loops)
16328 setup.sound_loops = FALSE;
16329 else if (audio.loops_available)
16331 setup.sound = setup.sound_loops = TRUE;
16333 SetAudioMode(setup.sound);
16336 RedrawSoundButtonGadget(id);
16340 case SOUND_CTRL_ID_SIMPLE:
16341 case SOUND_CTRL_ID_PANEL_SIMPLE:
16342 if (setup.sound_simple)
16343 setup.sound_simple = FALSE;
16344 else if (audio.sound_available)
16346 setup.sound = setup.sound_simple = TRUE;
16348 SetAudioMode(setup.sound);
16351 RedrawSoundButtonGadget(id);
16360 static void HandleGameButtons(struct GadgetInfo *gi)
16362 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16365 void HandleSoundButtonKeys(Key key)
16367 if (key == setup.shortcut.sound_simple)
16368 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16369 else if (key == setup.shortcut.sound_loops)
16370 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16371 else if (key == setup.shortcut.sound_music)
16372 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);