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_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void TestIfPlayerTouchesCustomElement(int, int);
1062 static void TestIfElementTouchesCustomElement(int, int);
1063 static void TestIfElementHitsCustomElement(int, int, int);
1065 static void HandleElementChange(int, int, int);
1066 static void ExecuteCustomElementAction(int, int, int, int);
1067 static boolean ChangeElement(int, int, int, int);
1069 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1070 #define CheckTriggeredElementChange(x, y, e, ev) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1072 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1073 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1074 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1075 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1076 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1078 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1081 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1082 #define CheckElementChange(x, y, e, te, ev) \
1083 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1084 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1085 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1086 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1087 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1088 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1089 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1091 static void PlayLevelSound(int, int, int);
1092 static void PlayLevelSoundNearest(int, int, int);
1093 static void PlayLevelSoundAction(int, int, int);
1094 static void PlayLevelSoundElementAction(int, int, int, int);
1095 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1096 static void PlayLevelSoundActionIfLoop(int, int, int);
1097 static void StopLevelSoundActionIfLoop(int, int, int);
1098 static void PlayLevelMusic(void);
1099 static void FadeLevelSoundsAndMusic(void);
1101 static void HandleGameButtons(struct GadgetInfo *);
1103 int AmoebaNeighbourNr(int, int);
1104 void AmoebaToDiamond(int, int);
1105 void ContinueMoving(int, int);
1106 void Bang(int, int);
1107 void InitMovDir(int, int);
1108 void InitAmoebaNr(int, int);
1109 void NewHighScore(int);
1111 void TestIfGoodThingHitsBadThing(int, int, int);
1112 void TestIfBadThingHitsGoodThing(int, int, int);
1113 void TestIfPlayerTouchesBadThing(int, int);
1114 void TestIfPlayerRunsIntoBadThing(int, int, int);
1115 void TestIfBadThingTouchesPlayer(int, int);
1116 void TestIfBadThingRunsIntoPlayer(int, int, int);
1117 void TestIfFriendTouchesBadThing(int, int);
1118 void TestIfBadThingTouchesFriend(int, int);
1119 void TestIfBadThingTouchesOtherBadThing(int, int);
1120 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1122 void KillPlayer(struct PlayerInfo *);
1123 void BuryPlayer(struct PlayerInfo *);
1124 void RemovePlayer(struct PlayerInfo *);
1125 void ExitPlayer(struct PlayerInfo *);
1127 static int getInvisibleActiveFromInvisibleElement(int);
1128 static int getInvisibleFromInvisibleActiveElement(int);
1130 static void TestFieldAfterSnapping(int, int, int, int, int);
1132 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1134 // for detection of endless loops, caused by custom element programming
1135 // (using maximal playfield width x 10 is just a rough approximation)
1136 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1138 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1140 if (recursion_loop_detected) \
1143 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1145 recursion_loop_detected = TRUE; \
1146 recursion_loop_element = (e); \
1149 recursion_loop_depth++; \
1152 #define RECURSION_LOOP_DETECTION_END() \
1154 recursion_loop_depth--; \
1157 static int recursion_loop_depth;
1158 static boolean recursion_loop_detected;
1159 static boolean recursion_loop_element;
1161 static int map_player_action[MAX_PLAYERS];
1164 // ----------------------------------------------------------------------------
1165 // definition of elements that automatically change to other elements after
1166 // a specified time, eventually calling a function when changing
1167 // ----------------------------------------------------------------------------
1169 // forward declaration for changer functions
1170 static void InitBuggyBase(int, int);
1171 static void WarnBuggyBase(int, int);
1173 static void InitTrap(int, int);
1174 static void ActivateTrap(int, int);
1175 static void ChangeActiveTrap(int, int);
1177 static void InitRobotWheel(int, int);
1178 static void RunRobotWheel(int, int);
1179 static void StopRobotWheel(int, int);
1181 static void InitTimegateWheel(int, int);
1182 static void RunTimegateWheel(int, int);
1184 static void InitMagicBallDelay(int, int);
1185 static void ActivateMagicBall(int, int);
1187 struct ChangingElementInfo
1192 void (*pre_change_function)(int x, int y);
1193 void (*change_function)(int x, int y);
1194 void (*post_change_function)(int x, int y);
1197 static struct ChangingElementInfo change_delay_list[] =
1232 EL_STEEL_EXIT_OPENING,
1240 EL_STEEL_EXIT_CLOSING,
1241 EL_STEEL_EXIT_CLOSED,
1264 EL_EM_STEEL_EXIT_OPENING,
1265 EL_EM_STEEL_EXIT_OPEN,
1272 EL_EM_STEEL_EXIT_CLOSING,
1296 EL_SWITCHGATE_OPENING,
1304 EL_SWITCHGATE_CLOSING,
1305 EL_SWITCHGATE_CLOSED,
1312 EL_TIMEGATE_OPENING,
1320 EL_TIMEGATE_CLOSING,
1329 EL_ACID_SPLASH_LEFT,
1337 EL_ACID_SPLASH_RIGHT,
1346 EL_SP_BUGGY_BASE_ACTIVATING,
1353 EL_SP_BUGGY_BASE_ACTIVATING,
1354 EL_SP_BUGGY_BASE_ACTIVE,
1361 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_ROBOT_WHEEL_ACTIVE,
1393 EL_TIMEGATE_SWITCH_ACTIVE,
1401 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1402 EL_DC_TIMEGATE_SWITCH,
1409 EL_EMC_MAGIC_BALL_ACTIVE,
1410 EL_EMC_MAGIC_BALL_ACTIVE,
1417 EL_EMC_SPRING_BUMPER_ACTIVE,
1418 EL_EMC_SPRING_BUMPER,
1425 EL_DIAGONAL_SHRINKING,
1433 EL_DIAGONAL_GROWING,
1454 int push_delay_fixed, push_delay_random;
1458 { EL_SPRING, 0, 0 },
1459 { EL_BALLOON, 0, 0 },
1461 { EL_SOKOBAN_OBJECT, 2, 0 },
1462 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1463 { EL_SATELLITE, 2, 0 },
1464 { EL_SP_DISK_YELLOW, 2, 0 },
1466 { EL_UNDEFINED, 0, 0 },
1474 move_stepsize_list[] =
1476 { EL_AMOEBA_DROP, 2 },
1477 { EL_AMOEBA_DROPPING, 2 },
1478 { EL_QUICKSAND_FILLING, 1 },
1479 { EL_QUICKSAND_EMPTYING, 1 },
1480 { EL_QUICKSAND_FAST_FILLING, 2 },
1481 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1482 { EL_MAGIC_WALL_FILLING, 2 },
1483 { EL_MAGIC_WALL_EMPTYING, 2 },
1484 { EL_BD_MAGIC_WALL_FILLING, 2 },
1485 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1486 { EL_DC_MAGIC_WALL_FILLING, 2 },
1487 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_UNDEFINED, 0 },
1497 collect_count_list[] =
1500 { EL_BD_DIAMOND, 1 },
1501 { EL_EMERALD_YELLOW, 1 },
1502 { EL_EMERALD_RED, 1 },
1503 { EL_EMERALD_PURPLE, 1 },
1505 { EL_SP_INFOTRON, 1 },
1509 { EL_UNDEFINED, 0 },
1517 access_direction_list[] =
1519 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1521 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1522 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1525 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1526 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1527 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1528 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1529 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1531 { EL_SP_PORT_LEFT, MV_RIGHT },
1532 { EL_SP_PORT_RIGHT, MV_LEFT },
1533 { EL_SP_PORT_UP, MV_DOWN },
1534 { EL_SP_PORT_DOWN, MV_UP },
1535 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1536 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1537 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1538 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1546 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1547 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1548 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1549 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1551 { EL_UNDEFINED, MV_NONE }
1554 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1556 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1557 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1558 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1559 IS_JUST_CHANGING(x, y))
1561 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1563 // static variables for playfield scan mode (scanning forward or backward)
1564 static int playfield_scan_start_x = 0;
1565 static int playfield_scan_start_y = 0;
1566 static int playfield_scan_delta_x = 1;
1567 static int playfield_scan_delta_y = 1;
1569 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1570 (y) >= 0 && (y) <= lev_fieldy - 1; \
1571 (y) += playfield_scan_delta_y) \
1572 for ((x) = playfield_scan_start_x; \
1573 (x) >= 0 && (x) <= lev_fieldx - 1; \
1574 (x) += playfield_scan_delta_x)
1577 void DEBUG_SetMaximumDynamite(void)
1581 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1582 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1583 local_player->inventory_element[local_player->inventory_size++] =
1588 static void InitPlayfieldScanModeVars(void)
1590 if (game.use_reverse_scan_direction)
1592 playfield_scan_start_x = lev_fieldx - 1;
1593 playfield_scan_start_y = lev_fieldy - 1;
1595 playfield_scan_delta_x = -1;
1596 playfield_scan_delta_y = -1;
1600 playfield_scan_start_x = 0;
1601 playfield_scan_start_y = 0;
1603 playfield_scan_delta_x = 1;
1604 playfield_scan_delta_y = 1;
1608 static void InitPlayfieldScanMode(int mode)
1610 game.use_reverse_scan_direction =
1611 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1613 InitPlayfieldScanModeVars();
1616 static int get_move_delay_from_stepsize(int move_stepsize)
1619 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1621 // make sure that stepsize value is always a power of 2
1622 move_stepsize = (1 << log_2(move_stepsize));
1624 return TILEX / move_stepsize;
1627 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1630 int player_nr = player->index_nr;
1631 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1632 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1634 // do no immediately change move delay -- the player might just be moving
1635 player->move_delay_value_next = move_delay;
1637 // information if player can move must be set separately
1638 player->cannot_move = cannot_move;
1642 player->move_delay = game.initial_move_delay[player_nr];
1643 player->move_delay_value = game.initial_move_delay_value[player_nr];
1645 player->move_delay_value_next = -1;
1647 player->move_delay_reset_counter = 0;
1651 void GetPlayerConfig(void)
1653 GameFrameDelay = setup.game_frame_delay;
1655 if (!audio.sound_available)
1656 setup.sound_simple = FALSE;
1658 if (!audio.loops_available)
1659 setup.sound_loops = FALSE;
1661 if (!audio.music_available)
1662 setup.sound_music = FALSE;
1664 if (!video.fullscreen_available)
1665 setup.fullscreen = FALSE;
1667 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1669 SetAudioMode(setup.sound);
1672 int GetElementFromGroupElement(int element)
1674 if (IS_GROUP_ELEMENT(element))
1676 struct ElementGroupInfo *group = element_info[element].group;
1677 int last_anim_random_frame = gfx.anim_random_frame;
1680 if (group->choice_mode == ANIM_RANDOM)
1681 gfx.anim_random_frame = RND(group->num_elements_resolved);
1683 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1684 group->choice_mode, 0,
1687 if (group->choice_mode == ANIM_RANDOM)
1688 gfx.anim_random_frame = last_anim_random_frame;
1690 group->choice_pos++;
1692 element = group->element_resolved[element_pos];
1698 static void IncrementSokobanFieldsNeeded(void)
1700 if (level.sb_fields_needed)
1701 game.sokoban_fields_still_needed++;
1704 static void IncrementSokobanObjectsNeeded(void)
1706 if (level.sb_objects_needed)
1707 game.sokoban_objects_still_needed++;
1710 static void DecrementSokobanFieldsNeeded(void)
1712 if (game.sokoban_fields_still_needed > 0)
1713 game.sokoban_fields_still_needed--;
1716 static void DecrementSokobanObjectsNeeded(void)
1718 if (game.sokoban_objects_still_needed > 0)
1719 game.sokoban_objects_still_needed--;
1722 static void InitPlayerField(int x, int y, int element, boolean init_game)
1724 if (element == EL_SP_MURPHY)
1728 if (stored_player[0].present)
1730 Tile[x][y] = EL_SP_MURPHY_CLONE;
1736 stored_player[0].initial_element = element;
1737 stored_player[0].use_murphy = TRUE;
1739 if (!level.use_artwork_element[0])
1740 stored_player[0].artwork_element = EL_SP_MURPHY;
1743 Tile[x][y] = EL_PLAYER_1;
1749 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1750 int jx = player->jx, jy = player->jy;
1752 player->present = TRUE;
1754 player->block_last_field = (element == EL_SP_MURPHY ?
1755 level.sp_block_last_field :
1756 level.block_last_field);
1758 // ---------- initialize player's last field block delay ------------------
1760 // always start with reliable default value (no adjustment needed)
1761 player->block_delay_adjustment = 0;
1763 // special case 1: in Supaplex, Murphy blocks last field one more frame
1764 if (player->block_last_field && element == EL_SP_MURPHY)
1765 player->block_delay_adjustment = 1;
1767 // special case 2: in game engines before 3.1.1, blocking was different
1768 if (game.use_block_last_field_bug)
1769 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1771 if (!network.enabled || player->connected_network)
1773 player->active = TRUE;
1775 // remove potentially duplicate players
1776 if (StorePlayer[jx][jy] == Tile[x][y])
1777 StorePlayer[jx][jy] = 0;
1779 StorePlayer[x][y] = Tile[x][y];
1781 #if DEBUG_INIT_PLAYER
1782 Debug("game:init:player", "- player element %d activated",
1783 player->element_nr);
1784 Debug("game:init:player", " (local player is %d and currently %s)",
1785 local_player->element_nr,
1786 local_player->active ? "active" : "not active");
1790 Tile[x][y] = EL_EMPTY;
1792 player->jx = player->last_jx = x;
1793 player->jy = player->last_jy = y;
1796 // always check if player was just killed and should be reanimated
1798 int player_nr = GET_PLAYER_NR(element);
1799 struct PlayerInfo *player = &stored_player[player_nr];
1801 if (player->active && player->killed)
1802 player->reanimated = TRUE; // if player was just killed, reanimate him
1806 static void InitField(int x, int y, boolean init_game)
1808 int element = Tile[x][y];
1817 InitPlayerField(x, y, element, init_game);
1820 case EL_SOKOBAN_FIELD_PLAYER:
1821 element = Tile[x][y] = EL_PLAYER_1;
1822 InitField(x, y, init_game);
1824 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1825 InitField(x, y, init_game);
1828 case EL_SOKOBAN_FIELD_EMPTY:
1829 IncrementSokobanFieldsNeeded();
1832 case EL_SOKOBAN_OBJECT:
1833 IncrementSokobanObjectsNeeded();
1837 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1838 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1839 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1843 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1844 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1845 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1846 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1855 case EL_SPACESHIP_RIGHT:
1856 case EL_SPACESHIP_UP:
1857 case EL_SPACESHIP_LEFT:
1858 case EL_SPACESHIP_DOWN:
1859 case EL_BD_BUTTERFLY:
1860 case EL_BD_BUTTERFLY_RIGHT:
1861 case EL_BD_BUTTERFLY_UP:
1862 case EL_BD_BUTTERFLY_LEFT:
1863 case EL_BD_BUTTERFLY_DOWN:
1865 case EL_BD_FIREFLY_RIGHT:
1866 case EL_BD_FIREFLY_UP:
1867 case EL_BD_FIREFLY_LEFT:
1868 case EL_BD_FIREFLY_DOWN:
1869 case EL_PACMAN_RIGHT:
1871 case EL_PACMAN_LEFT:
1872 case EL_PACMAN_DOWN:
1874 case EL_YAMYAM_LEFT:
1875 case EL_YAMYAM_RIGHT:
1877 case EL_YAMYAM_DOWN:
1878 case EL_DARK_YAMYAM:
1881 case EL_SP_SNIKSNAK:
1882 case EL_SP_ELECTRON:
1888 case EL_SPRING_LEFT:
1889 case EL_SPRING_RIGHT:
1893 case EL_AMOEBA_FULL:
1898 case EL_AMOEBA_DROP:
1899 if (y == lev_fieldy - 1)
1901 Tile[x][y] = EL_AMOEBA_GROWING;
1902 Store[x][y] = EL_AMOEBA_WET;
1906 case EL_DYNAMITE_ACTIVE:
1907 case EL_SP_DISK_RED_ACTIVE:
1908 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1909 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1910 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1912 MovDelay[x][y] = 96;
1915 case EL_EM_DYNAMITE_ACTIVE:
1916 MovDelay[x][y] = 32;
1920 game.lights_still_needed++;
1924 game.friends_still_needed++;
1929 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1932 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1933 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1934 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1935 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1946 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1947 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1948 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1950 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1952 game.belt_dir[belt_nr] = belt_dir;
1953 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1955 else // more than one switch -- set it like the first switch
1957 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1962 case EL_LIGHT_SWITCH_ACTIVE:
1964 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1967 case EL_INVISIBLE_STEELWALL:
1968 case EL_INVISIBLE_WALL:
1969 case EL_INVISIBLE_SAND:
1970 if (game.light_time_left > 0 ||
1971 game.lenses_time_left > 0)
1972 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1975 case EL_EMC_MAGIC_BALL:
1976 if (game.ball_active)
1977 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1980 case EL_EMC_MAGIC_BALL_SWITCH:
1981 if (game.ball_active)
1982 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1985 case EL_TRIGGER_PLAYER:
1986 case EL_TRIGGER_ELEMENT:
1987 case EL_TRIGGER_CE_VALUE:
1988 case EL_TRIGGER_CE_SCORE:
1990 case EL_ANY_ELEMENT:
1991 case EL_CURRENT_CE_VALUE:
1992 case EL_CURRENT_CE_SCORE:
2009 // reference elements should not be used on the playfield
2010 Tile[x][y] = EL_EMPTY;
2014 if (IS_CUSTOM_ELEMENT(element))
2016 if (CAN_MOVE(element))
2019 if (!element_info[element].use_last_ce_value || init_game)
2020 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2022 else if (IS_GROUP_ELEMENT(element))
2024 Tile[x][y] = GetElementFromGroupElement(element);
2026 InitField(x, y, init_game);
2033 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2036 static void InitField_WithBug1(int x, int y, boolean init_game)
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(Tile[x][y]))
2046 static void InitField_WithBug2(int x, int y, boolean init_game)
2048 int old_element = Tile[x][y];
2050 InitField(x, y, init_game);
2052 // not needed to call InitMovDir() -- already done by InitField()!
2053 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2054 CAN_MOVE(old_element) &&
2055 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2058 /* this case is in fact a combination of not less than three bugs:
2059 first, it calls InitMovDir() for elements that can move, although this is
2060 already done by InitField(); then, it checks the element that was at this
2061 field _before_ the call to InitField() (which can change it); lastly, it
2062 was not called for "mole with direction" elements, which were treated as
2063 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2067 static int get_key_element_from_nr(int key_nr)
2069 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2070 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2071 EL_EM_KEY_1 : EL_KEY_1);
2073 return key_base_element + key_nr;
2076 static int get_next_dropped_element(struct PlayerInfo *player)
2078 return (player->inventory_size > 0 ?
2079 player->inventory_element[player->inventory_size - 1] :
2080 player->inventory_infinite_element != EL_UNDEFINED ?
2081 player->inventory_infinite_element :
2082 player->dynabombs_left > 0 ?
2083 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2087 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2089 // pos >= 0: get element from bottom of the stack;
2090 // pos < 0: get element from top of the stack
2094 int min_inventory_size = -pos;
2095 int inventory_pos = player->inventory_size - min_inventory_size;
2096 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2098 return (player->inventory_size >= min_inventory_size ?
2099 player->inventory_element[inventory_pos] :
2100 player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 int min_dynabombs_left = pos + 1;
2109 int min_inventory_size = pos + 1 - player->dynabombs_left;
2110 int inventory_pos = pos - player->dynabombs_left;
2112 return (player->inventory_infinite_element != EL_UNDEFINED ?
2113 player->inventory_infinite_element :
2114 player->dynabombs_left >= min_dynabombs_left ?
2115 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 player->inventory_size >= min_inventory_size ?
2117 player->inventory_element[inventory_pos] :
2122 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2124 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2125 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2128 if (gpo1->sort_priority != gpo2->sort_priority)
2129 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2131 compare_result = gpo1->nr - gpo2->nr;
2133 return compare_result;
2136 int getPlayerInventorySize(int player_nr)
2138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2139 return game_em.ply[player_nr]->dynamite;
2140 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2141 return game_sp.red_disk_count;
2143 return stored_player[player_nr].inventory_size;
2146 static void InitGameControlValues(void)
2150 for (i = 0; game_panel_controls[i].nr != -1; i++)
2152 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2153 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2154 struct TextPosInfo *pos = gpc->pos;
2156 int type = gpc->type;
2160 Error("'game_panel_controls' structure corrupted at %d", i);
2162 Fail("this should not happen -- please debug");
2165 // force update of game controls after initialization
2166 gpc->value = gpc->last_value = -1;
2167 gpc->frame = gpc->last_frame = -1;
2168 gpc->gfx_frame = -1;
2170 // determine panel value width for later calculation of alignment
2171 if (type == TYPE_INTEGER || type == TYPE_STRING)
2173 pos->width = pos->size * getFontWidth(pos->font);
2174 pos->height = getFontHeight(pos->font);
2176 else if (type == TYPE_ELEMENT)
2178 pos->width = pos->size;
2179 pos->height = pos->size;
2182 // fill structure for game panel draw order
2184 gpo->sort_priority = pos->sort_priority;
2187 // sort game panel controls according to sort_priority and control number
2188 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2189 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2192 static void UpdatePlayfieldElementCount(void)
2194 boolean use_element_count = FALSE;
2197 // first check if it is needed at all to calculate playfield element count
2198 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2199 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2200 use_element_count = TRUE;
2202 if (!use_element_count)
2205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2206 element_info[i].element_count = 0;
2208 SCAN_PLAYFIELD(x, y)
2210 element_info[Tile[x][y]].element_count++;
2213 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2214 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2215 if (IS_IN_GROUP(j, i))
2216 element_info[EL_GROUP_START + i].element_count +=
2217 element_info[j].element_count;
2220 static void UpdateGameControlValues(void)
2223 int time = (game.LevelSolved ?
2224 game.LevelSolved_CountingTime :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 game_sp.time_played :
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 game_mm.energy_left :
2231 game.no_time_limit ? TimePlayed : TimeLeft);
2232 int score = (game.LevelSolved ?
2233 game.LevelSolved_CountingScore :
2234 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 game_em.lev->score :
2236 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2242 game_em.lev->gems_needed :
2243 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2244 game_sp.infotrons_still_needed :
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2246 game_mm.kettles_still_needed :
2247 game.gems_still_needed);
2248 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 game_em.lev->gems_needed > 0 :
2250 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2251 game_sp.infotrons_still_needed > 0 :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 game_mm.kettles_still_needed > 0 ||
2254 game_mm.lights_still_needed > 0 :
2255 game.gems_still_needed > 0 ||
2256 game.sokoban_fields_still_needed > 0 ||
2257 game.sokoban_objects_still_needed > 0 ||
2258 game.lights_still_needed > 0);
2259 int health = (game.LevelSolved ?
2260 game.LevelSolved_CountingHealth :
2261 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2262 MM_HEALTH(game_mm.laser_overload_value) :
2264 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2266 UpdatePlayfieldElementCount();
2268 // update game panel control values
2270 // used instead of "level_nr" (for network games)
2271 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2272 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2274 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2275 for (i = 0; i < MAX_NUM_KEYS; i++)
2276 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2277 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2278 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2280 if (game.centered_player_nr == -1)
2282 for (i = 0; i < MAX_PLAYERS; i++)
2284 // only one player in Supaplex game engine
2285 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2288 for (k = 0; k < MAX_NUM_KEYS; k++)
2290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2292 if (game_em.ply[i]->keys & (1 << k))
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2296 else if (stored_player[i].key[k])
2297 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2298 get_key_element_from_nr(k);
2301 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2302 getPlayerInventorySize(i);
2304 if (stored_player[i].num_white_keys > 0)
2305 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2308 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2309 stored_player[i].num_white_keys;
2314 int player_nr = game.centered_player_nr;
2316 for (k = 0; k < MAX_NUM_KEYS; k++)
2318 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2320 if (game_em.ply[player_nr]->keys & (1 << k))
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2324 else if (stored_player[player_nr].key[k])
2325 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2326 get_key_element_from_nr(k);
2329 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2330 getPlayerInventorySize(player_nr);
2332 if (stored_player[player_nr].num_white_keys > 0)
2333 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2336 stored_player[player_nr].num_white_keys;
2339 // re-arrange keys on game panel, if needed or if defined by style settings
2340 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2342 int nr = GAME_PANEL_KEY_1 + i;
2343 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2344 struct TextPosInfo *pos = gpc->pos;
2346 // skip check if key is not in the player's inventory
2347 if (gpc->value == EL_EMPTY)
2350 // check if keys should be arranged on panel from left to right
2351 if (pos->style == STYLE_LEFTMOST_POSITION)
2353 // check previous key positions (left from current key)
2354 for (k = 0; k < i; k++)
2356 int nr_new = GAME_PANEL_KEY_1 + k;
2358 if (game_panel_controls[nr_new].value == EL_EMPTY)
2360 game_panel_controls[nr_new].value = gpc->value;
2361 gpc->value = EL_EMPTY;
2368 // check if "undefined" keys can be placed at some other position
2369 if (pos->x == -1 && pos->y == -1)
2371 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2373 // 1st try: display key at the same position as normal or EM keys
2374 if (game_panel_controls[nr_new].value == EL_EMPTY)
2376 game_panel_controls[nr_new].value = gpc->value;
2380 // 2nd try: display key at the next free position in the key panel
2381 for (k = 0; k < STD_NUM_KEYS; k++)
2383 nr_new = GAME_PANEL_KEY_1 + k;
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2396 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2398 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, i);
2400 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2401 get_inventory_element_from_pos(local_player, -i - 1);
2404 game_panel_controls[GAME_PANEL_SCORE].value = score;
2405 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2407 game_panel_controls[GAME_PANEL_TIME].value = time;
2409 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2410 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2411 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2413 if (level.time == 0)
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2416 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2418 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2419 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2421 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2423 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2424 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2427 local_player->shield_normal_time_left;
2428 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2429 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2432 local_player->shield_deadly_time_left;
2434 game_panel_controls[GAME_PANEL_EXIT].value =
2435 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2439 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2440 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2441 EL_EMC_MAGIC_BALL_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2444 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2445 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2446 game.light_time_left;
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2449 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2450 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2451 game.timegate_time_left;
2453 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2454 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2456 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2457 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2458 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2459 game.lenses_time_left;
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2462 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2463 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2464 game.magnify_time_left;
2466 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2467 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2468 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2469 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2470 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2471 EL_BALLOON_SWITCH_NONE);
2473 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2474 local_player->dynabomb_count;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2476 local_player->dynabomb_size;
2477 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2478 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_PENGUINS].value =
2481 game.friends_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2484 game.sokoban_objects_still_needed;
2485 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2486 game.sokoban_fields_still_needed;
2488 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2489 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2491 for (i = 0; i < NUM_BELTS; i++)
2493 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2494 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2495 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2497 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2501 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2502 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2503 game.magic_wall_time_left;
2505 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2506 local_player->gravity;
2508 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2509 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2511 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2512 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2513 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2514 game.panel.element[i].id : EL_UNDEFINED);
2516 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2517 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2518 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2519 element_info[game.panel.element_count[i].id].element_count : 0);
2521 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2522 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2523 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2524 element_info[game.panel.ce_score[i].id].collect_score : 0);
2526 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2527 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2528 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2529 element_info[game.panel.ce_score_element[i].id].collect_score :
2532 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2533 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2534 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2536 // update game panel control frames
2538 for (i = 0; game_panel_controls[i].nr != -1; i++)
2540 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2542 if (gpc->type == TYPE_ELEMENT)
2544 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2546 int last_anim_random_frame = gfx.anim_random_frame;
2547 int element = gpc->value;
2548 int graphic = el2panelimg(element);
2549 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2550 sync_random_frame : INIT_GFX_RANDOM());
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = init_gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2570 gpc->gfx_frame = element_info[element].collect_score;
2572 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = last_anim_random_frame;
2578 else if (gpc->type == TYPE_GRAPHIC)
2580 if (gpc->graphic != IMG_UNDEFINED)
2582 int last_anim_random_frame = gfx.anim_random_frame;
2583 int graphic = gpc->graphic;
2584 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2585 sync_random_frame : INIT_GFX_RANDOM());
2587 if (gpc->value != gpc->last_value)
2590 gpc->gfx_random = init_gfx_random;
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2597 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2598 gpc->gfx_random = init_gfx_random;
2601 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2602 gfx.anim_random_frame = gpc->gfx_random;
2604 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2606 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2607 gfx.anim_random_frame = last_anim_random_frame;
2613 static void DisplayGameControlValues(void)
2615 boolean redraw_panel = FALSE;
2618 for (i = 0; game_panel_controls[i].nr != -1; i++)
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2622 if (PANEL_DEACTIVATED(gpc->pos))
2625 if (gpc->value == gpc->last_value &&
2626 gpc->frame == gpc->last_frame)
2629 redraw_panel = TRUE;
2635 // copy default game door content to main double buffer
2637 // !!! CHECK AGAIN !!!
2638 SetPanelBackground();
2639 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2640 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2642 // redraw game control buttons
2643 RedrawGameButtons();
2645 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2647 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2649 int nr = game_panel_order[i].nr;
2650 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2651 struct TextPosInfo *pos = gpc->pos;
2652 int type = gpc->type;
2653 int value = gpc->value;
2654 int frame = gpc->frame;
2655 int size = pos->size;
2656 int font = pos->font;
2657 boolean draw_masked = pos->draw_masked;
2658 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2660 if (PANEL_DEACTIVATED(pos))
2663 if (pos->class == get_hash_from_key("extra_panel_items") &&
2664 !setup.prefer_extra_panel_items)
2667 gpc->last_value = value;
2668 gpc->last_frame = frame;
2670 if (type == TYPE_INTEGER)
2672 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2673 nr == GAME_PANEL_TIME)
2675 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2677 if (use_dynamic_size) // use dynamic number of digits
2679 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2680 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2681 int size2 = size1 + 1;
2682 int font1 = pos->font;
2683 int font2 = pos->font_alt;
2685 size = (value < value_change ? size1 : size2);
2686 font = (value < value_change ? font1 : font2);
2690 // correct text size if "digits" is zero or less
2692 size = strlen(int2str(value, size));
2694 // dynamically correct text alignment
2695 pos->width = size * getFontWidth(font);
2697 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2698 int2str(value, size), font, mask_mode);
2700 else if (type == TYPE_ELEMENT)
2702 int element, graphic;
2706 int dst_x = PANEL_XPOS(pos);
2707 int dst_y = PANEL_YPOS(pos);
2709 if (value != EL_UNDEFINED && value != EL_EMPTY)
2712 graphic = el2panelimg(value);
2715 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2716 element, EL_NAME(element), size);
2719 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2722 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2725 width = graphic_info[graphic].width * size / TILESIZE;
2726 height = graphic_info[graphic].height * size / TILESIZE;
2729 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2732 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2736 else if (type == TYPE_GRAPHIC)
2738 int graphic = gpc->graphic;
2739 int graphic_active = gpc->graphic_active;
2743 int dst_x = PANEL_XPOS(pos);
2744 int dst_y = PANEL_YPOS(pos);
2745 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2746 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2748 if (graphic != IMG_UNDEFINED && !skip)
2750 if (pos->style == STYLE_REVERSE)
2751 value = 100 - value;
2753 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2755 if (pos->direction & MV_HORIZONTAL)
2757 width = graphic_info[graphic_active].width * value / 100;
2758 height = graphic_info[graphic_active].height;
2760 if (pos->direction == MV_LEFT)
2762 src_x += graphic_info[graphic_active].width - width;
2763 dst_x += graphic_info[graphic_active].width - width;
2768 width = graphic_info[graphic_active].width;
2769 height = graphic_info[graphic_active].height * value / 100;
2771 if (pos->direction == MV_UP)
2773 src_y += graphic_info[graphic_active].height - height;
2774 dst_y += graphic_info[graphic_active].height - height;
2779 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2782 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2785 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2787 if (pos->direction & MV_HORIZONTAL)
2789 if (pos->direction == MV_RIGHT)
2796 dst_x = PANEL_XPOS(pos);
2799 width = graphic_info[graphic].width - width;
2803 if (pos->direction == MV_DOWN)
2810 dst_y = PANEL_YPOS(pos);
2813 height = graphic_info[graphic].height - height;
2817 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2820 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2824 else if (type == TYPE_STRING)
2826 boolean active = (value != 0);
2827 char *state_normal = "off";
2828 char *state_active = "on";
2829 char *state = (active ? state_active : state_normal);
2830 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2831 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2832 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2833 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2835 if (nr == GAME_PANEL_GRAVITY_STATE)
2837 int font1 = pos->font; // (used for normal state)
2838 int font2 = pos->font_alt; // (used for active state)
2840 font = (active ? font2 : font1);
2849 // don't truncate output if "chars" is zero or less
2852 // dynamically correct text alignment
2853 pos->width = size * getFontWidth(font);
2856 s_cut = getStringCopyN(s, size);
2858 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2859 s_cut, font, mask_mode);
2865 redraw_mask |= REDRAW_DOOR_1;
2868 SetGameStatus(GAME_MODE_PLAYING);
2871 void UpdateAndDisplayGameControlValues(void)
2873 if (tape.deactivate_display)
2876 UpdateGameControlValues();
2877 DisplayGameControlValues();
2880 void UpdateGameDoorValues(void)
2882 UpdateGameControlValues();
2885 void DrawGameDoorValues(void)
2887 DisplayGameControlValues();
2891 // ============================================================================
2893 // ----------------------------------------------------------------------------
2894 // initialize game engine due to level / tape version number
2895 // ============================================================================
2897 static void InitGameEngine(void)
2899 int i, j, k, l, x, y;
2901 // set game engine from tape file when re-playing, else from level file
2902 game.engine_version = (tape.playing ? tape.engine_version :
2903 level.game_version);
2905 // set single or multi-player game mode (needed for re-playing tapes)
2906 game.team_mode = setup.team_mode;
2910 int num_players = 0;
2912 for (i = 0; i < MAX_PLAYERS; i++)
2913 if (tape.player_participates[i])
2916 // multi-player tapes contain input data for more than one player
2917 game.team_mode = (num_players > 1);
2921 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2922 level.game_version);
2923 Debug("game:init:level", " tape.file_version == %06d",
2925 Debug("game:init:level", " tape.game_version == %06d",
2927 Debug("game:init:level", " tape.engine_version == %06d",
2928 tape.engine_version);
2929 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2930 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2933 // --------------------------------------------------------------------------
2934 // set flags for bugs and changes according to active game engine version
2935 // --------------------------------------------------------------------------
2939 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2941 Bug was introduced in version:
2944 Bug was fixed in version:
2948 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2949 but the property "can fall" was missing, which caused some levels to be
2950 unsolvable. This was fixed in version 4.2.0.0.
2952 Affected levels/tapes:
2953 An example for a tape that was fixed by this bugfix is tape 029 from the
2954 level set "rnd_sam_bateman".
2955 The wrong behaviour will still be used for all levels or tapes that were
2956 created/recorded with it. An example for this is tape 023 from the level
2957 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2960 boolean use_amoeba_dropping_cannot_fall_bug =
2961 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2962 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2964 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2965 tape.game_version < VERSION_IDENT(4,2,0,0)));
2968 Summary of bugfix/change:
2969 Fixed move speed of elements entering or leaving magic wall.
2971 Fixed/changed in version:
2975 Before 2.0.1, move speed of elements entering or leaving magic wall was
2976 twice as fast as it is now.
2977 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 2.0.1, which might use the old behaviour before it was changed; known tapes
2982 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2985 above, but before it was known that this change would break tapes like the
2986 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2987 although the engine version while recording maybe was before 2.0.1. There
2988 are a lot of tapes that are affected by this exception, like tape 006 from
2989 the level set "rnd_conor_mancone".
2992 boolean use_old_move_stepsize_for_magic_wall =
2993 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2995 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2996 tape.game_version < VERSION_IDENT(4,2,0,0)));
2999 Summary of bugfix/change:
3000 Fixed handling for custom elements that change when pushed by the player.
3002 Fixed/changed in version:
3006 Before 3.1.0, custom elements that "change when pushing" changed directly
3007 after the player started pushing them (until then handled in "DigField()").
3008 Since 3.1.0, these custom elements are not changed until the "pushing"
3009 move of the element is finished (now handled in "ContinueMoving()").
3011 Affected levels/tapes:
3012 The first condition is generally needed for all levels/tapes before version
3013 3.1.0, which might use the old behaviour before it was changed; known tapes
3014 that are affected are some tapes from the level set "Walpurgis Gardens" by
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3018 above (including some development versions of 3.1.0), but before it was
3019 known that this change would break tapes like the above and was fixed in
3020 3.1.1, so that the changed behaviour was active although the engine version
3021 while recording maybe was before 3.1.0. There is at least one tape that is
3022 affected by this exception, which is the tape for the one-level set "Bug
3023 Machine" by Juergen Bonhagen.
3026 game.use_change_when_pushing_bug =
3027 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3029 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3030 tape.game_version < VERSION_IDENT(3,1,1,0)));
3033 Summary of bugfix/change:
3034 Fixed handling for blocking the field the player leaves when moving.
3036 Fixed/changed in version:
3040 Before 3.1.1, when "block last field when moving" was enabled, the field
3041 the player is leaving when moving was blocked for the time of the move,
3042 and was directly unblocked afterwards. This resulted in the last field
3043 being blocked for exactly one less than the number of frames of one player
3044 move. Additionally, even when blocking was disabled, the last field was
3045 blocked for exactly one frame.
3046 Since 3.1.1, due to changes in player movement handling, the last field
3047 is not blocked at all when blocking is disabled. When blocking is enabled,
3048 the last field is blocked for exactly the number of frames of one player
3049 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3050 last field is blocked for exactly one more than the number of frames of
3053 Affected levels/tapes:
3054 (!!! yet to be determined -- probably many !!!)
3057 game.use_block_last_field_bug =
3058 (game.engine_version < VERSION_IDENT(3,1,1,0));
3060 /* various special flags and settings for native Emerald Mine game engine */
3062 game_em.use_single_button =
3063 (game.engine_version > VERSION_IDENT(4,0,0,2));
3065 game_em.use_snap_key_bug =
3066 (game.engine_version < VERSION_IDENT(4,0,1,0));
3068 game_em.use_random_bug =
3069 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3071 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3073 game_em.use_old_explosions = use_old_em_engine;
3074 game_em.use_old_android = use_old_em_engine;
3075 game_em.use_old_push_elements = use_old_em_engine;
3076 game_em.use_old_push_into_acid = use_old_em_engine;
3078 game_em.use_wrap_around = !use_old_em_engine;
3080 // --------------------------------------------------------------------------
3082 // set maximal allowed number of custom element changes per game frame
3083 game.max_num_changes_per_frame = 1;
3085 // default scan direction: scan playfield from top/left to bottom/right
3086 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3088 // dynamically adjust element properties according to game engine version
3089 InitElementPropertiesEngine(game.engine_version);
3091 // ---------- initialize special element properties -------------------------
3093 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3094 if (use_amoeba_dropping_cannot_fall_bug)
3095 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3097 // ---------- initialize player's initial move delay ------------------------
3099 // dynamically adjust player properties according to level information
3100 for (i = 0; i < MAX_PLAYERS; i++)
3101 game.initial_move_delay_value[i] =
3102 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3104 // dynamically adjust player properties according to game engine version
3105 for (i = 0; i < MAX_PLAYERS; i++)
3106 game.initial_move_delay[i] =
3107 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3108 game.initial_move_delay_value[i] : 0);
3110 // ---------- initialize player's initial push delay ------------------------
3112 // dynamically adjust player properties according to game engine version
3113 game.initial_push_delay_value =
3114 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3116 // ---------- initialize changing elements ----------------------------------
3118 // initialize changing elements information
3119 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3121 struct ElementInfo *ei = &element_info[i];
3123 // this pointer might have been changed in the level editor
3124 ei->change = &ei->change_page[0];
3126 if (!IS_CUSTOM_ELEMENT(i))
3128 ei->change->target_element = EL_EMPTY_SPACE;
3129 ei->change->delay_fixed = 0;
3130 ei->change->delay_random = 0;
3131 ei->change->delay_frames = 1;
3134 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3136 ei->has_change_event[j] = FALSE;
3138 ei->event_page_nr[j] = 0;
3139 ei->event_page[j] = &ei->change_page[0];
3143 // add changing elements from pre-defined list
3144 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3146 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3147 struct ElementInfo *ei = &element_info[ch_delay->element];
3149 ei->change->target_element = ch_delay->target_element;
3150 ei->change->delay_fixed = ch_delay->change_delay;
3152 ei->change->pre_change_function = ch_delay->pre_change_function;
3153 ei->change->change_function = ch_delay->change_function;
3154 ei->change->post_change_function = ch_delay->post_change_function;
3156 ei->change->can_change = TRUE;
3157 ei->change->can_change_or_has_action = TRUE;
3159 ei->has_change_event[CE_DELAY] = TRUE;
3161 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3162 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3165 // ---------- initialize internal run-time variables ------------------------
3167 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3169 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3171 for (j = 0; j < ei->num_change_pages; j++)
3173 ei->change_page[j].can_change_or_has_action =
3174 (ei->change_page[j].can_change |
3175 ei->change_page[j].has_action);
3179 // add change events from custom element configuration
3180 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3182 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3184 for (j = 0; j < ei->num_change_pages; j++)
3186 if (!ei->change_page[j].can_change_or_has_action)
3189 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3191 // only add event page for the first page found with this event
3192 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3194 ei->has_change_event[k] = TRUE;
3196 ei->event_page_nr[k] = j;
3197 ei->event_page[k] = &ei->change_page[j];
3203 // ---------- initialize reference elements in change conditions ------------
3205 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3207 int element = EL_CUSTOM_START + i;
3208 struct ElementInfo *ei = &element_info[element];
3210 for (j = 0; j < ei->num_change_pages; j++)
3212 int trigger_element = ei->change_page[j].initial_trigger_element;
3214 if (trigger_element >= EL_PREV_CE_8 &&
3215 trigger_element <= EL_NEXT_CE_8)
3216 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3218 ei->change_page[j].trigger_element = trigger_element;
3222 // ---------- initialize run-time trigger player and element ----------------
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3231 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3232 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3233 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3234 ei->change_page[j].actual_trigger_ce_value = 0;
3235 ei->change_page[j].actual_trigger_ce_score = 0;
3239 // ---------- initialize trigger events -------------------------------------
3241 // initialize trigger events information
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3244 trigger_events[i][j] = FALSE;
3246 // add trigger events from element change event properties
3247 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3249 struct ElementInfo *ei = &element_info[i];
3251 for (j = 0; j < ei->num_change_pages; j++)
3253 if (!ei->change_page[j].can_change_or_has_action)
3256 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3258 int trigger_element = ei->change_page[j].trigger_element;
3260 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3262 if (ei->change_page[j].has_event[k])
3264 if (IS_GROUP_ELEMENT(trigger_element))
3266 struct ElementGroupInfo *group =
3267 element_info[trigger_element].group;
3269 for (l = 0; l < group->num_elements_resolved; l++)
3270 trigger_events[group->element_resolved[l]][k] = TRUE;
3272 else if (trigger_element == EL_ANY_ELEMENT)
3273 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3274 trigger_events[l][k] = TRUE;
3276 trigger_events[trigger_element][k] = TRUE;
3283 // ---------- initialize push delay -----------------------------------------
3285 // initialize push delay values to default
3286 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3290 // set default push delay values (corrected since version 3.0.7-1)
3291 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3293 element_info[i].push_delay_fixed = 2;
3294 element_info[i].push_delay_random = 8;
3298 element_info[i].push_delay_fixed = 8;
3299 element_info[i].push_delay_random = 8;
3304 // set push delay value for certain elements from pre-defined list
3305 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3307 int e = push_delay_list[i].element;
3309 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3310 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3313 // set push delay value for Supaplex elements for newer engine versions
3314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3316 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3318 if (IS_SP_ELEMENT(i))
3320 // set SP push delay to just enough to push under a falling zonk
3321 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3323 element_info[i].push_delay_fixed = delay;
3324 element_info[i].push_delay_random = 0;
3329 // ---------- initialize move stepsize --------------------------------------
3331 // initialize move stepsize values to default
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3336 // set move stepsize value for certain elements from pre-defined list
3337 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3339 int e = move_stepsize_list[i].element;
3341 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3343 // set move stepsize value for certain elements for older engine versions
3344 if (use_old_move_stepsize_for_magic_wall)
3346 if (e == EL_MAGIC_WALL_FILLING ||
3347 e == EL_MAGIC_WALL_EMPTYING ||
3348 e == EL_BD_MAGIC_WALL_FILLING ||
3349 e == EL_BD_MAGIC_WALL_EMPTYING)
3350 element_info[e].move_stepsize *= 2;
3354 // ---------- initialize collect score --------------------------------------
3356 // initialize collect score values for custom elements from initial value
3357 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3358 if (IS_CUSTOM_ELEMENT(i))
3359 element_info[i].collect_score = element_info[i].collect_score_initial;
3361 // ---------- initialize collect count --------------------------------------
3363 // initialize collect count values for non-custom elements
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3365 if (!IS_CUSTOM_ELEMENT(i))
3366 element_info[i].collect_count_initial = 0;
3368 // add collect count values for all elements from pre-defined list
3369 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3370 element_info[collect_count_list[i].element].collect_count_initial =
3371 collect_count_list[i].count;
3373 // ---------- initialize access direction -----------------------------------
3375 // initialize access direction values to default (access from every side)
3376 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3377 if (!IS_CUSTOM_ELEMENT(i))
3378 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3380 // set access direction value for certain elements from pre-defined list
3381 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3382 element_info[access_direction_list[i].element].access_direction =
3383 access_direction_list[i].direction;
3385 // ---------- initialize explosion content ----------------------------------
3386 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (IS_CUSTOM_ELEMENT(i))
3391 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3393 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3395 element_info[i].content.e[x][y] =
3396 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3397 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3398 i == EL_PLAYER_3 ? EL_EMERALD :
3399 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3400 i == EL_MOLE ? EL_EMERALD_RED :
3401 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3402 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3403 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3404 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3405 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3406 i == EL_WALL_EMERALD ? EL_EMERALD :
3407 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3408 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3409 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3410 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3411 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3412 i == EL_WALL_PEARL ? EL_PEARL :
3413 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3418 // ---------- initialize recursion detection --------------------------------
3419 recursion_loop_depth = 0;
3420 recursion_loop_detected = FALSE;
3421 recursion_loop_element = EL_UNDEFINED;
3423 // ---------- initialize graphics engine ------------------------------------
3424 game.scroll_delay_value =
3425 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3426 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3427 !setup.forced_scroll_delay ? 0 :
3428 setup.scroll_delay ? setup.scroll_delay_value : 0);
3429 game.scroll_delay_value =
3430 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3432 // ---------- initialize game engine snapshots ------------------------------
3433 for (i = 0; i < MAX_PLAYERS; i++)
3434 game.snapshot.last_action[i] = 0;
3435 game.snapshot.changed_action = FALSE;
3436 game.snapshot.collected_item = FALSE;
3437 game.snapshot.mode =
3438 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3439 SNAPSHOT_MODE_EVERY_STEP :
3440 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3441 SNAPSHOT_MODE_EVERY_MOVE :
3442 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3443 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3444 game.snapshot.save_snapshot = FALSE;
3446 // ---------- initialize level time for Supaplex engine ---------------------
3447 // Supaplex levels with time limit currently unsupported -- should be added
3448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3451 // ---------- initialize flags for handling game actions --------------------
3453 // set flags for game actions to default values
3454 game.use_key_actions = TRUE;
3455 game.use_mouse_actions = FALSE;
3457 // when using Mirror Magic game engine, handle mouse events only
3458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3460 game.use_key_actions = FALSE;
3461 game.use_mouse_actions = TRUE;
3464 // check for custom elements with mouse click events
3465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3469 int element = EL_CUSTOM_START + i;
3471 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3472 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3473 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3474 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3475 game.use_mouse_actions = TRUE;
3480 static int get_num_special_action(int element, int action_first,
3483 int num_special_action = 0;
3486 for (i = action_first; i <= action_last; i++)
3488 boolean found = FALSE;
3490 for (j = 0; j < NUM_DIRECTIONS; j++)
3491 if (el_act_dir2img(element, i, j) !=
3492 el_act_dir2img(element, ACTION_DEFAULT, j))
3496 num_special_action++;
3501 return num_special_action;
3505 // ============================================================================
3507 // ----------------------------------------------------------------------------
3508 // initialize and start new game
3509 // ============================================================================
3511 #if DEBUG_INIT_PLAYER
3512 static void DebugPrintPlayerStatus(char *message)
3519 Debug("game:init:player", "%s:", message);
3521 for (i = 0; i < MAX_PLAYERS; i++)
3523 struct PlayerInfo *player = &stored_player[i];
3525 Debug("game:init:player",
3526 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3530 player->connected_locally,
3531 player->connected_network,
3533 (local_player == player ? " (local player)" : ""));
3540 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3541 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3542 int fade_mask = REDRAW_FIELD;
3544 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3545 boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found
3546 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3547 int initial_move_dir = MV_DOWN;
3550 // required here to update video display before fading (FIX THIS)
3551 DrawMaskedBorder(REDRAW_DOOR_2);
3553 if (!game.restart_level)
3554 CloseDoor(DOOR_CLOSE_1);
3556 SetGameStatus(GAME_MODE_PLAYING);
3558 if (level_editor_test_game)
3559 FadeSkipNextFadeOut();
3561 FadeSetEnterScreen();
3564 fade_mask = REDRAW_ALL;
3566 FadeLevelSoundsAndMusic();
3568 ExpireSoundLoops(TRUE);
3572 if (level_editor_test_game)
3573 FadeSkipNextFadeIn();
3575 // needed if different viewport properties defined for playing
3576 ChangeViewportPropertiesIfNeeded();
3580 DrawCompleteVideoDisplay();
3582 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3585 InitGameControlValues();
3589 // initialize tape actions from game when recording tape
3590 tape.use_key_actions = game.use_key_actions;
3591 tape.use_mouse_actions = game.use_mouse_actions;
3593 // initialize visible playfield size when recording tape (for team mode)
3594 tape.scr_fieldx = SCR_FIELDX;
3595 tape.scr_fieldy = SCR_FIELDY;
3598 // don't play tapes over network
3599 network_playing = (network.enabled && !tape.playing);
3601 for (i = 0; i < MAX_PLAYERS; i++)
3603 struct PlayerInfo *player = &stored_player[i];
3605 player->index_nr = i;
3606 player->index_bit = (1 << i);
3607 player->element_nr = EL_PLAYER_1 + i;
3609 player->present = FALSE;
3610 player->active = FALSE;
3611 player->mapped = FALSE;
3613 player->killed = FALSE;
3614 player->reanimated = FALSE;
3615 player->buried = FALSE;
3618 player->effective_action = 0;
3619 player->programmed_action = 0;
3620 player->snap_action = 0;
3622 player->mouse_action.lx = 0;
3623 player->mouse_action.ly = 0;
3624 player->mouse_action.button = 0;
3625 player->mouse_action.button_hint = 0;
3627 player->effective_mouse_action.lx = 0;
3628 player->effective_mouse_action.ly = 0;
3629 player->effective_mouse_action.button = 0;
3630 player->effective_mouse_action.button_hint = 0;
3632 for (j = 0; j < MAX_NUM_KEYS; j++)
3633 player->key[j] = FALSE;
3635 player->num_white_keys = 0;
3637 player->dynabomb_count = 0;
3638 player->dynabomb_size = 1;
3639 player->dynabombs_left = 0;
3640 player->dynabomb_xl = FALSE;
3642 player->MovDir = initial_move_dir;
3645 player->GfxDir = initial_move_dir;
3646 player->GfxAction = ACTION_DEFAULT;
3648 player->StepFrame = 0;
3650 player->initial_element = player->element_nr;
3651 player->artwork_element =
3652 (level.use_artwork_element[i] ? level.artwork_element[i] :
3653 player->element_nr);
3654 player->use_murphy = FALSE;
3656 player->block_last_field = FALSE; // initialized in InitPlayerField()
3657 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3659 player->gravity = level.initial_player_gravity[i];
3661 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3663 player->actual_frame_counter = 0;
3665 player->step_counter = 0;
3667 player->last_move_dir = initial_move_dir;
3669 player->is_active = FALSE;
3671 player->is_waiting = FALSE;
3672 player->is_moving = FALSE;
3673 player->is_auto_moving = FALSE;
3674 player->is_digging = FALSE;
3675 player->is_snapping = FALSE;
3676 player->is_collecting = FALSE;
3677 player->is_pushing = FALSE;
3678 player->is_switching = FALSE;
3679 player->is_dropping = FALSE;
3680 player->is_dropping_pressed = FALSE;
3682 player->is_bored = FALSE;
3683 player->is_sleeping = FALSE;
3685 player->was_waiting = TRUE;
3686 player->was_moving = FALSE;
3687 player->was_snapping = FALSE;
3688 player->was_dropping = FALSE;
3690 player->force_dropping = FALSE;
3692 player->frame_counter_bored = -1;
3693 player->frame_counter_sleeping = -1;
3695 player->anim_delay_counter = 0;
3696 player->post_delay_counter = 0;
3698 player->dir_waiting = initial_move_dir;
3699 player->action_waiting = ACTION_DEFAULT;
3700 player->last_action_waiting = ACTION_DEFAULT;
3701 player->special_action_bored = ACTION_DEFAULT;
3702 player->special_action_sleeping = ACTION_DEFAULT;
3704 player->switch_x = -1;
3705 player->switch_y = -1;
3707 player->drop_x = -1;
3708 player->drop_y = -1;
3710 player->show_envelope = 0;
3712 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3714 player->push_delay = -1; // initialized when pushing starts
3715 player->push_delay_value = game.initial_push_delay_value;
3717 player->drop_delay = 0;
3718 player->drop_pressed_delay = 0;
3720 player->last_jx = -1;
3721 player->last_jy = -1;
3725 player->shield_normal_time_left = 0;
3726 player->shield_deadly_time_left = 0;
3728 player->last_removed_element = EL_UNDEFINED;
3730 player->inventory_infinite_element = EL_UNDEFINED;
3731 player->inventory_size = 0;
3733 if (level.use_initial_inventory[i])
3735 for (j = 0; j < level.initial_inventory_size[i]; j++)
3737 int element = level.initial_inventory_content[i][j];
3738 int collect_count = element_info[element].collect_count_initial;
3741 if (!IS_CUSTOM_ELEMENT(element))
3744 if (collect_count == 0)
3745 player->inventory_infinite_element = element;
3747 for (k = 0; k < collect_count; k++)
3748 if (player->inventory_size < MAX_INVENTORY_SIZE)
3749 player->inventory_element[player->inventory_size++] = element;
3753 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3754 SnapField(player, 0, 0);
3756 map_player_action[i] = i;
3759 network_player_action_received = FALSE;
3761 // initial null action
3762 if (network_playing)
3763 SendToServer_MovePlayer(MV_NONE);
3768 TimeLeft = level.time;
3771 ScreenMovDir = MV_NONE;
3775 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3777 game.robot_wheel_x = -1;
3778 game.robot_wheel_y = -1;
3783 game.all_players_gone = FALSE;
3785 game.LevelSolved = FALSE;
3786 game.GameOver = FALSE;
3788 game.GamePlayed = !tape.playing;
3790 game.LevelSolved_GameWon = FALSE;
3791 game.LevelSolved_GameEnd = FALSE;
3792 game.LevelSolved_SaveTape = FALSE;
3793 game.LevelSolved_SaveScore = FALSE;
3795 game.LevelSolved_CountingTime = 0;
3796 game.LevelSolved_CountingScore = 0;
3797 game.LevelSolved_CountingHealth = 0;
3799 game.panel.active = TRUE;
3801 game.no_time_limit = (level.time == 0);
3803 game.yamyam_content_nr = 0;
3804 game.robot_wheel_active = FALSE;
3805 game.magic_wall_active = FALSE;
3806 game.magic_wall_time_left = 0;
3807 game.light_time_left = 0;
3808 game.timegate_time_left = 0;
3809 game.switchgate_pos = 0;
3810 game.wind_direction = level.wind_direction_initial;
3812 game.time_final = 0;
3813 game.score_time_final = 0;
3816 game.score_final = 0;
3818 game.health = MAX_HEALTH;
3819 game.health_final = MAX_HEALTH;
3821 game.gems_still_needed = level.gems_needed;
3822 game.sokoban_fields_still_needed = 0;
3823 game.sokoban_objects_still_needed = 0;
3824 game.lights_still_needed = 0;
3825 game.players_still_needed = 0;
3826 game.friends_still_needed = 0;
3828 game.lenses_time_left = 0;
3829 game.magnify_time_left = 0;
3831 game.ball_active = level.ball_active_initial;
3832 game.ball_content_nr = 0;
3834 game.explosions_delayed = TRUE;
3836 game.envelope_active = FALSE;
3838 for (i = 0; i < NUM_BELTS; i++)
3840 game.belt_dir[i] = MV_NONE;
3841 game.belt_dir_nr[i] = 3; // not moving, next moving left
3844 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3845 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3847 #if DEBUG_INIT_PLAYER
3848 DebugPrintPlayerStatus("Player status at level initialization");
3851 SCAN_PLAYFIELD(x, y)
3853 Tile[x][y] = Last[x][y] = level.field[x][y];
3854 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3855 ChangeDelay[x][y] = 0;
3856 ChangePage[x][y] = -1;
3857 CustomValue[x][y] = 0; // initialized in InitField()
3858 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3860 WasJustMoving[x][y] = 0;
3861 WasJustFalling[x][y] = 0;
3862 CheckCollision[x][y] = 0;
3863 CheckImpact[x][y] = 0;
3865 Pushed[x][y] = FALSE;
3867 ChangeCount[x][y] = 0;
3868 ChangeEvent[x][y] = -1;
3870 ExplodePhase[x][y] = 0;
3871 ExplodeDelay[x][y] = 0;
3872 ExplodeField[x][y] = EX_TYPE_NONE;
3874 RunnerVisit[x][y] = 0;
3875 PlayerVisit[x][y] = 0;
3878 GfxRandom[x][y] = INIT_GFX_RANDOM();
3879 GfxElement[x][y] = EL_UNDEFINED;
3880 GfxAction[x][y] = ACTION_DEFAULT;
3881 GfxDir[x][y] = MV_NONE;
3882 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3885 SCAN_PLAYFIELD(x, y)
3887 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3889 if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
3891 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3894 InitField(x, y, TRUE);
3896 ResetGfxAnimation(x, y);
3901 for (i = 0; i < MAX_PLAYERS; i++)
3903 struct PlayerInfo *player = &stored_player[i];
3905 // set number of special actions for bored and sleeping animation
3906 player->num_special_action_bored =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_BORING_1, ACTION_BORING_LAST);
3909 player->num_special_action_sleeping =
3910 get_num_special_action(player->artwork_element,
3911 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3914 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3915 emulate_sb ? EMU_SOKOBAN :
3916 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3918 // initialize type of slippery elements
3919 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3921 if (!IS_CUSTOM_ELEMENT(i))
3923 // default: elements slip down either to the left or right randomly
3924 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3926 // SP style elements prefer to slip down on the left side
3927 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3928 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3930 // BD style elements prefer to slip down on the left side
3931 if (game.emulation == EMU_BOULDERDASH)
3932 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3936 // initialize explosion and ignition delay
3937 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3939 if (!IS_CUSTOM_ELEMENT(i))
3942 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3943 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3944 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3945 int last_phase = (num_phase + 1) * delay;
3946 int half_phase = (num_phase / 2) * delay;
3948 element_info[i].explosion_delay = last_phase - 1;
3949 element_info[i].ignition_delay = half_phase;
3951 if (i == EL_BLACK_ORB)
3952 element_info[i].ignition_delay = 1;
3956 // correct non-moving belts to start moving left
3957 for (i = 0; i < NUM_BELTS; i++)
3958 if (game.belt_dir[i] == MV_NONE)
3959 game.belt_dir_nr[i] = 3; // not moving, next moving left
3961 #if USE_NEW_PLAYER_ASSIGNMENTS
3962 // use preferred player also in local single-player mode
3963 if (!network.enabled && !game.team_mode)
3965 int new_index_nr = setup.network_player_nr;
3967 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3969 for (i = 0; i < MAX_PLAYERS; i++)
3970 stored_player[i].connected_locally = FALSE;
3972 stored_player[new_index_nr].connected_locally = TRUE;
3976 for (i = 0; i < MAX_PLAYERS; i++)
3978 stored_player[i].connected = FALSE;
3980 // in network game mode, the local player might not be the first player
3981 if (stored_player[i].connected_locally)
3982 local_player = &stored_player[i];
3985 if (!network.enabled)
3986 local_player->connected = TRUE;
3990 for (i = 0; i < MAX_PLAYERS; i++)
3991 stored_player[i].connected = tape.player_participates[i];
3993 else if (network.enabled)
3995 // add team mode players connected over the network (needed for correct
3996 // assignment of player figures from level to locally playing players)
3998 for (i = 0; i < MAX_PLAYERS; i++)
3999 if (stored_player[i].connected_network)
4000 stored_player[i].connected = TRUE;
4002 else if (game.team_mode)
4004 // try to guess locally connected team mode players (needed for correct
4005 // assignment of player figures from level to locally playing players)
4007 for (i = 0; i < MAX_PLAYERS; i++)
4008 if (setup.input[i].use_joystick ||
4009 setup.input[i].key.left != KSYM_UNDEFINED)
4010 stored_player[i].connected = TRUE;
4013 #if DEBUG_INIT_PLAYER
4014 DebugPrintPlayerStatus("Player status after level initialization");
4017 #if DEBUG_INIT_PLAYER
4018 Debug("game:init:player", "Reassigning players ...");
4021 // check if any connected player was not found in playfield
4022 for (i = 0; i < MAX_PLAYERS; i++)
4024 struct PlayerInfo *player = &stored_player[i];
4026 if (player->connected && !player->present)
4028 struct PlayerInfo *field_player = NULL;
4030 #if DEBUG_INIT_PLAYER
4031 Debug("game:init:player",
4032 "- looking for field player for player %d ...", i + 1);
4035 // assign first free player found that is present in the playfield
4037 // first try: look for unmapped playfield player that is not connected
4038 for (j = 0; j < MAX_PLAYERS; j++)
4039 if (field_player == NULL &&
4040 stored_player[j].present &&
4041 !stored_player[j].mapped &&
4042 !stored_player[j].connected)
4043 field_player = &stored_player[j];
4045 // second try: look for *any* unmapped playfield player
4046 for (j = 0; j < MAX_PLAYERS; j++)
4047 if (field_player == NULL &&
4048 stored_player[j].present &&
4049 !stored_player[j].mapped)
4050 field_player = &stored_player[j];
4052 if (field_player != NULL)
4054 int jx = field_player->jx, jy = field_player->jy;
4056 #if DEBUG_INIT_PLAYER
4057 Debug("game:init:player", "- found player %d",
4058 field_player->index_nr + 1);
4061 player->present = FALSE;
4062 player->active = FALSE;
4064 field_player->present = TRUE;
4065 field_player->active = TRUE;
4068 player->initial_element = field_player->initial_element;
4069 player->artwork_element = field_player->artwork_element;
4071 player->block_last_field = field_player->block_last_field;
4072 player->block_delay_adjustment = field_player->block_delay_adjustment;
4075 StorePlayer[jx][jy] = field_player->element_nr;
4077 field_player->jx = field_player->last_jx = jx;
4078 field_player->jy = field_player->last_jy = jy;
4080 if (local_player == player)
4081 local_player = field_player;
4083 map_player_action[field_player->index_nr] = i;
4085 field_player->mapped = TRUE;
4087 #if DEBUG_INIT_PLAYER
4088 Debug("game:init:player", "- map_player_action[%d] == %d",
4089 field_player->index_nr + 1, i + 1);
4094 if (player->connected && player->present)
4095 player->mapped = TRUE;
4098 #if DEBUG_INIT_PLAYER
4099 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4104 // check if any connected player was not found in playfield
4105 for (i = 0; i < MAX_PLAYERS; i++)
4107 struct PlayerInfo *player = &stored_player[i];
4109 if (player->connected && !player->present)
4111 for (j = 0; j < MAX_PLAYERS; j++)
4113 struct PlayerInfo *field_player = &stored_player[j];
4114 int jx = field_player->jx, jy = field_player->jy;
4116 // assign first free player found that is present in the playfield
4117 if (field_player->present && !field_player->connected)
4119 player->present = TRUE;
4120 player->active = TRUE;
4122 field_player->present = FALSE;
4123 field_player->active = FALSE;
4125 player->initial_element = field_player->initial_element;
4126 player->artwork_element = field_player->artwork_element;
4128 player->block_last_field = field_player->block_last_field;
4129 player->block_delay_adjustment = field_player->block_delay_adjustment;
4131 StorePlayer[jx][jy] = player->element_nr;
4133 player->jx = player->last_jx = jx;
4134 player->jy = player->last_jy = jy;
4144 Debug("game:init:player", "local_player->present == %d",
4145 local_player->present);
4148 // set focus to local player for network games, else to all players
4149 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4150 game.centered_player_nr_next = game.centered_player_nr;
4151 game.set_centered_player = FALSE;
4152 game.set_centered_player_wrap = FALSE;
4154 if (network_playing && tape.recording)
4156 // store client dependent player focus when recording network games
4157 tape.centered_player_nr_next = game.centered_player_nr_next;
4158 tape.set_centered_player = TRUE;
4163 // when playing a tape, eliminate all players who do not participate
4165 #if USE_NEW_PLAYER_ASSIGNMENTS
4167 if (!game.team_mode)
4169 for (i = 0; i < MAX_PLAYERS; i++)
4171 if (stored_player[i].active &&
4172 !tape.player_participates[map_player_action[i]])
4174 struct PlayerInfo *player = &stored_player[i];
4175 int jx = player->jx, jy = player->jy;
4177 #if DEBUG_INIT_PLAYER
4178 Debug("game:init:player", "Removing player %d at (%d, %d)",
4182 player->active = FALSE;
4183 StorePlayer[jx][jy] = 0;
4184 Tile[jx][jy] = EL_EMPTY;
4191 for (i = 0; i < MAX_PLAYERS; i++)
4193 if (stored_player[i].active &&
4194 !tape.player_participates[i])
4196 struct PlayerInfo *player = &stored_player[i];
4197 int jx = player->jx, jy = player->jy;
4199 player->active = FALSE;
4200 StorePlayer[jx][jy] = 0;
4201 Tile[jx][jy] = EL_EMPTY;
4206 else if (!network.enabled && !game.team_mode) // && !tape.playing
4208 // when in single player mode, eliminate all but the local player
4210 for (i = 0; i < MAX_PLAYERS; i++)
4212 struct PlayerInfo *player = &stored_player[i];
4214 if (player->active && player != local_player)
4216 int jx = player->jx, jy = player->jy;
4218 player->active = FALSE;
4219 player->present = FALSE;
4221 StorePlayer[jx][jy] = 0;
4222 Tile[jx][jy] = EL_EMPTY;
4227 for (i = 0; i < MAX_PLAYERS; i++)
4228 if (stored_player[i].active)
4229 game.players_still_needed++;
4231 if (level.solved_by_one_player)
4232 game.players_still_needed = 1;
4234 // when recording the game, store which players take part in the game
4237 #if USE_NEW_PLAYER_ASSIGNMENTS
4238 for (i = 0; i < MAX_PLAYERS; i++)
4239 if (stored_player[i].connected)
4240 tape.player_participates[i] = TRUE;
4242 for (i = 0; i < MAX_PLAYERS; i++)
4243 if (stored_player[i].active)
4244 tape.player_participates[i] = TRUE;
4248 #if DEBUG_INIT_PLAYER
4249 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4252 if (BorderElement == EL_EMPTY)
4255 SBX_Right = lev_fieldx - SCR_FIELDX;
4257 SBY_Lower = lev_fieldy - SCR_FIELDY;
4262 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4264 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4267 if (full_lev_fieldx <= SCR_FIELDX)
4268 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4269 if (full_lev_fieldy <= SCR_FIELDY)
4270 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4272 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4274 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4277 // if local player not found, look for custom element that might create
4278 // the player (make some assumptions about the right custom element)
4279 if (!local_player->present)
4281 int start_x = 0, start_y = 0;
4282 int found_rating = 0;
4283 int found_element = EL_UNDEFINED;
4284 int player_nr = local_player->index_nr;
4286 SCAN_PLAYFIELD(x, y)
4288 int element = Tile[x][y];
4293 if (level.use_start_element[player_nr] &&
4294 level.start_element[player_nr] == element &&
4301 found_element = element;
4304 if (!IS_CUSTOM_ELEMENT(element))
4307 if (CAN_CHANGE(element))
4309 for (i = 0; i < element_info[element].num_change_pages; i++)
4311 // check for player created from custom element as single target
4312 content = element_info[element].change_page[i].target_element;
4313 is_player = ELEM_IS_PLAYER(content);
4315 if (is_player && (found_rating < 3 ||
4316 (found_rating == 3 && element < found_element)))
4322 found_element = element;
4327 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4329 // check for player created from custom element as explosion content
4330 content = element_info[element].content.e[xx][yy];
4331 is_player = ELEM_IS_PLAYER(content);
4333 if (is_player && (found_rating < 2 ||
4334 (found_rating == 2 && element < found_element)))
4336 start_x = x + xx - 1;
4337 start_y = y + yy - 1;
4340 found_element = element;
4343 if (!CAN_CHANGE(element))
4346 for (i = 0; i < element_info[element].num_change_pages; i++)
4348 // check for player created from custom element as extended target
4350 element_info[element].change_page[i].target_content.e[xx][yy];
4352 is_player = ELEM_IS_PLAYER(content);
4354 if (is_player && (found_rating < 1 ||
4355 (found_rating == 1 && element < found_element)))
4357 start_x = x + xx - 1;
4358 start_y = y + yy - 1;
4361 found_element = element;
4367 scroll_x = SCROLL_POSITION_X(start_x);
4368 scroll_y = SCROLL_POSITION_Y(start_y);
4372 scroll_x = SCROLL_POSITION_X(local_player->jx);
4373 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4376 // !!! FIX THIS (START) !!!
4377 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4379 InitGameEngine_EM();
4381 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4383 InitGameEngine_SP();
4385 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4387 InitGameEngine_MM();
4391 DrawLevel(REDRAW_FIELD);
4394 // after drawing the level, correct some elements
4395 if (game.timegate_time_left == 0)
4396 CloseAllOpenTimegates();
4399 // blit playfield from scroll buffer to normal back buffer for fading in
4400 BlitScreenToBitmap(backbuffer);
4401 // !!! FIX THIS (END) !!!
4403 DrawMaskedBorder(fade_mask);
4408 // full screen redraw is required at this point in the following cases:
4409 // - special editor door undrawn when game was started from level editor
4410 // - drawing area (playfield) was changed and has to be removed completely
4411 redraw_mask = REDRAW_ALL;
4415 if (!game.restart_level)
4417 // copy default game door content to main double buffer
4419 // !!! CHECK AGAIN !!!
4420 SetPanelBackground();
4421 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4422 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4425 SetPanelBackground();
4426 SetDrawBackgroundMask(REDRAW_DOOR_1);
4428 UpdateAndDisplayGameControlValues();
4430 if (!game.restart_level)
4436 CreateGameButtons();
4441 // copy actual game door content to door double buffer for OpenDoor()
4442 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4444 OpenDoor(DOOR_OPEN_ALL);
4446 KeyboardAutoRepeatOffUnlessAutoplay();
4448 #if DEBUG_INIT_PLAYER
4449 DebugPrintPlayerStatus("Player status (final)");
4458 if (!game.restart_level && !tape.playing)
4460 LevelStats_incPlayed(level_nr);
4462 SaveLevelSetup_SeriesInfo();
4465 game.restart_level = FALSE;
4466 game.restart_game_message = NULL;
4468 game.request_active = FALSE;
4469 game.request_active_or_moving = FALSE;
4471 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4472 InitGameActions_MM();
4474 SaveEngineSnapshotToListInitial();
4476 if (!game.restart_level)
4478 PlaySound(SND_GAME_STARTING);
4480 if (setup.sound_music)
4485 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4486 int actual_player_x, int actual_player_y)
4488 // this is used for non-R'n'D game engines to update certain engine values
4490 // needed to determine if sounds are played within the visible screen area
4491 scroll_x = actual_scroll_x;
4492 scroll_y = actual_scroll_y;
4494 // needed to get player position for "follow finger" playing input method
4495 local_player->jx = actual_player_x;
4496 local_player->jy = actual_player_y;
4499 void InitMovDir(int x, int y)
4501 int i, element = Tile[x][y];
4502 static int xy[4][2] =
4509 static int direction[3][4] =
4511 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4512 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4513 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4522 Tile[x][y] = EL_BUG;
4523 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4526 case EL_SPACESHIP_RIGHT:
4527 case EL_SPACESHIP_UP:
4528 case EL_SPACESHIP_LEFT:
4529 case EL_SPACESHIP_DOWN:
4530 Tile[x][y] = EL_SPACESHIP;
4531 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4534 case EL_BD_BUTTERFLY_RIGHT:
4535 case EL_BD_BUTTERFLY_UP:
4536 case EL_BD_BUTTERFLY_LEFT:
4537 case EL_BD_BUTTERFLY_DOWN:
4538 Tile[x][y] = EL_BD_BUTTERFLY;
4539 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4542 case EL_BD_FIREFLY_RIGHT:
4543 case EL_BD_FIREFLY_UP:
4544 case EL_BD_FIREFLY_LEFT:
4545 case EL_BD_FIREFLY_DOWN:
4546 Tile[x][y] = EL_BD_FIREFLY;
4547 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4550 case EL_PACMAN_RIGHT:
4552 case EL_PACMAN_LEFT:
4553 case EL_PACMAN_DOWN:
4554 Tile[x][y] = EL_PACMAN;
4555 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4558 case EL_YAMYAM_LEFT:
4559 case EL_YAMYAM_RIGHT:
4561 case EL_YAMYAM_DOWN:
4562 Tile[x][y] = EL_YAMYAM;
4563 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4566 case EL_SP_SNIKSNAK:
4567 MovDir[x][y] = MV_UP;
4570 case EL_SP_ELECTRON:
4571 MovDir[x][y] = MV_LEFT;
4578 Tile[x][y] = EL_MOLE;
4579 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4582 case EL_SPRING_LEFT:
4583 case EL_SPRING_RIGHT:
4584 Tile[x][y] = EL_SPRING;
4585 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4589 if (IS_CUSTOM_ELEMENT(element))
4591 struct ElementInfo *ei = &element_info[element];
4592 int move_direction_initial = ei->move_direction_initial;
4593 int move_pattern = ei->move_pattern;
4595 if (move_direction_initial == MV_START_PREVIOUS)
4597 if (MovDir[x][y] != MV_NONE)
4600 move_direction_initial = MV_START_AUTOMATIC;
4603 if (move_direction_initial == MV_START_RANDOM)
4604 MovDir[x][y] = 1 << RND(4);
4605 else if (move_direction_initial & MV_ANY_DIRECTION)
4606 MovDir[x][y] = move_direction_initial;
4607 else if (move_pattern == MV_ALL_DIRECTIONS ||
4608 move_pattern == MV_TURNING_LEFT ||
4609 move_pattern == MV_TURNING_RIGHT ||
4610 move_pattern == MV_TURNING_LEFT_RIGHT ||
4611 move_pattern == MV_TURNING_RIGHT_LEFT ||
4612 move_pattern == MV_TURNING_RANDOM)
4613 MovDir[x][y] = 1 << RND(4);
4614 else if (move_pattern == MV_HORIZONTAL)
4615 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4616 else if (move_pattern == MV_VERTICAL)
4617 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4618 else if (move_pattern & MV_ANY_DIRECTION)
4619 MovDir[x][y] = element_info[element].move_pattern;
4620 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4621 move_pattern == MV_ALONG_RIGHT_SIDE)
4623 // use random direction as default start direction
4624 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4625 MovDir[x][y] = 1 << RND(4);
4627 for (i = 0; i < NUM_DIRECTIONS; i++)
4629 int x1 = x + xy[i][0];
4630 int y1 = y + xy[i][1];
4632 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4634 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4635 MovDir[x][y] = direction[0][i];
4637 MovDir[x][y] = direction[1][i];
4646 MovDir[x][y] = 1 << RND(4);
4648 if (element != EL_BUG &&
4649 element != EL_SPACESHIP &&
4650 element != EL_BD_BUTTERFLY &&
4651 element != EL_BD_FIREFLY)
4654 for (i = 0; i < NUM_DIRECTIONS; i++)
4656 int x1 = x + xy[i][0];
4657 int y1 = y + xy[i][1];
4659 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4661 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4663 MovDir[x][y] = direction[0][i];
4666 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4667 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4669 MovDir[x][y] = direction[1][i];
4678 GfxDir[x][y] = MovDir[x][y];
4681 void InitAmoebaNr(int x, int y)
4684 int group_nr = AmoebaNeighbourNr(x, y);
4688 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4690 if (AmoebaCnt[i] == 0)
4698 AmoebaNr[x][y] = group_nr;
4699 AmoebaCnt[group_nr]++;
4700 AmoebaCnt2[group_nr]++;
4703 static void LevelSolved(void)
4705 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4706 game.players_still_needed > 0)
4709 game.LevelSolved = TRUE;
4710 game.GameOver = TRUE;
4715 static int time_count_steps;
4716 static int time, time_final;
4717 static float score, score_final; // needed for time score < 10 for 10 seconds
4718 static int health, health_final;
4719 static int game_over_delay_1 = 0;
4720 static int game_over_delay_2 = 0;
4721 static int game_over_delay_3 = 0;
4722 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4723 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4725 if (!game.LevelSolved_GameWon)
4729 // do not start end game actions before the player stops moving (to exit)
4730 if (local_player->active && local_player->MovPos)
4733 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4734 game.score_time_final = (level.use_step_counter ? TimePlayed :
4735 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4737 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4738 game_em.lev->score :
4739 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4743 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4744 MM_HEALTH(game_mm.laser_overload_value) :
4747 game.LevelSolved_CountingTime = game.time_final;
4748 game.LevelSolved_CountingScore = game.score_final;
4749 game.LevelSolved_CountingHealth = game.health_final;
4751 game.LevelSolved_GameWon = TRUE;
4752 game.LevelSolved_SaveTape = tape.recording;
4753 game.LevelSolved_SaveScore = !tape.playing;
4757 LevelStats_incSolved(level_nr);
4759 SaveLevelSetup_SeriesInfo();
4762 if (tape.auto_play) // tape might already be stopped here
4763 tape.auto_play_level_solved = TRUE;
4767 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4768 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4769 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4771 time = time_final = game.time_final;
4772 score = score_final = game.score_final;
4773 health = health_final = game.health_final;
4777 int time_final_max = 999;
4778 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4779 int time_frames = 0;
4780 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4781 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4786 time_frames = time_frames_left;
4788 else if (game.no_time_limit && TimePlayed < time_final_max)
4790 time_final = time_final_max;
4791 time_frames = time_frames_final_max - time_frames_played;
4794 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4796 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4798 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4801 score_final += health * time_score;
4804 game.score_final = score_final;
4805 game.health_final = health_final;
4808 if (level_editor_test_game || !setup.count_score_after_game)
4811 score = score_final;
4813 game.LevelSolved_CountingTime = time;
4814 game.LevelSolved_CountingScore = score;
4816 game_panel_controls[GAME_PANEL_TIME].value = time;
4817 game_panel_controls[GAME_PANEL_SCORE].value = score;
4819 DisplayGameControlValues();
4822 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4824 // check if last player has left the level
4825 if (game.exit_x >= 0 &&
4828 int x = game.exit_x;
4829 int y = game.exit_y;
4830 int element = Tile[x][y];
4832 // close exit door after last player
4833 if ((game.all_players_gone &&
4834 (element == EL_EXIT_OPEN ||
4835 element == EL_SP_EXIT_OPEN ||
4836 element == EL_STEEL_EXIT_OPEN)) ||
4837 element == EL_EM_EXIT_OPEN ||
4838 element == EL_EM_STEEL_EXIT_OPEN)
4842 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4843 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4844 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4845 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4846 EL_EM_STEEL_EXIT_CLOSING);
4848 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4851 // player disappears
4852 DrawLevelField(x, y);
4855 for (i = 0; i < MAX_PLAYERS; i++)
4857 struct PlayerInfo *player = &stored_player[i];
4859 if (player->present)
4861 RemovePlayer(player);
4863 // player disappears
4864 DrawLevelField(player->jx, player->jy);
4869 PlaySound(SND_GAME_WINNING);
4872 if (setup.count_score_after_game)
4874 if (time != time_final)
4876 if (game_over_delay_1 > 0)
4878 game_over_delay_1--;
4883 int time_to_go = ABS(time_final - time);
4884 int time_count_dir = (time < time_final ? +1 : -1);
4886 if (time_to_go < time_count_steps)
4887 time_count_steps = 1;
4889 time += time_count_steps * time_count_dir;
4890 score += time_count_steps * time_score;
4892 // set final score to correct rounding differences after counting score
4893 if (time == time_final)
4894 score = score_final;
4896 game.LevelSolved_CountingTime = time;
4897 game.LevelSolved_CountingScore = score;
4899 game_panel_controls[GAME_PANEL_TIME].value = time;
4900 game_panel_controls[GAME_PANEL_SCORE].value = score;
4902 DisplayGameControlValues();
4904 if (time == time_final)
4905 StopSound(SND_GAME_LEVELTIME_BONUS);
4906 else if (setup.sound_loops)
4907 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4909 PlaySound(SND_GAME_LEVELTIME_BONUS);
4914 if (health != health_final)
4916 if (game_over_delay_2 > 0)
4918 game_over_delay_2--;
4923 int health_count_dir = (health < health_final ? +1 : -1);
4925 health += health_count_dir;
4926 score += time_score;
4928 game.LevelSolved_CountingHealth = health;
4929 game.LevelSolved_CountingScore = score;
4931 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4932 game_panel_controls[GAME_PANEL_SCORE].value = score;
4934 DisplayGameControlValues();
4936 if (health == health_final)
4937 StopSound(SND_GAME_LEVELTIME_BONUS);
4938 else if (setup.sound_loops)
4939 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4941 PlaySound(SND_GAME_LEVELTIME_BONUS);
4947 game.panel.active = FALSE;
4949 if (game_over_delay_3 > 0)
4951 game_over_delay_3--;
4961 // used instead of "level_nr" (needed for network games)
4962 int last_level_nr = levelset.level_nr;
4964 game.LevelSolved_GameEnd = TRUE;
4966 if (game.LevelSolved_SaveTape)
4968 // make sure that request dialog to save tape does not open door again
4969 if (!global.use_envelope_request)
4970 CloseDoor(DOOR_CLOSE_1);
4972 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4974 // set unique basename for score tape (also saved in high score table)
4975 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4978 // if no tape is to be saved, close both doors simultaneously
4979 CloseDoor(DOOR_CLOSE_ALL);
4981 if (level_editor_test_game)
4983 SetGameStatus(GAME_MODE_MAIN);
4990 if (!game.LevelSolved_SaveScore)
4992 SetGameStatus(GAME_MODE_MAIN);
4999 if (level_nr == leveldir_current->handicap_level)
5001 leveldir_current->handicap_level++;
5003 SaveLevelSetup_SeriesInfo();
5006 // save score and score tape before potentially erasing tape below
5007 NewHighScore(last_level_nr);
5009 if (setup.increment_levels &&
5010 level_nr < leveldir_current->last_level &&
5013 level_nr++; // advance to next level
5014 TapeErase(); // start with empty tape
5016 if (setup.auto_play_next_level)
5018 LoadLevel(level_nr);
5020 SaveLevelSetup_SeriesInfo();
5024 if (scores.last_added >= 0 && setup.show_scores_after_game)
5026 SetGameStatus(GAME_MODE_SCORES);
5028 DrawHallOfFame(last_level_nr);
5030 else if (setup.auto_play_next_level && setup.increment_levels &&
5031 last_level_nr < leveldir_current->last_level &&
5034 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5038 SetGameStatus(GAME_MODE_MAIN);
5044 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5045 boolean one_score_entry_per_name)
5049 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5052 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5054 struct ScoreEntry *entry = &list->entry[i];
5055 boolean score_is_better = (new_entry->score > entry->score);
5056 boolean score_is_equal = (new_entry->score == entry->score);
5057 boolean time_is_better = (new_entry->time < entry->time);
5058 boolean time_is_equal = (new_entry->time == entry->time);
5059 boolean better_by_score = (score_is_better ||
5060 (score_is_equal && time_is_better));
5061 boolean better_by_time = (time_is_better ||
5062 (time_is_equal && score_is_better));
5063 boolean is_better = (level.rate_time_over_score ? better_by_time :
5065 boolean entry_is_empty = (entry->score == 0 &&
5068 // prevent adding server score entries if also existing in local score file
5069 if (strEqual(new_entry->tape_basename, entry->tape_basename))
5072 if (is_better || entry_is_empty)
5074 // player has made it to the hall of fame
5076 if (i < MAX_SCORE_ENTRIES - 1)
5078 int m = MAX_SCORE_ENTRIES - 1;
5081 if (one_score_entry_per_name)
5083 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5084 if (strEqual(list->entry[l].name, new_entry->name))
5087 if (m == i) // player's new highscore overwrites his old one
5091 for (l = m; l > i; l--)
5092 list->entry[l] = list->entry[l - 1];
5097 *entry = *new_entry;
5101 else if (one_score_entry_per_name &&
5102 strEqual(entry->name, new_entry->name))
5104 // player already in high score list with better score or time
5113 void NewHighScore(int level_nr)
5115 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5116 boolean one_per_name = !program.many_scores_per_name;
5118 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5119 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5121 new_entry.score = game.score_final;
5122 new_entry.time = game.score_time_final;
5124 LoadScore(level_nr);
5126 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5128 if (scores.last_added >= 0)
5130 SaveScore(level_nr);
5132 if (game.LevelSolved_SaveTape)
5134 SaveScoreTape(level_nr);
5135 SaveServerScore(level_nr);
5138 // store last added local score entry (before merging server scores)
5139 scores.last_added_local = scores.last_added;
5143 void MergeServerScore(void)
5145 boolean one_per_name = !program.many_scores_per_name;
5148 for (i = 0; i < server_scores.num_entries; i++)
5150 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5152 if (pos >= 0 && pos <= scores.last_added)
5153 scores.last_added++;
5156 if (scores.last_added >= MAX_SCORE_ENTRIES)
5157 scores.last_added = -1;
5160 static int getElementMoveStepsizeExt(int x, int y, int direction)
5162 int element = Tile[x][y];
5163 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5164 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5165 int horiz_move = (dx != 0);
5166 int sign = (horiz_move ? dx : dy);
5167 int step = sign * element_info[element].move_stepsize;
5169 // special values for move stepsize for spring and things on conveyor belt
5172 if (CAN_FALL(element) &&
5173 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5174 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5175 else if (element == EL_SPRING)
5176 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5182 static int getElementMoveStepsize(int x, int y)
5184 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5187 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5189 if (player->GfxAction != action || player->GfxDir != dir)
5191 player->GfxAction = action;
5192 player->GfxDir = dir;
5194 player->StepFrame = 0;
5198 static void ResetGfxFrame(int x, int y)
5200 // profiling showed that "autotest" spends 10~20% of its time in this function
5201 if (DrawingDeactivatedField())
5204 int element = Tile[x][y];
5205 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5207 if (graphic_info[graphic].anim_global_sync)
5208 GfxFrame[x][y] = FrameCounter;
5209 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5210 GfxFrame[x][y] = CustomValue[x][y];
5211 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5212 GfxFrame[x][y] = element_info[element].collect_score;
5213 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5214 GfxFrame[x][y] = ChangeDelay[x][y];
5217 static void ResetGfxAnimation(int x, int y)
5219 GfxAction[x][y] = ACTION_DEFAULT;
5220 GfxDir[x][y] = MovDir[x][y];
5223 ResetGfxFrame(x, y);
5226 static void ResetRandomAnimationValue(int x, int y)
5228 GfxRandom[x][y] = INIT_GFX_RANDOM();
5231 static void InitMovingField(int x, int y, int direction)
5233 int element = Tile[x][y];
5234 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5235 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5238 boolean is_moving_before, is_moving_after;
5240 // check if element was/is moving or being moved before/after mode change
5241 is_moving_before = (WasJustMoving[x][y] != 0);
5242 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5244 // reset animation only for moving elements which change direction of moving
5245 // or which just started or stopped moving
5246 // (else CEs with property "can move" / "not moving" are reset each frame)
5247 if (is_moving_before != is_moving_after ||
5248 direction != MovDir[x][y])
5249 ResetGfxAnimation(x, y);
5251 MovDir[x][y] = direction;
5252 GfxDir[x][y] = direction;
5254 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5255 direction == MV_DOWN && CAN_FALL(element) ?
5256 ACTION_FALLING : ACTION_MOVING);
5258 // this is needed for CEs with property "can move" / "not moving"
5260 if (is_moving_after)
5262 if (Tile[newx][newy] == EL_EMPTY)
5263 Tile[newx][newy] = EL_BLOCKED;
5265 MovDir[newx][newy] = MovDir[x][y];
5267 CustomValue[newx][newy] = CustomValue[x][y];
5269 GfxFrame[newx][newy] = GfxFrame[x][y];
5270 GfxRandom[newx][newy] = GfxRandom[x][y];
5271 GfxAction[newx][newy] = GfxAction[x][y];
5272 GfxDir[newx][newy] = GfxDir[x][y];
5276 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5278 int direction = MovDir[x][y];
5279 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5280 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5286 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5288 int oldx = x, oldy = y;
5289 int direction = MovDir[x][y];
5291 if (direction == MV_LEFT)
5293 else if (direction == MV_RIGHT)
5295 else if (direction == MV_UP)
5297 else if (direction == MV_DOWN)
5300 *comes_from_x = oldx;
5301 *comes_from_y = oldy;
5304 static int MovingOrBlocked2Element(int x, int y)
5306 int element = Tile[x][y];
5308 if (element == EL_BLOCKED)
5312 Blocked2Moving(x, y, &oldx, &oldy);
5313 return Tile[oldx][oldy];
5319 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5321 // like MovingOrBlocked2Element(), but if element is moving
5322 // and (x,y) is the field the moving element is just leaving,
5323 // return EL_BLOCKED instead of the element value
5324 int element = Tile[x][y];
5326 if (IS_MOVING(x, y))
5328 if (element == EL_BLOCKED)
5332 Blocked2Moving(x, y, &oldx, &oldy);
5333 return Tile[oldx][oldy];
5342 static void RemoveField(int x, int y)
5344 Tile[x][y] = EL_EMPTY;
5350 CustomValue[x][y] = 0;
5353 ChangeDelay[x][y] = 0;
5354 ChangePage[x][y] = -1;
5355 Pushed[x][y] = FALSE;
5357 GfxElement[x][y] = EL_UNDEFINED;
5358 GfxAction[x][y] = ACTION_DEFAULT;
5359 GfxDir[x][y] = MV_NONE;
5362 static void RemoveMovingField(int x, int y)
5364 int oldx = x, oldy = y, newx = x, newy = y;
5365 int element = Tile[x][y];
5366 int next_element = EL_UNDEFINED;
5368 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5371 if (IS_MOVING(x, y))
5373 Moving2Blocked(x, y, &newx, &newy);
5375 if (Tile[newx][newy] != EL_BLOCKED)
5377 // element is moving, but target field is not free (blocked), but
5378 // already occupied by something different (example: acid pool);
5379 // in this case, only remove the moving field, but not the target
5381 RemoveField(oldx, oldy);
5383 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5385 TEST_DrawLevelField(oldx, oldy);
5390 else if (element == EL_BLOCKED)
5392 Blocked2Moving(x, y, &oldx, &oldy);
5393 if (!IS_MOVING(oldx, oldy))
5397 if (element == EL_BLOCKED &&
5398 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5399 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5400 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5401 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5402 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5403 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5404 next_element = get_next_element(Tile[oldx][oldy]);
5406 RemoveField(oldx, oldy);
5407 RemoveField(newx, newy);
5409 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5411 if (next_element != EL_UNDEFINED)
5412 Tile[oldx][oldy] = next_element;
5414 TEST_DrawLevelField(oldx, oldy);
5415 TEST_DrawLevelField(newx, newy);
5418 void DrawDynamite(int x, int y)
5420 int sx = SCREENX(x), sy = SCREENY(y);
5421 int graphic = el2img(Tile[x][y]);
5424 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5427 if (IS_WALKABLE_INSIDE(Back[x][y]))
5431 DrawLevelElement(x, y, Back[x][y]);
5432 else if (Store[x][y])
5433 DrawLevelElement(x, y, Store[x][y]);
5434 else if (game.use_masked_elements)
5435 DrawLevelElement(x, y, EL_EMPTY);
5437 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5439 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5440 DrawGraphicThruMask(sx, sy, graphic, frame);
5442 DrawGraphic(sx, sy, graphic, frame);
5445 static void CheckDynamite(int x, int y)
5447 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5451 if (MovDelay[x][y] != 0)
5454 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5460 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5465 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5467 boolean num_checked_players = 0;
5470 for (i = 0; i < MAX_PLAYERS; i++)
5472 if (stored_player[i].active)
5474 int sx = stored_player[i].jx;
5475 int sy = stored_player[i].jy;
5477 if (num_checked_players == 0)
5484 *sx1 = MIN(*sx1, sx);
5485 *sy1 = MIN(*sy1, sy);
5486 *sx2 = MAX(*sx2, sx);
5487 *sy2 = MAX(*sy2, sy);
5490 num_checked_players++;
5495 static boolean checkIfAllPlayersFitToScreen_RND(void)
5497 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5499 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5501 return (sx2 - sx1 < SCR_FIELDX &&
5502 sy2 - sy1 < SCR_FIELDY);
5505 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5507 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5509 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5511 *sx = (sx1 + sx2) / 2;
5512 *sy = (sy1 + sy2) / 2;
5515 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5516 boolean center_screen, boolean quick_relocation)
5518 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5519 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5520 boolean no_delay = (tape.warp_forward);
5521 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5522 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5523 int new_scroll_x, new_scroll_y;
5525 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5527 // case 1: quick relocation inside visible screen (without scrolling)
5534 if (!level.shifted_relocation || center_screen)
5536 // relocation _with_ centering of screen
5538 new_scroll_x = SCROLL_POSITION_X(x);
5539 new_scroll_y = SCROLL_POSITION_Y(y);
5543 // relocation _without_ centering of screen
5545 int center_scroll_x = SCROLL_POSITION_X(old_x);
5546 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5547 int offset_x = x + (scroll_x - center_scroll_x);
5548 int offset_y = y + (scroll_y - center_scroll_y);
5550 // for new screen position, apply previous offset to center position
5551 new_scroll_x = SCROLL_POSITION_X(offset_x);
5552 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5555 if (quick_relocation)
5557 // case 2: quick relocation (redraw without visible scrolling)
5559 scroll_x = new_scroll_x;
5560 scroll_y = new_scroll_y;
5567 // case 3: visible relocation (with scrolling to new position)
5569 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5571 SetVideoFrameDelay(wait_delay_value);
5573 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5575 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5576 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5578 if (dx == 0 && dy == 0) // no scrolling needed at all
5584 // set values for horizontal/vertical screen scrolling (half tile size)
5585 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5586 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5587 int pos_x = dx * TILEX / 2;
5588 int pos_y = dy * TILEY / 2;
5589 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5590 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5592 ScrollLevel(dx, dy);
5595 // scroll in two steps of half tile size to make things smoother
5596 BlitScreenToBitmapExt_RND(window, fx, fy);
5598 // scroll second step to align at full tile size
5599 BlitScreenToBitmap(window);
5605 SetVideoFrameDelay(frame_delay_value_old);
5608 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5610 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5611 int player_nr = GET_PLAYER_NR(el_player);
5612 struct PlayerInfo *player = &stored_player[player_nr];
5613 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5614 boolean no_delay = (tape.warp_forward);
5615 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5616 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5617 int old_jx = player->jx;
5618 int old_jy = player->jy;
5619 int old_element = Tile[old_jx][old_jy];
5620 int element = Tile[jx][jy];
5621 boolean player_relocated = (old_jx != jx || old_jy != jy);
5623 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5624 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5625 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5626 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5627 int leave_side_horiz = move_dir_horiz;
5628 int leave_side_vert = move_dir_vert;
5629 int enter_side = enter_side_horiz | enter_side_vert;
5630 int leave_side = leave_side_horiz | leave_side_vert;
5632 if (player->buried) // do not reanimate dead player
5635 if (!player_relocated) // no need to relocate the player
5638 if (IS_PLAYER(jx, jy)) // player already placed at new position
5640 RemoveField(jx, jy); // temporarily remove newly placed player
5641 DrawLevelField(jx, jy);
5644 if (player->present)
5646 while (player->MovPos)
5648 ScrollPlayer(player, SCROLL_GO_ON);
5649 ScrollScreen(NULL, SCROLL_GO_ON);
5651 AdvanceFrameAndPlayerCounters(player->index_nr);
5655 BackToFront_WithFrameDelay(wait_delay_value);
5658 DrawPlayer(player); // needed here only to cleanup last field
5659 DrawLevelField(player->jx, player->jy); // remove player graphic
5661 player->is_moving = FALSE;
5664 if (IS_CUSTOM_ELEMENT(old_element))
5665 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5667 player->index_bit, leave_side);
5669 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5671 player->index_bit, leave_side);
5673 Tile[jx][jy] = el_player;
5674 InitPlayerField(jx, jy, el_player, TRUE);
5676 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5677 possible that the relocation target field did not contain a player element,
5678 but a walkable element, to which the new player was relocated -- in this
5679 case, restore that (already initialized!) element on the player field */
5680 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5682 Tile[jx][jy] = element; // restore previously existing element
5685 // only visually relocate centered player
5686 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5687 FALSE, level.instant_relocation);
5689 TestIfPlayerTouchesBadThing(jx, jy);
5690 TestIfPlayerTouchesCustomElement(jx, jy);
5692 if (IS_CUSTOM_ELEMENT(element))
5693 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5694 player->index_bit, enter_side);
5696 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5697 player->index_bit, enter_side);
5699 if (player->is_switching)
5701 /* ensure that relocation while still switching an element does not cause
5702 a new element to be treated as also switched directly after relocation
5703 (this is important for teleporter switches that teleport the player to
5704 a place where another teleporter switch is in the same direction, which
5705 would then incorrectly be treated as immediately switched before the
5706 direction key that caused the switch was released) */
5708 player->switch_x += jx - old_jx;
5709 player->switch_y += jy - old_jy;
5713 static void Explode(int ex, int ey, int phase, int mode)
5719 // !!! eliminate this variable !!!
5720 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5722 if (game.explosions_delayed)
5724 ExplodeField[ex][ey] = mode;
5728 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5730 int center_element = Tile[ex][ey];
5731 int artwork_element, explosion_element; // set these values later
5733 // remove things displayed in background while burning dynamite
5734 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5737 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5739 // put moving element to center field (and let it explode there)
5740 center_element = MovingOrBlocked2Element(ex, ey);
5741 RemoveMovingField(ex, ey);
5742 Tile[ex][ey] = center_element;
5745 // now "center_element" is finally determined -- set related values now
5746 artwork_element = center_element; // for custom player artwork
5747 explosion_element = center_element; // for custom player artwork
5749 if (IS_PLAYER(ex, ey))
5751 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5753 artwork_element = stored_player[player_nr].artwork_element;
5755 if (level.use_explosion_element[player_nr])
5757 explosion_element = level.explosion_element[player_nr];
5758 artwork_element = explosion_element;
5762 if (mode == EX_TYPE_NORMAL ||
5763 mode == EX_TYPE_CENTER ||
5764 mode == EX_TYPE_CROSS)
5765 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5767 last_phase = element_info[explosion_element].explosion_delay + 1;
5769 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5771 int xx = x - ex + 1;
5772 int yy = y - ey + 1;
5775 if (!IN_LEV_FIELD(x, y) ||
5776 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5777 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5780 element = Tile[x][y];
5782 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5784 element = MovingOrBlocked2Element(x, y);
5786 if (!IS_EXPLOSION_PROOF(element))
5787 RemoveMovingField(x, y);
5790 // indestructible elements can only explode in center (but not flames)
5791 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5792 mode == EX_TYPE_BORDER)) ||
5793 element == EL_FLAMES)
5796 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5797 behaviour, for example when touching a yamyam that explodes to rocks
5798 with active deadly shield, a rock is created under the player !!! */
5799 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5801 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5802 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5803 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5805 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5808 if (IS_ACTIVE_BOMB(element))
5810 // re-activate things under the bomb like gate or penguin
5811 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5818 // save walkable background elements while explosion on same tile
5819 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5820 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5821 Back[x][y] = element;
5823 // ignite explodable elements reached by other explosion
5824 if (element == EL_EXPLOSION)
5825 element = Store2[x][y];
5827 if (AmoebaNr[x][y] &&
5828 (element == EL_AMOEBA_FULL ||
5829 element == EL_BD_AMOEBA ||
5830 element == EL_AMOEBA_GROWING))
5832 AmoebaCnt[AmoebaNr[x][y]]--;
5833 AmoebaCnt2[AmoebaNr[x][y]]--;
5838 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5840 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5842 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5844 if (PLAYERINFO(ex, ey)->use_murphy)
5845 Store[x][y] = EL_EMPTY;
5848 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5849 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5850 else if (ELEM_IS_PLAYER(center_element))
5851 Store[x][y] = EL_EMPTY;
5852 else if (center_element == EL_YAMYAM)
5853 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5854 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5855 Store[x][y] = element_info[center_element].content.e[xx][yy];
5857 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5858 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5859 // otherwise) -- FIX THIS !!!
5860 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5861 Store[x][y] = element_info[element].content.e[1][1];
5863 else if (!CAN_EXPLODE(element))
5864 Store[x][y] = element_info[element].content.e[1][1];
5867 Store[x][y] = EL_EMPTY;
5869 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5870 center_element == EL_AMOEBA_TO_DIAMOND)
5871 Store2[x][y] = element;
5873 Tile[x][y] = EL_EXPLOSION;
5874 GfxElement[x][y] = artwork_element;
5876 ExplodePhase[x][y] = 1;
5877 ExplodeDelay[x][y] = last_phase;
5882 if (center_element == EL_YAMYAM)
5883 game.yamyam_content_nr =
5884 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5896 GfxFrame[x][y] = 0; // restart explosion animation
5898 last_phase = ExplodeDelay[x][y];
5900 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5902 // this can happen if the player leaves an explosion just in time
5903 if (GfxElement[x][y] == EL_UNDEFINED)
5904 GfxElement[x][y] = EL_EMPTY;
5906 border_element = Store2[x][y];
5907 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5908 border_element = StorePlayer[x][y];
5910 if (phase == element_info[border_element].ignition_delay ||
5911 phase == last_phase)
5913 boolean border_explosion = FALSE;
5915 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5916 !PLAYER_EXPLOSION_PROTECTED(x, y))
5918 KillPlayerUnlessExplosionProtected(x, y);
5919 border_explosion = TRUE;
5921 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5923 Tile[x][y] = Store2[x][y];
5926 border_explosion = TRUE;
5928 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5930 AmoebaToDiamond(x, y);
5932 border_explosion = TRUE;
5935 // if an element just explodes due to another explosion (chain-reaction),
5936 // do not immediately end the new explosion when it was the last frame of
5937 // the explosion (as it would be done in the following "if"-statement!)
5938 if (border_explosion && phase == last_phase)
5942 if (phase == last_phase)
5946 element = Tile[x][y] = Store[x][y];
5947 Store[x][y] = Store2[x][y] = 0;
5948 GfxElement[x][y] = EL_UNDEFINED;
5950 // player can escape from explosions and might therefore be still alive
5951 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5952 element <= EL_PLAYER_IS_EXPLODING_4)
5954 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5955 int explosion_element = EL_PLAYER_1 + player_nr;
5956 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5957 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5959 if (level.use_explosion_element[player_nr])
5960 explosion_element = level.explosion_element[player_nr];
5962 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5963 element_info[explosion_element].content.e[xx][yy]);
5966 // restore probably existing indestructible background element
5967 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5968 element = Tile[x][y] = Back[x][y];
5971 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5972 GfxDir[x][y] = MV_NONE;
5973 ChangeDelay[x][y] = 0;
5974 ChangePage[x][y] = -1;
5976 CustomValue[x][y] = 0;
5978 InitField_WithBug2(x, y, FALSE);
5980 TEST_DrawLevelField(x, y);
5982 TestIfElementTouchesCustomElement(x, y);
5984 if (GFX_CRUMBLED(element))
5985 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5987 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5988 StorePlayer[x][y] = 0;
5990 if (ELEM_IS_PLAYER(element))
5991 RelocatePlayer(x, y, element);
5993 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5995 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5996 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5999 TEST_DrawLevelFieldCrumbled(x, y);
6001 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6003 DrawLevelElement(x, y, Back[x][y]);
6004 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6006 else if (IS_WALKABLE_UNDER(Back[x][y]))
6008 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6009 DrawLevelElementThruMask(x, y, Back[x][y]);
6011 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6012 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6016 static void DynaExplode(int ex, int ey)
6019 int dynabomb_element = Tile[ex][ey];
6020 int dynabomb_size = 1;
6021 boolean dynabomb_xl = FALSE;
6022 struct PlayerInfo *player;
6023 static int xy[4][2] =
6031 if (IS_ACTIVE_BOMB(dynabomb_element))
6033 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6034 dynabomb_size = player->dynabomb_size;
6035 dynabomb_xl = player->dynabomb_xl;
6036 player->dynabombs_left++;
6039 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6041 for (i = 0; i < NUM_DIRECTIONS; i++)
6043 for (j = 1; j <= dynabomb_size; j++)
6045 int x = ex + j * xy[i][0];
6046 int y = ey + j * xy[i][1];
6049 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6052 element = Tile[x][y];
6054 // do not restart explosions of fields with active bombs
6055 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6058 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6060 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6061 !IS_DIGGABLE(element) && !dynabomb_xl)
6067 void Bang(int x, int y)
6069 int element = MovingOrBlocked2Element(x, y);
6070 int explosion_type = EX_TYPE_NORMAL;
6072 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6074 struct PlayerInfo *player = PLAYERINFO(x, y);
6076 element = Tile[x][y] = player->initial_element;
6078 if (level.use_explosion_element[player->index_nr])
6080 int explosion_element = level.explosion_element[player->index_nr];
6082 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6083 explosion_type = EX_TYPE_CROSS;
6084 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6085 explosion_type = EX_TYPE_CENTER;
6093 case EL_BD_BUTTERFLY:
6096 case EL_DARK_YAMYAM:
6100 RaiseScoreElement(element);
6103 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6104 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6105 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6106 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6107 case EL_DYNABOMB_INCREASE_NUMBER:
6108 case EL_DYNABOMB_INCREASE_SIZE:
6109 case EL_DYNABOMB_INCREASE_POWER:
6110 explosion_type = EX_TYPE_DYNA;
6113 case EL_DC_LANDMINE:
6114 explosion_type = EX_TYPE_CENTER;
6119 case EL_LAMP_ACTIVE:
6120 case EL_AMOEBA_TO_DIAMOND:
6121 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6122 explosion_type = EX_TYPE_CENTER;
6126 if (element_info[element].explosion_type == EXPLODES_CROSS)
6127 explosion_type = EX_TYPE_CROSS;
6128 else if (element_info[element].explosion_type == EXPLODES_1X1)
6129 explosion_type = EX_TYPE_CENTER;
6133 if (explosion_type == EX_TYPE_DYNA)
6136 Explode(x, y, EX_PHASE_START, explosion_type);
6138 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6141 static void SplashAcid(int x, int y)
6143 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6144 (!IN_LEV_FIELD(x - 1, y - 2) ||
6145 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6146 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6148 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6149 (!IN_LEV_FIELD(x + 1, y - 2) ||
6150 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6151 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6153 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6156 static void InitBeltMovement(void)
6158 static int belt_base_element[4] =
6160 EL_CONVEYOR_BELT_1_LEFT,
6161 EL_CONVEYOR_BELT_2_LEFT,
6162 EL_CONVEYOR_BELT_3_LEFT,
6163 EL_CONVEYOR_BELT_4_LEFT
6165 static int belt_base_active_element[4] =
6167 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6168 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6169 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6170 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6175 // set frame order for belt animation graphic according to belt direction
6176 for (i = 0; i < NUM_BELTS; i++)
6180 for (j = 0; j < NUM_BELT_PARTS; j++)
6182 int element = belt_base_active_element[belt_nr] + j;
6183 int graphic_1 = el2img(element);
6184 int graphic_2 = el2panelimg(element);
6186 if (game.belt_dir[i] == MV_LEFT)
6188 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6189 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6193 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6194 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6199 SCAN_PLAYFIELD(x, y)
6201 int element = Tile[x][y];
6203 for (i = 0; i < NUM_BELTS; i++)
6205 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6207 int e_belt_nr = getBeltNrFromBeltElement(element);
6210 if (e_belt_nr == belt_nr)
6212 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6214 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6221 static void ToggleBeltSwitch(int x, int y)
6223 static int belt_base_element[4] =
6225 EL_CONVEYOR_BELT_1_LEFT,
6226 EL_CONVEYOR_BELT_2_LEFT,
6227 EL_CONVEYOR_BELT_3_LEFT,
6228 EL_CONVEYOR_BELT_4_LEFT
6230 static int belt_base_active_element[4] =
6232 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6233 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6234 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6235 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6237 static int belt_base_switch_element[4] =
6239 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6240 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6241 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6242 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6244 static int belt_move_dir[4] =
6252 int element = Tile[x][y];
6253 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6254 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6255 int belt_dir = belt_move_dir[belt_dir_nr];
6258 if (!IS_BELT_SWITCH(element))
6261 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6262 game.belt_dir[belt_nr] = belt_dir;
6264 if (belt_dir_nr == 3)
6267 // set frame order for belt animation graphic according to belt direction
6268 for (i = 0; i < NUM_BELT_PARTS; i++)
6270 int element = belt_base_active_element[belt_nr] + i;
6271 int graphic_1 = el2img(element);
6272 int graphic_2 = el2panelimg(element);
6274 if (belt_dir == MV_LEFT)
6276 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6277 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6281 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6282 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6286 SCAN_PLAYFIELD(xx, yy)
6288 int element = Tile[xx][yy];
6290 if (IS_BELT_SWITCH(element))
6292 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6294 if (e_belt_nr == belt_nr)
6296 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6297 TEST_DrawLevelField(xx, yy);
6300 else if (IS_BELT(element) && belt_dir != MV_NONE)
6302 int e_belt_nr = getBeltNrFromBeltElement(element);
6304 if (e_belt_nr == belt_nr)
6306 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6308 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6309 TEST_DrawLevelField(xx, yy);
6312 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6314 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6316 if (e_belt_nr == belt_nr)
6318 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6320 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6321 TEST_DrawLevelField(xx, yy);
6327 static void ToggleSwitchgateSwitch(int x, int y)
6331 game.switchgate_pos = !game.switchgate_pos;
6333 SCAN_PLAYFIELD(xx, yy)
6335 int element = Tile[xx][yy];
6337 if (element == EL_SWITCHGATE_SWITCH_UP)
6339 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6340 TEST_DrawLevelField(xx, yy);
6342 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6344 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6345 TEST_DrawLevelField(xx, yy);
6347 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6349 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6350 TEST_DrawLevelField(xx, yy);
6352 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6354 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6355 TEST_DrawLevelField(xx, yy);
6357 else if (element == EL_SWITCHGATE_OPEN ||
6358 element == EL_SWITCHGATE_OPENING)
6360 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6362 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6364 else if (element == EL_SWITCHGATE_CLOSED ||
6365 element == EL_SWITCHGATE_CLOSING)
6367 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6369 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6374 static int getInvisibleActiveFromInvisibleElement(int element)
6376 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6377 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6378 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6382 static int getInvisibleFromInvisibleActiveElement(int element)
6384 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6385 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6386 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6390 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6394 SCAN_PLAYFIELD(x, y)
6396 int element = Tile[x][y];
6398 if (element == EL_LIGHT_SWITCH &&
6399 game.light_time_left > 0)
6401 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6402 TEST_DrawLevelField(x, y);
6404 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6405 game.light_time_left == 0)
6407 Tile[x][y] = EL_LIGHT_SWITCH;
6408 TEST_DrawLevelField(x, y);
6410 else if (element == EL_EMC_DRIPPER &&
6411 game.light_time_left > 0)
6413 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6414 TEST_DrawLevelField(x, y);
6416 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6417 game.light_time_left == 0)
6419 Tile[x][y] = EL_EMC_DRIPPER;
6420 TEST_DrawLevelField(x, y);
6422 else if (element == EL_INVISIBLE_STEELWALL ||
6423 element == EL_INVISIBLE_WALL ||
6424 element == EL_INVISIBLE_SAND)
6426 if (game.light_time_left > 0)
6427 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6429 TEST_DrawLevelField(x, y);
6431 // uncrumble neighbour fields, if needed
6432 if (element == EL_INVISIBLE_SAND)
6433 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6435 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6436 element == EL_INVISIBLE_WALL_ACTIVE ||
6437 element == EL_INVISIBLE_SAND_ACTIVE)
6439 if (game.light_time_left == 0)
6440 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6442 TEST_DrawLevelField(x, y);
6444 // re-crumble neighbour fields, if needed
6445 if (element == EL_INVISIBLE_SAND)
6446 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6451 static void RedrawAllInvisibleElementsForLenses(void)
6455 SCAN_PLAYFIELD(x, y)
6457 int element = Tile[x][y];
6459 if (element == EL_EMC_DRIPPER &&
6460 game.lenses_time_left > 0)
6462 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6463 TEST_DrawLevelField(x, y);
6465 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6466 game.lenses_time_left == 0)
6468 Tile[x][y] = EL_EMC_DRIPPER;
6469 TEST_DrawLevelField(x, y);
6471 else if (element == EL_INVISIBLE_STEELWALL ||
6472 element == EL_INVISIBLE_WALL ||
6473 element == EL_INVISIBLE_SAND)
6475 if (game.lenses_time_left > 0)
6476 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6478 TEST_DrawLevelField(x, y);
6480 // uncrumble neighbour fields, if needed
6481 if (element == EL_INVISIBLE_SAND)
6482 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6484 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6485 element == EL_INVISIBLE_WALL_ACTIVE ||
6486 element == EL_INVISIBLE_SAND_ACTIVE)
6488 if (game.lenses_time_left == 0)
6489 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6491 TEST_DrawLevelField(x, y);
6493 // re-crumble neighbour fields, if needed
6494 if (element == EL_INVISIBLE_SAND)
6495 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6500 static void RedrawAllInvisibleElementsForMagnifier(void)
6504 SCAN_PLAYFIELD(x, y)
6506 int element = Tile[x][y];
6508 if (element == EL_EMC_FAKE_GRASS &&
6509 game.magnify_time_left > 0)
6511 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6512 TEST_DrawLevelField(x, y);
6514 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6515 game.magnify_time_left == 0)
6517 Tile[x][y] = EL_EMC_FAKE_GRASS;
6518 TEST_DrawLevelField(x, y);
6520 else if (IS_GATE_GRAY(element) &&
6521 game.magnify_time_left > 0)
6523 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6524 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6525 IS_EM_GATE_GRAY(element) ?
6526 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6527 IS_EMC_GATE_GRAY(element) ?
6528 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6529 IS_DC_GATE_GRAY(element) ?
6530 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6532 TEST_DrawLevelField(x, y);
6534 else if (IS_GATE_GRAY_ACTIVE(element) &&
6535 game.magnify_time_left == 0)
6537 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6538 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6539 IS_EM_GATE_GRAY_ACTIVE(element) ?
6540 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6541 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6542 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6543 IS_DC_GATE_GRAY_ACTIVE(element) ?
6544 EL_DC_GATE_WHITE_GRAY :
6546 TEST_DrawLevelField(x, y);
6551 static void ToggleLightSwitch(int x, int y)
6553 int element = Tile[x][y];
6555 game.light_time_left =
6556 (element == EL_LIGHT_SWITCH ?
6557 level.time_light * FRAMES_PER_SECOND : 0);
6559 RedrawAllLightSwitchesAndInvisibleElements();
6562 static void ActivateTimegateSwitch(int x, int y)
6566 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6568 SCAN_PLAYFIELD(xx, yy)
6570 int element = Tile[xx][yy];
6572 if (element == EL_TIMEGATE_CLOSED ||
6573 element == EL_TIMEGATE_CLOSING)
6575 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6576 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6580 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6582 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6583 TEST_DrawLevelField(xx, yy);
6589 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6590 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6593 static void Impact(int x, int y)
6595 boolean last_line = (y == lev_fieldy - 1);
6596 boolean object_hit = FALSE;
6597 boolean impact = (last_line || object_hit);
6598 int element = Tile[x][y];
6599 int smashed = EL_STEELWALL;
6601 if (!last_line) // check if element below was hit
6603 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6606 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6607 MovDir[x][y + 1] != MV_DOWN ||
6608 MovPos[x][y + 1] <= TILEY / 2));
6610 // do not smash moving elements that left the smashed field in time
6611 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6612 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6615 #if USE_QUICKSAND_IMPACT_BUGFIX
6616 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6618 RemoveMovingField(x, y + 1);
6619 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6620 Tile[x][y + 2] = EL_ROCK;
6621 TEST_DrawLevelField(x, y + 2);
6626 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6628 RemoveMovingField(x, y + 1);
6629 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6630 Tile[x][y + 2] = EL_ROCK;
6631 TEST_DrawLevelField(x, y + 2);
6638 smashed = MovingOrBlocked2Element(x, y + 1);
6640 impact = (last_line || object_hit);
6643 if (!last_line && smashed == EL_ACID) // element falls into acid
6645 SplashAcid(x, y + 1);
6649 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6650 // only reset graphic animation if graphic really changes after impact
6652 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6654 ResetGfxAnimation(x, y);
6655 TEST_DrawLevelField(x, y);
6658 if (impact && CAN_EXPLODE_IMPACT(element))
6663 else if (impact && element == EL_PEARL &&
6664 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6666 ResetGfxAnimation(x, y);
6668 Tile[x][y] = EL_PEARL_BREAKING;
6669 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6672 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6674 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6679 if (impact && element == EL_AMOEBA_DROP)
6681 if (object_hit && IS_PLAYER(x, y + 1))
6682 KillPlayerUnlessEnemyProtected(x, y + 1);
6683 else if (object_hit && smashed == EL_PENGUIN)
6687 Tile[x][y] = EL_AMOEBA_GROWING;
6688 Store[x][y] = EL_AMOEBA_WET;
6690 ResetRandomAnimationValue(x, y);
6695 if (object_hit) // check which object was hit
6697 if ((CAN_PASS_MAGIC_WALL(element) &&
6698 (smashed == EL_MAGIC_WALL ||
6699 smashed == EL_BD_MAGIC_WALL)) ||
6700 (CAN_PASS_DC_MAGIC_WALL(element) &&
6701 smashed == EL_DC_MAGIC_WALL))
6704 int activated_magic_wall =
6705 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6706 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6707 EL_DC_MAGIC_WALL_ACTIVE);
6709 // activate magic wall / mill
6710 SCAN_PLAYFIELD(xx, yy)
6712 if (Tile[xx][yy] == smashed)
6713 Tile[xx][yy] = activated_magic_wall;
6716 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6717 game.magic_wall_active = TRUE;
6719 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6720 SND_MAGIC_WALL_ACTIVATING :
6721 smashed == EL_BD_MAGIC_WALL ?
6722 SND_BD_MAGIC_WALL_ACTIVATING :
6723 SND_DC_MAGIC_WALL_ACTIVATING));
6726 if (IS_PLAYER(x, y + 1))
6728 if (CAN_SMASH_PLAYER(element))
6730 KillPlayerUnlessEnemyProtected(x, y + 1);
6734 else if (smashed == EL_PENGUIN)
6736 if (CAN_SMASH_PLAYER(element))
6742 else if (element == EL_BD_DIAMOND)
6744 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6750 else if (((element == EL_SP_INFOTRON ||
6751 element == EL_SP_ZONK) &&
6752 (smashed == EL_SP_SNIKSNAK ||
6753 smashed == EL_SP_ELECTRON ||
6754 smashed == EL_SP_DISK_ORANGE)) ||
6755 (element == EL_SP_INFOTRON &&
6756 smashed == EL_SP_DISK_YELLOW))
6761 else if (CAN_SMASH_EVERYTHING(element))
6763 if (IS_CLASSIC_ENEMY(smashed) ||
6764 CAN_EXPLODE_SMASHED(smashed))
6769 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6771 if (smashed == EL_LAMP ||
6772 smashed == EL_LAMP_ACTIVE)
6777 else if (smashed == EL_NUT)
6779 Tile[x][y + 1] = EL_NUT_BREAKING;
6780 PlayLevelSound(x, y, SND_NUT_BREAKING);
6781 RaiseScoreElement(EL_NUT);
6784 else if (smashed == EL_PEARL)
6786 ResetGfxAnimation(x, y);
6788 Tile[x][y + 1] = EL_PEARL_BREAKING;
6789 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6792 else if (smashed == EL_DIAMOND)
6794 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6795 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6798 else if (IS_BELT_SWITCH(smashed))
6800 ToggleBeltSwitch(x, y + 1);
6802 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6803 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6804 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6805 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6807 ToggleSwitchgateSwitch(x, y + 1);
6809 else if (smashed == EL_LIGHT_SWITCH ||
6810 smashed == EL_LIGHT_SWITCH_ACTIVE)
6812 ToggleLightSwitch(x, y + 1);
6816 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6818 CheckElementChangeBySide(x, y + 1, smashed, element,
6819 CE_SWITCHED, CH_SIDE_TOP);
6820 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6826 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6831 // play sound of magic wall / mill
6833 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6834 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6835 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6837 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6838 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6839 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6840 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6841 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6842 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6847 // play sound of object that hits the ground
6848 if (last_line || object_hit)
6849 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6852 static void TurnRoundExt(int x, int y)
6864 { 0, 0 }, { 0, 0 }, { 0, 0 },
6869 int left, right, back;
6873 { MV_DOWN, MV_UP, MV_RIGHT },
6874 { MV_UP, MV_DOWN, MV_LEFT },
6876 { MV_LEFT, MV_RIGHT, MV_DOWN },
6880 { MV_RIGHT, MV_LEFT, MV_UP }
6883 int element = Tile[x][y];
6884 int move_pattern = element_info[element].move_pattern;
6886 int old_move_dir = MovDir[x][y];
6887 int left_dir = turn[old_move_dir].left;
6888 int right_dir = turn[old_move_dir].right;
6889 int back_dir = turn[old_move_dir].back;
6891 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6892 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6893 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6894 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6896 int left_x = x + left_dx, left_y = y + left_dy;
6897 int right_x = x + right_dx, right_y = y + right_dy;
6898 int move_x = x + move_dx, move_y = y + move_dy;
6902 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6904 TestIfBadThingTouchesOtherBadThing(x, y);
6906 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6907 MovDir[x][y] = right_dir;
6908 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6909 MovDir[x][y] = left_dir;
6911 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6913 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6916 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6918 TestIfBadThingTouchesOtherBadThing(x, y);
6920 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6921 MovDir[x][y] = left_dir;
6922 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6923 MovDir[x][y] = right_dir;
6925 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6927 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6930 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6932 TestIfBadThingTouchesOtherBadThing(x, y);
6934 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6935 MovDir[x][y] = left_dir;
6936 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6937 MovDir[x][y] = right_dir;
6939 if (MovDir[x][y] != old_move_dir)
6942 else if (element == EL_YAMYAM)
6944 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6945 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6947 if (can_turn_left && can_turn_right)
6948 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6949 else if (can_turn_left)
6950 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6951 else if (can_turn_right)
6952 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6954 MovDir[x][y] = back_dir;
6956 MovDelay[x][y] = 16 + 16 * RND(3);
6958 else if (element == EL_DARK_YAMYAM)
6960 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6962 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6965 if (can_turn_left && can_turn_right)
6966 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6967 else if (can_turn_left)
6968 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6969 else if (can_turn_right)
6970 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6972 MovDir[x][y] = back_dir;
6974 MovDelay[x][y] = 16 + 16 * RND(3);
6976 else if (element == EL_PACMAN)
6978 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6979 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6981 if (can_turn_left && can_turn_right)
6982 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6983 else if (can_turn_left)
6984 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6985 else if (can_turn_right)
6986 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6988 MovDir[x][y] = back_dir;
6990 MovDelay[x][y] = 6 + RND(40);
6992 else if (element == EL_PIG)
6994 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6995 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6996 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6997 boolean should_turn_left, should_turn_right, should_move_on;
6999 int rnd = RND(rnd_value);
7001 should_turn_left = (can_turn_left &&
7003 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7004 y + back_dy + left_dy)));
7005 should_turn_right = (can_turn_right &&
7007 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7008 y + back_dy + right_dy)));
7009 should_move_on = (can_move_on &&
7012 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7013 y + move_dy + left_dy) ||
7014 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7015 y + move_dy + right_dy)));
7017 if (should_turn_left || should_turn_right || should_move_on)
7019 if (should_turn_left && should_turn_right && should_move_on)
7020 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7021 rnd < 2 * rnd_value / 3 ? right_dir :
7023 else if (should_turn_left && should_turn_right)
7024 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7025 else if (should_turn_left && should_move_on)
7026 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7027 else if (should_turn_right && should_move_on)
7028 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7029 else if (should_turn_left)
7030 MovDir[x][y] = left_dir;
7031 else if (should_turn_right)
7032 MovDir[x][y] = right_dir;
7033 else if (should_move_on)
7034 MovDir[x][y] = old_move_dir;
7036 else if (can_move_on && rnd > rnd_value / 8)
7037 MovDir[x][y] = old_move_dir;
7038 else if (can_turn_left && can_turn_right)
7039 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7040 else if (can_turn_left && rnd > rnd_value / 8)
7041 MovDir[x][y] = left_dir;
7042 else if (can_turn_right && rnd > rnd_value/8)
7043 MovDir[x][y] = right_dir;
7045 MovDir[x][y] = back_dir;
7047 xx = x + move_xy[MovDir[x][y]].dx;
7048 yy = y + move_xy[MovDir[x][y]].dy;
7050 if (!IN_LEV_FIELD(xx, yy) ||
7051 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7052 MovDir[x][y] = old_move_dir;
7056 else if (element == EL_DRAGON)
7058 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7059 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7060 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7062 int rnd = RND(rnd_value);
7064 if (can_move_on && rnd > rnd_value / 8)
7065 MovDir[x][y] = old_move_dir;
7066 else if (can_turn_left && can_turn_right)
7067 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7068 else if (can_turn_left && rnd > rnd_value / 8)
7069 MovDir[x][y] = left_dir;
7070 else if (can_turn_right && rnd > rnd_value / 8)
7071 MovDir[x][y] = right_dir;
7073 MovDir[x][y] = back_dir;
7075 xx = x + move_xy[MovDir[x][y]].dx;
7076 yy = y + move_xy[MovDir[x][y]].dy;
7078 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7079 MovDir[x][y] = old_move_dir;
7083 else if (element == EL_MOLE)
7085 boolean can_move_on =
7086 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7087 IS_AMOEBOID(Tile[move_x][move_y]) ||
7088 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7091 boolean can_turn_left =
7092 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7093 IS_AMOEBOID(Tile[left_x][left_y])));
7095 boolean can_turn_right =
7096 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7097 IS_AMOEBOID(Tile[right_x][right_y])));
7099 if (can_turn_left && can_turn_right)
7100 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7101 else if (can_turn_left)
7102 MovDir[x][y] = left_dir;
7104 MovDir[x][y] = right_dir;
7107 if (MovDir[x][y] != old_move_dir)
7110 else if (element == EL_BALLOON)
7112 MovDir[x][y] = game.wind_direction;
7115 else if (element == EL_SPRING)
7117 if (MovDir[x][y] & MV_HORIZONTAL)
7119 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7120 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7122 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7123 ResetGfxAnimation(move_x, move_y);
7124 TEST_DrawLevelField(move_x, move_y);
7126 MovDir[x][y] = back_dir;
7128 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7129 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7130 MovDir[x][y] = MV_NONE;
7135 else if (element == EL_ROBOT ||
7136 element == EL_SATELLITE ||
7137 element == EL_PENGUIN ||
7138 element == EL_EMC_ANDROID)
7140 int attr_x = -1, attr_y = -1;
7142 if (game.all_players_gone)
7144 attr_x = game.exit_x;
7145 attr_y = game.exit_y;
7151 for (i = 0; i < MAX_PLAYERS; i++)
7153 struct PlayerInfo *player = &stored_player[i];
7154 int jx = player->jx, jy = player->jy;
7156 if (!player->active)
7160 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7168 if (element == EL_ROBOT &&
7169 game.robot_wheel_x >= 0 &&
7170 game.robot_wheel_y >= 0 &&
7171 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7172 game.engine_version < VERSION_IDENT(3,1,0,0)))
7174 attr_x = game.robot_wheel_x;
7175 attr_y = game.robot_wheel_y;
7178 if (element == EL_PENGUIN)
7181 static int xy[4][2] =
7189 for (i = 0; i < NUM_DIRECTIONS; i++)
7191 int ex = x + xy[i][0];
7192 int ey = y + xy[i][1];
7194 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7195 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7196 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7197 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7206 MovDir[x][y] = MV_NONE;
7208 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7209 else if (attr_x > x)
7210 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7212 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7213 else if (attr_y > y)
7214 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7216 if (element == EL_ROBOT)
7220 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7221 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7222 Moving2Blocked(x, y, &newx, &newy);
7224 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7225 MovDelay[x][y] = 8 + 8 * !RND(3);
7227 MovDelay[x][y] = 16;
7229 else if (element == EL_PENGUIN)
7235 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7237 boolean first_horiz = RND(2);
7238 int new_move_dir = MovDir[x][y];
7241 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7242 Moving2Blocked(x, y, &newx, &newy);
7244 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7248 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7249 Moving2Blocked(x, y, &newx, &newy);
7251 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7254 MovDir[x][y] = old_move_dir;
7258 else if (element == EL_SATELLITE)
7264 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7266 boolean first_horiz = RND(2);
7267 int new_move_dir = MovDir[x][y];
7270 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7271 Moving2Blocked(x, y, &newx, &newy);
7273 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7277 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7278 Moving2Blocked(x, y, &newx, &newy);
7280 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7283 MovDir[x][y] = old_move_dir;
7287 else if (element == EL_EMC_ANDROID)
7289 static int check_pos[16] =
7291 -1, // 0 => (invalid)
7294 -1, // 3 => (invalid)
7296 0, // 5 => MV_LEFT | MV_UP
7297 2, // 6 => MV_RIGHT | MV_UP
7298 -1, // 7 => (invalid)
7300 6, // 9 => MV_LEFT | MV_DOWN
7301 4, // 10 => MV_RIGHT | MV_DOWN
7302 -1, // 11 => (invalid)
7303 -1, // 12 => (invalid)
7304 -1, // 13 => (invalid)
7305 -1, // 14 => (invalid)
7306 -1, // 15 => (invalid)
7314 { -1, -1, MV_LEFT | MV_UP },
7316 { +1, -1, MV_RIGHT | MV_UP },
7317 { +1, 0, MV_RIGHT },
7318 { +1, +1, MV_RIGHT | MV_DOWN },
7320 { -1, +1, MV_LEFT | MV_DOWN },
7323 int start_pos, check_order;
7324 boolean can_clone = FALSE;
7327 // check if there is any free field around current position
7328 for (i = 0; i < 8; i++)
7330 int newx = x + check_xy[i].dx;
7331 int newy = y + check_xy[i].dy;
7333 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7341 if (can_clone) // randomly find an element to clone
7345 start_pos = check_pos[RND(8)];
7346 check_order = (RND(2) ? -1 : +1);
7348 for (i = 0; i < 8; i++)
7350 int pos_raw = start_pos + i * check_order;
7351 int pos = (pos_raw + 8) % 8;
7352 int newx = x + check_xy[pos].dx;
7353 int newy = y + check_xy[pos].dy;
7355 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7357 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7358 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7360 Store[x][y] = Tile[newx][newy];
7369 if (can_clone) // randomly find a direction to move
7373 start_pos = check_pos[RND(8)];
7374 check_order = (RND(2) ? -1 : +1);
7376 for (i = 0; i < 8; i++)
7378 int pos_raw = start_pos + i * check_order;
7379 int pos = (pos_raw + 8) % 8;
7380 int newx = x + check_xy[pos].dx;
7381 int newy = y + check_xy[pos].dy;
7382 int new_move_dir = check_xy[pos].dir;
7384 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7386 MovDir[x][y] = new_move_dir;
7387 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7396 if (can_clone) // cloning and moving successful
7399 // cannot clone -- try to move towards player
7401 start_pos = check_pos[MovDir[x][y] & 0x0f];
7402 check_order = (RND(2) ? -1 : +1);
7404 for (i = 0; i < 3; i++)
7406 // first check start_pos, then previous/next or (next/previous) pos
7407 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7408 int pos = (pos_raw + 8) % 8;
7409 int newx = x + check_xy[pos].dx;
7410 int newy = y + check_xy[pos].dy;
7411 int new_move_dir = check_xy[pos].dir;
7413 if (IS_PLAYER(newx, newy))
7416 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7418 MovDir[x][y] = new_move_dir;
7419 MovDelay[x][y] = level.android_move_time * 8 + 1;
7426 else if (move_pattern == MV_TURNING_LEFT ||
7427 move_pattern == MV_TURNING_RIGHT ||
7428 move_pattern == MV_TURNING_LEFT_RIGHT ||
7429 move_pattern == MV_TURNING_RIGHT_LEFT ||
7430 move_pattern == MV_TURNING_RANDOM ||
7431 move_pattern == MV_ALL_DIRECTIONS)
7433 boolean can_turn_left =
7434 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7435 boolean can_turn_right =
7436 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7438 if (element_info[element].move_stepsize == 0) // "not moving"
7441 if (move_pattern == MV_TURNING_LEFT)
7442 MovDir[x][y] = left_dir;
7443 else if (move_pattern == MV_TURNING_RIGHT)
7444 MovDir[x][y] = right_dir;
7445 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7446 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7447 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7448 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7449 else if (move_pattern == MV_TURNING_RANDOM)
7450 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7451 can_turn_right && !can_turn_left ? right_dir :
7452 RND(2) ? left_dir : right_dir);
7453 else if (can_turn_left && can_turn_right)
7454 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7455 else if (can_turn_left)
7456 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7457 else if (can_turn_right)
7458 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7460 MovDir[x][y] = back_dir;
7462 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7464 else if (move_pattern == MV_HORIZONTAL ||
7465 move_pattern == MV_VERTICAL)
7467 if (move_pattern & old_move_dir)
7468 MovDir[x][y] = back_dir;
7469 else if (move_pattern == MV_HORIZONTAL)
7470 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7471 else if (move_pattern == MV_VERTICAL)
7472 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7474 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7476 else if (move_pattern & MV_ANY_DIRECTION)
7478 MovDir[x][y] = move_pattern;
7479 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7481 else if (move_pattern & MV_WIND_DIRECTION)
7483 MovDir[x][y] = game.wind_direction;
7484 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7486 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7488 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7489 MovDir[x][y] = left_dir;
7490 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7491 MovDir[x][y] = right_dir;
7493 if (MovDir[x][y] != old_move_dir)
7494 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7496 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7498 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7499 MovDir[x][y] = right_dir;
7500 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7501 MovDir[x][y] = left_dir;
7503 if (MovDir[x][y] != old_move_dir)
7504 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7506 else if (move_pattern == MV_TOWARDS_PLAYER ||
7507 move_pattern == MV_AWAY_FROM_PLAYER)
7509 int attr_x = -1, attr_y = -1;
7511 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7513 if (game.all_players_gone)
7515 attr_x = game.exit_x;
7516 attr_y = game.exit_y;
7522 for (i = 0; i < MAX_PLAYERS; i++)
7524 struct PlayerInfo *player = &stored_player[i];
7525 int jx = player->jx, jy = player->jy;
7527 if (!player->active)
7531 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7539 MovDir[x][y] = MV_NONE;
7541 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7542 else if (attr_x > x)
7543 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7545 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7546 else if (attr_y > y)
7547 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7549 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7551 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7553 boolean first_horiz = RND(2);
7554 int new_move_dir = MovDir[x][y];
7556 if (element_info[element].move_stepsize == 0) // "not moving"
7558 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7559 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7565 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7566 Moving2Blocked(x, y, &newx, &newy);
7568 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7572 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7573 Moving2Blocked(x, y, &newx, &newy);
7575 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7578 MovDir[x][y] = old_move_dir;
7581 else if (move_pattern == MV_WHEN_PUSHED ||
7582 move_pattern == MV_WHEN_DROPPED)
7584 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7585 MovDir[x][y] = MV_NONE;
7589 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7591 static int test_xy[7][2] =
7601 static int test_dir[7] =
7611 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7612 int move_preference = -1000000; // start with very low preference
7613 int new_move_dir = MV_NONE;
7614 int start_test = RND(4);
7617 for (i = 0; i < NUM_DIRECTIONS; i++)
7619 int move_dir = test_dir[start_test + i];
7620 int move_dir_preference;
7622 xx = x + test_xy[start_test + i][0];
7623 yy = y + test_xy[start_test + i][1];
7625 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7626 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7628 new_move_dir = move_dir;
7633 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7636 move_dir_preference = -1 * RunnerVisit[xx][yy];
7637 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7638 move_dir_preference = PlayerVisit[xx][yy];
7640 if (move_dir_preference > move_preference)
7642 // prefer field that has not been visited for the longest time
7643 move_preference = move_dir_preference;
7644 new_move_dir = move_dir;
7646 else if (move_dir_preference == move_preference &&
7647 move_dir == old_move_dir)
7649 // prefer last direction when all directions are preferred equally
7650 move_preference = move_dir_preference;
7651 new_move_dir = move_dir;
7655 MovDir[x][y] = new_move_dir;
7656 if (old_move_dir != new_move_dir)
7657 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7661 static void TurnRound(int x, int y)
7663 int direction = MovDir[x][y];
7667 GfxDir[x][y] = MovDir[x][y];
7669 if (direction != MovDir[x][y])
7673 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7675 ResetGfxFrame(x, y);
7678 static boolean JustBeingPushed(int x, int y)
7682 for (i = 0; i < MAX_PLAYERS; i++)
7684 struct PlayerInfo *player = &stored_player[i];
7686 if (player->active && player->is_pushing && player->MovPos)
7688 int next_jx = player->jx + (player->jx - player->last_jx);
7689 int next_jy = player->jy + (player->jy - player->last_jy);
7691 if (x == next_jx && y == next_jy)
7699 static void StartMoving(int x, int y)
7701 boolean started_moving = FALSE; // some elements can fall _and_ move
7702 int element = Tile[x][y];
7707 if (MovDelay[x][y] == 0)
7708 GfxAction[x][y] = ACTION_DEFAULT;
7710 if (CAN_FALL(element) && y < lev_fieldy - 1)
7712 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7713 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7714 if (JustBeingPushed(x, y))
7717 if (element == EL_QUICKSAND_FULL)
7719 if (IS_FREE(x, y + 1))
7721 InitMovingField(x, y, MV_DOWN);
7722 started_moving = TRUE;
7724 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7725 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7726 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7727 Store[x][y] = EL_ROCK;
7729 Store[x][y] = EL_ROCK;
7732 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7734 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7736 if (!MovDelay[x][y])
7738 MovDelay[x][y] = TILEY + 1;
7740 ResetGfxAnimation(x, y);
7741 ResetGfxAnimation(x, y + 1);
7746 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7747 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7754 Tile[x][y] = EL_QUICKSAND_EMPTY;
7755 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7756 Store[x][y + 1] = Store[x][y];
7759 PlayLevelSoundAction(x, y, ACTION_FILLING);
7761 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7763 if (!MovDelay[x][y])
7765 MovDelay[x][y] = TILEY + 1;
7767 ResetGfxAnimation(x, y);
7768 ResetGfxAnimation(x, y + 1);
7773 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7774 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7781 Tile[x][y] = EL_QUICKSAND_EMPTY;
7782 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7783 Store[x][y + 1] = Store[x][y];
7786 PlayLevelSoundAction(x, y, ACTION_FILLING);
7789 else if (element == EL_QUICKSAND_FAST_FULL)
7791 if (IS_FREE(x, y + 1))
7793 InitMovingField(x, y, MV_DOWN);
7794 started_moving = TRUE;
7796 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7797 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7798 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7799 Store[x][y] = EL_ROCK;
7801 Store[x][y] = EL_ROCK;
7804 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7806 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7808 if (!MovDelay[x][y])
7810 MovDelay[x][y] = TILEY + 1;
7812 ResetGfxAnimation(x, y);
7813 ResetGfxAnimation(x, y + 1);
7818 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7819 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7826 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7827 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7828 Store[x][y + 1] = Store[x][y];
7831 PlayLevelSoundAction(x, y, ACTION_FILLING);
7833 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7835 if (!MovDelay[x][y])
7837 MovDelay[x][y] = TILEY + 1;
7839 ResetGfxAnimation(x, y);
7840 ResetGfxAnimation(x, y + 1);
7845 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7846 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7853 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7854 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7855 Store[x][y + 1] = Store[x][y];
7858 PlayLevelSoundAction(x, y, ACTION_FILLING);
7861 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7862 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7864 InitMovingField(x, y, MV_DOWN);
7865 started_moving = TRUE;
7867 Tile[x][y] = EL_QUICKSAND_FILLING;
7868 Store[x][y] = element;
7870 PlayLevelSoundAction(x, y, ACTION_FILLING);
7872 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7873 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7875 InitMovingField(x, y, MV_DOWN);
7876 started_moving = TRUE;
7878 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7879 Store[x][y] = element;
7881 PlayLevelSoundAction(x, y, ACTION_FILLING);
7883 else if (element == EL_MAGIC_WALL_FULL)
7885 if (IS_FREE(x, y + 1))
7887 InitMovingField(x, y, MV_DOWN);
7888 started_moving = TRUE;
7890 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7891 Store[x][y] = EL_CHANGED(Store[x][y]);
7893 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7895 if (!MovDelay[x][y])
7896 MovDelay[x][y] = TILEY / 4 + 1;
7905 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7906 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7907 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7911 else if (element == EL_BD_MAGIC_WALL_FULL)
7913 if (IS_FREE(x, y + 1))
7915 InitMovingField(x, y, MV_DOWN);
7916 started_moving = TRUE;
7918 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7919 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7921 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7923 if (!MovDelay[x][y])
7924 MovDelay[x][y] = TILEY / 4 + 1;
7933 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7934 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7935 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7939 else if (element == EL_DC_MAGIC_WALL_FULL)
7941 if (IS_FREE(x, y + 1))
7943 InitMovingField(x, y, MV_DOWN);
7944 started_moving = TRUE;
7946 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7947 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7949 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7951 if (!MovDelay[x][y])
7952 MovDelay[x][y] = TILEY / 4 + 1;
7961 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7962 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7963 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7967 else if ((CAN_PASS_MAGIC_WALL(element) &&
7968 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7969 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7970 (CAN_PASS_DC_MAGIC_WALL(element) &&
7971 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7974 InitMovingField(x, y, MV_DOWN);
7975 started_moving = TRUE;
7978 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7979 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7980 EL_DC_MAGIC_WALL_FILLING);
7981 Store[x][y] = element;
7983 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7985 SplashAcid(x, y + 1);
7987 InitMovingField(x, y, MV_DOWN);
7988 started_moving = TRUE;
7990 Store[x][y] = EL_ACID;
7993 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7994 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7995 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7996 CAN_FALL(element) && WasJustFalling[x][y] &&
7997 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7999 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8000 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8001 (Tile[x][y + 1] == EL_BLOCKED)))
8003 /* this is needed for a special case not covered by calling "Impact()"
8004 from "ContinueMoving()": if an element moves to a tile directly below
8005 another element which was just falling on that tile (which was empty
8006 in the previous frame), the falling element above would just stop
8007 instead of smashing the element below (in previous version, the above
8008 element was just checked for "moving" instead of "falling", resulting
8009 in incorrect smashes caused by horizontal movement of the above
8010 element; also, the case of the player being the element to smash was
8011 simply not covered here... :-/ ) */
8013 CheckCollision[x][y] = 0;
8014 CheckImpact[x][y] = 0;
8018 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8020 if (MovDir[x][y] == MV_NONE)
8022 InitMovingField(x, y, MV_DOWN);
8023 started_moving = TRUE;
8026 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8028 if (WasJustFalling[x][y]) // prevent animation from being restarted
8029 MovDir[x][y] = MV_DOWN;
8031 InitMovingField(x, y, MV_DOWN);
8032 started_moving = TRUE;
8034 else if (element == EL_AMOEBA_DROP)
8036 Tile[x][y] = EL_AMOEBA_GROWING;
8037 Store[x][y] = EL_AMOEBA_WET;
8039 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8040 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8041 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8042 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8044 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8045 (IS_FREE(x - 1, y + 1) ||
8046 Tile[x - 1][y + 1] == EL_ACID));
8047 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8048 (IS_FREE(x + 1, y + 1) ||
8049 Tile[x + 1][y + 1] == EL_ACID));
8050 boolean can_fall_any = (can_fall_left || can_fall_right);
8051 boolean can_fall_both = (can_fall_left && can_fall_right);
8052 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8054 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8056 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8057 can_fall_right = FALSE;
8058 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8059 can_fall_left = FALSE;
8060 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8061 can_fall_right = FALSE;
8062 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8063 can_fall_left = FALSE;
8065 can_fall_any = (can_fall_left || can_fall_right);
8066 can_fall_both = FALSE;
8071 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8072 can_fall_right = FALSE; // slip down on left side
8074 can_fall_left = !(can_fall_right = RND(2));
8076 can_fall_both = FALSE;
8081 // if not determined otherwise, prefer left side for slipping down
8082 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8083 started_moving = TRUE;
8086 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8088 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8089 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8090 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8091 int belt_dir = game.belt_dir[belt_nr];
8093 if ((belt_dir == MV_LEFT && left_is_free) ||
8094 (belt_dir == MV_RIGHT && right_is_free))
8096 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8098 InitMovingField(x, y, belt_dir);
8099 started_moving = TRUE;
8101 Pushed[x][y] = TRUE;
8102 Pushed[nextx][y] = TRUE;
8104 GfxAction[x][y] = ACTION_DEFAULT;
8108 MovDir[x][y] = 0; // if element was moving, stop it
8113 // not "else if" because of elements that can fall and move (EL_SPRING)
8114 if (CAN_MOVE(element) && !started_moving)
8116 int move_pattern = element_info[element].move_pattern;
8119 Moving2Blocked(x, y, &newx, &newy);
8121 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8124 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8125 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8127 WasJustMoving[x][y] = 0;
8128 CheckCollision[x][y] = 0;
8130 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8132 if (Tile[x][y] != element) // element has changed
8136 if (!MovDelay[x][y]) // start new movement phase
8138 // all objects that can change their move direction after each step
8139 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8141 if (element != EL_YAMYAM &&
8142 element != EL_DARK_YAMYAM &&
8143 element != EL_PACMAN &&
8144 !(move_pattern & MV_ANY_DIRECTION) &&
8145 move_pattern != MV_TURNING_LEFT &&
8146 move_pattern != MV_TURNING_RIGHT &&
8147 move_pattern != MV_TURNING_LEFT_RIGHT &&
8148 move_pattern != MV_TURNING_RIGHT_LEFT &&
8149 move_pattern != MV_TURNING_RANDOM)
8153 if (MovDelay[x][y] && (element == EL_BUG ||
8154 element == EL_SPACESHIP ||
8155 element == EL_SP_SNIKSNAK ||
8156 element == EL_SP_ELECTRON ||
8157 element == EL_MOLE))
8158 TEST_DrawLevelField(x, y);
8162 if (MovDelay[x][y]) // wait some time before next movement
8166 if (element == EL_ROBOT ||
8167 element == EL_YAMYAM ||
8168 element == EL_DARK_YAMYAM)
8170 DrawLevelElementAnimationIfNeeded(x, y, element);
8171 PlayLevelSoundAction(x, y, ACTION_WAITING);
8173 else if (element == EL_SP_ELECTRON)
8174 DrawLevelElementAnimationIfNeeded(x, y, element);
8175 else if (element == EL_DRAGON)
8178 int dir = MovDir[x][y];
8179 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8180 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8181 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8182 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8183 dir == MV_UP ? IMG_FLAMES_1_UP :
8184 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8185 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8187 GfxAction[x][y] = ACTION_ATTACKING;
8189 if (IS_PLAYER(x, y))
8190 DrawPlayerField(x, y);
8192 TEST_DrawLevelField(x, y);
8194 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8196 for (i = 1; i <= 3; i++)
8198 int xx = x + i * dx;
8199 int yy = y + i * dy;
8200 int sx = SCREENX(xx);
8201 int sy = SCREENY(yy);
8202 int flame_graphic = graphic + (i - 1);
8204 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8209 int flamed = MovingOrBlocked2Element(xx, yy);
8211 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8214 RemoveMovingField(xx, yy);
8216 ChangeDelay[xx][yy] = 0;
8218 Tile[xx][yy] = EL_FLAMES;
8220 if (IN_SCR_FIELD(sx, sy))
8222 TEST_DrawLevelFieldCrumbled(xx, yy);
8223 DrawGraphic(sx, sy, flame_graphic, frame);
8228 if (Tile[xx][yy] == EL_FLAMES)
8229 Tile[xx][yy] = EL_EMPTY;
8230 TEST_DrawLevelField(xx, yy);
8235 if (MovDelay[x][y]) // element still has to wait some time
8237 PlayLevelSoundAction(x, y, ACTION_WAITING);
8243 // now make next step
8245 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8247 if (DONT_COLLIDE_WITH(element) &&
8248 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8249 !PLAYER_ENEMY_PROTECTED(newx, newy))
8251 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8256 else if (CAN_MOVE_INTO_ACID(element) &&
8257 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8258 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8259 (MovDir[x][y] == MV_DOWN ||
8260 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8262 SplashAcid(newx, newy);
8263 Store[x][y] = EL_ACID;
8265 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8267 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8268 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8269 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8270 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8273 TEST_DrawLevelField(x, y);
8275 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8276 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8277 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8279 game.friends_still_needed--;
8280 if (!game.friends_still_needed &&
8282 game.all_players_gone)
8287 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8289 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8290 TEST_DrawLevelField(newx, newy);
8292 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8294 else if (!IS_FREE(newx, newy))
8296 GfxAction[x][y] = ACTION_WAITING;
8298 if (IS_PLAYER(x, y))
8299 DrawPlayerField(x, y);
8301 TEST_DrawLevelField(x, y);
8306 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8308 if (IS_FOOD_PIG(Tile[newx][newy]))
8310 if (IS_MOVING(newx, newy))
8311 RemoveMovingField(newx, newy);
8314 Tile[newx][newy] = EL_EMPTY;
8315 TEST_DrawLevelField(newx, newy);
8318 PlayLevelSound(x, y, SND_PIG_DIGGING);
8320 else if (!IS_FREE(newx, newy))
8322 if (IS_PLAYER(x, y))
8323 DrawPlayerField(x, y);
8325 TEST_DrawLevelField(x, y);
8330 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8332 if (Store[x][y] != EL_EMPTY)
8334 boolean can_clone = FALSE;
8337 // check if element to clone is still there
8338 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8340 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8348 // cannot clone or target field not free anymore -- do not clone
8349 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8350 Store[x][y] = EL_EMPTY;
8353 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8355 if (IS_MV_DIAGONAL(MovDir[x][y]))
8357 int diagonal_move_dir = MovDir[x][y];
8358 int stored = Store[x][y];
8359 int change_delay = 8;
8362 // android is moving diagonally
8364 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8366 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8367 GfxElement[x][y] = EL_EMC_ANDROID;
8368 GfxAction[x][y] = ACTION_SHRINKING;
8369 GfxDir[x][y] = diagonal_move_dir;
8370 ChangeDelay[x][y] = change_delay;
8372 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8375 DrawLevelGraphicAnimation(x, y, graphic);
8376 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8378 if (Tile[newx][newy] == EL_ACID)
8380 SplashAcid(newx, newy);
8385 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8387 Store[newx][newy] = EL_EMC_ANDROID;
8388 GfxElement[newx][newy] = EL_EMC_ANDROID;
8389 GfxAction[newx][newy] = ACTION_GROWING;
8390 GfxDir[newx][newy] = diagonal_move_dir;
8391 ChangeDelay[newx][newy] = change_delay;
8393 graphic = el_act_dir2img(GfxElement[newx][newy],
8394 GfxAction[newx][newy], GfxDir[newx][newy]);
8396 DrawLevelGraphicAnimation(newx, newy, graphic);
8397 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8403 Tile[newx][newy] = EL_EMPTY;
8404 TEST_DrawLevelField(newx, newy);
8406 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8409 else if (!IS_FREE(newx, newy))
8414 else if (IS_CUSTOM_ELEMENT(element) &&
8415 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8417 if (!DigFieldByCE(newx, newy, element))
8420 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8422 RunnerVisit[x][y] = FrameCounter;
8423 PlayerVisit[x][y] /= 8; // expire player visit path
8426 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8428 if (!IS_FREE(newx, newy))
8430 if (IS_PLAYER(x, y))
8431 DrawPlayerField(x, y);
8433 TEST_DrawLevelField(x, y);
8439 boolean wanna_flame = !RND(10);
8440 int dx = newx - x, dy = newy - y;
8441 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8442 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8443 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8444 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8445 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8446 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8449 IS_CLASSIC_ENEMY(element1) ||
8450 IS_CLASSIC_ENEMY(element2)) &&
8451 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8452 element1 != EL_FLAMES && element2 != EL_FLAMES)
8454 ResetGfxAnimation(x, y);
8455 GfxAction[x][y] = ACTION_ATTACKING;
8457 if (IS_PLAYER(x, y))
8458 DrawPlayerField(x, y);
8460 TEST_DrawLevelField(x, y);
8462 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8464 MovDelay[x][y] = 50;
8466 Tile[newx][newy] = EL_FLAMES;
8467 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8468 Tile[newx1][newy1] = EL_FLAMES;
8469 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8470 Tile[newx2][newy2] = EL_FLAMES;
8476 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8477 Tile[newx][newy] == EL_DIAMOND)
8479 if (IS_MOVING(newx, newy))
8480 RemoveMovingField(newx, newy);
8483 Tile[newx][newy] = EL_EMPTY;
8484 TEST_DrawLevelField(newx, newy);
8487 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8489 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8490 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8492 if (AmoebaNr[newx][newy])
8494 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8495 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8496 Tile[newx][newy] == EL_BD_AMOEBA)
8497 AmoebaCnt[AmoebaNr[newx][newy]]--;
8500 if (IS_MOVING(newx, newy))
8502 RemoveMovingField(newx, newy);
8506 Tile[newx][newy] = EL_EMPTY;
8507 TEST_DrawLevelField(newx, newy);
8510 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8512 else if ((element == EL_PACMAN || element == EL_MOLE)
8513 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8515 if (AmoebaNr[newx][newy])
8517 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8518 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8519 Tile[newx][newy] == EL_BD_AMOEBA)
8520 AmoebaCnt[AmoebaNr[newx][newy]]--;
8523 if (element == EL_MOLE)
8525 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8526 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8528 ResetGfxAnimation(x, y);
8529 GfxAction[x][y] = ACTION_DIGGING;
8530 TEST_DrawLevelField(x, y);
8532 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8534 return; // wait for shrinking amoeba
8536 else // element == EL_PACMAN
8538 Tile[newx][newy] = EL_EMPTY;
8539 TEST_DrawLevelField(newx, newy);
8540 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8543 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8544 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8545 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8547 // wait for shrinking amoeba to completely disappear
8550 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8552 // object was running against a wall
8556 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8557 DrawLevelElementAnimation(x, y, element);
8559 if (DONT_TOUCH(element))
8560 TestIfBadThingTouchesPlayer(x, y);
8565 InitMovingField(x, y, MovDir[x][y]);
8567 PlayLevelSoundAction(x, y, ACTION_MOVING);
8571 ContinueMoving(x, y);
8574 void ContinueMoving(int x, int y)
8576 int element = Tile[x][y];
8577 struct ElementInfo *ei = &element_info[element];
8578 int direction = MovDir[x][y];
8579 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8580 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8581 int newx = x + dx, newy = y + dy;
8582 int stored = Store[x][y];
8583 int stored_new = Store[newx][newy];
8584 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8585 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8586 boolean last_line = (newy == lev_fieldy - 1);
8587 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8589 if (pushed_by_player) // special case: moving object pushed by player
8591 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8593 else if (use_step_delay) // special case: moving object has step delay
8595 if (!MovDelay[x][y])
8596 MovPos[x][y] += getElementMoveStepsize(x, y);
8601 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8605 TEST_DrawLevelField(x, y);
8607 return; // element is still waiting
8610 else // normal case: generically moving object
8612 MovPos[x][y] += getElementMoveStepsize(x, y);
8615 if (ABS(MovPos[x][y]) < TILEX)
8617 TEST_DrawLevelField(x, y);
8619 return; // element is still moving
8622 // element reached destination field
8624 Tile[x][y] = EL_EMPTY;
8625 Tile[newx][newy] = element;
8626 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8628 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8630 element = Tile[newx][newy] = EL_ACID;
8632 else if (element == EL_MOLE)
8634 Tile[x][y] = EL_SAND;
8636 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8638 else if (element == EL_QUICKSAND_FILLING)
8640 element = Tile[newx][newy] = get_next_element(element);
8641 Store[newx][newy] = Store[x][y];
8643 else if (element == EL_QUICKSAND_EMPTYING)
8645 Tile[x][y] = get_next_element(element);
8646 element = Tile[newx][newy] = Store[x][y];
8648 else if (element == EL_QUICKSAND_FAST_FILLING)
8650 element = Tile[newx][newy] = get_next_element(element);
8651 Store[newx][newy] = Store[x][y];
8653 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8655 Tile[x][y] = get_next_element(element);
8656 element = Tile[newx][newy] = Store[x][y];
8658 else if (element == EL_MAGIC_WALL_FILLING)
8660 element = Tile[newx][newy] = get_next_element(element);
8661 if (!game.magic_wall_active)
8662 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8663 Store[newx][newy] = Store[x][y];
8665 else if (element == EL_MAGIC_WALL_EMPTYING)
8667 Tile[x][y] = get_next_element(element);
8668 if (!game.magic_wall_active)
8669 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8670 element = Tile[newx][newy] = Store[x][y];
8672 InitField(newx, newy, FALSE);
8674 else if (element == EL_BD_MAGIC_WALL_FILLING)
8676 element = Tile[newx][newy] = get_next_element(element);
8677 if (!game.magic_wall_active)
8678 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8679 Store[newx][newy] = Store[x][y];
8681 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8683 Tile[x][y] = get_next_element(element);
8684 if (!game.magic_wall_active)
8685 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8686 element = Tile[newx][newy] = Store[x][y];
8688 InitField(newx, newy, FALSE);
8690 else if (element == EL_DC_MAGIC_WALL_FILLING)
8692 element = Tile[newx][newy] = get_next_element(element);
8693 if (!game.magic_wall_active)
8694 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8695 Store[newx][newy] = Store[x][y];
8697 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8699 Tile[x][y] = get_next_element(element);
8700 if (!game.magic_wall_active)
8701 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8702 element = Tile[newx][newy] = Store[x][y];
8704 InitField(newx, newy, FALSE);
8706 else if (element == EL_AMOEBA_DROPPING)
8708 Tile[x][y] = get_next_element(element);
8709 element = Tile[newx][newy] = Store[x][y];
8711 else if (element == EL_SOKOBAN_OBJECT)
8714 Tile[x][y] = Back[x][y];
8716 if (Back[newx][newy])
8717 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8719 Back[x][y] = Back[newx][newy] = 0;
8722 Store[x][y] = EL_EMPTY;
8727 MovDelay[newx][newy] = 0;
8729 if (CAN_CHANGE_OR_HAS_ACTION(element))
8731 // copy element change control values to new field
8732 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8733 ChangePage[newx][newy] = ChangePage[x][y];
8734 ChangeCount[newx][newy] = ChangeCount[x][y];
8735 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8738 CustomValue[newx][newy] = CustomValue[x][y];
8740 ChangeDelay[x][y] = 0;
8741 ChangePage[x][y] = -1;
8742 ChangeCount[x][y] = 0;
8743 ChangeEvent[x][y] = -1;
8745 CustomValue[x][y] = 0;
8747 // copy animation control values to new field
8748 GfxFrame[newx][newy] = GfxFrame[x][y];
8749 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8750 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8751 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8753 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8755 // some elements can leave other elements behind after moving
8756 if (ei->move_leave_element != EL_EMPTY &&
8757 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8758 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8760 int move_leave_element = ei->move_leave_element;
8762 // this makes it possible to leave the removed element again
8763 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8764 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8766 Tile[x][y] = move_leave_element;
8768 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8769 MovDir[x][y] = direction;
8771 InitField(x, y, FALSE);
8773 if (GFX_CRUMBLED(Tile[x][y]))
8774 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8776 if (ELEM_IS_PLAYER(move_leave_element))
8777 RelocatePlayer(x, y, move_leave_element);
8780 // do this after checking for left-behind element
8781 ResetGfxAnimation(x, y); // reset animation values for old field
8783 if (!CAN_MOVE(element) ||
8784 (CAN_FALL(element) && direction == MV_DOWN &&
8785 (element == EL_SPRING ||
8786 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8787 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8788 GfxDir[x][y] = MovDir[newx][newy] = 0;
8790 TEST_DrawLevelField(x, y);
8791 TEST_DrawLevelField(newx, newy);
8793 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8795 // prevent pushed element from moving on in pushed direction
8796 if (pushed_by_player && CAN_MOVE(element) &&
8797 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8798 !(element_info[element].move_pattern & direction))
8799 TurnRound(newx, newy);
8801 // prevent elements on conveyor belt from moving on in last direction
8802 if (pushed_by_conveyor && CAN_FALL(element) &&
8803 direction & MV_HORIZONTAL)
8804 MovDir[newx][newy] = 0;
8806 if (!pushed_by_player)
8808 int nextx = newx + dx, nexty = newy + dy;
8809 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8811 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8813 if (CAN_FALL(element) && direction == MV_DOWN)
8814 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8816 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8817 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8819 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8820 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8823 if (DONT_TOUCH(element)) // object may be nasty to player or others
8825 TestIfBadThingTouchesPlayer(newx, newy);
8826 TestIfBadThingTouchesFriend(newx, newy);
8828 if (!IS_CUSTOM_ELEMENT(element))
8829 TestIfBadThingTouchesOtherBadThing(newx, newy);
8831 else if (element == EL_PENGUIN)
8832 TestIfFriendTouchesBadThing(newx, newy);
8834 if (DONT_GET_HIT_BY(element))
8836 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8839 // give the player one last chance (one more frame) to move away
8840 if (CAN_FALL(element) && direction == MV_DOWN &&
8841 (last_line || (!IS_FREE(x, newy + 1) &&
8842 (!IS_PLAYER(x, newy + 1) ||
8843 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8846 if (pushed_by_player && !game.use_change_when_pushing_bug)
8848 int push_side = MV_DIR_OPPOSITE(direction);
8849 struct PlayerInfo *player = PLAYERINFO(x, y);
8851 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8852 player->index_bit, push_side);
8853 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8854 player->index_bit, push_side);
8857 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8858 MovDelay[newx][newy] = 1;
8860 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8862 TestIfElementTouchesCustomElement(x, y); // empty or new element
8863 TestIfElementHitsCustomElement(newx, newy, direction);
8864 TestIfPlayerTouchesCustomElement(newx, newy);
8865 TestIfElementTouchesCustomElement(newx, newy);
8867 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8868 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8869 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8870 MV_DIR_OPPOSITE(direction));
8873 int AmoebaNeighbourNr(int ax, int ay)
8876 int element = Tile[ax][ay];
8878 static int xy[4][2] =
8886 for (i = 0; i < NUM_DIRECTIONS; i++)
8888 int x = ax + xy[i][0];
8889 int y = ay + xy[i][1];
8891 if (!IN_LEV_FIELD(x, y))
8894 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8895 group_nr = AmoebaNr[x][y];
8901 static void AmoebaMerge(int ax, int ay)
8903 int i, x, y, xx, yy;
8904 int new_group_nr = AmoebaNr[ax][ay];
8905 static int xy[4][2] =
8913 if (new_group_nr == 0)
8916 for (i = 0; i < NUM_DIRECTIONS; i++)
8921 if (!IN_LEV_FIELD(x, y))
8924 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8925 Tile[x][y] == EL_BD_AMOEBA ||
8926 Tile[x][y] == EL_AMOEBA_DEAD) &&
8927 AmoebaNr[x][y] != new_group_nr)
8929 int old_group_nr = AmoebaNr[x][y];
8931 if (old_group_nr == 0)
8934 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8935 AmoebaCnt[old_group_nr] = 0;
8936 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8937 AmoebaCnt2[old_group_nr] = 0;
8939 SCAN_PLAYFIELD(xx, yy)
8941 if (AmoebaNr[xx][yy] == old_group_nr)
8942 AmoebaNr[xx][yy] = new_group_nr;
8948 void AmoebaToDiamond(int ax, int ay)
8952 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8954 int group_nr = AmoebaNr[ax][ay];
8959 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8960 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8966 SCAN_PLAYFIELD(x, y)
8968 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8971 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8975 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8976 SND_AMOEBA_TURNING_TO_GEM :
8977 SND_AMOEBA_TURNING_TO_ROCK));
8982 static int xy[4][2] =
8990 for (i = 0; i < NUM_DIRECTIONS; i++)
8995 if (!IN_LEV_FIELD(x, y))
8998 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9000 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9001 SND_AMOEBA_TURNING_TO_GEM :
9002 SND_AMOEBA_TURNING_TO_ROCK));
9009 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9012 int group_nr = AmoebaNr[ax][ay];
9013 boolean done = FALSE;
9018 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9019 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9025 SCAN_PLAYFIELD(x, y)
9027 if (AmoebaNr[x][y] == group_nr &&
9028 (Tile[x][y] == EL_AMOEBA_DEAD ||
9029 Tile[x][y] == EL_BD_AMOEBA ||
9030 Tile[x][y] == EL_AMOEBA_GROWING))
9033 Tile[x][y] = new_element;
9034 InitField(x, y, FALSE);
9035 TEST_DrawLevelField(x, y);
9041 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9042 SND_BD_AMOEBA_TURNING_TO_ROCK :
9043 SND_BD_AMOEBA_TURNING_TO_GEM));
9046 static void AmoebaGrowing(int x, int y)
9048 static unsigned int sound_delay = 0;
9049 static unsigned int sound_delay_value = 0;
9051 if (!MovDelay[x][y]) // start new growing cycle
9055 if (DelayReached(&sound_delay, sound_delay_value))
9057 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9058 sound_delay_value = 30;
9062 if (MovDelay[x][y]) // wait some time before growing bigger
9065 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9067 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9068 6 - MovDelay[x][y]);
9070 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9073 if (!MovDelay[x][y])
9075 Tile[x][y] = Store[x][y];
9077 TEST_DrawLevelField(x, y);
9082 static void AmoebaShrinking(int x, int y)
9084 static unsigned int sound_delay = 0;
9085 static unsigned int sound_delay_value = 0;
9087 if (!MovDelay[x][y]) // start new shrinking cycle
9091 if (DelayReached(&sound_delay, sound_delay_value))
9092 sound_delay_value = 30;
9095 if (MovDelay[x][y]) // wait some time before shrinking
9098 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9100 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9101 6 - MovDelay[x][y]);
9103 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9106 if (!MovDelay[x][y])
9108 Tile[x][y] = EL_EMPTY;
9109 TEST_DrawLevelField(x, y);
9111 // don't let mole enter this field in this cycle;
9112 // (give priority to objects falling to this field from above)
9118 static void AmoebaReproduce(int ax, int ay)
9121 int element = Tile[ax][ay];
9122 int graphic = el2img(element);
9123 int newax = ax, neway = ay;
9124 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9125 static int xy[4][2] =
9133 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9135 Tile[ax][ay] = EL_AMOEBA_DEAD;
9136 TEST_DrawLevelField(ax, ay);
9140 if (IS_ANIMATED(graphic))
9141 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9143 if (!MovDelay[ax][ay]) // start making new amoeba field
9144 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9146 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9149 if (MovDelay[ax][ay])
9153 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9156 int x = ax + xy[start][0];
9157 int y = ay + xy[start][1];
9159 if (!IN_LEV_FIELD(x, y))
9162 if (IS_FREE(x, y) ||
9163 CAN_GROW_INTO(Tile[x][y]) ||
9164 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9165 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9171 if (newax == ax && neway == ay)
9174 else // normal or "filled" (BD style) amoeba
9177 boolean waiting_for_player = FALSE;
9179 for (i = 0; i < NUM_DIRECTIONS; i++)
9181 int j = (start + i) % 4;
9182 int x = ax + xy[j][0];
9183 int y = ay + xy[j][1];
9185 if (!IN_LEV_FIELD(x, y))
9188 if (IS_FREE(x, y) ||
9189 CAN_GROW_INTO(Tile[x][y]) ||
9190 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9191 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9197 else if (IS_PLAYER(x, y))
9198 waiting_for_player = TRUE;
9201 if (newax == ax && neway == ay) // amoeba cannot grow
9203 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9205 Tile[ax][ay] = EL_AMOEBA_DEAD;
9206 TEST_DrawLevelField(ax, ay);
9207 AmoebaCnt[AmoebaNr[ax][ay]]--;
9209 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9211 if (element == EL_AMOEBA_FULL)
9212 AmoebaToDiamond(ax, ay);
9213 else if (element == EL_BD_AMOEBA)
9214 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9219 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9221 // amoeba gets larger by growing in some direction
9223 int new_group_nr = AmoebaNr[ax][ay];
9226 if (new_group_nr == 0)
9228 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9230 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9236 AmoebaNr[newax][neway] = new_group_nr;
9237 AmoebaCnt[new_group_nr]++;
9238 AmoebaCnt2[new_group_nr]++;
9240 // if amoeba touches other amoeba(s) after growing, unify them
9241 AmoebaMerge(newax, neway);
9243 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9245 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9251 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9252 (neway == lev_fieldy - 1 && newax != ax))
9254 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9255 Store[newax][neway] = element;
9257 else if (neway == ay || element == EL_EMC_DRIPPER)
9259 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9261 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9265 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9266 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9267 Store[ax][ay] = EL_AMOEBA_DROP;
9268 ContinueMoving(ax, ay);
9272 TEST_DrawLevelField(newax, neway);
9275 static void Life(int ax, int ay)
9279 int element = Tile[ax][ay];
9280 int graphic = el2img(element);
9281 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9283 boolean changed = FALSE;
9285 if (IS_ANIMATED(graphic))
9286 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9291 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9292 MovDelay[ax][ay] = life_time;
9294 if (MovDelay[ax][ay]) // wait some time before next cycle
9297 if (MovDelay[ax][ay])
9301 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9303 int xx = ax+x1, yy = ay+y1;
9304 int old_element = Tile[xx][yy];
9305 int num_neighbours = 0;
9307 if (!IN_LEV_FIELD(xx, yy))
9310 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9312 int x = xx+x2, y = yy+y2;
9314 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9317 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9318 boolean is_neighbour = FALSE;
9320 if (level.use_life_bugs)
9322 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9323 (IS_FREE(x, y) && Stop[x][y]));
9326 (Last[x][y] == element || is_player_cell);
9332 boolean is_free = FALSE;
9334 if (level.use_life_bugs)
9335 is_free = (IS_FREE(xx, yy));
9337 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9339 if (xx == ax && yy == ay) // field in the middle
9341 if (num_neighbours < life_parameter[0] ||
9342 num_neighbours > life_parameter[1])
9344 Tile[xx][yy] = EL_EMPTY;
9345 if (Tile[xx][yy] != old_element)
9346 TEST_DrawLevelField(xx, yy);
9347 Stop[xx][yy] = TRUE;
9351 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9352 { // free border field
9353 if (num_neighbours >= life_parameter[2] &&
9354 num_neighbours <= life_parameter[3])
9356 Tile[xx][yy] = element;
9357 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9358 if (Tile[xx][yy] != old_element)
9359 TEST_DrawLevelField(xx, yy);
9360 Stop[xx][yy] = TRUE;
9367 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9368 SND_GAME_OF_LIFE_GROWING);
9371 static void InitRobotWheel(int x, int y)
9373 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9376 static void RunRobotWheel(int x, int y)
9378 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9381 static void StopRobotWheel(int x, int y)
9383 if (game.robot_wheel_x == x &&
9384 game.robot_wheel_y == y)
9386 game.robot_wheel_x = -1;
9387 game.robot_wheel_y = -1;
9388 game.robot_wheel_active = FALSE;
9392 static void InitTimegateWheel(int x, int y)
9394 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9397 static void RunTimegateWheel(int x, int y)
9399 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9402 static void InitMagicBallDelay(int x, int y)
9404 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9407 static void ActivateMagicBall(int bx, int by)
9411 if (level.ball_random)
9413 int pos_border = RND(8); // select one of the eight border elements
9414 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9415 int xx = pos_content % 3;
9416 int yy = pos_content / 3;
9421 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9422 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9426 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9428 int xx = x - bx + 1;
9429 int yy = y - by + 1;
9431 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9432 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9436 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9439 static void CheckExit(int x, int y)
9441 if (game.gems_still_needed > 0 ||
9442 game.sokoban_fields_still_needed > 0 ||
9443 game.sokoban_objects_still_needed > 0 ||
9444 game.lights_still_needed > 0)
9446 int element = Tile[x][y];
9447 int graphic = el2img(element);
9449 if (IS_ANIMATED(graphic))
9450 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9455 // do not re-open exit door closed after last player
9456 if (game.all_players_gone)
9459 Tile[x][y] = EL_EXIT_OPENING;
9461 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9464 static void CheckExitEM(int x, int y)
9466 if (game.gems_still_needed > 0 ||
9467 game.sokoban_fields_still_needed > 0 ||
9468 game.sokoban_objects_still_needed > 0 ||
9469 game.lights_still_needed > 0)
9471 int element = Tile[x][y];
9472 int graphic = el2img(element);
9474 if (IS_ANIMATED(graphic))
9475 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9480 // do not re-open exit door closed after last player
9481 if (game.all_players_gone)
9484 Tile[x][y] = EL_EM_EXIT_OPENING;
9486 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9489 static void CheckExitSteel(int x, int y)
9491 if (game.gems_still_needed > 0 ||
9492 game.sokoban_fields_still_needed > 0 ||
9493 game.sokoban_objects_still_needed > 0 ||
9494 game.lights_still_needed > 0)
9496 int element = Tile[x][y];
9497 int graphic = el2img(element);
9499 if (IS_ANIMATED(graphic))
9500 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9505 // do not re-open exit door closed after last player
9506 if (game.all_players_gone)
9509 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9511 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9514 static void CheckExitSteelEM(int x, int y)
9516 if (game.gems_still_needed > 0 ||
9517 game.sokoban_fields_still_needed > 0 ||
9518 game.sokoban_objects_still_needed > 0 ||
9519 game.lights_still_needed > 0)
9521 int element = Tile[x][y];
9522 int graphic = el2img(element);
9524 if (IS_ANIMATED(graphic))
9525 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9530 // do not re-open exit door closed after last player
9531 if (game.all_players_gone)
9534 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9536 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9539 static void CheckExitSP(int x, int y)
9541 if (game.gems_still_needed > 0)
9543 int element = Tile[x][y];
9544 int graphic = el2img(element);
9546 if (IS_ANIMATED(graphic))
9547 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9552 // do not re-open exit door closed after last player
9553 if (game.all_players_gone)
9556 Tile[x][y] = EL_SP_EXIT_OPENING;
9558 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9561 static void CloseAllOpenTimegates(void)
9565 SCAN_PLAYFIELD(x, y)
9567 int element = Tile[x][y];
9569 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9571 Tile[x][y] = EL_TIMEGATE_CLOSING;
9573 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9578 static void DrawTwinkleOnField(int x, int y)
9580 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9583 if (Tile[x][y] == EL_BD_DIAMOND)
9586 if (MovDelay[x][y] == 0) // next animation frame
9587 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9589 if (MovDelay[x][y] != 0) // wait some time before next frame
9593 DrawLevelElementAnimation(x, y, Tile[x][y]);
9595 if (MovDelay[x][y] != 0)
9597 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9598 10 - MovDelay[x][y]);
9600 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9605 static void MauerWaechst(int x, int y)
9609 if (!MovDelay[x][y]) // next animation frame
9610 MovDelay[x][y] = 3 * delay;
9612 if (MovDelay[x][y]) // wait some time before next frame
9616 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9618 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9619 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9621 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9624 if (!MovDelay[x][y])
9626 if (MovDir[x][y] == MV_LEFT)
9628 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9629 TEST_DrawLevelField(x - 1, y);
9631 else if (MovDir[x][y] == MV_RIGHT)
9633 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9634 TEST_DrawLevelField(x + 1, y);
9636 else if (MovDir[x][y] == MV_UP)
9638 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9639 TEST_DrawLevelField(x, y - 1);
9643 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9644 TEST_DrawLevelField(x, y + 1);
9647 Tile[x][y] = Store[x][y];
9649 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9650 TEST_DrawLevelField(x, y);
9655 static void MauerAbleger(int ax, int ay)
9657 int element = Tile[ax][ay];
9658 int graphic = el2img(element);
9659 boolean oben_frei = FALSE, unten_frei = FALSE;
9660 boolean links_frei = FALSE, rechts_frei = FALSE;
9661 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9662 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9663 boolean new_wall = FALSE;
9665 if (IS_ANIMATED(graphic))
9666 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9668 if (!MovDelay[ax][ay]) // start building new wall
9669 MovDelay[ax][ay] = 6;
9671 if (MovDelay[ax][ay]) // wait some time before building new wall
9674 if (MovDelay[ax][ay])
9678 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9680 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9682 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9684 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9687 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9688 element == EL_EXPANDABLE_WALL_ANY)
9692 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9693 Store[ax][ay-1] = element;
9694 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9695 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9696 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9697 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9702 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9703 Store[ax][ay+1] = element;
9704 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9705 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9706 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9707 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9712 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9713 element == EL_EXPANDABLE_WALL_ANY ||
9714 element == EL_EXPANDABLE_WALL ||
9715 element == EL_BD_EXPANDABLE_WALL)
9719 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9720 Store[ax-1][ay] = element;
9721 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9722 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9723 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9724 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9730 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9731 Store[ax+1][ay] = element;
9732 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9733 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9734 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9735 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9740 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9741 TEST_DrawLevelField(ax, ay);
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_WALL_HORIZONTAL ||
9754 element == EL_EXPANDABLE_WALL) &&
9755 ((links_massiv && rechts_massiv) ||
9756 element == EL_EXPANDABLE_WALL_VERTICAL))
9757 Tile[ax][ay] = EL_WALL;
9760 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9763 static void MauerAblegerStahl(int ax, int ay)
9765 int element = Tile[ax][ay];
9766 int graphic = el2img(element);
9767 boolean oben_frei = FALSE, unten_frei = FALSE;
9768 boolean links_frei = FALSE, rechts_frei = FALSE;
9769 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9770 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9771 boolean new_wall = FALSE;
9773 if (IS_ANIMATED(graphic))
9774 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9776 if (!MovDelay[ax][ay]) // start building new wall
9777 MovDelay[ax][ay] = 6;
9779 if (MovDelay[ax][ay]) // wait some time before building new wall
9782 if (MovDelay[ax][ay])
9786 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9788 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9790 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9792 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9795 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9796 element == EL_EXPANDABLE_STEELWALL_ANY)
9800 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9801 Store[ax][ay-1] = element;
9802 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9803 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9804 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9805 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9810 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9811 Store[ax][ay+1] = element;
9812 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9813 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9814 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9815 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9820 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9821 element == EL_EXPANDABLE_STEELWALL_ANY)
9825 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9826 Store[ax-1][ay] = element;
9827 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9828 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9829 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9830 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9836 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9837 Store[ax+1][ay] = element;
9838 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9839 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9840 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9841 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9846 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9848 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9849 unten_massiv = TRUE;
9850 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9851 links_massiv = TRUE;
9852 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9853 rechts_massiv = TRUE;
9855 if (((oben_massiv && unten_massiv) ||
9856 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9857 ((links_massiv && rechts_massiv) ||
9858 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9859 Tile[ax][ay] = EL_STEELWALL;
9862 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9865 static void CheckForDragon(int x, int y)
9868 boolean dragon_found = FALSE;
9869 static int xy[4][2] =
9877 for (i = 0; i < NUM_DIRECTIONS; i++)
9879 for (j = 0; j < 4; j++)
9881 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9883 if (IN_LEV_FIELD(xx, yy) &&
9884 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9886 if (Tile[xx][yy] == EL_DRAGON)
9887 dragon_found = TRUE;
9896 for (i = 0; i < NUM_DIRECTIONS; i++)
9898 for (j = 0; j < 3; j++)
9900 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9902 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9904 Tile[xx][yy] = EL_EMPTY;
9905 TEST_DrawLevelField(xx, yy);
9914 static void InitBuggyBase(int x, int y)
9916 int element = Tile[x][y];
9917 int activating_delay = FRAMES_PER_SECOND / 4;
9920 (element == EL_SP_BUGGY_BASE ?
9921 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9922 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9924 element == EL_SP_BUGGY_BASE_ACTIVE ?
9925 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9928 static void WarnBuggyBase(int x, int y)
9931 static int xy[4][2] =
9939 for (i = 0; i < NUM_DIRECTIONS; i++)
9941 int xx = x + xy[i][0];
9942 int yy = y + xy[i][1];
9944 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9946 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9953 static void InitTrap(int x, int y)
9955 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9958 static void ActivateTrap(int x, int y)
9960 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9963 static void ChangeActiveTrap(int x, int y)
9965 int graphic = IMG_TRAP_ACTIVE;
9967 // if new animation frame was drawn, correct crumbled sand border
9968 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9969 TEST_DrawLevelFieldCrumbled(x, y);
9972 static int getSpecialActionElement(int element, int number, int base_element)
9974 return (element != EL_EMPTY ? element :
9975 number != -1 ? base_element + number - 1 :
9979 static int getModifiedActionNumber(int value_old, int operator, int operand,
9980 int value_min, int value_max)
9982 int value_new = (operator == CA_MODE_SET ? operand :
9983 operator == CA_MODE_ADD ? value_old + operand :
9984 operator == CA_MODE_SUBTRACT ? value_old - operand :
9985 operator == CA_MODE_MULTIPLY ? value_old * operand :
9986 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9987 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9990 return (value_new < value_min ? value_min :
9991 value_new > value_max ? value_max :
9995 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9997 struct ElementInfo *ei = &element_info[element];
9998 struct ElementChangeInfo *change = &ei->change_page[page];
9999 int target_element = change->target_element;
10000 int action_type = change->action_type;
10001 int action_mode = change->action_mode;
10002 int action_arg = change->action_arg;
10003 int action_element = change->action_element;
10006 if (!change->has_action)
10009 // ---------- determine action paramater values -----------------------------
10011 int level_time_value =
10012 (level.time > 0 ? TimeLeft :
10015 int action_arg_element_raw =
10016 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10017 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10018 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10019 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10020 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10021 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10022 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10024 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10026 int action_arg_direction =
10027 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10028 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10029 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10030 change->actual_trigger_side :
10031 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10032 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10035 int action_arg_number_min =
10036 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10039 int action_arg_number_max =
10040 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10041 action_type == CA_SET_LEVEL_GEMS ? 999 :
10042 action_type == CA_SET_LEVEL_TIME ? 9999 :
10043 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10044 action_type == CA_SET_CE_VALUE ? 9999 :
10045 action_type == CA_SET_CE_SCORE ? 9999 :
10048 int action_arg_number_reset =
10049 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10050 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10051 action_type == CA_SET_LEVEL_TIME ? level.time :
10052 action_type == CA_SET_LEVEL_SCORE ? 0 :
10053 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10054 action_type == CA_SET_CE_SCORE ? 0 :
10057 int action_arg_number =
10058 (action_arg <= CA_ARG_MAX ? action_arg :
10059 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10060 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10061 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10062 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10063 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10064 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10065 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10066 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10067 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10068 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10069 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10070 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10071 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10072 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10073 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10074 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10075 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10076 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10077 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10078 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10079 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10082 int action_arg_number_old =
10083 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10084 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10085 action_type == CA_SET_LEVEL_SCORE ? game.score :
10086 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10087 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10090 int action_arg_number_new =
10091 getModifiedActionNumber(action_arg_number_old,
10092 action_mode, action_arg_number,
10093 action_arg_number_min, action_arg_number_max);
10095 int trigger_player_bits =
10096 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10097 change->actual_trigger_player_bits : change->trigger_player);
10099 int action_arg_player_bits =
10100 (action_arg >= CA_ARG_PLAYER_1 &&
10101 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10102 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10103 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10106 // ---------- execute action -----------------------------------------------
10108 switch (action_type)
10115 // ---------- level actions ----------------------------------------------
10117 case CA_RESTART_LEVEL:
10119 game.restart_level = TRUE;
10124 case CA_SHOW_ENVELOPE:
10126 int element = getSpecialActionElement(action_arg_element,
10127 action_arg_number, EL_ENVELOPE_1);
10129 if (IS_ENVELOPE(element))
10130 local_player->show_envelope = element;
10135 case CA_SET_LEVEL_TIME:
10137 if (level.time > 0) // only modify limited time value
10139 TimeLeft = action_arg_number_new;
10141 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10143 DisplayGameControlValues();
10145 if (!TimeLeft && setup.time_limit)
10146 for (i = 0; i < MAX_PLAYERS; i++)
10147 KillPlayer(&stored_player[i]);
10153 case CA_SET_LEVEL_SCORE:
10155 game.score = action_arg_number_new;
10157 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10159 DisplayGameControlValues();
10164 case CA_SET_LEVEL_GEMS:
10166 game.gems_still_needed = action_arg_number_new;
10168 game.snapshot.collected_item = TRUE;
10170 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10172 DisplayGameControlValues();
10177 case CA_SET_LEVEL_WIND:
10179 game.wind_direction = action_arg_direction;
10184 case CA_SET_LEVEL_RANDOM_SEED:
10186 // ensure that setting a new random seed while playing is predictable
10187 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10192 // ---------- player actions ---------------------------------------------
10194 case CA_MOVE_PLAYER:
10195 case CA_MOVE_PLAYER_NEW:
10197 // automatically move to the next field in specified direction
10198 for (i = 0; i < MAX_PLAYERS; i++)
10199 if (trigger_player_bits & (1 << i))
10200 if (action_type == CA_MOVE_PLAYER ||
10201 stored_player[i].MovPos == 0)
10202 stored_player[i].programmed_action = action_arg_direction;
10207 case CA_EXIT_PLAYER:
10209 for (i = 0; i < MAX_PLAYERS; i++)
10210 if (action_arg_player_bits & (1 << i))
10211 ExitPlayer(&stored_player[i]);
10213 if (game.players_still_needed == 0)
10219 case CA_KILL_PLAYER:
10221 for (i = 0; i < MAX_PLAYERS; i++)
10222 if (action_arg_player_bits & (1 << i))
10223 KillPlayer(&stored_player[i]);
10228 case CA_SET_PLAYER_KEYS:
10230 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10231 int element = getSpecialActionElement(action_arg_element,
10232 action_arg_number, EL_KEY_1);
10234 if (IS_KEY(element))
10236 for (i = 0; i < MAX_PLAYERS; i++)
10238 if (trigger_player_bits & (1 << i))
10240 stored_player[i].key[KEY_NR(element)] = key_state;
10242 DrawGameDoorValues();
10250 case CA_SET_PLAYER_SPEED:
10252 for (i = 0; i < MAX_PLAYERS; i++)
10254 if (trigger_player_bits & (1 << i))
10256 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10258 if (action_arg == CA_ARG_SPEED_FASTER &&
10259 stored_player[i].cannot_move)
10261 action_arg_number = STEPSIZE_VERY_SLOW;
10263 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10264 action_arg == CA_ARG_SPEED_FASTER)
10266 action_arg_number = 2;
10267 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10270 else if (action_arg == CA_ARG_NUMBER_RESET)
10272 action_arg_number = level.initial_player_stepsize[i];
10276 getModifiedActionNumber(move_stepsize,
10279 action_arg_number_min,
10280 action_arg_number_max);
10282 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10289 case CA_SET_PLAYER_SHIELD:
10291 for (i = 0; i < MAX_PLAYERS; i++)
10293 if (trigger_player_bits & (1 << i))
10295 if (action_arg == CA_ARG_SHIELD_OFF)
10297 stored_player[i].shield_normal_time_left = 0;
10298 stored_player[i].shield_deadly_time_left = 0;
10300 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10302 stored_player[i].shield_normal_time_left = 999999;
10304 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10306 stored_player[i].shield_normal_time_left = 999999;
10307 stored_player[i].shield_deadly_time_left = 999999;
10315 case CA_SET_PLAYER_GRAVITY:
10317 for (i = 0; i < MAX_PLAYERS; i++)
10319 if (trigger_player_bits & (1 << i))
10321 stored_player[i].gravity =
10322 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10323 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10324 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10325 stored_player[i].gravity);
10332 case CA_SET_PLAYER_ARTWORK:
10334 for (i = 0; i < MAX_PLAYERS; i++)
10336 if (trigger_player_bits & (1 << i))
10338 int artwork_element = action_arg_element;
10340 if (action_arg == CA_ARG_ELEMENT_RESET)
10342 (level.use_artwork_element[i] ? level.artwork_element[i] :
10343 stored_player[i].element_nr);
10345 if (stored_player[i].artwork_element != artwork_element)
10346 stored_player[i].Frame = 0;
10348 stored_player[i].artwork_element = artwork_element;
10350 SetPlayerWaiting(&stored_player[i], FALSE);
10352 // set number of special actions for bored and sleeping animation
10353 stored_player[i].num_special_action_bored =
10354 get_num_special_action(artwork_element,
10355 ACTION_BORING_1, ACTION_BORING_LAST);
10356 stored_player[i].num_special_action_sleeping =
10357 get_num_special_action(artwork_element,
10358 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10365 case CA_SET_PLAYER_INVENTORY:
10367 for (i = 0; i < MAX_PLAYERS; i++)
10369 struct PlayerInfo *player = &stored_player[i];
10372 if (trigger_player_bits & (1 << i))
10374 int inventory_element = action_arg_element;
10376 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10377 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10378 action_arg == CA_ARG_ELEMENT_ACTION)
10380 int element = inventory_element;
10381 int collect_count = element_info[element].collect_count_initial;
10383 if (!IS_CUSTOM_ELEMENT(element))
10386 if (collect_count == 0)
10387 player->inventory_infinite_element = element;
10389 for (k = 0; k < collect_count; k++)
10390 if (player->inventory_size < MAX_INVENTORY_SIZE)
10391 player->inventory_element[player->inventory_size++] =
10394 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10395 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10396 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10398 if (player->inventory_infinite_element != EL_UNDEFINED &&
10399 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10400 action_arg_element_raw))
10401 player->inventory_infinite_element = EL_UNDEFINED;
10403 for (k = 0, j = 0; j < player->inventory_size; j++)
10405 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10406 action_arg_element_raw))
10407 player->inventory_element[k++] = player->inventory_element[j];
10410 player->inventory_size = k;
10412 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10414 if (player->inventory_size > 0)
10416 for (j = 0; j < player->inventory_size - 1; j++)
10417 player->inventory_element[j] = player->inventory_element[j + 1];
10419 player->inventory_size--;
10422 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10424 if (player->inventory_size > 0)
10425 player->inventory_size--;
10427 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10429 player->inventory_infinite_element = EL_UNDEFINED;
10430 player->inventory_size = 0;
10432 else if (action_arg == CA_ARG_INVENTORY_RESET)
10434 player->inventory_infinite_element = EL_UNDEFINED;
10435 player->inventory_size = 0;
10437 if (level.use_initial_inventory[i])
10439 for (j = 0; j < level.initial_inventory_size[i]; j++)
10441 int element = level.initial_inventory_content[i][j];
10442 int collect_count = element_info[element].collect_count_initial;
10444 if (!IS_CUSTOM_ELEMENT(element))
10447 if (collect_count == 0)
10448 player->inventory_infinite_element = element;
10450 for (k = 0; k < collect_count; k++)
10451 if (player->inventory_size < MAX_INVENTORY_SIZE)
10452 player->inventory_element[player->inventory_size++] =
10463 // ---------- CE actions -------------------------------------------------
10465 case CA_SET_CE_VALUE:
10467 int last_ce_value = CustomValue[x][y];
10469 CustomValue[x][y] = action_arg_number_new;
10471 if (CustomValue[x][y] != last_ce_value)
10473 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10474 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10476 if (CustomValue[x][y] == 0)
10478 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10479 ChangeCount[x][y] = 0; // allow at least one more change
10481 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10482 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10489 case CA_SET_CE_SCORE:
10491 int last_ce_score = ei->collect_score;
10493 ei->collect_score = action_arg_number_new;
10495 if (ei->collect_score != last_ce_score)
10497 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10498 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10500 if (ei->collect_score == 0)
10504 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10505 ChangeCount[x][y] = 0; // allow at least one more change
10507 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10508 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10511 This is a very special case that seems to be a mixture between
10512 CheckElementChange() and CheckTriggeredElementChange(): while
10513 the first one only affects single elements that are triggered
10514 directly, the second one affects multiple elements in the playfield
10515 that are triggered indirectly by another element. This is a third
10516 case: Changing the CE score always affects multiple identical CEs,
10517 so every affected CE must be checked, not only the single CE for
10518 which the CE score was changed in the first place (as every instance
10519 of that CE shares the same CE score, and therefore also can change)!
10521 SCAN_PLAYFIELD(xx, yy)
10523 if (Tile[xx][yy] == element)
10524 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10525 CE_SCORE_GETS_ZERO);
10533 case CA_SET_CE_ARTWORK:
10535 int artwork_element = action_arg_element;
10536 boolean reset_frame = FALSE;
10539 if (action_arg == CA_ARG_ELEMENT_RESET)
10540 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10543 if (ei->gfx_element != artwork_element)
10544 reset_frame = TRUE;
10546 ei->gfx_element = artwork_element;
10548 SCAN_PLAYFIELD(xx, yy)
10550 if (Tile[xx][yy] == element)
10554 ResetGfxAnimation(xx, yy);
10555 ResetRandomAnimationValue(xx, yy);
10558 TEST_DrawLevelField(xx, yy);
10565 // ---------- engine actions ---------------------------------------------
10567 case CA_SET_ENGINE_SCAN_MODE:
10569 InitPlayfieldScanMode(action_arg);
10579 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10581 int old_element = Tile[x][y];
10582 int new_element = GetElementFromGroupElement(element);
10583 int previous_move_direction = MovDir[x][y];
10584 int last_ce_value = CustomValue[x][y];
10585 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10586 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10587 boolean add_player_onto_element = (new_element_is_player &&
10588 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10589 IS_WALKABLE(old_element));
10591 if (!add_player_onto_element)
10593 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10594 RemoveMovingField(x, y);
10598 Tile[x][y] = new_element;
10600 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10601 MovDir[x][y] = previous_move_direction;
10603 if (element_info[new_element].use_last_ce_value)
10604 CustomValue[x][y] = last_ce_value;
10606 InitField_WithBug1(x, y, FALSE);
10608 new_element = Tile[x][y]; // element may have changed
10610 ResetGfxAnimation(x, y);
10611 ResetRandomAnimationValue(x, y);
10613 TEST_DrawLevelField(x, y);
10615 if (GFX_CRUMBLED(new_element))
10616 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10619 // check if element under the player changes from accessible to unaccessible
10620 // (needed for special case of dropping element which then changes)
10621 // (must be checked after creating new element for walkable group elements)
10622 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10623 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10630 // "ChangeCount" not set yet to allow "entered by player" change one time
10631 if (new_element_is_player)
10632 RelocatePlayer(x, y, new_element);
10635 ChangeCount[x][y]++; // count number of changes in the same frame
10637 TestIfBadThingTouchesPlayer(x, y);
10638 TestIfPlayerTouchesCustomElement(x, y);
10639 TestIfElementTouchesCustomElement(x, y);
10642 static void CreateField(int x, int y, int element)
10644 CreateFieldExt(x, y, element, FALSE);
10647 static void CreateElementFromChange(int x, int y, int element)
10649 element = GET_VALID_RUNTIME_ELEMENT(element);
10651 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10653 int old_element = Tile[x][y];
10655 // prevent changed element from moving in same engine frame
10656 // unless both old and new element can either fall or move
10657 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10658 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10662 CreateFieldExt(x, y, element, TRUE);
10665 static boolean ChangeElement(int x, int y, int element, int page)
10667 struct ElementInfo *ei = &element_info[element];
10668 struct ElementChangeInfo *change = &ei->change_page[page];
10669 int ce_value = CustomValue[x][y];
10670 int ce_score = ei->collect_score;
10671 int target_element;
10672 int old_element = Tile[x][y];
10674 // always use default change event to prevent running into a loop
10675 if (ChangeEvent[x][y] == -1)
10676 ChangeEvent[x][y] = CE_DELAY;
10678 if (ChangeEvent[x][y] == CE_DELAY)
10680 // reset actual trigger element, trigger player and action element
10681 change->actual_trigger_element = EL_EMPTY;
10682 change->actual_trigger_player = EL_EMPTY;
10683 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10684 change->actual_trigger_side = CH_SIDE_NONE;
10685 change->actual_trigger_ce_value = 0;
10686 change->actual_trigger_ce_score = 0;
10689 // do not change elements more than a specified maximum number of changes
10690 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10693 ChangeCount[x][y]++; // count number of changes in the same frame
10695 if (change->explode)
10702 if (change->use_target_content)
10704 boolean complete_replace = TRUE;
10705 boolean can_replace[3][3];
10708 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10711 boolean is_walkable;
10712 boolean is_diggable;
10713 boolean is_collectible;
10714 boolean is_removable;
10715 boolean is_destructible;
10716 int ex = x + xx - 1;
10717 int ey = y + yy - 1;
10718 int content_element = change->target_content.e[xx][yy];
10721 can_replace[xx][yy] = TRUE;
10723 if (ex == x && ey == y) // do not check changing element itself
10726 if (content_element == EL_EMPTY_SPACE)
10728 can_replace[xx][yy] = FALSE; // do not replace border with space
10733 if (!IN_LEV_FIELD(ex, ey))
10735 can_replace[xx][yy] = FALSE;
10736 complete_replace = FALSE;
10743 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10744 e = MovingOrBlocked2Element(ex, ey);
10746 is_empty = (IS_FREE(ex, ey) ||
10747 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10749 is_walkable = (is_empty || IS_WALKABLE(e));
10750 is_diggable = (is_empty || IS_DIGGABLE(e));
10751 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10752 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10753 is_removable = (is_diggable || is_collectible);
10755 can_replace[xx][yy] =
10756 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10757 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10758 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10759 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10760 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10761 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10762 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10764 if (!can_replace[xx][yy])
10765 complete_replace = FALSE;
10768 if (!change->only_if_complete || complete_replace)
10770 boolean something_has_changed = FALSE;
10772 if (change->only_if_complete && change->use_random_replace &&
10773 RND(100) < change->random_percentage)
10776 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10778 int ex = x + xx - 1;
10779 int ey = y + yy - 1;
10780 int content_element;
10782 if (can_replace[xx][yy] && (!change->use_random_replace ||
10783 RND(100) < change->random_percentage))
10785 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10786 RemoveMovingField(ex, ey);
10788 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10790 content_element = change->target_content.e[xx][yy];
10791 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10792 ce_value, ce_score);
10794 CreateElementFromChange(ex, ey, target_element);
10796 something_has_changed = TRUE;
10798 // for symmetry reasons, freeze newly created border elements
10799 if (ex != x || ey != y)
10800 Stop[ex][ey] = TRUE; // no more moving in this frame
10804 if (something_has_changed)
10806 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10807 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10813 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10814 ce_value, ce_score);
10816 if (element == EL_DIAGONAL_GROWING ||
10817 element == EL_DIAGONAL_SHRINKING)
10819 target_element = Store[x][y];
10821 Store[x][y] = EL_EMPTY;
10824 CreateElementFromChange(x, y, target_element);
10826 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10827 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10830 // this uses direct change before indirect change
10831 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10836 static void HandleElementChange(int x, int y, int page)
10838 int element = MovingOrBlocked2Element(x, y);
10839 struct ElementInfo *ei = &element_info[element];
10840 struct ElementChangeInfo *change = &ei->change_page[page];
10841 boolean handle_action_before_change = FALSE;
10844 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10845 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10847 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10848 x, y, element, element_info[element].token_name);
10849 Debug("game:playing:HandleElementChange", "This should never happen!");
10853 // this can happen with classic bombs on walkable, changing elements
10854 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10859 if (ChangeDelay[x][y] == 0) // initialize element change
10861 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10863 if (change->can_change)
10865 // !!! not clear why graphic animation should be reset at all here !!!
10866 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10867 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10870 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10872 When using an animation frame delay of 1 (this only happens with
10873 "sp_zonk.moving.left/right" in the classic graphics), the default
10874 (non-moving) animation shows wrong animation frames (while the
10875 moving animation, like "sp_zonk.moving.left/right", is correct,
10876 so this graphical bug never shows up with the classic graphics).
10877 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10878 be drawn instead of the correct frames 0,1,2,3. This is caused by
10879 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10880 an element change: First when the change delay ("ChangeDelay[][]")
10881 counter has reached zero after decrementing, then a second time in
10882 the next frame (after "GfxFrame[][]" was already incremented) when
10883 "ChangeDelay[][]" is reset to the initial delay value again.
10885 This causes frame 0 to be drawn twice, while the last frame won't
10886 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10888 As some animations may already be cleverly designed around this bug
10889 (at least the "Snake Bite" snake tail animation does this), it cannot
10890 simply be fixed here without breaking such existing animations.
10891 Unfortunately, it cannot easily be detected if a graphics set was
10892 designed "before" or "after" the bug was fixed. As a workaround,
10893 a new graphics set option "game.graphics_engine_version" was added
10894 to be able to specify the game's major release version for which the
10895 graphics set was designed, which can then be used to decide if the
10896 bugfix should be used (version 4 and above) or not (version 3 or
10897 below, or if no version was specified at all, as with old sets).
10899 (The wrong/fixed animation frames can be tested with the test level set
10900 "test_gfxframe" and level "000", which contains a specially prepared
10901 custom element at level position (x/y) == (11/9) which uses the zonk
10902 animation mentioned above. Using "game.graphics_engine_version: 4"
10903 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10904 This can also be seen from the debug output for this test element.)
10907 // when a custom element is about to change (for example by change delay),
10908 // do not reset graphic animation when the custom element is moving
10909 if (game.graphics_engine_version < 4 &&
10912 ResetGfxAnimation(x, y);
10913 ResetRandomAnimationValue(x, y);
10916 if (change->pre_change_function)
10917 change->pre_change_function(x, y);
10921 ChangeDelay[x][y]--;
10923 if (ChangeDelay[x][y] != 0) // continue element change
10925 if (change->can_change)
10927 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10929 if (IS_ANIMATED(graphic))
10930 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10932 if (change->change_function)
10933 change->change_function(x, y);
10936 else // finish element change
10938 if (ChangePage[x][y] != -1) // remember page from delayed change
10940 page = ChangePage[x][y];
10941 ChangePage[x][y] = -1;
10943 change = &ei->change_page[page];
10946 if (IS_MOVING(x, y)) // never change a running system ;-)
10948 ChangeDelay[x][y] = 1; // try change after next move step
10949 ChangePage[x][y] = page; // remember page to use for change
10954 // special case: set new level random seed before changing element
10955 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10956 handle_action_before_change = TRUE;
10958 if (change->has_action && handle_action_before_change)
10959 ExecuteCustomElementAction(x, y, element, page);
10961 if (change->can_change)
10963 if (ChangeElement(x, y, element, page))
10965 if (change->post_change_function)
10966 change->post_change_function(x, y);
10970 if (change->has_action && !handle_action_before_change)
10971 ExecuteCustomElementAction(x, y, element, page);
10975 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10976 int trigger_element,
10978 int trigger_player,
10982 boolean change_done_any = FALSE;
10983 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10986 if (!(trigger_events[trigger_element][trigger_event]))
10989 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10991 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10993 int element = EL_CUSTOM_START + i;
10994 boolean change_done = FALSE;
10997 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10998 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11001 for (p = 0; p < element_info[element].num_change_pages; p++)
11003 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11005 if (change->can_change_or_has_action &&
11006 change->has_event[trigger_event] &&
11007 change->trigger_side & trigger_side &&
11008 change->trigger_player & trigger_player &&
11009 change->trigger_page & trigger_page_bits &&
11010 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11012 change->actual_trigger_element = trigger_element;
11013 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11014 change->actual_trigger_player_bits = trigger_player;
11015 change->actual_trigger_side = trigger_side;
11016 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11017 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11019 if ((change->can_change && !change_done) || change->has_action)
11023 SCAN_PLAYFIELD(x, y)
11025 if (Tile[x][y] == element)
11027 if (change->can_change && !change_done)
11029 // if element already changed in this frame, not only prevent
11030 // another element change (checked in ChangeElement()), but
11031 // also prevent additional element actions for this element
11033 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11034 !level.use_action_after_change_bug)
11037 ChangeDelay[x][y] = 1;
11038 ChangeEvent[x][y] = trigger_event;
11040 HandleElementChange(x, y, p);
11042 else if (change->has_action)
11044 // if element already changed in this frame, not only prevent
11045 // another element change (checked in ChangeElement()), but
11046 // also prevent additional element actions for this element
11048 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11049 !level.use_action_after_change_bug)
11052 ExecuteCustomElementAction(x, y, element, p);
11053 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11058 if (change->can_change)
11060 change_done = TRUE;
11061 change_done_any = TRUE;
11068 RECURSION_LOOP_DETECTION_END();
11070 return change_done_any;
11073 static boolean CheckElementChangeExt(int x, int y,
11075 int trigger_element,
11077 int trigger_player,
11080 boolean change_done = FALSE;
11083 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11084 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11087 if (Tile[x][y] == EL_BLOCKED)
11089 Blocked2Moving(x, y, &x, &y);
11090 element = Tile[x][y];
11093 // check if element has already changed or is about to change after moving
11094 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11095 Tile[x][y] != element) ||
11097 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11098 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11099 ChangePage[x][y] != -1)))
11102 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11104 for (p = 0; p < element_info[element].num_change_pages; p++)
11106 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11108 /* check trigger element for all events where the element that is checked
11109 for changing interacts with a directly adjacent element -- this is
11110 different to element changes that affect other elements to change on the
11111 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11112 boolean check_trigger_element =
11113 (trigger_event == CE_TOUCHING_X ||
11114 trigger_event == CE_HITTING_X ||
11115 trigger_event == CE_HIT_BY_X ||
11116 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11118 if (change->can_change_or_has_action &&
11119 change->has_event[trigger_event] &&
11120 change->trigger_side & trigger_side &&
11121 change->trigger_player & trigger_player &&
11122 (!check_trigger_element ||
11123 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11125 change->actual_trigger_element = trigger_element;
11126 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11127 change->actual_trigger_player_bits = trigger_player;
11128 change->actual_trigger_side = trigger_side;
11129 change->actual_trigger_ce_value = CustomValue[x][y];
11130 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11132 // special case: trigger element not at (x,y) position for some events
11133 if (check_trigger_element)
11145 { 0, 0 }, { 0, 0 }, { 0, 0 },
11149 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11150 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11152 change->actual_trigger_ce_value = CustomValue[xx][yy];
11153 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11156 if (change->can_change && !change_done)
11158 ChangeDelay[x][y] = 1;
11159 ChangeEvent[x][y] = trigger_event;
11161 HandleElementChange(x, y, p);
11163 change_done = TRUE;
11165 else if (change->has_action)
11167 ExecuteCustomElementAction(x, y, element, p);
11168 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11173 RECURSION_LOOP_DETECTION_END();
11175 return change_done;
11178 static void PlayPlayerSound(struct PlayerInfo *player)
11180 int jx = player->jx, jy = player->jy;
11181 int sound_element = player->artwork_element;
11182 int last_action = player->last_action_waiting;
11183 int action = player->action_waiting;
11185 if (player->is_waiting)
11187 if (action != last_action)
11188 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11190 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11194 if (action != last_action)
11195 StopSound(element_info[sound_element].sound[last_action]);
11197 if (last_action == ACTION_SLEEPING)
11198 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11202 static void PlayAllPlayersSound(void)
11206 for (i = 0; i < MAX_PLAYERS; i++)
11207 if (stored_player[i].active)
11208 PlayPlayerSound(&stored_player[i]);
11211 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11213 boolean last_waiting = player->is_waiting;
11214 int move_dir = player->MovDir;
11216 player->dir_waiting = move_dir;
11217 player->last_action_waiting = player->action_waiting;
11221 if (!last_waiting) // not waiting -> waiting
11223 player->is_waiting = TRUE;
11225 player->frame_counter_bored =
11227 game.player_boring_delay_fixed +
11228 GetSimpleRandom(game.player_boring_delay_random);
11229 player->frame_counter_sleeping =
11231 game.player_sleeping_delay_fixed +
11232 GetSimpleRandom(game.player_sleeping_delay_random);
11234 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11237 if (game.player_sleeping_delay_fixed +
11238 game.player_sleeping_delay_random > 0 &&
11239 player->anim_delay_counter == 0 &&
11240 player->post_delay_counter == 0 &&
11241 FrameCounter >= player->frame_counter_sleeping)
11242 player->is_sleeping = TRUE;
11243 else if (game.player_boring_delay_fixed +
11244 game.player_boring_delay_random > 0 &&
11245 FrameCounter >= player->frame_counter_bored)
11246 player->is_bored = TRUE;
11248 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11249 player->is_bored ? ACTION_BORING :
11252 if (player->is_sleeping && player->use_murphy)
11254 // special case for sleeping Murphy when leaning against non-free tile
11256 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11257 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11258 !IS_MOVING(player->jx - 1, player->jy)))
11259 move_dir = MV_LEFT;
11260 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11261 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11262 !IS_MOVING(player->jx + 1, player->jy)))
11263 move_dir = MV_RIGHT;
11265 player->is_sleeping = FALSE;
11267 player->dir_waiting = move_dir;
11270 if (player->is_sleeping)
11272 if (player->num_special_action_sleeping > 0)
11274 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11276 int last_special_action = player->special_action_sleeping;
11277 int num_special_action = player->num_special_action_sleeping;
11278 int special_action =
11279 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11280 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11281 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11282 last_special_action + 1 : ACTION_SLEEPING);
11283 int special_graphic =
11284 el_act_dir2img(player->artwork_element, special_action, move_dir);
11286 player->anim_delay_counter =
11287 graphic_info[special_graphic].anim_delay_fixed +
11288 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11289 player->post_delay_counter =
11290 graphic_info[special_graphic].post_delay_fixed +
11291 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11293 player->special_action_sleeping = special_action;
11296 if (player->anim_delay_counter > 0)
11298 player->action_waiting = player->special_action_sleeping;
11299 player->anim_delay_counter--;
11301 else if (player->post_delay_counter > 0)
11303 player->post_delay_counter--;
11307 else if (player->is_bored)
11309 if (player->num_special_action_bored > 0)
11311 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11313 int special_action =
11314 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11315 int special_graphic =
11316 el_act_dir2img(player->artwork_element, special_action, move_dir);
11318 player->anim_delay_counter =
11319 graphic_info[special_graphic].anim_delay_fixed +
11320 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11321 player->post_delay_counter =
11322 graphic_info[special_graphic].post_delay_fixed +
11323 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11325 player->special_action_bored = special_action;
11328 if (player->anim_delay_counter > 0)
11330 player->action_waiting = player->special_action_bored;
11331 player->anim_delay_counter--;
11333 else if (player->post_delay_counter > 0)
11335 player->post_delay_counter--;
11340 else if (last_waiting) // waiting -> not waiting
11342 player->is_waiting = FALSE;
11343 player->is_bored = FALSE;
11344 player->is_sleeping = FALSE;
11346 player->frame_counter_bored = -1;
11347 player->frame_counter_sleeping = -1;
11349 player->anim_delay_counter = 0;
11350 player->post_delay_counter = 0;
11352 player->dir_waiting = player->MovDir;
11353 player->action_waiting = ACTION_DEFAULT;
11355 player->special_action_bored = ACTION_DEFAULT;
11356 player->special_action_sleeping = ACTION_DEFAULT;
11360 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11362 if ((!player->is_moving && player->was_moving) ||
11363 (player->MovPos == 0 && player->was_moving) ||
11364 (player->is_snapping && !player->was_snapping) ||
11365 (player->is_dropping && !player->was_dropping))
11367 if (!CheckSaveEngineSnapshotToList())
11370 player->was_moving = FALSE;
11371 player->was_snapping = TRUE;
11372 player->was_dropping = TRUE;
11376 if (player->is_moving)
11377 player->was_moving = TRUE;
11379 if (!player->is_snapping)
11380 player->was_snapping = FALSE;
11382 if (!player->is_dropping)
11383 player->was_dropping = FALSE;
11386 static struct MouseActionInfo mouse_action_last = { 0 };
11387 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11388 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11391 CheckSaveEngineSnapshotToList();
11393 mouse_action_last = mouse_action;
11396 static void CheckSingleStepMode(struct PlayerInfo *player)
11398 if (tape.single_step && tape.recording && !tape.pausing)
11400 // as it is called "single step mode", just return to pause mode when the
11401 // player stopped moving after one tile (or never starts moving at all)
11402 // (reverse logic needed here in case single step mode used in team mode)
11403 if (player->is_moving ||
11404 player->is_pushing ||
11405 player->is_dropping_pressed ||
11406 player->effective_mouse_action.button)
11407 game.enter_single_step_mode = FALSE;
11410 CheckSaveEngineSnapshot(player);
11413 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11415 int left = player_action & JOY_LEFT;
11416 int right = player_action & JOY_RIGHT;
11417 int up = player_action & JOY_UP;
11418 int down = player_action & JOY_DOWN;
11419 int button1 = player_action & JOY_BUTTON_1;
11420 int button2 = player_action & JOY_BUTTON_2;
11421 int dx = (left ? -1 : right ? 1 : 0);
11422 int dy = (up ? -1 : down ? 1 : 0);
11424 if (!player->active || tape.pausing)
11430 SnapField(player, dx, dy);
11434 DropElement(player);
11436 MovePlayer(player, dx, dy);
11439 CheckSingleStepMode(player);
11441 SetPlayerWaiting(player, FALSE);
11443 return player_action;
11447 // no actions for this player (no input at player's configured device)
11449 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11450 SnapField(player, 0, 0);
11451 CheckGravityMovementWhenNotMoving(player);
11453 if (player->MovPos == 0)
11454 SetPlayerWaiting(player, TRUE);
11456 if (player->MovPos == 0) // needed for tape.playing
11457 player->is_moving = FALSE;
11459 player->is_dropping = FALSE;
11460 player->is_dropping_pressed = FALSE;
11461 player->drop_pressed_delay = 0;
11463 CheckSingleStepMode(player);
11469 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11472 if (!tape.use_mouse_actions)
11475 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11476 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11477 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11480 static void SetTapeActionFromMouseAction(byte *tape_action,
11481 struct MouseActionInfo *mouse_action)
11483 if (!tape.use_mouse_actions)
11486 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11487 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11488 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11491 static void CheckLevelSolved(void)
11493 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11495 if (game_em.level_solved &&
11496 !game_em.game_over) // game won
11500 game_em.game_over = TRUE;
11502 game.all_players_gone = TRUE;
11505 if (game_em.game_over) // game lost
11506 game.all_players_gone = TRUE;
11508 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11510 if (game_sp.level_solved &&
11511 !game_sp.game_over) // game won
11515 game_sp.game_over = TRUE;
11517 game.all_players_gone = TRUE;
11520 if (game_sp.game_over) // game lost
11521 game.all_players_gone = TRUE;
11523 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11525 if (game_mm.level_solved &&
11526 !game_mm.game_over) // game won
11530 game_mm.game_over = TRUE;
11532 game.all_players_gone = TRUE;
11535 if (game_mm.game_over) // game lost
11536 game.all_players_gone = TRUE;
11540 static void CheckLevelTime(void)
11544 if (TimeFrames >= FRAMES_PER_SECOND)
11549 for (i = 0; i < MAX_PLAYERS; i++)
11551 struct PlayerInfo *player = &stored_player[i];
11553 if (SHIELD_ON(player))
11555 player->shield_normal_time_left--;
11557 if (player->shield_deadly_time_left > 0)
11558 player->shield_deadly_time_left--;
11562 if (!game.LevelSolved && !level.use_step_counter)
11570 if (TimeLeft <= 10 && setup.time_limit)
11571 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11573 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11574 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11576 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11578 if (!TimeLeft && setup.time_limit)
11580 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11581 game_em.lev->killed_out_of_time = TRUE;
11583 for (i = 0; i < MAX_PLAYERS; i++)
11584 KillPlayer(&stored_player[i]);
11587 else if (game.no_time_limit && !game.all_players_gone)
11589 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11592 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11595 if (tape.recording || tape.playing)
11596 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11599 if (tape.recording || tape.playing)
11600 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11602 UpdateAndDisplayGameControlValues();
11605 void AdvanceFrameAndPlayerCounters(int player_nr)
11609 // advance frame counters (global frame counter and time frame counter)
11613 // advance player counters (counters for move delay, move animation etc.)
11614 for (i = 0; i < MAX_PLAYERS; i++)
11616 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11617 int move_delay_value = stored_player[i].move_delay_value;
11618 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11620 if (!advance_player_counters) // not all players may be affected
11623 if (move_frames == 0) // less than one move per game frame
11625 int stepsize = TILEX / move_delay_value;
11626 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11627 int count = (stored_player[i].is_moving ?
11628 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11630 if (count % delay == 0)
11634 stored_player[i].Frame += move_frames;
11636 if (stored_player[i].MovPos != 0)
11637 stored_player[i].StepFrame += move_frames;
11639 if (stored_player[i].move_delay > 0)
11640 stored_player[i].move_delay--;
11642 // due to bugs in previous versions, counter must count up, not down
11643 if (stored_player[i].push_delay != -1)
11644 stored_player[i].push_delay++;
11646 if (stored_player[i].drop_delay > 0)
11647 stored_player[i].drop_delay--;
11649 if (stored_player[i].is_dropping_pressed)
11650 stored_player[i].drop_pressed_delay++;
11654 void StartGameActions(boolean init_network_game, boolean record_tape,
11657 unsigned int new_random_seed = InitRND(random_seed);
11660 TapeStartRecording(new_random_seed);
11662 if (init_network_game)
11664 SendToServer_LevelFile();
11665 SendToServer_StartPlaying();
11673 static void GameActionsExt(void)
11676 static unsigned int game_frame_delay = 0;
11678 unsigned int game_frame_delay_value;
11679 byte *recorded_player_action;
11680 byte summarized_player_action = 0;
11681 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11684 // detect endless loops, caused by custom element programming
11685 if (recursion_loop_detected && recursion_loop_depth == 0)
11687 char *message = getStringCat3("Internal Error! Element ",
11688 EL_NAME(recursion_loop_element),
11689 " caused endless loop! Quit the game?");
11691 Warn("element '%s' caused endless loop in game engine",
11692 EL_NAME(recursion_loop_element));
11694 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11696 recursion_loop_detected = FALSE; // if game should be continued
11703 if (game.restart_level)
11704 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11706 CheckLevelSolved();
11708 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11711 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11714 if (game_status != GAME_MODE_PLAYING) // status might have changed
11717 game_frame_delay_value =
11718 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11720 if (tape.playing && tape.warp_forward && !tape.pausing)
11721 game_frame_delay_value = 0;
11723 SetVideoFrameDelay(game_frame_delay_value);
11725 // (de)activate virtual buttons depending on current game status
11726 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11728 if (game.all_players_gone) // if no players there to be controlled anymore
11729 SetOverlayActive(FALSE);
11730 else if (!tape.playing) // if game continues after tape stopped playing
11731 SetOverlayActive(TRUE);
11736 // ---------- main game synchronization point ----------
11738 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11740 Debug("game:playing:skip", "skip == %d", skip);
11743 // ---------- main game synchronization point ----------
11745 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11749 if (network_playing && !network_player_action_received)
11751 // try to get network player actions in time
11753 // last chance to get network player actions without main loop delay
11754 HandleNetworking();
11756 // game was quit by network peer
11757 if (game_status != GAME_MODE_PLAYING)
11760 // check if network player actions still missing and game still running
11761 if (!network_player_action_received && !checkGameEnded())
11762 return; // failed to get network player actions in time
11764 // do not yet reset "network_player_action_received" (for tape.pausing)
11770 // at this point we know that we really continue executing the game
11772 network_player_action_received = FALSE;
11774 // when playing tape, read previously recorded player input from tape data
11775 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11777 local_player->effective_mouse_action = local_player->mouse_action;
11779 if (recorded_player_action != NULL)
11780 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11781 recorded_player_action);
11783 // TapePlayAction() may return NULL when toggling to "pause before death"
11787 if (tape.set_centered_player)
11789 game.centered_player_nr_next = tape.centered_player_nr_next;
11790 game.set_centered_player = TRUE;
11793 for (i = 0; i < MAX_PLAYERS; i++)
11795 summarized_player_action |= stored_player[i].action;
11797 if (!network_playing && (game.team_mode || tape.playing))
11798 stored_player[i].effective_action = stored_player[i].action;
11801 if (network_playing && !checkGameEnded())
11802 SendToServer_MovePlayer(summarized_player_action);
11804 // summarize all actions at local players mapped input device position
11805 // (this allows using different input devices in single player mode)
11806 if (!network.enabled && !game.team_mode)
11807 stored_player[map_player_action[local_player->index_nr]].effective_action =
11808 summarized_player_action;
11810 // summarize all actions at centered player in local team mode
11811 if (tape.recording &&
11812 setup.team_mode && !network.enabled &&
11813 setup.input_on_focus &&
11814 game.centered_player_nr != -1)
11816 for (i = 0; i < MAX_PLAYERS; i++)
11817 stored_player[map_player_action[i]].effective_action =
11818 (i == game.centered_player_nr ? summarized_player_action : 0);
11821 if (recorded_player_action != NULL)
11822 for (i = 0; i < MAX_PLAYERS; i++)
11823 stored_player[i].effective_action = recorded_player_action[i];
11825 for (i = 0; i < MAX_PLAYERS; i++)
11827 tape_action[i] = stored_player[i].effective_action;
11829 /* (this may happen in the RND game engine if a player was not present on
11830 the playfield on level start, but appeared later from a custom element */
11831 if (setup.team_mode &&
11834 !tape.player_participates[i])
11835 tape.player_participates[i] = TRUE;
11838 SetTapeActionFromMouseAction(tape_action,
11839 &local_player->effective_mouse_action);
11841 // only record actions from input devices, but not programmed actions
11842 if (tape.recording)
11843 TapeRecordAction(tape_action);
11845 // remember if game was played (especially after tape stopped playing)
11846 if (!tape.playing && summarized_player_action)
11847 game.GamePlayed = TRUE;
11849 #if USE_NEW_PLAYER_ASSIGNMENTS
11850 // !!! also map player actions in single player mode !!!
11851 // if (game.team_mode)
11854 byte mapped_action[MAX_PLAYERS];
11856 #if DEBUG_PLAYER_ACTIONS
11857 for (i = 0; i < MAX_PLAYERS; i++)
11858 DebugContinued("", "%d, ", stored_player[i].effective_action);
11861 for (i = 0; i < MAX_PLAYERS; i++)
11862 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11864 for (i = 0; i < MAX_PLAYERS; i++)
11865 stored_player[i].effective_action = mapped_action[i];
11867 #if DEBUG_PLAYER_ACTIONS
11868 DebugContinued("", "=> ");
11869 for (i = 0; i < MAX_PLAYERS; i++)
11870 DebugContinued("", "%d, ", stored_player[i].effective_action);
11871 DebugContinued("game:playing:player", "\n");
11874 #if DEBUG_PLAYER_ACTIONS
11877 for (i = 0; i < MAX_PLAYERS; i++)
11878 DebugContinued("", "%d, ", stored_player[i].effective_action);
11879 DebugContinued("game:playing:player", "\n");
11884 for (i = 0; i < MAX_PLAYERS; i++)
11886 // allow engine snapshot in case of changed movement attempt
11887 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11888 (stored_player[i].effective_action & KEY_MOTION))
11889 game.snapshot.changed_action = TRUE;
11891 // allow engine snapshot in case of snapping/dropping attempt
11892 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11893 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11894 game.snapshot.changed_action = TRUE;
11896 game.snapshot.last_action[i] = stored_player[i].effective_action;
11899 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11901 GameActions_EM_Main();
11903 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11905 GameActions_SP_Main();
11907 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11909 GameActions_MM_Main();
11913 GameActions_RND_Main();
11916 BlitScreenToBitmap(backbuffer);
11918 CheckLevelSolved();
11921 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11923 if (global.show_frames_per_second)
11925 static unsigned int fps_counter = 0;
11926 static int fps_frames = 0;
11927 unsigned int fps_delay_ms = Counter() - fps_counter;
11931 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11933 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11936 fps_counter = Counter();
11938 // always draw FPS to screen after FPS value was updated
11939 redraw_mask |= REDRAW_FPS;
11942 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11943 if (GetDrawDeactivationMask() == REDRAW_NONE)
11944 redraw_mask |= REDRAW_FPS;
11948 static void GameActions_CheckSaveEngineSnapshot(void)
11950 if (!game.snapshot.save_snapshot)
11953 // clear flag for saving snapshot _before_ saving snapshot
11954 game.snapshot.save_snapshot = FALSE;
11956 SaveEngineSnapshotToList();
11959 void GameActions(void)
11963 GameActions_CheckSaveEngineSnapshot();
11966 void GameActions_EM_Main(void)
11968 byte effective_action[MAX_PLAYERS];
11969 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11972 for (i = 0; i < MAX_PLAYERS; i++)
11973 effective_action[i] = stored_player[i].effective_action;
11975 GameActions_EM(effective_action, warp_mode);
11978 void GameActions_SP_Main(void)
11980 byte effective_action[MAX_PLAYERS];
11981 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11984 for (i = 0; i < MAX_PLAYERS; i++)
11985 effective_action[i] = stored_player[i].effective_action;
11987 GameActions_SP(effective_action, warp_mode);
11989 for (i = 0; i < MAX_PLAYERS; i++)
11991 if (stored_player[i].force_dropping)
11992 stored_player[i].action |= KEY_BUTTON_DROP;
11994 stored_player[i].force_dropping = FALSE;
11998 void GameActions_MM_Main(void)
12000 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12002 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12005 void GameActions_RND_Main(void)
12010 void GameActions_RND(void)
12012 static struct MouseActionInfo mouse_action_last = { 0 };
12013 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12014 int magic_wall_x = 0, magic_wall_y = 0;
12015 int i, x, y, element, graphic, last_gfx_frame;
12017 InitPlayfieldScanModeVars();
12019 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12021 SCAN_PLAYFIELD(x, y)
12023 ChangeCount[x][y] = 0;
12024 ChangeEvent[x][y] = -1;
12028 if (game.set_centered_player)
12030 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12032 // switching to "all players" only possible if all players fit to screen
12033 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12035 game.centered_player_nr_next = game.centered_player_nr;
12036 game.set_centered_player = FALSE;
12039 // do not switch focus to non-existing (or non-active) player
12040 if (game.centered_player_nr_next >= 0 &&
12041 !stored_player[game.centered_player_nr_next].active)
12043 game.centered_player_nr_next = game.centered_player_nr;
12044 game.set_centered_player = FALSE;
12048 if (game.set_centered_player &&
12049 ScreenMovPos == 0) // screen currently aligned at tile position
12053 if (game.centered_player_nr_next == -1)
12055 setScreenCenteredToAllPlayers(&sx, &sy);
12059 sx = stored_player[game.centered_player_nr_next].jx;
12060 sy = stored_player[game.centered_player_nr_next].jy;
12063 game.centered_player_nr = game.centered_player_nr_next;
12064 game.set_centered_player = FALSE;
12066 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12067 DrawGameDoorValues();
12070 // check single step mode (set flag and clear again if any player is active)
12071 game.enter_single_step_mode =
12072 (tape.single_step && tape.recording && !tape.pausing);
12074 for (i = 0; i < MAX_PLAYERS; i++)
12076 int actual_player_action = stored_player[i].effective_action;
12079 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12080 - rnd_equinox_tetrachloride 048
12081 - rnd_equinox_tetrachloride_ii 096
12082 - rnd_emanuel_schmieg 002
12083 - doctor_sloan_ww 001, 020
12085 if (stored_player[i].MovPos == 0)
12086 CheckGravityMovement(&stored_player[i]);
12089 // overwrite programmed action with tape action
12090 if (stored_player[i].programmed_action)
12091 actual_player_action = stored_player[i].programmed_action;
12093 PlayerActions(&stored_player[i], actual_player_action);
12095 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12098 // single step pause mode may already have been toggled by "ScrollPlayer()"
12099 if (game.enter_single_step_mode && !tape.pausing)
12100 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12102 ScrollScreen(NULL, SCROLL_GO_ON);
12104 /* for backwards compatibility, the following code emulates a fixed bug that
12105 occured when pushing elements (causing elements that just made their last
12106 pushing step to already (if possible) make their first falling step in the
12107 same game frame, which is bad); this code is also needed to use the famous
12108 "spring push bug" which is used in older levels and might be wanted to be
12109 used also in newer levels, but in this case the buggy pushing code is only
12110 affecting the "spring" element and no other elements */
12112 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12114 for (i = 0; i < MAX_PLAYERS; i++)
12116 struct PlayerInfo *player = &stored_player[i];
12117 int x = player->jx;
12118 int y = player->jy;
12120 if (player->active && player->is_pushing && player->is_moving &&
12122 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12123 Tile[x][y] == EL_SPRING))
12125 ContinueMoving(x, y);
12127 // continue moving after pushing (this is actually a bug)
12128 if (!IS_MOVING(x, y))
12129 Stop[x][y] = FALSE;
12134 SCAN_PLAYFIELD(x, y)
12136 Last[x][y] = Tile[x][y];
12138 ChangeCount[x][y] = 0;
12139 ChangeEvent[x][y] = -1;
12141 // this must be handled before main playfield loop
12142 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12145 if (MovDelay[x][y] <= 0)
12149 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12152 if (MovDelay[x][y] <= 0)
12154 int element = Store[x][y];
12155 int move_direction = MovDir[x][y];
12156 int player_index_bit = Store2[x][y];
12162 TEST_DrawLevelField(x, y);
12164 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12169 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12171 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12173 Debug("game:playing:GameActions_RND", "This should never happen!");
12175 ChangePage[x][y] = -1;
12179 Stop[x][y] = FALSE;
12180 if (WasJustMoving[x][y] > 0)
12181 WasJustMoving[x][y]--;
12182 if (WasJustFalling[x][y] > 0)
12183 WasJustFalling[x][y]--;
12184 if (CheckCollision[x][y] > 0)
12185 CheckCollision[x][y]--;
12186 if (CheckImpact[x][y] > 0)
12187 CheckImpact[x][y]--;
12191 /* reset finished pushing action (not done in ContinueMoving() to allow
12192 continuous pushing animation for elements with zero push delay) */
12193 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12195 ResetGfxAnimation(x, y);
12196 TEST_DrawLevelField(x, y);
12200 if (IS_BLOCKED(x, y))
12204 Blocked2Moving(x, y, &oldx, &oldy);
12205 if (!IS_MOVING(oldx, oldy))
12207 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12208 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12209 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12210 Debug("game:playing:GameActions_RND", "This should never happen!");
12216 if (mouse_action.button)
12218 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12219 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12221 x = mouse_action.lx;
12222 y = mouse_action.ly;
12223 element = Tile[x][y];
12227 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12228 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12232 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12233 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12237 SCAN_PLAYFIELD(x, y)
12239 element = Tile[x][y];
12240 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12241 last_gfx_frame = GfxFrame[x][y];
12243 ResetGfxFrame(x, y);
12245 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12246 DrawLevelGraphicAnimation(x, y, graphic);
12248 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12249 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12250 ResetRandomAnimationValue(x, y);
12252 SetRandomAnimationValue(x, y);
12254 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12256 if (IS_INACTIVE(element))
12258 if (IS_ANIMATED(graphic))
12259 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12264 // this may take place after moving, so 'element' may have changed
12265 if (IS_CHANGING(x, y) &&
12266 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12268 int page = element_info[element].event_page_nr[CE_DELAY];
12270 HandleElementChange(x, y, page);
12272 element = Tile[x][y];
12273 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12276 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12280 element = Tile[x][y];
12281 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12283 if (IS_ANIMATED(graphic) &&
12284 !IS_MOVING(x, y) &&
12286 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12288 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12289 TEST_DrawTwinkleOnField(x, y);
12291 else if (element == EL_ACID)
12294 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12296 else if ((element == EL_EXIT_OPEN ||
12297 element == EL_EM_EXIT_OPEN ||
12298 element == EL_SP_EXIT_OPEN ||
12299 element == EL_STEEL_EXIT_OPEN ||
12300 element == EL_EM_STEEL_EXIT_OPEN ||
12301 element == EL_SP_TERMINAL ||
12302 element == EL_SP_TERMINAL_ACTIVE ||
12303 element == EL_EXTRA_TIME ||
12304 element == EL_SHIELD_NORMAL ||
12305 element == EL_SHIELD_DEADLY) &&
12306 IS_ANIMATED(graphic))
12307 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12308 else if (IS_MOVING(x, y))
12309 ContinueMoving(x, y);
12310 else if (IS_ACTIVE_BOMB(element))
12311 CheckDynamite(x, y);
12312 else if (element == EL_AMOEBA_GROWING)
12313 AmoebaGrowing(x, y);
12314 else if (element == EL_AMOEBA_SHRINKING)
12315 AmoebaShrinking(x, y);
12317 #if !USE_NEW_AMOEBA_CODE
12318 else if (IS_AMOEBALIVE(element))
12319 AmoebaReproduce(x, y);
12322 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12324 else if (element == EL_EXIT_CLOSED)
12326 else if (element == EL_EM_EXIT_CLOSED)
12328 else if (element == EL_STEEL_EXIT_CLOSED)
12329 CheckExitSteel(x, y);
12330 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12331 CheckExitSteelEM(x, y);
12332 else if (element == EL_SP_EXIT_CLOSED)
12334 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12335 element == EL_EXPANDABLE_STEELWALL_GROWING)
12336 MauerWaechst(x, y);
12337 else if (element == EL_EXPANDABLE_WALL ||
12338 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12339 element == EL_EXPANDABLE_WALL_VERTICAL ||
12340 element == EL_EXPANDABLE_WALL_ANY ||
12341 element == EL_BD_EXPANDABLE_WALL)
12342 MauerAbleger(x, y);
12343 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12344 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12345 element == EL_EXPANDABLE_STEELWALL_ANY)
12346 MauerAblegerStahl(x, y);
12347 else if (element == EL_FLAMES)
12348 CheckForDragon(x, y);
12349 else if (element == EL_EXPLOSION)
12350 ; // drawing of correct explosion animation is handled separately
12351 else if (element == EL_ELEMENT_SNAPPING ||
12352 element == EL_DIAGONAL_SHRINKING ||
12353 element == EL_DIAGONAL_GROWING)
12355 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12357 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12359 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12360 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12362 if (IS_BELT_ACTIVE(element))
12363 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12365 if (game.magic_wall_active)
12367 int jx = local_player->jx, jy = local_player->jy;
12369 // play the element sound at the position nearest to the player
12370 if ((element == EL_MAGIC_WALL_FULL ||
12371 element == EL_MAGIC_WALL_ACTIVE ||
12372 element == EL_MAGIC_WALL_EMPTYING ||
12373 element == EL_BD_MAGIC_WALL_FULL ||
12374 element == EL_BD_MAGIC_WALL_ACTIVE ||
12375 element == EL_BD_MAGIC_WALL_EMPTYING ||
12376 element == EL_DC_MAGIC_WALL_FULL ||
12377 element == EL_DC_MAGIC_WALL_ACTIVE ||
12378 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12379 ABS(x - jx) + ABS(y - jy) <
12380 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12388 #if USE_NEW_AMOEBA_CODE
12389 // new experimental amoeba growth stuff
12390 if (!(FrameCounter % 8))
12392 static unsigned int random = 1684108901;
12394 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12396 x = RND(lev_fieldx);
12397 y = RND(lev_fieldy);
12398 element = Tile[x][y];
12400 if (!IS_PLAYER(x,y) &&
12401 (element == EL_EMPTY ||
12402 CAN_GROW_INTO(element) ||
12403 element == EL_QUICKSAND_EMPTY ||
12404 element == EL_QUICKSAND_FAST_EMPTY ||
12405 element == EL_ACID_SPLASH_LEFT ||
12406 element == EL_ACID_SPLASH_RIGHT))
12408 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12409 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12410 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12411 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12412 Tile[x][y] = EL_AMOEBA_DROP;
12415 random = random * 129 + 1;
12420 game.explosions_delayed = FALSE;
12422 SCAN_PLAYFIELD(x, y)
12424 element = Tile[x][y];
12426 if (ExplodeField[x][y])
12427 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12428 else if (element == EL_EXPLOSION)
12429 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12431 ExplodeField[x][y] = EX_TYPE_NONE;
12434 game.explosions_delayed = TRUE;
12436 if (game.magic_wall_active)
12438 if (!(game.magic_wall_time_left % 4))
12440 int element = Tile[magic_wall_x][magic_wall_y];
12442 if (element == EL_BD_MAGIC_WALL_FULL ||
12443 element == EL_BD_MAGIC_WALL_ACTIVE ||
12444 element == EL_BD_MAGIC_WALL_EMPTYING)
12445 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12446 else if (element == EL_DC_MAGIC_WALL_FULL ||
12447 element == EL_DC_MAGIC_WALL_ACTIVE ||
12448 element == EL_DC_MAGIC_WALL_EMPTYING)
12449 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12451 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12454 if (game.magic_wall_time_left > 0)
12456 game.magic_wall_time_left--;
12458 if (!game.magic_wall_time_left)
12460 SCAN_PLAYFIELD(x, y)
12462 element = Tile[x][y];
12464 if (element == EL_MAGIC_WALL_ACTIVE ||
12465 element == EL_MAGIC_WALL_FULL)
12467 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12468 TEST_DrawLevelField(x, y);
12470 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12471 element == EL_BD_MAGIC_WALL_FULL)
12473 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12474 TEST_DrawLevelField(x, y);
12476 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12477 element == EL_DC_MAGIC_WALL_FULL)
12479 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12480 TEST_DrawLevelField(x, y);
12484 game.magic_wall_active = FALSE;
12489 if (game.light_time_left > 0)
12491 game.light_time_left--;
12493 if (game.light_time_left == 0)
12494 RedrawAllLightSwitchesAndInvisibleElements();
12497 if (game.timegate_time_left > 0)
12499 game.timegate_time_left--;
12501 if (game.timegate_time_left == 0)
12502 CloseAllOpenTimegates();
12505 if (game.lenses_time_left > 0)
12507 game.lenses_time_left--;
12509 if (game.lenses_time_left == 0)
12510 RedrawAllInvisibleElementsForLenses();
12513 if (game.magnify_time_left > 0)
12515 game.magnify_time_left--;
12517 if (game.magnify_time_left == 0)
12518 RedrawAllInvisibleElementsForMagnifier();
12521 for (i = 0; i < MAX_PLAYERS; i++)
12523 struct PlayerInfo *player = &stored_player[i];
12525 if (SHIELD_ON(player))
12527 if (player->shield_deadly_time_left)
12528 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12529 else if (player->shield_normal_time_left)
12530 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12534 #if USE_DELAYED_GFX_REDRAW
12535 SCAN_PLAYFIELD(x, y)
12537 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12539 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12540 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12542 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12543 DrawLevelField(x, y);
12545 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12546 DrawLevelFieldCrumbled(x, y);
12548 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12549 DrawLevelFieldCrumbledNeighbours(x, y);
12551 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12552 DrawTwinkleOnField(x, y);
12555 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12560 PlayAllPlayersSound();
12562 for (i = 0; i < MAX_PLAYERS; i++)
12564 struct PlayerInfo *player = &stored_player[i];
12566 if (player->show_envelope != 0 && (!player->active ||
12567 player->MovPos == 0))
12569 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12571 player->show_envelope = 0;
12575 // use random number generator in every frame to make it less predictable
12576 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12579 mouse_action_last = mouse_action;
12582 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12584 int min_x = x, min_y = y, max_x = x, max_y = y;
12585 int scr_fieldx = getScreenFieldSizeX();
12586 int scr_fieldy = getScreenFieldSizeY();
12589 for (i = 0; i < MAX_PLAYERS; i++)
12591 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12593 if (!stored_player[i].active || &stored_player[i] == player)
12596 min_x = MIN(min_x, jx);
12597 min_y = MIN(min_y, jy);
12598 max_x = MAX(max_x, jx);
12599 max_y = MAX(max_y, jy);
12602 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12605 static boolean AllPlayersInVisibleScreen(void)
12609 for (i = 0; i < MAX_PLAYERS; i++)
12611 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12613 if (!stored_player[i].active)
12616 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12623 void ScrollLevel(int dx, int dy)
12625 int scroll_offset = 2 * TILEX_VAR;
12628 BlitBitmap(drawto_field, drawto_field,
12629 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12630 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12631 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12632 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12633 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12634 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12638 x = (dx == 1 ? BX1 : BX2);
12639 for (y = BY1; y <= BY2; y++)
12640 DrawScreenField(x, y);
12645 y = (dy == 1 ? BY1 : BY2);
12646 for (x = BX1; x <= BX2; x++)
12647 DrawScreenField(x, y);
12650 redraw_mask |= REDRAW_FIELD;
12653 static boolean canFallDown(struct PlayerInfo *player)
12655 int jx = player->jx, jy = player->jy;
12657 return (IN_LEV_FIELD(jx, jy + 1) &&
12658 (IS_FREE(jx, jy + 1) ||
12659 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12660 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12661 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12664 static boolean canPassField(int x, int y, int move_dir)
12666 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12667 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12668 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12669 int nextx = x + dx;
12670 int nexty = y + dy;
12671 int element = Tile[x][y];
12673 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12674 !CAN_MOVE(element) &&
12675 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12676 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12677 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12680 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12682 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12683 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12684 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12688 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12689 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12690 (IS_DIGGABLE(Tile[newx][newy]) ||
12691 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12692 canPassField(newx, newy, move_dir)));
12695 static void CheckGravityMovement(struct PlayerInfo *player)
12697 if (player->gravity && !player->programmed_action)
12699 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12700 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12701 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12702 int jx = player->jx, jy = player->jy;
12703 boolean player_is_moving_to_valid_field =
12704 (!player_is_snapping &&
12705 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12706 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12707 boolean player_can_fall_down = canFallDown(player);
12709 if (player_can_fall_down &&
12710 !player_is_moving_to_valid_field)
12711 player->programmed_action = MV_DOWN;
12715 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12717 return CheckGravityMovement(player);
12719 if (player->gravity && !player->programmed_action)
12721 int jx = player->jx, jy = player->jy;
12722 boolean field_under_player_is_free =
12723 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12724 boolean player_is_standing_on_valid_field =
12725 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12726 (IS_WALKABLE(Tile[jx][jy]) &&
12727 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12729 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12730 player->programmed_action = MV_DOWN;
12735 MovePlayerOneStep()
12736 -----------------------------------------------------------------------------
12737 dx, dy: direction (non-diagonal) to try to move the player to
12738 real_dx, real_dy: direction as read from input device (can be diagonal)
12741 boolean MovePlayerOneStep(struct PlayerInfo *player,
12742 int dx, int dy, int real_dx, int real_dy)
12744 int jx = player->jx, jy = player->jy;
12745 int new_jx = jx + dx, new_jy = jy + dy;
12747 boolean player_can_move = !player->cannot_move;
12749 if (!player->active || (!dx && !dy))
12750 return MP_NO_ACTION;
12752 player->MovDir = (dx < 0 ? MV_LEFT :
12753 dx > 0 ? MV_RIGHT :
12755 dy > 0 ? MV_DOWN : MV_NONE);
12757 if (!IN_LEV_FIELD(new_jx, new_jy))
12758 return MP_NO_ACTION;
12760 if (!player_can_move)
12762 if (player->MovPos == 0)
12764 player->is_moving = FALSE;
12765 player->is_digging = FALSE;
12766 player->is_collecting = FALSE;
12767 player->is_snapping = FALSE;
12768 player->is_pushing = FALSE;
12772 if (!network.enabled && game.centered_player_nr == -1 &&
12773 !AllPlayersInSight(player, new_jx, new_jy))
12774 return MP_NO_ACTION;
12776 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12777 if (can_move != MP_MOVING)
12780 // check if DigField() has caused relocation of the player
12781 if (player->jx != jx || player->jy != jy)
12782 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12784 StorePlayer[jx][jy] = 0;
12785 player->last_jx = jx;
12786 player->last_jy = jy;
12787 player->jx = new_jx;
12788 player->jy = new_jy;
12789 StorePlayer[new_jx][new_jy] = player->element_nr;
12791 if (player->move_delay_value_next != -1)
12793 player->move_delay_value = player->move_delay_value_next;
12794 player->move_delay_value_next = -1;
12798 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12800 player->step_counter++;
12802 PlayerVisit[jx][jy] = FrameCounter;
12804 player->is_moving = TRUE;
12807 // should better be called in MovePlayer(), but this breaks some tapes
12808 ScrollPlayer(player, SCROLL_INIT);
12814 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12816 int jx = player->jx, jy = player->jy;
12817 int old_jx = jx, old_jy = jy;
12818 int moved = MP_NO_ACTION;
12820 if (!player->active)
12825 if (player->MovPos == 0)
12827 player->is_moving = FALSE;
12828 player->is_digging = FALSE;
12829 player->is_collecting = FALSE;
12830 player->is_snapping = FALSE;
12831 player->is_pushing = FALSE;
12837 if (player->move_delay > 0)
12840 player->move_delay = -1; // set to "uninitialized" value
12842 // store if player is automatically moved to next field
12843 player->is_auto_moving = (player->programmed_action != MV_NONE);
12845 // remove the last programmed player action
12846 player->programmed_action = 0;
12848 if (player->MovPos)
12850 // should only happen if pre-1.2 tape recordings are played
12851 // this is only for backward compatibility
12853 int original_move_delay_value = player->move_delay_value;
12856 Debug("game:playing:MovePlayer",
12857 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12861 // scroll remaining steps with finest movement resolution
12862 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12864 while (player->MovPos)
12866 ScrollPlayer(player, SCROLL_GO_ON);
12867 ScrollScreen(NULL, SCROLL_GO_ON);
12869 AdvanceFrameAndPlayerCounters(player->index_nr);
12872 BackToFront_WithFrameDelay(0);
12875 player->move_delay_value = original_move_delay_value;
12878 player->is_active = FALSE;
12880 if (player->last_move_dir & MV_HORIZONTAL)
12882 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12883 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12887 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12888 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12891 if (!moved && !player->is_active)
12893 player->is_moving = FALSE;
12894 player->is_digging = FALSE;
12895 player->is_collecting = FALSE;
12896 player->is_snapping = FALSE;
12897 player->is_pushing = FALSE;
12903 if (moved & MP_MOVING && !ScreenMovPos &&
12904 (player->index_nr == game.centered_player_nr ||
12905 game.centered_player_nr == -1))
12907 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12909 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12911 // actual player has left the screen -- scroll in that direction
12912 if (jx != old_jx) // player has moved horizontally
12913 scroll_x += (jx - old_jx);
12914 else // player has moved vertically
12915 scroll_y += (jy - old_jy);
12919 int offset_raw = game.scroll_delay_value;
12921 if (jx != old_jx) // player has moved horizontally
12923 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12924 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12925 int new_scroll_x = jx - MIDPOSX + offset_x;
12927 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12928 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12929 scroll_x = new_scroll_x;
12931 // don't scroll over playfield boundaries
12932 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12934 // don't scroll more than one field at a time
12935 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12937 // don't scroll against the player's moving direction
12938 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12939 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12940 scroll_x = old_scroll_x;
12942 else // player has moved vertically
12944 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12945 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12946 int new_scroll_y = jy - MIDPOSY + offset_y;
12948 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12949 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12950 scroll_y = new_scroll_y;
12952 // don't scroll over playfield boundaries
12953 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12955 // don't scroll more than one field at a time
12956 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12958 // don't scroll against the player's moving direction
12959 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12960 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12961 scroll_y = old_scroll_y;
12965 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12967 if (!network.enabled && game.centered_player_nr == -1 &&
12968 !AllPlayersInVisibleScreen())
12970 scroll_x = old_scroll_x;
12971 scroll_y = old_scroll_y;
12975 ScrollScreen(player, SCROLL_INIT);
12976 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12981 player->StepFrame = 0;
12983 if (moved & MP_MOVING)
12985 if (old_jx != jx && old_jy == jy)
12986 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12987 else if (old_jx == jx && old_jy != jy)
12988 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12990 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12992 player->last_move_dir = player->MovDir;
12993 player->is_moving = TRUE;
12994 player->is_snapping = FALSE;
12995 player->is_switching = FALSE;
12996 player->is_dropping = FALSE;
12997 player->is_dropping_pressed = FALSE;
12998 player->drop_pressed_delay = 0;
13001 // should better be called here than above, but this breaks some tapes
13002 ScrollPlayer(player, SCROLL_INIT);
13007 CheckGravityMovementWhenNotMoving(player);
13009 player->is_moving = FALSE;
13011 /* at this point, the player is allowed to move, but cannot move right now
13012 (e.g. because of something blocking the way) -- ensure that the player
13013 is also allowed to move in the next frame (in old versions before 3.1.1,
13014 the player was forced to wait again for eight frames before next try) */
13016 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13017 player->move_delay = 0; // allow direct movement in the next frame
13020 if (player->move_delay == -1) // not yet initialized by DigField()
13021 player->move_delay = player->move_delay_value;
13023 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13025 TestIfPlayerTouchesBadThing(jx, jy);
13026 TestIfPlayerTouchesCustomElement(jx, jy);
13029 if (!player->active)
13030 RemovePlayer(player);
13035 void ScrollPlayer(struct PlayerInfo *player, int mode)
13037 int jx = player->jx, jy = player->jy;
13038 int last_jx = player->last_jx, last_jy = player->last_jy;
13039 int move_stepsize = TILEX / player->move_delay_value;
13041 if (!player->active)
13044 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13047 if (mode == SCROLL_INIT)
13049 player->actual_frame_counter = FrameCounter;
13050 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13052 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13053 Tile[last_jx][last_jy] == EL_EMPTY)
13055 int last_field_block_delay = 0; // start with no blocking at all
13056 int block_delay_adjustment = player->block_delay_adjustment;
13058 // if player blocks last field, add delay for exactly one move
13059 if (player->block_last_field)
13061 last_field_block_delay += player->move_delay_value;
13063 // when blocking enabled, prevent moving up despite gravity
13064 if (player->gravity && player->MovDir == MV_UP)
13065 block_delay_adjustment = -1;
13068 // add block delay adjustment (also possible when not blocking)
13069 last_field_block_delay += block_delay_adjustment;
13071 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13072 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13075 if (player->MovPos != 0) // player has not yet reached destination
13078 else if (!FrameReached(&player->actual_frame_counter, 1))
13081 if (player->MovPos != 0)
13083 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13084 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13086 // before DrawPlayer() to draw correct player graphic for this case
13087 if (player->MovPos == 0)
13088 CheckGravityMovement(player);
13091 if (player->MovPos == 0) // player reached destination field
13093 if (player->move_delay_reset_counter > 0)
13095 player->move_delay_reset_counter--;
13097 if (player->move_delay_reset_counter == 0)
13099 // continue with normal speed after quickly moving through gate
13100 HALVE_PLAYER_SPEED(player);
13102 // be able to make the next move without delay
13103 player->move_delay = 0;
13107 player->last_jx = jx;
13108 player->last_jy = jy;
13110 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13111 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13112 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13113 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13114 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13115 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13116 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13117 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13119 ExitPlayer(player);
13121 if (game.players_still_needed == 0 &&
13122 (game.friends_still_needed == 0 ||
13123 IS_SP_ELEMENT(Tile[jx][jy])))
13127 // this breaks one level: "machine", level 000
13129 int move_direction = player->MovDir;
13130 int enter_side = MV_DIR_OPPOSITE(move_direction);
13131 int leave_side = move_direction;
13132 int old_jx = last_jx;
13133 int old_jy = last_jy;
13134 int old_element = Tile[old_jx][old_jy];
13135 int new_element = Tile[jx][jy];
13137 if (IS_CUSTOM_ELEMENT(old_element))
13138 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13140 player->index_bit, leave_side);
13142 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13143 CE_PLAYER_LEAVES_X,
13144 player->index_bit, leave_side);
13146 if (IS_CUSTOM_ELEMENT(new_element))
13147 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13148 player->index_bit, enter_side);
13150 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13151 CE_PLAYER_ENTERS_X,
13152 player->index_bit, enter_side);
13154 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13155 CE_MOVE_OF_X, move_direction);
13158 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13160 TestIfPlayerTouchesBadThing(jx, jy);
13161 TestIfPlayerTouchesCustomElement(jx, jy);
13163 /* needed because pushed element has not yet reached its destination,
13164 so it would trigger a change event at its previous field location */
13165 if (!player->is_pushing)
13166 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13168 if (level.finish_dig_collect &&
13169 (player->is_digging || player->is_collecting))
13171 int last_element = player->last_removed_element;
13172 int move_direction = player->MovDir;
13173 int enter_side = MV_DIR_OPPOSITE(move_direction);
13174 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13175 CE_PLAYER_COLLECTS_X);
13177 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13178 player->index_bit, enter_side);
13180 player->last_removed_element = EL_UNDEFINED;
13183 if (!player->active)
13184 RemovePlayer(player);
13187 if (level.use_step_counter)
13197 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13198 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13200 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13202 DisplayGameControlValues();
13204 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13205 for (i = 0; i < MAX_PLAYERS; i++)
13206 KillPlayer(&stored_player[i]);
13208 else if (game.no_time_limit && !game.all_players_gone)
13210 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13212 DisplayGameControlValues();
13216 if (tape.single_step && tape.recording && !tape.pausing &&
13217 !player->programmed_action)
13218 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13220 if (!player->programmed_action)
13221 CheckSaveEngineSnapshot(player);
13225 void ScrollScreen(struct PlayerInfo *player, int mode)
13227 static unsigned int screen_frame_counter = 0;
13229 if (mode == SCROLL_INIT)
13231 // set scrolling step size according to actual player's moving speed
13232 ScrollStepSize = TILEX / player->move_delay_value;
13234 screen_frame_counter = FrameCounter;
13235 ScreenMovDir = player->MovDir;
13236 ScreenMovPos = player->MovPos;
13237 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13240 else if (!FrameReached(&screen_frame_counter, 1))
13245 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13246 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13247 redraw_mask |= REDRAW_FIELD;
13250 ScreenMovDir = MV_NONE;
13253 void TestIfPlayerTouchesCustomElement(int x, int y)
13255 static int xy[4][2] =
13262 static int trigger_sides[4][2] =
13264 // center side border side
13265 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13266 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13267 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13268 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13270 static int touch_dir[4] =
13272 MV_LEFT | MV_RIGHT,
13277 int center_element = Tile[x][y]; // should always be non-moving!
13280 for (i = 0; i < NUM_DIRECTIONS; i++)
13282 int xx = x + xy[i][0];
13283 int yy = y + xy[i][1];
13284 int center_side = trigger_sides[i][0];
13285 int border_side = trigger_sides[i][1];
13286 int border_element;
13288 if (!IN_LEV_FIELD(xx, yy))
13291 if (IS_PLAYER(x, y)) // player found at center element
13293 struct PlayerInfo *player = PLAYERINFO(x, y);
13295 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13296 border_element = Tile[xx][yy]; // may be moving!
13297 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13298 border_element = Tile[xx][yy];
13299 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13300 border_element = MovingOrBlocked2Element(xx, yy);
13302 continue; // center and border element do not touch
13304 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13305 player->index_bit, border_side);
13306 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13307 CE_PLAYER_TOUCHES_X,
13308 player->index_bit, border_side);
13311 /* use player element that is initially defined in the level playfield,
13312 not the player element that corresponds to the runtime player number
13313 (example: a level that contains EL_PLAYER_3 as the only player would
13314 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13315 int player_element = PLAYERINFO(x, y)->initial_element;
13317 CheckElementChangeBySide(xx, yy, border_element, player_element,
13318 CE_TOUCHING_X, border_side);
13321 else if (IS_PLAYER(xx, yy)) // player found at border element
13323 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13325 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13327 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13328 continue; // center and border element do not touch
13331 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13332 player->index_bit, center_side);
13333 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13334 CE_PLAYER_TOUCHES_X,
13335 player->index_bit, center_side);
13338 /* use player element that is initially defined in the level playfield,
13339 not the player element that corresponds to the runtime player number
13340 (example: a level that contains EL_PLAYER_3 as the only player would
13341 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13342 int player_element = PLAYERINFO(xx, yy)->initial_element;
13344 CheckElementChangeBySide(x, y, center_element, player_element,
13345 CE_TOUCHING_X, center_side);
13353 void TestIfElementTouchesCustomElement(int x, int y)
13355 static int xy[4][2] =
13362 static int trigger_sides[4][2] =
13364 // center side border side
13365 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13366 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13367 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13368 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13370 static int touch_dir[4] =
13372 MV_LEFT | MV_RIGHT,
13377 boolean change_center_element = FALSE;
13378 int center_element = Tile[x][y]; // should always be non-moving!
13379 int border_element_old[NUM_DIRECTIONS];
13382 for (i = 0; i < NUM_DIRECTIONS; i++)
13384 int xx = x + xy[i][0];
13385 int yy = y + xy[i][1];
13386 int border_element;
13388 border_element_old[i] = -1;
13390 if (!IN_LEV_FIELD(xx, yy))
13393 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13394 border_element = Tile[xx][yy]; // may be moving!
13395 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13396 border_element = Tile[xx][yy];
13397 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13398 border_element = MovingOrBlocked2Element(xx, yy);
13400 continue; // center and border element do not touch
13402 border_element_old[i] = border_element;
13405 for (i = 0; i < NUM_DIRECTIONS; i++)
13407 int xx = x + xy[i][0];
13408 int yy = y + xy[i][1];
13409 int center_side = trigger_sides[i][0];
13410 int border_element = border_element_old[i];
13412 if (border_element == -1)
13415 // check for change of border element
13416 CheckElementChangeBySide(xx, yy, border_element, center_element,
13417 CE_TOUCHING_X, center_side);
13419 // (center element cannot be player, so we dont have to check this here)
13422 for (i = 0; i < NUM_DIRECTIONS; i++)
13424 int xx = x + xy[i][0];
13425 int yy = y + xy[i][1];
13426 int border_side = trigger_sides[i][1];
13427 int border_element = border_element_old[i];
13429 if (border_element == -1)
13432 // check for change of center element (but change it only once)
13433 if (!change_center_element)
13434 change_center_element =
13435 CheckElementChangeBySide(x, y, center_element, border_element,
13436 CE_TOUCHING_X, border_side);
13438 if (IS_PLAYER(xx, yy))
13440 /* use player element that is initially defined in the level playfield,
13441 not the player element that corresponds to the runtime player number
13442 (example: a level that contains EL_PLAYER_3 as the only player would
13443 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13444 int player_element = PLAYERINFO(xx, yy)->initial_element;
13446 CheckElementChangeBySide(x, y, center_element, player_element,
13447 CE_TOUCHING_X, border_side);
13452 void TestIfElementHitsCustomElement(int x, int y, int direction)
13454 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13455 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13456 int hitx = x + dx, hity = y + dy;
13457 int hitting_element = Tile[x][y];
13458 int touched_element;
13460 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13463 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13464 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13466 if (IN_LEV_FIELD(hitx, hity))
13468 int opposite_direction = MV_DIR_OPPOSITE(direction);
13469 int hitting_side = direction;
13470 int touched_side = opposite_direction;
13471 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13472 MovDir[hitx][hity] != direction ||
13473 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13479 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13480 CE_HITTING_X, touched_side);
13482 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13483 CE_HIT_BY_X, hitting_side);
13485 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13486 CE_HIT_BY_SOMETHING, opposite_direction);
13488 if (IS_PLAYER(hitx, hity))
13490 /* use player element that is initially defined in the level playfield,
13491 not the player element that corresponds to the runtime player number
13492 (example: a level that contains EL_PLAYER_3 as the only player would
13493 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13494 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13496 CheckElementChangeBySide(x, y, hitting_element, player_element,
13497 CE_HITTING_X, touched_side);
13502 // "hitting something" is also true when hitting the playfield border
13503 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13504 CE_HITTING_SOMETHING, direction);
13507 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13509 int i, kill_x = -1, kill_y = -1;
13511 int bad_element = -1;
13512 static int test_xy[4][2] =
13519 static int test_dir[4] =
13527 for (i = 0; i < NUM_DIRECTIONS; i++)
13529 int test_x, test_y, test_move_dir, test_element;
13531 test_x = good_x + test_xy[i][0];
13532 test_y = good_y + test_xy[i][1];
13534 if (!IN_LEV_FIELD(test_x, test_y))
13538 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13540 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13542 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13543 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13545 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13546 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13550 bad_element = test_element;
13556 if (kill_x != -1 || kill_y != -1)
13558 if (IS_PLAYER(good_x, good_y))
13560 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13562 if (player->shield_deadly_time_left > 0 &&
13563 !IS_INDESTRUCTIBLE(bad_element))
13564 Bang(kill_x, kill_y);
13565 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13566 KillPlayer(player);
13569 Bang(good_x, good_y);
13573 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13575 int i, kill_x = -1, kill_y = -1;
13576 int bad_element = Tile[bad_x][bad_y];
13577 static int test_xy[4][2] =
13584 static int touch_dir[4] =
13586 MV_LEFT | MV_RIGHT,
13591 static int test_dir[4] =
13599 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13602 for (i = 0; i < NUM_DIRECTIONS; i++)
13604 int test_x, test_y, test_move_dir, test_element;
13606 test_x = bad_x + test_xy[i][0];
13607 test_y = bad_y + test_xy[i][1];
13609 if (!IN_LEV_FIELD(test_x, test_y))
13613 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13615 test_element = Tile[test_x][test_y];
13617 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13618 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13620 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13621 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13623 // good thing is player or penguin that does not move away
13624 if (IS_PLAYER(test_x, test_y))
13626 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13628 if (bad_element == EL_ROBOT && player->is_moving)
13629 continue; // robot does not kill player if he is moving
13631 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13633 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13634 continue; // center and border element do not touch
13642 else if (test_element == EL_PENGUIN)
13652 if (kill_x != -1 || kill_y != -1)
13654 if (IS_PLAYER(kill_x, kill_y))
13656 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13658 if (player->shield_deadly_time_left > 0 &&
13659 !IS_INDESTRUCTIBLE(bad_element))
13660 Bang(bad_x, bad_y);
13661 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13662 KillPlayer(player);
13665 Bang(kill_x, kill_y);
13669 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13671 int bad_element = Tile[bad_x][bad_y];
13672 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13673 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13674 int test_x = bad_x + dx, test_y = bad_y + dy;
13675 int test_move_dir, test_element;
13676 int kill_x = -1, kill_y = -1;
13678 if (!IN_LEV_FIELD(test_x, test_y))
13682 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13684 test_element = Tile[test_x][test_y];
13686 if (test_move_dir != bad_move_dir)
13688 // good thing can be player or penguin that does not move away
13689 if (IS_PLAYER(test_x, test_y))
13691 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13693 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13694 player as being hit when he is moving towards the bad thing, because
13695 the "get hit by" condition would be lost after the player stops) */
13696 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13697 return; // player moves away from bad thing
13702 else if (test_element == EL_PENGUIN)
13709 if (kill_x != -1 || kill_y != -1)
13711 if (IS_PLAYER(kill_x, kill_y))
13713 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13715 if (player->shield_deadly_time_left > 0 &&
13716 !IS_INDESTRUCTIBLE(bad_element))
13717 Bang(bad_x, bad_y);
13718 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13719 KillPlayer(player);
13722 Bang(kill_x, kill_y);
13726 void TestIfPlayerTouchesBadThing(int x, int y)
13728 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13731 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13733 TestIfGoodThingHitsBadThing(x, y, move_dir);
13736 void TestIfBadThingTouchesPlayer(int x, int y)
13738 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13741 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13743 TestIfBadThingHitsGoodThing(x, y, move_dir);
13746 void TestIfFriendTouchesBadThing(int x, int y)
13748 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13751 void TestIfBadThingTouchesFriend(int x, int y)
13753 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13756 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13758 int i, kill_x = bad_x, kill_y = bad_y;
13759 static int xy[4][2] =
13767 for (i = 0; i < NUM_DIRECTIONS; i++)
13771 x = bad_x + xy[i][0];
13772 y = bad_y + xy[i][1];
13773 if (!IN_LEV_FIELD(x, y))
13776 element = Tile[x][y];
13777 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13778 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13786 if (kill_x != bad_x || kill_y != bad_y)
13787 Bang(bad_x, bad_y);
13790 void KillPlayer(struct PlayerInfo *player)
13792 int jx = player->jx, jy = player->jy;
13794 if (!player->active)
13798 Debug("game:playing:KillPlayer",
13799 "0: killed == %d, active == %d, reanimated == %d",
13800 player->killed, player->active, player->reanimated);
13803 /* the following code was introduced to prevent an infinite loop when calling
13805 -> CheckTriggeredElementChangeExt()
13806 -> ExecuteCustomElementAction()
13808 -> (infinitely repeating the above sequence of function calls)
13809 which occurs when killing the player while having a CE with the setting
13810 "kill player X when explosion of <player X>"; the solution using a new
13811 field "player->killed" was chosen for backwards compatibility, although
13812 clever use of the fields "player->active" etc. would probably also work */
13814 if (player->killed)
13818 player->killed = TRUE;
13820 // remove accessible field at the player's position
13821 Tile[jx][jy] = EL_EMPTY;
13823 // deactivate shield (else Bang()/Explode() would not work right)
13824 player->shield_normal_time_left = 0;
13825 player->shield_deadly_time_left = 0;
13828 Debug("game:playing:KillPlayer",
13829 "1: killed == %d, active == %d, reanimated == %d",
13830 player->killed, player->active, player->reanimated);
13836 Debug("game:playing:KillPlayer",
13837 "2: killed == %d, active == %d, reanimated == %d",
13838 player->killed, player->active, player->reanimated);
13841 if (player->reanimated) // killed player may have been reanimated
13842 player->killed = player->reanimated = FALSE;
13844 BuryPlayer(player);
13847 static void KillPlayerUnlessEnemyProtected(int x, int y)
13849 if (!PLAYER_ENEMY_PROTECTED(x, y))
13850 KillPlayer(PLAYERINFO(x, y));
13853 static void KillPlayerUnlessExplosionProtected(int x, int y)
13855 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13856 KillPlayer(PLAYERINFO(x, y));
13859 void BuryPlayer(struct PlayerInfo *player)
13861 int jx = player->jx, jy = player->jy;
13863 if (!player->active)
13866 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13867 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13869 RemovePlayer(player);
13871 player->buried = TRUE;
13873 if (game.all_players_gone)
13874 game.GameOver = TRUE;
13877 void RemovePlayer(struct PlayerInfo *player)
13879 int jx = player->jx, jy = player->jy;
13880 int i, found = FALSE;
13882 player->present = FALSE;
13883 player->active = FALSE;
13885 // required for some CE actions (even if the player is not active anymore)
13886 player->MovPos = 0;
13888 if (!ExplodeField[jx][jy])
13889 StorePlayer[jx][jy] = 0;
13891 if (player->is_moving)
13892 TEST_DrawLevelField(player->last_jx, player->last_jy);
13894 for (i = 0; i < MAX_PLAYERS; i++)
13895 if (stored_player[i].active)
13900 game.all_players_gone = TRUE;
13901 game.GameOver = TRUE;
13904 game.exit_x = game.robot_wheel_x = jx;
13905 game.exit_y = game.robot_wheel_y = jy;
13908 void ExitPlayer(struct PlayerInfo *player)
13910 DrawPlayer(player); // needed here only to cleanup last field
13911 RemovePlayer(player);
13913 if (game.players_still_needed > 0)
13914 game.players_still_needed--;
13917 static void SetFieldForSnapping(int x, int y, int element, int direction,
13918 int player_index_bit)
13920 struct ElementInfo *ei = &element_info[element];
13921 int direction_bit = MV_DIR_TO_BIT(direction);
13922 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13923 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13924 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13926 Tile[x][y] = EL_ELEMENT_SNAPPING;
13927 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13928 MovDir[x][y] = direction;
13929 Store[x][y] = element;
13930 Store2[x][y] = player_index_bit;
13932 ResetGfxAnimation(x, y);
13934 GfxElement[x][y] = element;
13935 GfxAction[x][y] = action;
13936 GfxDir[x][y] = direction;
13937 GfxFrame[x][y] = -1;
13940 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13941 int player_index_bit)
13943 TestIfElementTouchesCustomElement(x, y); // for empty space
13945 if (level.finish_dig_collect)
13947 int dig_side = MV_DIR_OPPOSITE(direction);
13949 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13950 player_index_bit, dig_side);
13955 =============================================================================
13956 checkDiagonalPushing()
13957 -----------------------------------------------------------------------------
13958 check if diagonal input device direction results in pushing of object
13959 (by checking if the alternative direction is walkable, diggable, ...)
13960 =============================================================================
13963 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13964 int x, int y, int real_dx, int real_dy)
13966 int jx, jy, dx, dy, xx, yy;
13968 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13971 // diagonal direction: check alternative direction
13976 xx = jx + (dx == 0 ? real_dx : 0);
13977 yy = jy + (dy == 0 ? real_dy : 0);
13979 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13983 =============================================================================
13985 -----------------------------------------------------------------------------
13986 x, y: field next to player (non-diagonal) to try to dig to
13987 real_dx, real_dy: direction as read from input device (can be diagonal)
13988 =============================================================================
13991 static int DigField(struct PlayerInfo *player,
13992 int oldx, int oldy, int x, int y,
13993 int real_dx, int real_dy, int mode)
13995 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13996 boolean player_was_pushing = player->is_pushing;
13997 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13998 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13999 int jx = oldx, jy = oldy;
14000 int dx = x - jx, dy = y - jy;
14001 int nextx = x + dx, nexty = y + dy;
14002 int move_direction = (dx == -1 ? MV_LEFT :
14003 dx == +1 ? MV_RIGHT :
14005 dy == +1 ? MV_DOWN : MV_NONE);
14006 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14007 int dig_side = MV_DIR_OPPOSITE(move_direction);
14008 int old_element = Tile[jx][jy];
14009 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14012 if (is_player) // function can also be called by EL_PENGUIN
14014 if (player->MovPos == 0)
14016 player->is_digging = FALSE;
14017 player->is_collecting = FALSE;
14020 if (player->MovPos == 0) // last pushing move finished
14021 player->is_pushing = FALSE;
14023 if (mode == DF_NO_PUSH) // player just stopped pushing
14025 player->is_switching = FALSE;
14026 player->push_delay = -1;
14028 return MP_NO_ACTION;
14032 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14033 old_element = Back[jx][jy];
14035 // in case of element dropped at player position, check background
14036 else if (Back[jx][jy] != EL_EMPTY &&
14037 game.engine_version >= VERSION_IDENT(2,2,0,0))
14038 old_element = Back[jx][jy];
14040 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14041 return MP_NO_ACTION; // field has no opening in this direction
14043 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14044 return MP_NO_ACTION; // field has no opening in this direction
14046 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14050 Tile[jx][jy] = player->artwork_element;
14051 InitMovingField(jx, jy, MV_DOWN);
14052 Store[jx][jy] = EL_ACID;
14053 ContinueMoving(jx, jy);
14054 BuryPlayer(player);
14056 return MP_DONT_RUN_INTO;
14059 if (player_can_move && DONT_RUN_INTO(element))
14061 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14063 return MP_DONT_RUN_INTO;
14066 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14067 return MP_NO_ACTION;
14069 collect_count = element_info[element].collect_count_initial;
14071 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14072 return MP_NO_ACTION;
14074 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14075 player_can_move = player_can_move_or_snap;
14077 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14078 game.engine_version >= VERSION_IDENT(2,2,0,0))
14080 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14081 player->index_bit, dig_side);
14082 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14083 player->index_bit, dig_side);
14085 if (element == EL_DC_LANDMINE)
14088 if (Tile[x][y] != element) // field changed by snapping
14091 return MP_NO_ACTION;
14094 if (player->gravity && is_player && !player->is_auto_moving &&
14095 canFallDown(player) && move_direction != MV_DOWN &&
14096 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14097 return MP_NO_ACTION; // player cannot walk here due to gravity
14099 if (player_can_move &&
14100 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14102 int sound_element = SND_ELEMENT(element);
14103 int sound_action = ACTION_WALKING;
14105 if (IS_RND_GATE(element))
14107 if (!player->key[RND_GATE_NR(element)])
14108 return MP_NO_ACTION;
14110 else if (IS_RND_GATE_GRAY(element))
14112 if (!player->key[RND_GATE_GRAY_NR(element)])
14113 return MP_NO_ACTION;
14115 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14117 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14118 return MP_NO_ACTION;
14120 else if (element == EL_EXIT_OPEN ||
14121 element == EL_EM_EXIT_OPEN ||
14122 element == EL_EM_EXIT_OPENING ||
14123 element == EL_STEEL_EXIT_OPEN ||
14124 element == EL_EM_STEEL_EXIT_OPEN ||
14125 element == EL_EM_STEEL_EXIT_OPENING ||
14126 element == EL_SP_EXIT_OPEN ||
14127 element == EL_SP_EXIT_OPENING)
14129 sound_action = ACTION_PASSING; // player is passing exit
14131 else if (element == EL_EMPTY)
14133 sound_action = ACTION_MOVING; // nothing to walk on
14136 // play sound from background or player, whatever is available
14137 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14138 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14140 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14142 else if (player_can_move &&
14143 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14145 if (!ACCESS_FROM(element, opposite_direction))
14146 return MP_NO_ACTION; // field not accessible from this direction
14148 if (CAN_MOVE(element)) // only fixed elements can be passed!
14149 return MP_NO_ACTION;
14151 if (IS_EM_GATE(element))
14153 if (!player->key[EM_GATE_NR(element)])
14154 return MP_NO_ACTION;
14156 else if (IS_EM_GATE_GRAY(element))
14158 if (!player->key[EM_GATE_GRAY_NR(element)])
14159 return MP_NO_ACTION;
14161 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14163 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14164 return MP_NO_ACTION;
14166 else if (IS_EMC_GATE(element))
14168 if (!player->key[EMC_GATE_NR(element)])
14169 return MP_NO_ACTION;
14171 else if (IS_EMC_GATE_GRAY(element))
14173 if (!player->key[EMC_GATE_GRAY_NR(element)])
14174 return MP_NO_ACTION;
14176 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14178 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14179 return MP_NO_ACTION;
14181 else if (element == EL_DC_GATE_WHITE ||
14182 element == EL_DC_GATE_WHITE_GRAY ||
14183 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14185 if (player->num_white_keys == 0)
14186 return MP_NO_ACTION;
14188 player->num_white_keys--;
14190 else if (IS_SP_PORT(element))
14192 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14193 element == EL_SP_GRAVITY_PORT_RIGHT ||
14194 element == EL_SP_GRAVITY_PORT_UP ||
14195 element == EL_SP_GRAVITY_PORT_DOWN)
14196 player->gravity = !player->gravity;
14197 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14198 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14199 element == EL_SP_GRAVITY_ON_PORT_UP ||
14200 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14201 player->gravity = TRUE;
14202 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14203 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14204 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14205 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14206 player->gravity = FALSE;
14209 // automatically move to the next field with double speed
14210 player->programmed_action = move_direction;
14212 if (player->move_delay_reset_counter == 0)
14214 player->move_delay_reset_counter = 2; // two double speed steps
14216 DOUBLE_PLAYER_SPEED(player);
14219 PlayLevelSoundAction(x, y, ACTION_PASSING);
14221 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14225 if (mode != DF_SNAP)
14227 GfxElement[x][y] = GFX_ELEMENT(element);
14228 player->is_digging = TRUE;
14231 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14233 // use old behaviour for old levels (digging)
14234 if (!level.finish_dig_collect)
14236 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14237 player->index_bit, dig_side);
14239 // if digging triggered player relocation, finish digging tile
14240 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14241 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14244 if (mode == DF_SNAP)
14246 if (level.block_snap_field)
14247 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14249 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14251 // use old behaviour for old levels (snapping)
14252 if (!level.finish_dig_collect)
14253 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14254 player->index_bit, dig_side);
14257 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14261 if (is_player && mode != DF_SNAP)
14263 GfxElement[x][y] = element;
14264 player->is_collecting = TRUE;
14267 if (element == EL_SPEED_PILL)
14269 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14271 else if (element == EL_EXTRA_TIME && level.time > 0)
14273 TimeLeft += level.extra_time;
14275 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14277 DisplayGameControlValues();
14279 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14281 player->shield_normal_time_left += level.shield_normal_time;
14282 if (element == EL_SHIELD_DEADLY)
14283 player->shield_deadly_time_left += level.shield_deadly_time;
14285 else if (element == EL_DYNAMITE ||
14286 element == EL_EM_DYNAMITE ||
14287 element == EL_SP_DISK_RED)
14289 if (player->inventory_size < MAX_INVENTORY_SIZE)
14290 player->inventory_element[player->inventory_size++] = element;
14292 DrawGameDoorValues();
14294 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14296 player->dynabomb_count++;
14297 player->dynabombs_left++;
14299 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14301 player->dynabomb_size++;
14303 else if (element == EL_DYNABOMB_INCREASE_POWER)
14305 player->dynabomb_xl = TRUE;
14307 else if (IS_KEY(element))
14309 player->key[KEY_NR(element)] = TRUE;
14311 DrawGameDoorValues();
14313 else if (element == EL_DC_KEY_WHITE)
14315 player->num_white_keys++;
14317 // display white keys?
14318 // DrawGameDoorValues();
14320 else if (IS_ENVELOPE(element))
14322 player->show_envelope = element;
14324 else if (element == EL_EMC_LENSES)
14326 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14328 RedrawAllInvisibleElementsForLenses();
14330 else if (element == EL_EMC_MAGNIFIER)
14332 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14334 RedrawAllInvisibleElementsForMagnifier();
14336 else if (IS_DROPPABLE(element) ||
14337 IS_THROWABLE(element)) // can be collected and dropped
14341 if (collect_count == 0)
14342 player->inventory_infinite_element = element;
14344 for (i = 0; i < collect_count; i++)
14345 if (player->inventory_size < MAX_INVENTORY_SIZE)
14346 player->inventory_element[player->inventory_size++] = element;
14348 DrawGameDoorValues();
14350 else if (collect_count > 0)
14352 game.gems_still_needed -= collect_count;
14353 if (game.gems_still_needed < 0)
14354 game.gems_still_needed = 0;
14356 game.snapshot.collected_item = TRUE;
14358 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14360 DisplayGameControlValues();
14363 RaiseScoreElement(element);
14364 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14366 // use old behaviour for old levels (collecting)
14367 if (!level.finish_dig_collect && is_player)
14369 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14370 player->index_bit, dig_side);
14372 // if collecting triggered player relocation, finish collecting tile
14373 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14374 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14377 if (mode == DF_SNAP)
14379 if (level.block_snap_field)
14380 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14382 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14384 // use old behaviour for old levels (snapping)
14385 if (!level.finish_dig_collect)
14386 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14387 player->index_bit, dig_side);
14390 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14392 if (mode == DF_SNAP && element != EL_BD_ROCK)
14393 return MP_NO_ACTION;
14395 if (CAN_FALL(element) && dy)
14396 return MP_NO_ACTION;
14398 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14399 !(element == EL_SPRING && level.use_spring_bug))
14400 return MP_NO_ACTION;
14402 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14403 ((move_direction & MV_VERTICAL &&
14404 ((element_info[element].move_pattern & MV_LEFT &&
14405 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14406 (element_info[element].move_pattern & MV_RIGHT &&
14407 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14408 (move_direction & MV_HORIZONTAL &&
14409 ((element_info[element].move_pattern & MV_UP &&
14410 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14411 (element_info[element].move_pattern & MV_DOWN &&
14412 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14413 return MP_NO_ACTION;
14415 // do not push elements already moving away faster than player
14416 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14417 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14418 return MP_NO_ACTION;
14420 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14422 if (player->push_delay_value == -1 || !player_was_pushing)
14423 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14425 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14427 if (player->push_delay_value == -1)
14428 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14430 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14432 if (!player->is_pushing)
14433 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14436 player->is_pushing = TRUE;
14437 player->is_active = TRUE;
14439 if (!(IN_LEV_FIELD(nextx, nexty) &&
14440 (IS_FREE(nextx, nexty) ||
14441 (IS_SB_ELEMENT(element) &&
14442 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14443 (IS_CUSTOM_ELEMENT(element) &&
14444 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14445 return MP_NO_ACTION;
14447 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14448 return MP_NO_ACTION;
14450 if (player->push_delay == -1) // new pushing; restart delay
14451 player->push_delay = 0;
14453 if (player->push_delay < player->push_delay_value &&
14454 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14455 element != EL_SPRING && element != EL_BALLOON)
14457 // make sure that there is no move delay before next try to push
14458 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14459 player->move_delay = 0;
14461 return MP_NO_ACTION;
14464 if (IS_CUSTOM_ELEMENT(element) &&
14465 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14467 if (!DigFieldByCE(nextx, nexty, element))
14468 return MP_NO_ACTION;
14471 if (IS_SB_ELEMENT(element))
14473 boolean sokoban_task_solved = FALSE;
14475 if (element == EL_SOKOBAN_FIELD_FULL)
14477 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14479 IncrementSokobanFieldsNeeded();
14480 IncrementSokobanObjectsNeeded();
14483 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14485 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14487 DecrementSokobanFieldsNeeded();
14488 DecrementSokobanObjectsNeeded();
14490 // sokoban object was pushed from empty field to sokoban field
14491 if (Back[x][y] == EL_EMPTY)
14492 sokoban_task_solved = TRUE;
14495 Tile[x][y] = EL_SOKOBAN_OBJECT;
14497 if (Back[x][y] == Back[nextx][nexty])
14498 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14499 else if (Back[x][y] != 0)
14500 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14503 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14506 if (sokoban_task_solved &&
14507 game.sokoban_fields_still_needed == 0 &&
14508 game.sokoban_objects_still_needed == 0 &&
14509 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14511 game.players_still_needed = 0;
14515 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14519 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14521 InitMovingField(x, y, move_direction);
14522 GfxAction[x][y] = ACTION_PUSHING;
14524 if (mode == DF_SNAP)
14525 ContinueMoving(x, y);
14527 MovPos[x][y] = (dx != 0 ? dx : dy);
14529 Pushed[x][y] = TRUE;
14530 Pushed[nextx][nexty] = TRUE;
14532 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14533 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14535 player->push_delay_value = -1; // get new value later
14537 // check for element change _after_ element has been pushed
14538 if (game.use_change_when_pushing_bug)
14540 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14541 player->index_bit, dig_side);
14542 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14543 player->index_bit, dig_side);
14546 else if (IS_SWITCHABLE(element))
14548 if (PLAYER_SWITCHING(player, x, y))
14550 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14551 player->index_bit, dig_side);
14556 player->is_switching = TRUE;
14557 player->switch_x = x;
14558 player->switch_y = y;
14560 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14562 if (element == EL_ROBOT_WHEEL)
14564 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14566 game.robot_wheel_x = x;
14567 game.robot_wheel_y = y;
14568 game.robot_wheel_active = TRUE;
14570 TEST_DrawLevelField(x, y);
14572 else if (element == EL_SP_TERMINAL)
14576 SCAN_PLAYFIELD(xx, yy)
14578 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14582 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14584 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14586 ResetGfxAnimation(xx, yy);
14587 TEST_DrawLevelField(xx, yy);
14591 else if (IS_BELT_SWITCH(element))
14593 ToggleBeltSwitch(x, y);
14595 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14596 element == EL_SWITCHGATE_SWITCH_DOWN ||
14597 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14598 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14600 ToggleSwitchgateSwitch(x, y);
14602 else if (element == EL_LIGHT_SWITCH ||
14603 element == EL_LIGHT_SWITCH_ACTIVE)
14605 ToggleLightSwitch(x, y);
14607 else if (element == EL_TIMEGATE_SWITCH ||
14608 element == EL_DC_TIMEGATE_SWITCH)
14610 ActivateTimegateSwitch(x, y);
14612 else if (element == EL_BALLOON_SWITCH_LEFT ||
14613 element == EL_BALLOON_SWITCH_RIGHT ||
14614 element == EL_BALLOON_SWITCH_UP ||
14615 element == EL_BALLOON_SWITCH_DOWN ||
14616 element == EL_BALLOON_SWITCH_NONE ||
14617 element == EL_BALLOON_SWITCH_ANY)
14619 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14620 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14621 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14622 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14623 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14626 else if (element == EL_LAMP)
14628 Tile[x][y] = EL_LAMP_ACTIVE;
14629 game.lights_still_needed--;
14631 ResetGfxAnimation(x, y);
14632 TEST_DrawLevelField(x, y);
14634 else if (element == EL_TIME_ORB_FULL)
14636 Tile[x][y] = EL_TIME_ORB_EMPTY;
14638 if (level.time > 0 || level.use_time_orb_bug)
14640 TimeLeft += level.time_orb_time;
14641 game.no_time_limit = FALSE;
14643 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14645 DisplayGameControlValues();
14648 ResetGfxAnimation(x, y);
14649 TEST_DrawLevelField(x, y);
14651 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14652 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14656 game.ball_active = !game.ball_active;
14658 SCAN_PLAYFIELD(xx, yy)
14660 int e = Tile[xx][yy];
14662 if (game.ball_active)
14664 if (e == EL_EMC_MAGIC_BALL)
14665 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14666 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14667 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14671 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14672 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14673 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14674 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14679 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14680 player->index_bit, dig_side);
14682 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14683 player->index_bit, dig_side);
14685 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14686 player->index_bit, dig_side);
14692 if (!PLAYER_SWITCHING(player, x, y))
14694 player->is_switching = TRUE;
14695 player->switch_x = x;
14696 player->switch_y = y;
14698 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14699 player->index_bit, dig_side);
14700 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14701 player->index_bit, dig_side);
14703 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14704 player->index_bit, dig_side);
14705 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14706 player->index_bit, dig_side);
14709 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14710 player->index_bit, dig_side);
14711 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14712 player->index_bit, dig_side);
14714 return MP_NO_ACTION;
14717 player->push_delay = -1;
14719 if (is_player) // function can also be called by EL_PENGUIN
14721 if (Tile[x][y] != element) // really digged/collected something
14723 player->is_collecting = !player->is_digging;
14724 player->is_active = TRUE;
14726 player->last_removed_element = element;
14733 static boolean DigFieldByCE(int x, int y, int digging_element)
14735 int element = Tile[x][y];
14737 if (!IS_FREE(x, y))
14739 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14740 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14743 // no element can dig solid indestructible elements
14744 if (IS_INDESTRUCTIBLE(element) &&
14745 !IS_DIGGABLE(element) &&
14746 !IS_COLLECTIBLE(element))
14749 if (AmoebaNr[x][y] &&
14750 (element == EL_AMOEBA_FULL ||
14751 element == EL_BD_AMOEBA ||
14752 element == EL_AMOEBA_GROWING))
14754 AmoebaCnt[AmoebaNr[x][y]]--;
14755 AmoebaCnt2[AmoebaNr[x][y]]--;
14758 if (IS_MOVING(x, y))
14759 RemoveMovingField(x, y);
14763 TEST_DrawLevelField(x, y);
14766 // if digged element was about to explode, prevent the explosion
14767 ExplodeField[x][y] = EX_TYPE_NONE;
14769 PlayLevelSoundAction(x, y, action);
14772 Store[x][y] = EL_EMPTY;
14774 // this makes it possible to leave the removed element again
14775 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14776 Store[x][y] = element;
14781 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14783 int jx = player->jx, jy = player->jy;
14784 int x = jx + dx, y = jy + dy;
14785 int snap_direction = (dx == -1 ? MV_LEFT :
14786 dx == +1 ? MV_RIGHT :
14788 dy == +1 ? MV_DOWN : MV_NONE);
14789 boolean can_continue_snapping = (level.continuous_snapping &&
14790 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14792 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14795 if (!player->active || !IN_LEV_FIELD(x, y))
14803 if (player->MovPos == 0)
14804 player->is_pushing = FALSE;
14806 player->is_snapping = FALSE;
14808 if (player->MovPos == 0)
14810 player->is_moving = FALSE;
14811 player->is_digging = FALSE;
14812 player->is_collecting = FALSE;
14818 // prevent snapping with already pressed snap key when not allowed
14819 if (player->is_snapping && !can_continue_snapping)
14822 player->MovDir = snap_direction;
14824 if (player->MovPos == 0)
14826 player->is_moving = FALSE;
14827 player->is_digging = FALSE;
14828 player->is_collecting = FALSE;
14831 player->is_dropping = FALSE;
14832 player->is_dropping_pressed = FALSE;
14833 player->drop_pressed_delay = 0;
14835 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14838 player->is_snapping = TRUE;
14839 player->is_active = TRUE;
14841 if (player->MovPos == 0)
14843 player->is_moving = FALSE;
14844 player->is_digging = FALSE;
14845 player->is_collecting = FALSE;
14848 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14849 TEST_DrawLevelField(player->last_jx, player->last_jy);
14851 TEST_DrawLevelField(x, y);
14856 static boolean DropElement(struct PlayerInfo *player)
14858 int old_element, new_element;
14859 int dropx = player->jx, dropy = player->jy;
14860 int drop_direction = player->MovDir;
14861 int drop_side = drop_direction;
14862 int drop_element = get_next_dropped_element(player);
14864 /* do not drop an element on top of another element; when holding drop key
14865 pressed without moving, dropped element must move away before the next
14866 element can be dropped (this is especially important if the next element
14867 is dynamite, which can be placed on background for historical reasons) */
14868 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14871 if (IS_THROWABLE(drop_element))
14873 dropx += GET_DX_FROM_DIR(drop_direction);
14874 dropy += GET_DY_FROM_DIR(drop_direction);
14876 if (!IN_LEV_FIELD(dropx, dropy))
14880 old_element = Tile[dropx][dropy]; // old element at dropping position
14881 new_element = drop_element; // default: no change when dropping
14883 // check if player is active, not moving and ready to drop
14884 if (!player->active || player->MovPos || player->drop_delay > 0)
14887 // check if player has anything that can be dropped
14888 if (new_element == EL_UNDEFINED)
14891 // only set if player has anything that can be dropped
14892 player->is_dropping_pressed = TRUE;
14894 // check if drop key was pressed long enough for EM style dynamite
14895 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14898 // check if anything can be dropped at the current position
14899 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14902 // collected custom elements can only be dropped on empty fields
14903 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14906 if (old_element != EL_EMPTY)
14907 Back[dropx][dropy] = old_element; // store old element on this field
14909 ResetGfxAnimation(dropx, dropy);
14910 ResetRandomAnimationValue(dropx, dropy);
14912 if (player->inventory_size > 0 ||
14913 player->inventory_infinite_element != EL_UNDEFINED)
14915 if (player->inventory_size > 0)
14917 player->inventory_size--;
14919 DrawGameDoorValues();
14921 if (new_element == EL_DYNAMITE)
14922 new_element = EL_DYNAMITE_ACTIVE;
14923 else if (new_element == EL_EM_DYNAMITE)
14924 new_element = EL_EM_DYNAMITE_ACTIVE;
14925 else if (new_element == EL_SP_DISK_RED)
14926 new_element = EL_SP_DISK_RED_ACTIVE;
14929 Tile[dropx][dropy] = new_element;
14931 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14932 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14933 el2img(Tile[dropx][dropy]), 0);
14935 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14937 // needed if previous element just changed to "empty" in the last frame
14938 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14940 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14941 player->index_bit, drop_side);
14942 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14944 player->index_bit, drop_side);
14946 TestIfElementTouchesCustomElement(dropx, dropy);
14948 else // player is dropping a dyna bomb
14950 player->dynabombs_left--;
14952 Tile[dropx][dropy] = new_element;
14954 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14955 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14956 el2img(Tile[dropx][dropy]), 0);
14958 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14961 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14962 InitField_WithBug1(dropx, dropy, FALSE);
14964 new_element = Tile[dropx][dropy]; // element might have changed
14966 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14967 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14969 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14970 MovDir[dropx][dropy] = drop_direction;
14972 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14974 // do not cause impact style collision by dropping elements that can fall
14975 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14978 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14979 player->is_dropping = TRUE;
14981 player->drop_pressed_delay = 0;
14982 player->is_dropping_pressed = FALSE;
14984 player->drop_x = dropx;
14985 player->drop_y = dropy;
14990 // ----------------------------------------------------------------------------
14991 // game sound playing functions
14992 // ----------------------------------------------------------------------------
14994 static int *loop_sound_frame = NULL;
14995 static int *loop_sound_volume = NULL;
14997 void InitPlayLevelSound(void)
14999 int num_sounds = getSoundListSize();
15001 checked_free(loop_sound_frame);
15002 checked_free(loop_sound_volume);
15004 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15005 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15008 static void PlayLevelSound(int x, int y, int nr)
15010 int sx = SCREENX(x), sy = SCREENY(y);
15011 int volume, stereo_position;
15012 int max_distance = 8;
15013 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15015 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15016 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15019 if (!IN_LEV_FIELD(x, y) ||
15020 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15021 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15024 volume = SOUND_MAX_VOLUME;
15026 if (!IN_SCR_FIELD(sx, sy))
15028 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15029 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15031 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15034 stereo_position = (SOUND_MAX_LEFT +
15035 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15036 (SCR_FIELDX + 2 * max_distance));
15038 if (IS_LOOP_SOUND(nr))
15040 /* This assures that quieter loop sounds do not overwrite louder ones,
15041 while restarting sound volume comparison with each new game frame. */
15043 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15046 loop_sound_volume[nr] = volume;
15047 loop_sound_frame[nr] = FrameCounter;
15050 PlaySoundExt(nr, volume, stereo_position, type);
15053 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15055 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15056 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15057 y < LEVELY(BY1) ? LEVELY(BY1) :
15058 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15062 static void PlayLevelSoundAction(int x, int y, int action)
15064 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15067 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15069 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15071 if (sound_effect != SND_UNDEFINED)
15072 PlayLevelSound(x, y, sound_effect);
15075 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15078 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15080 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15081 PlayLevelSound(x, y, sound_effect);
15084 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15086 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15088 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15089 PlayLevelSound(x, y, sound_effect);
15092 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15094 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15096 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15097 StopSound(sound_effect);
15100 static int getLevelMusicNr(void)
15102 if (levelset.music[level_nr] != MUS_UNDEFINED)
15103 return levelset.music[level_nr]; // from config file
15105 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15108 static void FadeLevelSounds(void)
15113 static void FadeLevelMusic(void)
15115 int music_nr = getLevelMusicNr();
15116 char *curr_music = getCurrentlyPlayingMusicFilename();
15117 char *next_music = getMusicInfoEntryFilename(music_nr);
15119 if (!strEqual(curr_music, next_music))
15123 void FadeLevelSoundsAndMusic(void)
15129 static void PlayLevelMusic(void)
15131 int music_nr = getLevelMusicNr();
15132 char *curr_music = getCurrentlyPlayingMusicFilename();
15133 char *next_music = getMusicInfoEntryFilename(music_nr);
15135 if (!strEqual(curr_music, next_music))
15136 PlayMusicLoop(music_nr);
15139 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15141 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15143 int x = xx - offset;
15144 int y = yy - offset;
15149 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15153 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15157 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15161 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15165 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15169 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15173 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15176 case SOUND_android_clone:
15177 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15180 case SOUND_android_move:
15181 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15185 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15189 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15193 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15196 case SOUND_eater_eat:
15197 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15201 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15204 case SOUND_collect:
15205 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15208 case SOUND_diamond:
15209 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15213 // !!! CHECK THIS !!!
15215 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15217 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15221 case SOUND_wonderfall:
15222 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15226 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15230 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15234 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15238 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15242 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15246 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15250 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15254 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15257 case SOUND_exit_open:
15258 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15261 case SOUND_exit_leave:
15262 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15265 case SOUND_dynamite:
15266 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15270 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15274 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15278 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15282 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15286 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15290 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15294 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15299 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15301 int element = map_element_SP_to_RND(element_sp);
15302 int action = map_action_SP_to_RND(action_sp);
15303 int offset = (setup.sp_show_border_elements ? 0 : 1);
15304 int x = xx - offset;
15305 int y = yy - offset;
15307 PlayLevelSoundElementAction(x, y, element, action);
15310 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15312 int element = map_element_MM_to_RND(element_mm);
15313 int action = map_action_MM_to_RND(action_mm);
15315 int x = xx - offset;
15316 int y = yy - offset;
15318 if (!IS_MM_ELEMENT(element))
15319 element = EL_MM_DEFAULT;
15321 PlayLevelSoundElementAction(x, y, element, action);
15324 void PlaySound_MM(int sound_mm)
15326 int sound = map_sound_MM_to_RND(sound_mm);
15328 if (sound == SND_UNDEFINED)
15334 void PlaySoundLoop_MM(int sound_mm)
15336 int sound = map_sound_MM_to_RND(sound_mm);
15338 if (sound == SND_UNDEFINED)
15341 PlaySoundLoop(sound);
15344 void StopSound_MM(int sound_mm)
15346 int sound = map_sound_MM_to_RND(sound_mm);
15348 if (sound == SND_UNDEFINED)
15354 void RaiseScore(int value)
15356 game.score += value;
15358 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15360 DisplayGameControlValues();
15363 void RaiseScoreElement(int element)
15368 case EL_BD_DIAMOND:
15369 case EL_EMERALD_YELLOW:
15370 case EL_EMERALD_RED:
15371 case EL_EMERALD_PURPLE:
15372 case EL_SP_INFOTRON:
15373 RaiseScore(level.score[SC_EMERALD]);
15376 RaiseScore(level.score[SC_DIAMOND]);
15379 RaiseScore(level.score[SC_CRYSTAL]);
15382 RaiseScore(level.score[SC_PEARL]);
15385 case EL_BD_BUTTERFLY:
15386 case EL_SP_ELECTRON:
15387 RaiseScore(level.score[SC_BUG]);
15390 case EL_BD_FIREFLY:
15391 case EL_SP_SNIKSNAK:
15392 RaiseScore(level.score[SC_SPACESHIP]);
15395 case EL_DARK_YAMYAM:
15396 RaiseScore(level.score[SC_YAMYAM]);
15399 RaiseScore(level.score[SC_ROBOT]);
15402 RaiseScore(level.score[SC_PACMAN]);
15405 RaiseScore(level.score[SC_NUT]);
15408 case EL_EM_DYNAMITE:
15409 case EL_SP_DISK_RED:
15410 case EL_DYNABOMB_INCREASE_NUMBER:
15411 case EL_DYNABOMB_INCREASE_SIZE:
15412 case EL_DYNABOMB_INCREASE_POWER:
15413 RaiseScore(level.score[SC_DYNAMITE]);
15415 case EL_SHIELD_NORMAL:
15416 case EL_SHIELD_DEADLY:
15417 RaiseScore(level.score[SC_SHIELD]);
15419 case EL_EXTRA_TIME:
15420 RaiseScore(level.extra_time_score);
15434 case EL_DC_KEY_WHITE:
15435 RaiseScore(level.score[SC_KEY]);
15438 RaiseScore(element_info[element].collect_score);
15443 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15445 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15449 // prevent short reactivation of overlay buttons while closing door
15450 SetOverlayActive(FALSE);
15452 // door may still be open due to skipped or envelope style request
15453 CloseDoor(DOOR_CLOSE_1);
15456 if (network.enabled)
15457 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15461 FadeSkipNextFadeIn();
15463 SetGameStatus(GAME_MODE_MAIN);
15468 else // continue playing the game
15470 if (tape.playing && tape.deactivate_display)
15471 TapeDeactivateDisplayOff(TRUE);
15473 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15475 if (tape.playing && tape.deactivate_display)
15476 TapeDeactivateDisplayOn();
15480 void RequestQuitGame(boolean escape_key_pressed)
15482 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15483 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15484 level_editor_test_game);
15485 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15488 RequestQuitGameExt(skip_request, quick_quit,
15489 "Do you really want to quit the game?");
15492 void RequestRestartGame(char *message)
15494 game.restart_game_message = NULL;
15496 boolean has_started_game = hasStartedNetworkGame();
15497 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15499 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15501 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15505 // needed in case of envelope request to close game panel
15506 CloseDoor(DOOR_CLOSE_1);
15508 SetGameStatus(GAME_MODE_MAIN);
15514 void CheckGameOver(void)
15516 static boolean last_game_over = FALSE;
15517 static int game_over_delay = 0;
15518 int game_over_delay_value = 50;
15519 boolean game_over = checkGameFailed();
15521 // do not handle game over if request dialog is already active
15522 if (game.request_active)
15525 // do not ask to play again if game was never actually played
15526 if (!game.GamePlayed)
15531 last_game_over = FALSE;
15532 game_over_delay = game_over_delay_value;
15537 if (game_over_delay > 0)
15544 if (last_game_over != game_over)
15545 game.restart_game_message = (hasStartedNetworkGame() ?
15546 "Game over! Play it again?" :
15549 last_game_over = game_over;
15552 boolean checkGameSolved(void)
15554 // set for all game engines if level was solved
15555 return game.LevelSolved_GameEnd;
15558 boolean checkGameFailed(void)
15560 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15561 return (game_em.game_over && !game_em.level_solved);
15562 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15563 return (game_sp.game_over && !game_sp.level_solved);
15564 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15565 return (game_mm.game_over && !game_mm.level_solved);
15566 else // GAME_ENGINE_TYPE_RND
15567 return (game.GameOver && !game.LevelSolved);
15570 boolean checkGameEnded(void)
15572 return (checkGameSolved() || checkGameFailed());
15576 // ----------------------------------------------------------------------------
15577 // random generator functions
15578 // ----------------------------------------------------------------------------
15580 unsigned int InitEngineRandom_RND(int seed)
15582 game.num_random_calls = 0;
15584 return InitEngineRandom(seed);
15587 unsigned int RND(int max)
15591 game.num_random_calls++;
15593 return GetEngineRandom(max);
15600 // ----------------------------------------------------------------------------
15601 // game engine snapshot handling functions
15602 // ----------------------------------------------------------------------------
15604 struct EngineSnapshotInfo
15606 // runtime values for custom element collect score
15607 int collect_score[NUM_CUSTOM_ELEMENTS];
15609 // runtime values for group element choice position
15610 int choice_pos[NUM_GROUP_ELEMENTS];
15612 // runtime values for belt position animations
15613 int belt_graphic[4][NUM_BELT_PARTS];
15614 int belt_anim_mode[4][NUM_BELT_PARTS];
15617 static struct EngineSnapshotInfo engine_snapshot_rnd;
15618 static char *snapshot_level_identifier = NULL;
15619 static int snapshot_level_nr = -1;
15621 static void SaveEngineSnapshotValues_RND(void)
15623 static int belt_base_active_element[4] =
15625 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15626 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15627 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15628 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15632 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15634 int element = EL_CUSTOM_START + i;
15636 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15639 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15641 int element = EL_GROUP_START + i;
15643 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15646 for (i = 0; i < 4; i++)
15648 for (j = 0; j < NUM_BELT_PARTS; j++)
15650 int element = belt_base_active_element[i] + j;
15651 int graphic = el2img(element);
15652 int anim_mode = graphic_info[graphic].anim_mode;
15654 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15655 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15660 static void LoadEngineSnapshotValues_RND(void)
15662 unsigned int num_random_calls = game.num_random_calls;
15665 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15667 int element = EL_CUSTOM_START + i;
15669 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15672 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15674 int element = EL_GROUP_START + i;
15676 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15679 for (i = 0; i < 4; i++)
15681 for (j = 0; j < NUM_BELT_PARTS; j++)
15683 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15684 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15686 graphic_info[graphic].anim_mode = anim_mode;
15690 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15692 InitRND(tape.random_seed);
15693 for (i = 0; i < num_random_calls; i++)
15697 if (game.num_random_calls != num_random_calls)
15699 Error("number of random calls out of sync");
15700 Error("number of random calls should be %d", num_random_calls);
15701 Error("number of random calls is %d", game.num_random_calls);
15703 Fail("this should not happen -- please debug");
15707 void FreeEngineSnapshotSingle(void)
15709 FreeSnapshotSingle();
15711 setString(&snapshot_level_identifier, NULL);
15712 snapshot_level_nr = -1;
15715 void FreeEngineSnapshotList(void)
15717 FreeSnapshotList();
15720 static ListNode *SaveEngineSnapshotBuffers(void)
15722 ListNode *buffers = NULL;
15724 // copy some special values to a structure better suited for the snapshot
15726 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15727 SaveEngineSnapshotValues_RND();
15728 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15729 SaveEngineSnapshotValues_EM();
15730 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15731 SaveEngineSnapshotValues_SP(&buffers);
15732 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15733 SaveEngineSnapshotValues_MM(&buffers);
15735 // save values stored in special snapshot structure
15737 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15738 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15739 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15740 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15741 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15742 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15743 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15744 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15746 // save further RND engine values
15748 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15749 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15750 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15752 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15753 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15754 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15755 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15756 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15758 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15759 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15760 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15762 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15764 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15765 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15767 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15768 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15769 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15770 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15771 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15772 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15773 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15774 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15775 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15776 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15777 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15778 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15779 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15780 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15781 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15782 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15783 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15784 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15786 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15787 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15789 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15790 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15791 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15793 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15794 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15796 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15797 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15798 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15799 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15800 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15802 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15803 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15806 ListNode *node = engine_snapshot_list_rnd;
15809 while (node != NULL)
15811 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15816 Debug("game:playing:SaveEngineSnapshotBuffers",
15817 "size of engine snapshot: %d bytes", num_bytes);
15823 void SaveEngineSnapshotSingle(void)
15825 ListNode *buffers = SaveEngineSnapshotBuffers();
15827 // finally save all snapshot buffers to single snapshot
15828 SaveSnapshotSingle(buffers);
15830 // save level identification information
15831 setString(&snapshot_level_identifier, leveldir_current->identifier);
15832 snapshot_level_nr = level_nr;
15835 boolean CheckSaveEngineSnapshotToList(void)
15837 boolean save_snapshot =
15838 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15839 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15840 game.snapshot.changed_action) ||
15841 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15842 game.snapshot.collected_item));
15844 game.snapshot.changed_action = FALSE;
15845 game.snapshot.collected_item = FALSE;
15846 game.snapshot.save_snapshot = save_snapshot;
15848 return save_snapshot;
15851 void SaveEngineSnapshotToList(void)
15853 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15857 ListNode *buffers = SaveEngineSnapshotBuffers();
15859 // finally save all snapshot buffers to snapshot list
15860 SaveSnapshotToList(buffers);
15863 void SaveEngineSnapshotToListInitial(void)
15865 FreeEngineSnapshotList();
15867 SaveEngineSnapshotToList();
15870 static void LoadEngineSnapshotValues(void)
15872 // restore special values from snapshot structure
15874 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15875 LoadEngineSnapshotValues_RND();
15876 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15877 LoadEngineSnapshotValues_EM();
15878 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15879 LoadEngineSnapshotValues_SP();
15880 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15881 LoadEngineSnapshotValues_MM();
15884 void LoadEngineSnapshotSingle(void)
15886 LoadSnapshotSingle();
15888 LoadEngineSnapshotValues();
15891 static void LoadEngineSnapshot_Undo(int steps)
15893 LoadSnapshotFromList_Older(steps);
15895 LoadEngineSnapshotValues();
15898 static void LoadEngineSnapshot_Redo(int steps)
15900 LoadSnapshotFromList_Newer(steps);
15902 LoadEngineSnapshotValues();
15905 boolean CheckEngineSnapshotSingle(void)
15907 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15908 snapshot_level_nr == level_nr);
15911 boolean CheckEngineSnapshotList(void)
15913 return CheckSnapshotList();
15917 // ---------- new game button stuff -------------------------------------------
15924 boolean *setup_value;
15925 boolean allowed_on_tape;
15926 boolean is_touch_button;
15928 } gamebutton_info[NUM_GAME_BUTTONS] =
15931 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15932 GAME_CTRL_ID_STOP, NULL,
15933 TRUE, FALSE, "stop game"
15936 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15937 GAME_CTRL_ID_PAUSE, NULL,
15938 TRUE, FALSE, "pause game"
15941 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15942 GAME_CTRL_ID_PLAY, NULL,
15943 TRUE, FALSE, "play game"
15946 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15947 GAME_CTRL_ID_UNDO, NULL,
15948 TRUE, FALSE, "undo step"
15951 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15952 GAME_CTRL_ID_REDO, NULL,
15953 TRUE, FALSE, "redo step"
15956 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15957 GAME_CTRL_ID_SAVE, NULL,
15958 TRUE, FALSE, "save game"
15961 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15962 GAME_CTRL_ID_PAUSE2, NULL,
15963 TRUE, FALSE, "pause game"
15966 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15967 GAME_CTRL_ID_LOAD, NULL,
15968 TRUE, FALSE, "load game"
15971 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15972 GAME_CTRL_ID_PANEL_STOP, NULL,
15973 FALSE, FALSE, "stop game"
15976 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15977 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15978 FALSE, FALSE, "pause game"
15981 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15982 GAME_CTRL_ID_PANEL_PLAY, NULL,
15983 FALSE, FALSE, "play game"
15986 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15987 GAME_CTRL_ID_TOUCH_STOP, NULL,
15988 FALSE, TRUE, "stop game"
15991 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15992 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15993 FALSE, TRUE, "pause game"
15996 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15997 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15998 TRUE, FALSE, "background music on/off"
16001 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16002 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16003 TRUE, FALSE, "sound loops on/off"
16006 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16007 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16008 TRUE, FALSE, "normal sounds on/off"
16011 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16012 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16013 FALSE, FALSE, "background music on/off"
16016 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16017 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16018 FALSE, FALSE, "sound loops on/off"
16021 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16022 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16023 FALSE, FALSE, "normal sounds on/off"
16027 void CreateGameButtons(void)
16031 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16033 int graphic = gamebutton_info[i].graphic;
16034 struct GraphicInfo *gfx = &graphic_info[graphic];
16035 struct XY *pos = gamebutton_info[i].pos;
16036 struct GadgetInfo *gi;
16039 unsigned int event_mask;
16040 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16041 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16042 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16043 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16044 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16045 int gd_x = gfx->src_x;
16046 int gd_y = gfx->src_y;
16047 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16048 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16049 int gd_xa = gfx->src_x + gfx->active_xoffset;
16050 int gd_ya = gfx->src_y + gfx->active_yoffset;
16051 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16052 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16053 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16054 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16057 if (gfx->bitmap == NULL)
16059 game_gadget[id] = NULL;
16064 if (id == GAME_CTRL_ID_STOP ||
16065 id == GAME_CTRL_ID_PANEL_STOP ||
16066 id == GAME_CTRL_ID_TOUCH_STOP ||
16067 id == GAME_CTRL_ID_PLAY ||
16068 id == GAME_CTRL_ID_PANEL_PLAY ||
16069 id == GAME_CTRL_ID_SAVE ||
16070 id == GAME_CTRL_ID_LOAD)
16072 button_type = GD_TYPE_NORMAL_BUTTON;
16074 event_mask = GD_EVENT_RELEASED;
16076 else if (id == GAME_CTRL_ID_UNDO ||
16077 id == GAME_CTRL_ID_REDO)
16079 button_type = GD_TYPE_NORMAL_BUTTON;
16081 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16085 button_type = GD_TYPE_CHECK_BUTTON;
16086 checked = (gamebutton_info[i].setup_value != NULL ?
16087 *gamebutton_info[i].setup_value : FALSE);
16088 event_mask = GD_EVENT_PRESSED;
16091 gi = CreateGadget(GDI_CUSTOM_ID, id,
16092 GDI_IMAGE_ID, graphic,
16093 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16096 GDI_WIDTH, gfx->width,
16097 GDI_HEIGHT, gfx->height,
16098 GDI_TYPE, button_type,
16099 GDI_STATE, GD_BUTTON_UNPRESSED,
16100 GDI_CHECKED, checked,
16101 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16102 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16103 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16104 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16105 GDI_DIRECT_DRAW, FALSE,
16106 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16107 GDI_EVENT_MASK, event_mask,
16108 GDI_CALLBACK_ACTION, HandleGameButtons,
16112 Fail("cannot create gadget");
16114 game_gadget[id] = gi;
16118 void FreeGameButtons(void)
16122 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16123 FreeGadget(game_gadget[i]);
16126 static void UnmapGameButtonsAtSamePosition(int id)
16130 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16132 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16133 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16134 UnmapGadget(game_gadget[i]);
16137 static void UnmapGameButtonsAtSamePosition_All(void)
16139 if (setup.show_snapshot_buttons)
16141 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16142 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16143 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16147 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16148 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16149 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16151 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16152 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16153 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16157 static void MapGameButtonsAtSamePosition(int id)
16161 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16163 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16164 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16165 MapGadget(game_gadget[i]);
16167 UnmapGameButtonsAtSamePosition_All();
16170 void MapUndoRedoButtons(void)
16172 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16173 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16175 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16176 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16179 void UnmapUndoRedoButtons(void)
16181 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16182 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16184 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16185 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16188 void ModifyPauseButtons(void)
16192 GAME_CTRL_ID_PAUSE,
16193 GAME_CTRL_ID_PAUSE2,
16194 GAME_CTRL_ID_PANEL_PAUSE,
16195 GAME_CTRL_ID_TOUCH_PAUSE,
16200 for (i = 0; ids[i] > -1; i++)
16201 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16204 static void MapGameButtonsExt(boolean on_tape)
16208 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16209 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16210 i != GAME_CTRL_ID_UNDO &&
16211 i != GAME_CTRL_ID_REDO)
16212 MapGadget(game_gadget[i]);
16214 UnmapGameButtonsAtSamePosition_All();
16216 RedrawGameButtons();
16219 static void UnmapGameButtonsExt(boolean on_tape)
16223 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16224 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16225 UnmapGadget(game_gadget[i]);
16228 static void RedrawGameButtonsExt(boolean on_tape)
16232 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16233 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16234 RedrawGadget(game_gadget[i]);
16237 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16242 gi->checked = state;
16245 static void RedrawSoundButtonGadget(int id)
16247 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16248 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16249 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16250 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16251 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16252 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16255 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16256 RedrawGadget(game_gadget[id2]);
16259 void MapGameButtons(void)
16261 MapGameButtonsExt(FALSE);
16264 void UnmapGameButtons(void)
16266 UnmapGameButtonsExt(FALSE);
16269 void RedrawGameButtons(void)
16271 RedrawGameButtonsExt(FALSE);
16274 void MapGameButtonsOnTape(void)
16276 MapGameButtonsExt(TRUE);
16279 void UnmapGameButtonsOnTape(void)
16281 UnmapGameButtonsExt(TRUE);
16284 void RedrawGameButtonsOnTape(void)
16286 RedrawGameButtonsExt(TRUE);
16289 static void GameUndoRedoExt(void)
16291 ClearPlayerAction();
16293 tape.pausing = TRUE;
16296 UpdateAndDisplayGameControlValues();
16298 DrawCompleteVideoDisplay();
16299 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16300 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16301 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16306 static void GameUndo(int steps)
16308 if (!CheckEngineSnapshotList())
16311 LoadEngineSnapshot_Undo(steps);
16316 static void GameRedo(int steps)
16318 if (!CheckEngineSnapshotList())
16321 LoadEngineSnapshot_Redo(steps);
16326 static void HandleGameButtonsExt(int id, int button)
16328 static boolean game_undo_executed = FALSE;
16329 int steps = BUTTON_STEPSIZE(button);
16330 boolean handle_game_buttons =
16331 (game_status == GAME_MODE_PLAYING ||
16332 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16334 if (!handle_game_buttons)
16339 case GAME_CTRL_ID_STOP:
16340 case GAME_CTRL_ID_PANEL_STOP:
16341 case GAME_CTRL_ID_TOUCH_STOP:
16342 if (game_status == GAME_MODE_MAIN)
16348 RequestQuitGame(FALSE);
16352 case GAME_CTRL_ID_PAUSE:
16353 case GAME_CTRL_ID_PAUSE2:
16354 case GAME_CTRL_ID_PANEL_PAUSE:
16355 case GAME_CTRL_ID_TOUCH_PAUSE:
16356 if (network.enabled && game_status == GAME_MODE_PLAYING)
16359 SendToServer_ContinuePlaying();
16361 SendToServer_PausePlaying();
16364 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16366 game_undo_executed = FALSE;
16370 case GAME_CTRL_ID_PLAY:
16371 case GAME_CTRL_ID_PANEL_PLAY:
16372 if (game_status == GAME_MODE_MAIN)
16374 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16376 else if (tape.pausing)
16378 if (network.enabled)
16379 SendToServer_ContinuePlaying();
16381 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16385 case GAME_CTRL_ID_UNDO:
16386 // Important: When using "save snapshot when collecting an item" mode,
16387 // load last (current) snapshot for first "undo" after pressing "pause"
16388 // (else the last-but-one snapshot would be loaded, because the snapshot
16389 // pointer already points to the last snapshot when pressing "pause",
16390 // which is fine for "every step/move" mode, but not for "every collect")
16391 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16392 !game_undo_executed)
16395 game_undo_executed = TRUE;
16400 case GAME_CTRL_ID_REDO:
16404 case GAME_CTRL_ID_SAVE:
16408 case GAME_CTRL_ID_LOAD:
16412 case SOUND_CTRL_ID_MUSIC:
16413 case SOUND_CTRL_ID_PANEL_MUSIC:
16414 if (setup.sound_music)
16416 setup.sound_music = FALSE;
16420 else if (audio.music_available)
16422 setup.sound = setup.sound_music = TRUE;
16424 SetAudioMode(setup.sound);
16426 if (game_status == GAME_MODE_PLAYING)
16430 RedrawSoundButtonGadget(id);
16434 case SOUND_CTRL_ID_LOOPS:
16435 case SOUND_CTRL_ID_PANEL_LOOPS:
16436 if (setup.sound_loops)
16437 setup.sound_loops = FALSE;
16438 else if (audio.loops_available)
16440 setup.sound = setup.sound_loops = TRUE;
16442 SetAudioMode(setup.sound);
16445 RedrawSoundButtonGadget(id);
16449 case SOUND_CTRL_ID_SIMPLE:
16450 case SOUND_CTRL_ID_PANEL_SIMPLE:
16451 if (setup.sound_simple)
16452 setup.sound_simple = FALSE;
16453 else if (audio.sound_available)
16455 setup.sound = setup.sound_simple = TRUE;
16457 SetAudioMode(setup.sound);
16460 RedrawSoundButtonGadget(id);
16469 static void HandleGameButtons(struct GadgetInfo *gi)
16471 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16474 void HandleSoundButtonKeys(Key key)
16476 if (key == setup.shortcut.sound_simple)
16477 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16478 else if (key == setup.shortcut.sound_loops)
16479 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16480 else if (key == setup.shortcut.sound_music)
16481 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);