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 int 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;
4963 int highlight_position;
4965 game.LevelSolved_GameEnd = TRUE;
4967 if (game.LevelSolved_SaveTape)
4969 // make sure that request dialog to save tape does not open door again
4970 if (!global.use_envelope_request)
4971 CloseDoor(DOOR_CLOSE_1);
4973 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4975 // set unique basename for score tape (also saved in high score table)
4976 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4979 // if no tape is to be saved, close both doors simultaneously
4980 CloseDoor(DOOR_CLOSE_ALL);
4982 if (level_editor_test_game)
4984 SetGameStatus(GAME_MODE_MAIN);
4991 if (!game.LevelSolved_SaveScore)
4993 SetGameStatus(GAME_MODE_MAIN);
5000 if (level_nr == leveldir_current->handicap_level)
5002 leveldir_current->handicap_level++;
5004 SaveLevelSetup_SeriesInfo();
5007 // save score and score tape before potentially erasing tape below
5008 highlight_position = NewHighScore(last_level_nr);
5010 if (setup.increment_levels &&
5011 level_nr < leveldir_current->last_level &&
5014 level_nr++; // advance to next level
5015 TapeErase(); // start with empty tape
5017 if (setup.auto_play_next_level)
5019 LoadLevel(level_nr);
5021 SaveLevelSetup_SeriesInfo();
5025 if (highlight_position >= 0 && setup.show_scores_after_game)
5027 SetGameStatus(GAME_MODE_SCORES);
5029 DrawHallOfFame(last_level_nr, highlight_position);
5031 else if (setup.auto_play_next_level && setup.increment_levels &&
5032 last_level_nr < leveldir_current->last_level &&
5035 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5039 SetGameStatus(GAME_MODE_MAIN);
5045 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry)
5047 boolean one_score_entry_per_name = !program.many_scores_per_name;
5050 if (strEqual(setup.player_name, EMPTY_PLAYER_NAME))
5053 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5055 struct ScoreEntry *entry = &list->entry[i];
5056 boolean score_is_better = (new_entry->score > entry->score);
5057 boolean score_is_equal = (new_entry->score == entry->score);
5058 boolean time_is_better = (new_entry->time < entry->time);
5059 boolean time_is_equal = (new_entry->time == entry->time);
5060 boolean better_by_score = (score_is_better ||
5061 (score_is_equal && time_is_better));
5062 boolean better_by_time = (time_is_better ||
5063 (time_is_equal && score_is_better));
5064 boolean is_better = (level.rate_time_over_score ? better_by_time :
5066 boolean entry_is_empty = (entry->score == 0 &&
5069 if (is_better || entry_is_empty)
5071 // player has made it to the hall of fame
5073 if (i < MAX_SCORE_ENTRIES - 1)
5075 int m = MAX_SCORE_ENTRIES - 1;
5078 if (one_score_entry_per_name)
5080 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5081 if (strEqual(list->entry[l].name, setup.player_name))
5084 if (m == i) // player's new highscore overwrites his old one
5088 for (l = m; l > i; l--)
5089 list->entry[l] = list->entry[l - 1];
5094 *entry = *new_entry;
5098 else if (one_score_entry_per_name &&
5099 strEqual(entry->name, setup.player_name))
5101 // player already in high score list with better score or time
5110 int NewHighScore(int level_nr)
5112 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5114 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5115 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5117 new_entry.score = game.score_final;
5118 new_entry.time = game.score_time_final;
5120 LoadScore(level_nr);
5122 int position = addScoreEntry(&scores, &new_entry);
5126 SaveScore(level_nr);
5128 if (game.LevelSolved_SaveTape)
5129 SaveScoreTape(level_nr);
5135 static int getElementMoveStepsizeExt(int x, int y, int direction)
5137 int element = Tile[x][y];
5138 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5139 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5140 int horiz_move = (dx != 0);
5141 int sign = (horiz_move ? dx : dy);
5142 int step = sign * element_info[element].move_stepsize;
5144 // special values for move stepsize for spring and things on conveyor belt
5147 if (CAN_FALL(element) &&
5148 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5149 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5150 else if (element == EL_SPRING)
5151 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5157 static int getElementMoveStepsize(int x, int y)
5159 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5162 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5164 if (player->GfxAction != action || player->GfxDir != dir)
5166 player->GfxAction = action;
5167 player->GfxDir = dir;
5169 player->StepFrame = 0;
5173 static void ResetGfxFrame(int x, int y)
5175 // profiling showed that "autotest" spends 10~20% of its time in this function
5176 if (DrawingDeactivatedField())
5179 int element = Tile[x][y];
5180 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5182 if (graphic_info[graphic].anim_global_sync)
5183 GfxFrame[x][y] = FrameCounter;
5184 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5185 GfxFrame[x][y] = CustomValue[x][y];
5186 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5187 GfxFrame[x][y] = element_info[element].collect_score;
5188 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5189 GfxFrame[x][y] = ChangeDelay[x][y];
5192 static void ResetGfxAnimation(int x, int y)
5194 GfxAction[x][y] = ACTION_DEFAULT;
5195 GfxDir[x][y] = MovDir[x][y];
5198 ResetGfxFrame(x, y);
5201 static void ResetRandomAnimationValue(int x, int y)
5203 GfxRandom[x][y] = INIT_GFX_RANDOM();
5206 static void InitMovingField(int x, int y, int direction)
5208 int element = Tile[x][y];
5209 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5210 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5213 boolean is_moving_before, is_moving_after;
5215 // check if element was/is moving or being moved before/after mode change
5216 is_moving_before = (WasJustMoving[x][y] != 0);
5217 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5219 // reset animation only for moving elements which change direction of moving
5220 // or which just started or stopped moving
5221 // (else CEs with property "can move" / "not moving" are reset each frame)
5222 if (is_moving_before != is_moving_after ||
5223 direction != MovDir[x][y])
5224 ResetGfxAnimation(x, y);
5226 MovDir[x][y] = direction;
5227 GfxDir[x][y] = direction;
5229 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5230 direction == MV_DOWN && CAN_FALL(element) ?
5231 ACTION_FALLING : ACTION_MOVING);
5233 // this is needed for CEs with property "can move" / "not moving"
5235 if (is_moving_after)
5237 if (Tile[newx][newy] == EL_EMPTY)
5238 Tile[newx][newy] = EL_BLOCKED;
5240 MovDir[newx][newy] = MovDir[x][y];
5242 CustomValue[newx][newy] = CustomValue[x][y];
5244 GfxFrame[newx][newy] = GfxFrame[x][y];
5245 GfxRandom[newx][newy] = GfxRandom[x][y];
5246 GfxAction[newx][newy] = GfxAction[x][y];
5247 GfxDir[newx][newy] = GfxDir[x][y];
5251 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5253 int direction = MovDir[x][y];
5254 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5255 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5261 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5263 int oldx = x, oldy = y;
5264 int direction = MovDir[x][y];
5266 if (direction == MV_LEFT)
5268 else if (direction == MV_RIGHT)
5270 else if (direction == MV_UP)
5272 else if (direction == MV_DOWN)
5275 *comes_from_x = oldx;
5276 *comes_from_y = oldy;
5279 static int MovingOrBlocked2Element(int x, int y)
5281 int element = Tile[x][y];
5283 if (element == EL_BLOCKED)
5287 Blocked2Moving(x, y, &oldx, &oldy);
5288 return Tile[oldx][oldy];
5294 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5296 // like MovingOrBlocked2Element(), but if element is moving
5297 // and (x,y) is the field the moving element is just leaving,
5298 // return EL_BLOCKED instead of the element value
5299 int element = Tile[x][y];
5301 if (IS_MOVING(x, y))
5303 if (element == EL_BLOCKED)
5307 Blocked2Moving(x, y, &oldx, &oldy);
5308 return Tile[oldx][oldy];
5317 static void RemoveField(int x, int y)
5319 Tile[x][y] = EL_EMPTY;
5325 CustomValue[x][y] = 0;
5328 ChangeDelay[x][y] = 0;
5329 ChangePage[x][y] = -1;
5330 Pushed[x][y] = FALSE;
5332 GfxElement[x][y] = EL_UNDEFINED;
5333 GfxAction[x][y] = ACTION_DEFAULT;
5334 GfxDir[x][y] = MV_NONE;
5337 static void RemoveMovingField(int x, int y)
5339 int oldx = x, oldy = y, newx = x, newy = y;
5340 int element = Tile[x][y];
5341 int next_element = EL_UNDEFINED;
5343 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5346 if (IS_MOVING(x, y))
5348 Moving2Blocked(x, y, &newx, &newy);
5350 if (Tile[newx][newy] != EL_BLOCKED)
5352 // element is moving, but target field is not free (blocked), but
5353 // already occupied by something different (example: acid pool);
5354 // in this case, only remove the moving field, but not the target
5356 RemoveField(oldx, oldy);
5358 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5360 TEST_DrawLevelField(oldx, oldy);
5365 else if (element == EL_BLOCKED)
5367 Blocked2Moving(x, y, &oldx, &oldy);
5368 if (!IS_MOVING(oldx, oldy))
5372 if (element == EL_BLOCKED &&
5373 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5374 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5375 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5376 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5377 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5378 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5379 next_element = get_next_element(Tile[oldx][oldy]);
5381 RemoveField(oldx, oldy);
5382 RemoveField(newx, newy);
5384 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5386 if (next_element != EL_UNDEFINED)
5387 Tile[oldx][oldy] = next_element;
5389 TEST_DrawLevelField(oldx, oldy);
5390 TEST_DrawLevelField(newx, newy);
5393 void DrawDynamite(int x, int y)
5395 int sx = SCREENX(x), sy = SCREENY(y);
5396 int graphic = el2img(Tile[x][y]);
5399 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5402 if (IS_WALKABLE_INSIDE(Back[x][y]))
5406 DrawLevelElement(x, y, Back[x][y]);
5407 else if (Store[x][y])
5408 DrawLevelElement(x, y, Store[x][y]);
5409 else if (game.use_masked_elements)
5410 DrawLevelElement(x, y, EL_EMPTY);
5412 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5414 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5415 DrawGraphicThruMask(sx, sy, graphic, frame);
5417 DrawGraphic(sx, sy, graphic, frame);
5420 static void CheckDynamite(int x, int y)
5422 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5426 if (MovDelay[x][y] != 0)
5429 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5435 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5440 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5442 boolean num_checked_players = 0;
5445 for (i = 0; i < MAX_PLAYERS; i++)
5447 if (stored_player[i].active)
5449 int sx = stored_player[i].jx;
5450 int sy = stored_player[i].jy;
5452 if (num_checked_players == 0)
5459 *sx1 = MIN(*sx1, sx);
5460 *sy1 = MIN(*sy1, sy);
5461 *sx2 = MAX(*sx2, sx);
5462 *sy2 = MAX(*sy2, sy);
5465 num_checked_players++;
5470 static boolean checkIfAllPlayersFitToScreen_RND(void)
5472 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5474 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5476 return (sx2 - sx1 < SCR_FIELDX &&
5477 sy2 - sy1 < SCR_FIELDY);
5480 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5482 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5484 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5486 *sx = (sx1 + sx2) / 2;
5487 *sy = (sy1 + sy2) / 2;
5490 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5491 boolean center_screen, boolean quick_relocation)
5493 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5494 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5495 boolean no_delay = (tape.warp_forward);
5496 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5497 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5498 int new_scroll_x, new_scroll_y;
5500 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5502 // case 1: quick relocation inside visible screen (without scrolling)
5509 if (!level.shifted_relocation || center_screen)
5511 // relocation _with_ centering of screen
5513 new_scroll_x = SCROLL_POSITION_X(x);
5514 new_scroll_y = SCROLL_POSITION_Y(y);
5518 // relocation _without_ centering of screen
5520 int center_scroll_x = SCROLL_POSITION_X(old_x);
5521 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5522 int offset_x = x + (scroll_x - center_scroll_x);
5523 int offset_y = y + (scroll_y - center_scroll_y);
5525 // for new screen position, apply previous offset to center position
5526 new_scroll_x = SCROLL_POSITION_X(offset_x);
5527 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5530 if (quick_relocation)
5532 // case 2: quick relocation (redraw without visible scrolling)
5534 scroll_x = new_scroll_x;
5535 scroll_y = new_scroll_y;
5542 // case 3: visible relocation (with scrolling to new position)
5544 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5546 SetVideoFrameDelay(wait_delay_value);
5548 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5550 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5551 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5553 if (dx == 0 && dy == 0) // no scrolling needed at all
5559 // set values for horizontal/vertical screen scrolling (half tile size)
5560 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5561 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5562 int pos_x = dx * TILEX / 2;
5563 int pos_y = dy * TILEY / 2;
5564 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5565 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5567 ScrollLevel(dx, dy);
5570 // scroll in two steps of half tile size to make things smoother
5571 BlitScreenToBitmapExt_RND(window, fx, fy);
5573 // scroll second step to align at full tile size
5574 BlitScreenToBitmap(window);
5580 SetVideoFrameDelay(frame_delay_value_old);
5583 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5585 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5586 int player_nr = GET_PLAYER_NR(el_player);
5587 struct PlayerInfo *player = &stored_player[player_nr];
5588 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5589 boolean no_delay = (tape.warp_forward);
5590 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5591 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5592 int old_jx = player->jx;
5593 int old_jy = player->jy;
5594 int old_element = Tile[old_jx][old_jy];
5595 int element = Tile[jx][jy];
5596 boolean player_relocated = (old_jx != jx || old_jy != jy);
5598 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5599 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5600 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5601 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5602 int leave_side_horiz = move_dir_horiz;
5603 int leave_side_vert = move_dir_vert;
5604 int enter_side = enter_side_horiz | enter_side_vert;
5605 int leave_side = leave_side_horiz | leave_side_vert;
5607 if (player->buried) // do not reanimate dead player
5610 if (!player_relocated) // no need to relocate the player
5613 if (IS_PLAYER(jx, jy)) // player already placed at new position
5615 RemoveField(jx, jy); // temporarily remove newly placed player
5616 DrawLevelField(jx, jy);
5619 if (player->present)
5621 while (player->MovPos)
5623 ScrollPlayer(player, SCROLL_GO_ON);
5624 ScrollScreen(NULL, SCROLL_GO_ON);
5626 AdvanceFrameAndPlayerCounters(player->index_nr);
5630 BackToFront_WithFrameDelay(wait_delay_value);
5633 DrawPlayer(player); // needed here only to cleanup last field
5634 DrawLevelField(player->jx, player->jy); // remove player graphic
5636 player->is_moving = FALSE;
5639 if (IS_CUSTOM_ELEMENT(old_element))
5640 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5642 player->index_bit, leave_side);
5644 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5646 player->index_bit, leave_side);
5648 Tile[jx][jy] = el_player;
5649 InitPlayerField(jx, jy, el_player, TRUE);
5651 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5652 possible that the relocation target field did not contain a player element,
5653 but a walkable element, to which the new player was relocated -- in this
5654 case, restore that (already initialized!) element on the player field */
5655 if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element
5657 Tile[jx][jy] = element; // restore previously existing element
5660 // only visually relocate centered player
5661 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5662 FALSE, level.instant_relocation);
5664 TestIfPlayerTouchesBadThing(jx, jy);
5665 TestIfPlayerTouchesCustomElement(jx, jy);
5667 if (IS_CUSTOM_ELEMENT(element))
5668 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5669 player->index_bit, enter_side);
5671 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5672 player->index_bit, enter_side);
5674 if (player->is_switching)
5676 /* ensure that relocation while still switching an element does not cause
5677 a new element to be treated as also switched directly after relocation
5678 (this is important for teleporter switches that teleport the player to
5679 a place where another teleporter switch is in the same direction, which
5680 would then incorrectly be treated as immediately switched before the
5681 direction key that caused the switch was released) */
5683 player->switch_x += jx - old_jx;
5684 player->switch_y += jy - old_jy;
5688 static void Explode(int ex, int ey, int phase, int mode)
5694 // !!! eliminate this variable !!!
5695 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5697 if (game.explosions_delayed)
5699 ExplodeField[ex][ey] = mode;
5703 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5705 int center_element = Tile[ex][ey];
5706 int artwork_element, explosion_element; // set these values later
5708 // remove things displayed in background while burning dynamite
5709 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5712 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5714 // put moving element to center field (and let it explode there)
5715 center_element = MovingOrBlocked2Element(ex, ey);
5716 RemoveMovingField(ex, ey);
5717 Tile[ex][ey] = center_element;
5720 // now "center_element" is finally determined -- set related values now
5721 artwork_element = center_element; // for custom player artwork
5722 explosion_element = center_element; // for custom player artwork
5724 if (IS_PLAYER(ex, ey))
5726 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5728 artwork_element = stored_player[player_nr].artwork_element;
5730 if (level.use_explosion_element[player_nr])
5732 explosion_element = level.explosion_element[player_nr];
5733 artwork_element = explosion_element;
5737 if (mode == EX_TYPE_NORMAL ||
5738 mode == EX_TYPE_CENTER ||
5739 mode == EX_TYPE_CROSS)
5740 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5742 last_phase = element_info[explosion_element].explosion_delay + 1;
5744 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5746 int xx = x - ex + 1;
5747 int yy = y - ey + 1;
5750 if (!IN_LEV_FIELD(x, y) ||
5751 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5752 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5755 element = Tile[x][y];
5757 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5759 element = MovingOrBlocked2Element(x, y);
5761 if (!IS_EXPLOSION_PROOF(element))
5762 RemoveMovingField(x, y);
5765 // indestructible elements can only explode in center (but not flames)
5766 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5767 mode == EX_TYPE_BORDER)) ||
5768 element == EL_FLAMES)
5771 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5772 behaviour, for example when touching a yamyam that explodes to rocks
5773 with active deadly shield, a rock is created under the player !!! */
5774 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5776 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5777 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5778 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5780 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5783 if (IS_ACTIVE_BOMB(element))
5785 // re-activate things under the bomb like gate or penguin
5786 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5793 // save walkable background elements while explosion on same tile
5794 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5795 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5796 Back[x][y] = element;
5798 // ignite explodable elements reached by other explosion
5799 if (element == EL_EXPLOSION)
5800 element = Store2[x][y];
5802 if (AmoebaNr[x][y] &&
5803 (element == EL_AMOEBA_FULL ||
5804 element == EL_BD_AMOEBA ||
5805 element == EL_AMOEBA_GROWING))
5807 AmoebaCnt[AmoebaNr[x][y]]--;
5808 AmoebaCnt2[AmoebaNr[x][y]]--;
5813 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5815 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5817 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5819 if (PLAYERINFO(ex, ey)->use_murphy)
5820 Store[x][y] = EL_EMPTY;
5823 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5824 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5825 else if (ELEM_IS_PLAYER(center_element))
5826 Store[x][y] = EL_EMPTY;
5827 else if (center_element == EL_YAMYAM)
5828 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5829 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5830 Store[x][y] = element_info[center_element].content.e[xx][yy];
5832 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5833 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5834 // otherwise) -- FIX THIS !!!
5835 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5836 Store[x][y] = element_info[element].content.e[1][1];
5838 else if (!CAN_EXPLODE(element))
5839 Store[x][y] = element_info[element].content.e[1][1];
5842 Store[x][y] = EL_EMPTY;
5844 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5845 center_element == EL_AMOEBA_TO_DIAMOND)
5846 Store2[x][y] = element;
5848 Tile[x][y] = EL_EXPLOSION;
5849 GfxElement[x][y] = artwork_element;
5851 ExplodePhase[x][y] = 1;
5852 ExplodeDelay[x][y] = last_phase;
5857 if (center_element == EL_YAMYAM)
5858 game.yamyam_content_nr =
5859 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5871 GfxFrame[x][y] = 0; // restart explosion animation
5873 last_phase = ExplodeDelay[x][y];
5875 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5877 // this can happen if the player leaves an explosion just in time
5878 if (GfxElement[x][y] == EL_UNDEFINED)
5879 GfxElement[x][y] = EL_EMPTY;
5881 border_element = Store2[x][y];
5882 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5883 border_element = StorePlayer[x][y];
5885 if (phase == element_info[border_element].ignition_delay ||
5886 phase == last_phase)
5888 boolean border_explosion = FALSE;
5890 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5891 !PLAYER_EXPLOSION_PROTECTED(x, y))
5893 KillPlayerUnlessExplosionProtected(x, y);
5894 border_explosion = TRUE;
5896 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5898 Tile[x][y] = Store2[x][y];
5901 border_explosion = TRUE;
5903 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5905 AmoebaToDiamond(x, y);
5907 border_explosion = TRUE;
5910 // if an element just explodes due to another explosion (chain-reaction),
5911 // do not immediately end the new explosion when it was the last frame of
5912 // the explosion (as it would be done in the following "if"-statement!)
5913 if (border_explosion && phase == last_phase)
5917 if (phase == last_phase)
5921 element = Tile[x][y] = Store[x][y];
5922 Store[x][y] = Store2[x][y] = 0;
5923 GfxElement[x][y] = EL_UNDEFINED;
5925 // player can escape from explosions and might therefore be still alive
5926 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5927 element <= EL_PLAYER_IS_EXPLODING_4)
5929 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5930 int explosion_element = EL_PLAYER_1 + player_nr;
5931 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5932 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5934 if (level.use_explosion_element[player_nr])
5935 explosion_element = level.explosion_element[player_nr];
5937 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5938 element_info[explosion_element].content.e[xx][yy]);
5941 // restore probably existing indestructible background element
5942 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5943 element = Tile[x][y] = Back[x][y];
5946 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5947 GfxDir[x][y] = MV_NONE;
5948 ChangeDelay[x][y] = 0;
5949 ChangePage[x][y] = -1;
5951 CustomValue[x][y] = 0;
5953 InitField_WithBug2(x, y, FALSE);
5955 TEST_DrawLevelField(x, y);
5957 TestIfElementTouchesCustomElement(x, y);
5959 if (GFX_CRUMBLED(element))
5960 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5962 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5963 StorePlayer[x][y] = 0;
5965 if (ELEM_IS_PLAYER(element))
5966 RelocatePlayer(x, y, element);
5968 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
5970 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
5971 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5974 TEST_DrawLevelFieldCrumbled(x, y);
5976 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
5978 DrawLevelElement(x, y, Back[x][y]);
5979 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
5981 else if (IS_WALKABLE_UNDER(Back[x][y]))
5983 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5984 DrawLevelElementThruMask(x, y, Back[x][y]);
5986 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
5987 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
5991 static void DynaExplode(int ex, int ey)
5994 int dynabomb_element = Tile[ex][ey];
5995 int dynabomb_size = 1;
5996 boolean dynabomb_xl = FALSE;
5997 struct PlayerInfo *player;
5998 static int xy[4][2] =
6006 if (IS_ACTIVE_BOMB(dynabomb_element))
6008 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6009 dynabomb_size = player->dynabomb_size;
6010 dynabomb_xl = player->dynabomb_xl;
6011 player->dynabombs_left++;
6014 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6016 for (i = 0; i < NUM_DIRECTIONS; i++)
6018 for (j = 1; j <= dynabomb_size; j++)
6020 int x = ex + j * xy[i][0];
6021 int y = ey + j * xy[i][1];
6024 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6027 element = Tile[x][y];
6029 // do not restart explosions of fields with active bombs
6030 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6033 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6035 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6036 !IS_DIGGABLE(element) && !dynabomb_xl)
6042 void Bang(int x, int y)
6044 int element = MovingOrBlocked2Element(x, y);
6045 int explosion_type = EX_TYPE_NORMAL;
6047 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6049 struct PlayerInfo *player = PLAYERINFO(x, y);
6051 element = Tile[x][y] = player->initial_element;
6053 if (level.use_explosion_element[player->index_nr])
6055 int explosion_element = level.explosion_element[player->index_nr];
6057 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6058 explosion_type = EX_TYPE_CROSS;
6059 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6060 explosion_type = EX_TYPE_CENTER;
6068 case EL_BD_BUTTERFLY:
6071 case EL_DARK_YAMYAM:
6075 RaiseScoreElement(element);
6078 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6079 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6080 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6081 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6082 case EL_DYNABOMB_INCREASE_NUMBER:
6083 case EL_DYNABOMB_INCREASE_SIZE:
6084 case EL_DYNABOMB_INCREASE_POWER:
6085 explosion_type = EX_TYPE_DYNA;
6088 case EL_DC_LANDMINE:
6089 explosion_type = EX_TYPE_CENTER;
6094 case EL_LAMP_ACTIVE:
6095 case EL_AMOEBA_TO_DIAMOND:
6096 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6097 explosion_type = EX_TYPE_CENTER;
6101 if (element_info[element].explosion_type == EXPLODES_CROSS)
6102 explosion_type = EX_TYPE_CROSS;
6103 else if (element_info[element].explosion_type == EXPLODES_1X1)
6104 explosion_type = EX_TYPE_CENTER;
6108 if (explosion_type == EX_TYPE_DYNA)
6111 Explode(x, y, EX_PHASE_START, explosion_type);
6113 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6116 static void SplashAcid(int x, int y)
6118 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6119 (!IN_LEV_FIELD(x - 1, y - 2) ||
6120 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6121 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6123 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6124 (!IN_LEV_FIELD(x + 1, y - 2) ||
6125 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6126 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6128 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6131 static void InitBeltMovement(void)
6133 static int belt_base_element[4] =
6135 EL_CONVEYOR_BELT_1_LEFT,
6136 EL_CONVEYOR_BELT_2_LEFT,
6137 EL_CONVEYOR_BELT_3_LEFT,
6138 EL_CONVEYOR_BELT_4_LEFT
6140 static int belt_base_active_element[4] =
6142 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6143 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6144 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6145 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6150 // set frame order for belt animation graphic according to belt direction
6151 for (i = 0; i < NUM_BELTS; i++)
6155 for (j = 0; j < NUM_BELT_PARTS; j++)
6157 int element = belt_base_active_element[belt_nr] + j;
6158 int graphic_1 = el2img(element);
6159 int graphic_2 = el2panelimg(element);
6161 if (game.belt_dir[i] == MV_LEFT)
6163 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6164 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6168 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6169 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6174 SCAN_PLAYFIELD(x, y)
6176 int element = Tile[x][y];
6178 for (i = 0; i < NUM_BELTS; i++)
6180 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6182 int e_belt_nr = getBeltNrFromBeltElement(element);
6185 if (e_belt_nr == belt_nr)
6187 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6189 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6196 static void ToggleBeltSwitch(int x, int y)
6198 static int belt_base_element[4] =
6200 EL_CONVEYOR_BELT_1_LEFT,
6201 EL_CONVEYOR_BELT_2_LEFT,
6202 EL_CONVEYOR_BELT_3_LEFT,
6203 EL_CONVEYOR_BELT_4_LEFT
6205 static int belt_base_active_element[4] =
6207 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6208 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6209 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6210 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6212 static int belt_base_switch_element[4] =
6214 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6215 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6216 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6217 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6219 static int belt_move_dir[4] =
6227 int element = Tile[x][y];
6228 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6229 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6230 int belt_dir = belt_move_dir[belt_dir_nr];
6233 if (!IS_BELT_SWITCH(element))
6236 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6237 game.belt_dir[belt_nr] = belt_dir;
6239 if (belt_dir_nr == 3)
6242 // set frame order for belt animation graphic according to belt direction
6243 for (i = 0; i < NUM_BELT_PARTS; i++)
6245 int element = belt_base_active_element[belt_nr] + i;
6246 int graphic_1 = el2img(element);
6247 int graphic_2 = el2panelimg(element);
6249 if (belt_dir == MV_LEFT)
6251 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6252 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6256 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6257 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6261 SCAN_PLAYFIELD(xx, yy)
6263 int element = Tile[xx][yy];
6265 if (IS_BELT_SWITCH(element))
6267 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6269 if (e_belt_nr == belt_nr)
6271 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6272 TEST_DrawLevelField(xx, yy);
6275 else if (IS_BELT(element) && belt_dir != MV_NONE)
6277 int e_belt_nr = getBeltNrFromBeltElement(element);
6279 if (e_belt_nr == belt_nr)
6281 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6283 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6284 TEST_DrawLevelField(xx, yy);
6287 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6289 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6291 if (e_belt_nr == belt_nr)
6293 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6295 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6296 TEST_DrawLevelField(xx, yy);
6302 static void ToggleSwitchgateSwitch(int x, int y)
6306 game.switchgate_pos = !game.switchgate_pos;
6308 SCAN_PLAYFIELD(xx, yy)
6310 int element = Tile[xx][yy];
6312 if (element == EL_SWITCHGATE_SWITCH_UP)
6314 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6315 TEST_DrawLevelField(xx, yy);
6317 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6319 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6320 TEST_DrawLevelField(xx, yy);
6322 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6324 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6325 TEST_DrawLevelField(xx, yy);
6327 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6329 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6330 TEST_DrawLevelField(xx, yy);
6332 else if (element == EL_SWITCHGATE_OPEN ||
6333 element == EL_SWITCHGATE_OPENING)
6335 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6337 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6339 else if (element == EL_SWITCHGATE_CLOSED ||
6340 element == EL_SWITCHGATE_CLOSING)
6342 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6344 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6349 static int getInvisibleActiveFromInvisibleElement(int element)
6351 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6352 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6353 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6357 static int getInvisibleFromInvisibleActiveElement(int element)
6359 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6360 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6361 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6365 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6369 SCAN_PLAYFIELD(x, y)
6371 int element = Tile[x][y];
6373 if (element == EL_LIGHT_SWITCH &&
6374 game.light_time_left > 0)
6376 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6377 TEST_DrawLevelField(x, y);
6379 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6380 game.light_time_left == 0)
6382 Tile[x][y] = EL_LIGHT_SWITCH;
6383 TEST_DrawLevelField(x, y);
6385 else if (element == EL_EMC_DRIPPER &&
6386 game.light_time_left > 0)
6388 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6389 TEST_DrawLevelField(x, y);
6391 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6392 game.light_time_left == 0)
6394 Tile[x][y] = EL_EMC_DRIPPER;
6395 TEST_DrawLevelField(x, y);
6397 else if (element == EL_INVISIBLE_STEELWALL ||
6398 element == EL_INVISIBLE_WALL ||
6399 element == EL_INVISIBLE_SAND)
6401 if (game.light_time_left > 0)
6402 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6404 TEST_DrawLevelField(x, y);
6406 // uncrumble neighbour fields, if needed
6407 if (element == EL_INVISIBLE_SAND)
6408 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6410 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6411 element == EL_INVISIBLE_WALL_ACTIVE ||
6412 element == EL_INVISIBLE_SAND_ACTIVE)
6414 if (game.light_time_left == 0)
6415 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6417 TEST_DrawLevelField(x, y);
6419 // re-crumble neighbour fields, if needed
6420 if (element == EL_INVISIBLE_SAND)
6421 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6426 static void RedrawAllInvisibleElementsForLenses(void)
6430 SCAN_PLAYFIELD(x, y)
6432 int element = Tile[x][y];
6434 if (element == EL_EMC_DRIPPER &&
6435 game.lenses_time_left > 0)
6437 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6438 TEST_DrawLevelField(x, y);
6440 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6441 game.lenses_time_left == 0)
6443 Tile[x][y] = EL_EMC_DRIPPER;
6444 TEST_DrawLevelField(x, y);
6446 else if (element == EL_INVISIBLE_STEELWALL ||
6447 element == EL_INVISIBLE_WALL ||
6448 element == EL_INVISIBLE_SAND)
6450 if (game.lenses_time_left > 0)
6451 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6453 TEST_DrawLevelField(x, y);
6455 // uncrumble neighbour fields, if needed
6456 if (element == EL_INVISIBLE_SAND)
6457 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6459 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6460 element == EL_INVISIBLE_WALL_ACTIVE ||
6461 element == EL_INVISIBLE_SAND_ACTIVE)
6463 if (game.lenses_time_left == 0)
6464 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6466 TEST_DrawLevelField(x, y);
6468 // re-crumble neighbour fields, if needed
6469 if (element == EL_INVISIBLE_SAND)
6470 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6475 static void RedrawAllInvisibleElementsForMagnifier(void)
6479 SCAN_PLAYFIELD(x, y)
6481 int element = Tile[x][y];
6483 if (element == EL_EMC_FAKE_GRASS &&
6484 game.magnify_time_left > 0)
6486 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6487 TEST_DrawLevelField(x, y);
6489 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6490 game.magnify_time_left == 0)
6492 Tile[x][y] = EL_EMC_FAKE_GRASS;
6493 TEST_DrawLevelField(x, y);
6495 else if (IS_GATE_GRAY(element) &&
6496 game.magnify_time_left > 0)
6498 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6499 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6500 IS_EM_GATE_GRAY(element) ?
6501 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6502 IS_EMC_GATE_GRAY(element) ?
6503 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6504 IS_DC_GATE_GRAY(element) ?
6505 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6507 TEST_DrawLevelField(x, y);
6509 else if (IS_GATE_GRAY_ACTIVE(element) &&
6510 game.magnify_time_left == 0)
6512 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6513 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6514 IS_EM_GATE_GRAY_ACTIVE(element) ?
6515 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6516 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6517 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6518 IS_DC_GATE_GRAY_ACTIVE(element) ?
6519 EL_DC_GATE_WHITE_GRAY :
6521 TEST_DrawLevelField(x, y);
6526 static void ToggleLightSwitch(int x, int y)
6528 int element = Tile[x][y];
6530 game.light_time_left =
6531 (element == EL_LIGHT_SWITCH ?
6532 level.time_light * FRAMES_PER_SECOND : 0);
6534 RedrawAllLightSwitchesAndInvisibleElements();
6537 static void ActivateTimegateSwitch(int x, int y)
6541 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6543 SCAN_PLAYFIELD(xx, yy)
6545 int element = Tile[xx][yy];
6547 if (element == EL_TIMEGATE_CLOSED ||
6548 element == EL_TIMEGATE_CLOSING)
6550 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6551 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6555 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6557 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6558 TEST_DrawLevelField(xx, yy);
6564 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6565 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6568 static void Impact(int x, int y)
6570 boolean last_line = (y == lev_fieldy - 1);
6571 boolean object_hit = FALSE;
6572 boolean impact = (last_line || object_hit);
6573 int element = Tile[x][y];
6574 int smashed = EL_STEELWALL;
6576 if (!last_line) // check if element below was hit
6578 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6581 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6582 MovDir[x][y + 1] != MV_DOWN ||
6583 MovPos[x][y + 1] <= TILEY / 2));
6585 // do not smash moving elements that left the smashed field in time
6586 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6587 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6590 #if USE_QUICKSAND_IMPACT_BUGFIX
6591 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6593 RemoveMovingField(x, y + 1);
6594 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6595 Tile[x][y + 2] = EL_ROCK;
6596 TEST_DrawLevelField(x, y + 2);
6601 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6603 RemoveMovingField(x, y + 1);
6604 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6605 Tile[x][y + 2] = EL_ROCK;
6606 TEST_DrawLevelField(x, y + 2);
6613 smashed = MovingOrBlocked2Element(x, y + 1);
6615 impact = (last_line || object_hit);
6618 if (!last_line && smashed == EL_ACID) // element falls into acid
6620 SplashAcid(x, y + 1);
6624 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6625 // only reset graphic animation if graphic really changes after impact
6627 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6629 ResetGfxAnimation(x, y);
6630 TEST_DrawLevelField(x, y);
6633 if (impact && CAN_EXPLODE_IMPACT(element))
6638 else if (impact && element == EL_PEARL &&
6639 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6641 ResetGfxAnimation(x, y);
6643 Tile[x][y] = EL_PEARL_BREAKING;
6644 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6647 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6649 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6654 if (impact && element == EL_AMOEBA_DROP)
6656 if (object_hit && IS_PLAYER(x, y + 1))
6657 KillPlayerUnlessEnemyProtected(x, y + 1);
6658 else if (object_hit && smashed == EL_PENGUIN)
6662 Tile[x][y] = EL_AMOEBA_GROWING;
6663 Store[x][y] = EL_AMOEBA_WET;
6665 ResetRandomAnimationValue(x, y);
6670 if (object_hit) // check which object was hit
6672 if ((CAN_PASS_MAGIC_WALL(element) &&
6673 (smashed == EL_MAGIC_WALL ||
6674 smashed == EL_BD_MAGIC_WALL)) ||
6675 (CAN_PASS_DC_MAGIC_WALL(element) &&
6676 smashed == EL_DC_MAGIC_WALL))
6679 int activated_magic_wall =
6680 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6681 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6682 EL_DC_MAGIC_WALL_ACTIVE);
6684 // activate magic wall / mill
6685 SCAN_PLAYFIELD(xx, yy)
6687 if (Tile[xx][yy] == smashed)
6688 Tile[xx][yy] = activated_magic_wall;
6691 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6692 game.magic_wall_active = TRUE;
6694 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6695 SND_MAGIC_WALL_ACTIVATING :
6696 smashed == EL_BD_MAGIC_WALL ?
6697 SND_BD_MAGIC_WALL_ACTIVATING :
6698 SND_DC_MAGIC_WALL_ACTIVATING));
6701 if (IS_PLAYER(x, y + 1))
6703 if (CAN_SMASH_PLAYER(element))
6705 KillPlayerUnlessEnemyProtected(x, y + 1);
6709 else if (smashed == EL_PENGUIN)
6711 if (CAN_SMASH_PLAYER(element))
6717 else if (element == EL_BD_DIAMOND)
6719 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6725 else if (((element == EL_SP_INFOTRON ||
6726 element == EL_SP_ZONK) &&
6727 (smashed == EL_SP_SNIKSNAK ||
6728 smashed == EL_SP_ELECTRON ||
6729 smashed == EL_SP_DISK_ORANGE)) ||
6730 (element == EL_SP_INFOTRON &&
6731 smashed == EL_SP_DISK_YELLOW))
6736 else if (CAN_SMASH_EVERYTHING(element))
6738 if (IS_CLASSIC_ENEMY(smashed) ||
6739 CAN_EXPLODE_SMASHED(smashed))
6744 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6746 if (smashed == EL_LAMP ||
6747 smashed == EL_LAMP_ACTIVE)
6752 else if (smashed == EL_NUT)
6754 Tile[x][y + 1] = EL_NUT_BREAKING;
6755 PlayLevelSound(x, y, SND_NUT_BREAKING);
6756 RaiseScoreElement(EL_NUT);
6759 else if (smashed == EL_PEARL)
6761 ResetGfxAnimation(x, y);
6763 Tile[x][y + 1] = EL_PEARL_BREAKING;
6764 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6767 else if (smashed == EL_DIAMOND)
6769 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6770 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6773 else if (IS_BELT_SWITCH(smashed))
6775 ToggleBeltSwitch(x, y + 1);
6777 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6778 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6779 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6780 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6782 ToggleSwitchgateSwitch(x, y + 1);
6784 else if (smashed == EL_LIGHT_SWITCH ||
6785 smashed == EL_LIGHT_SWITCH_ACTIVE)
6787 ToggleLightSwitch(x, y + 1);
6791 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6793 CheckElementChangeBySide(x, y + 1, smashed, element,
6794 CE_SWITCHED, CH_SIDE_TOP);
6795 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6801 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6806 // play sound of magic wall / mill
6808 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6809 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6810 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6812 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6813 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6814 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6815 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6816 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6817 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6822 // play sound of object that hits the ground
6823 if (last_line || object_hit)
6824 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6827 static void TurnRoundExt(int x, int y)
6839 { 0, 0 }, { 0, 0 }, { 0, 0 },
6844 int left, right, back;
6848 { MV_DOWN, MV_UP, MV_RIGHT },
6849 { MV_UP, MV_DOWN, MV_LEFT },
6851 { MV_LEFT, MV_RIGHT, MV_DOWN },
6855 { MV_RIGHT, MV_LEFT, MV_UP }
6858 int element = Tile[x][y];
6859 int move_pattern = element_info[element].move_pattern;
6861 int old_move_dir = MovDir[x][y];
6862 int left_dir = turn[old_move_dir].left;
6863 int right_dir = turn[old_move_dir].right;
6864 int back_dir = turn[old_move_dir].back;
6866 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6867 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6868 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6869 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6871 int left_x = x + left_dx, left_y = y + left_dy;
6872 int right_x = x + right_dx, right_y = y + right_dy;
6873 int move_x = x + move_dx, move_y = y + move_dy;
6877 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6879 TestIfBadThingTouchesOtherBadThing(x, y);
6881 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6882 MovDir[x][y] = right_dir;
6883 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6884 MovDir[x][y] = left_dir;
6886 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6888 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6891 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6893 TestIfBadThingTouchesOtherBadThing(x, y);
6895 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6896 MovDir[x][y] = left_dir;
6897 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6898 MovDir[x][y] = right_dir;
6900 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6902 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6905 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6907 TestIfBadThingTouchesOtherBadThing(x, y);
6909 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6910 MovDir[x][y] = left_dir;
6911 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6912 MovDir[x][y] = right_dir;
6914 if (MovDir[x][y] != old_move_dir)
6917 else if (element == EL_YAMYAM)
6919 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6920 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6922 if (can_turn_left && can_turn_right)
6923 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6924 else if (can_turn_left)
6925 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6926 else if (can_turn_right)
6927 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6929 MovDir[x][y] = back_dir;
6931 MovDelay[x][y] = 16 + 16 * RND(3);
6933 else if (element == EL_DARK_YAMYAM)
6935 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6937 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6940 if (can_turn_left && can_turn_right)
6941 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6942 else if (can_turn_left)
6943 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6944 else if (can_turn_right)
6945 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6947 MovDir[x][y] = back_dir;
6949 MovDelay[x][y] = 16 + 16 * RND(3);
6951 else if (element == EL_PACMAN)
6953 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6954 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6956 if (can_turn_left && can_turn_right)
6957 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6958 else if (can_turn_left)
6959 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6960 else if (can_turn_right)
6961 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6963 MovDir[x][y] = back_dir;
6965 MovDelay[x][y] = 6 + RND(40);
6967 else if (element == EL_PIG)
6969 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
6970 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
6971 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
6972 boolean should_turn_left, should_turn_right, should_move_on;
6974 int rnd = RND(rnd_value);
6976 should_turn_left = (can_turn_left &&
6978 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
6979 y + back_dy + left_dy)));
6980 should_turn_right = (can_turn_right &&
6982 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
6983 y + back_dy + right_dy)));
6984 should_move_on = (can_move_on &&
6987 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
6988 y + move_dy + left_dy) ||
6989 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
6990 y + move_dy + right_dy)));
6992 if (should_turn_left || should_turn_right || should_move_on)
6994 if (should_turn_left && should_turn_right && should_move_on)
6995 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
6996 rnd < 2 * rnd_value / 3 ? right_dir :
6998 else if (should_turn_left && should_turn_right)
6999 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7000 else if (should_turn_left && should_move_on)
7001 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7002 else if (should_turn_right && should_move_on)
7003 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7004 else if (should_turn_left)
7005 MovDir[x][y] = left_dir;
7006 else if (should_turn_right)
7007 MovDir[x][y] = right_dir;
7008 else if (should_move_on)
7009 MovDir[x][y] = old_move_dir;
7011 else if (can_move_on && rnd > rnd_value / 8)
7012 MovDir[x][y] = old_move_dir;
7013 else if (can_turn_left && can_turn_right)
7014 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7015 else if (can_turn_left && rnd > rnd_value / 8)
7016 MovDir[x][y] = left_dir;
7017 else if (can_turn_right && rnd > rnd_value/8)
7018 MovDir[x][y] = right_dir;
7020 MovDir[x][y] = back_dir;
7022 xx = x + move_xy[MovDir[x][y]].dx;
7023 yy = y + move_xy[MovDir[x][y]].dy;
7025 if (!IN_LEV_FIELD(xx, yy) ||
7026 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7027 MovDir[x][y] = old_move_dir;
7031 else if (element == EL_DRAGON)
7033 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7034 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7035 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7037 int rnd = RND(rnd_value);
7039 if (can_move_on && rnd > rnd_value / 8)
7040 MovDir[x][y] = old_move_dir;
7041 else if (can_turn_left && can_turn_right)
7042 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7043 else if (can_turn_left && rnd > rnd_value / 8)
7044 MovDir[x][y] = left_dir;
7045 else if (can_turn_right && rnd > rnd_value / 8)
7046 MovDir[x][y] = right_dir;
7048 MovDir[x][y] = back_dir;
7050 xx = x + move_xy[MovDir[x][y]].dx;
7051 yy = y + move_xy[MovDir[x][y]].dy;
7053 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7054 MovDir[x][y] = old_move_dir;
7058 else if (element == EL_MOLE)
7060 boolean can_move_on =
7061 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7062 IS_AMOEBOID(Tile[move_x][move_y]) ||
7063 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7066 boolean can_turn_left =
7067 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7068 IS_AMOEBOID(Tile[left_x][left_y])));
7070 boolean can_turn_right =
7071 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7072 IS_AMOEBOID(Tile[right_x][right_y])));
7074 if (can_turn_left && can_turn_right)
7075 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7076 else if (can_turn_left)
7077 MovDir[x][y] = left_dir;
7079 MovDir[x][y] = right_dir;
7082 if (MovDir[x][y] != old_move_dir)
7085 else if (element == EL_BALLOON)
7087 MovDir[x][y] = game.wind_direction;
7090 else if (element == EL_SPRING)
7092 if (MovDir[x][y] & MV_HORIZONTAL)
7094 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7095 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7097 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7098 ResetGfxAnimation(move_x, move_y);
7099 TEST_DrawLevelField(move_x, move_y);
7101 MovDir[x][y] = back_dir;
7103 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7104 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7105 MovDir[x][y] = MV_NONE;
7110 else if (element == EL_ROBOT ||
7111 element == EL_SATELLITE ||
7112 element == EL_PENGUIN ||
7113 element == EL_EMC_ANDROID)
7115 int attr_x = -1, attr_y = -1;
7117 if (game.all_players_gone)
7119 attr_x = game.exit_x;
7120 attr_y = game.exit_y;
7126 for (i = 0; i < MAX_PLAYERS; i++)
7128 struct PlayerInfo *player = &stored_player[i];
7129 int jx = player->jx, jy = player->jy;
7131 if (!player->active)
7135 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7143 if (element == EL_ROBOT &&
7144 game.robot_wheel_x >= 0 &&
7145 game.robot_wheel_y >= 0 &&
7146 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7147 game.engine_version < VERSION_IDENT(3,1,0,0)))
7149 attr_x = game.robot_wheel_x;
7150 attr_y = game.robot_wheel_y;
7153 if (element == EL_PENGUIN)
7156 static int xy[4][2] =
7164 for (i = 0; i < NUM_DIRECTIONS; i++)
7166 int ex = x + xy[i][0];
7167 int ey = y + xy[i][1];
7169 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7170 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7171 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7172 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7181 MovDir[x][y] = MV_NONE;
7183 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7184 else if (attr_x > x)
7185 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7187 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7188 else if (attr_y > y)
7189 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7191 if (element == EL_ROBOT)
7195 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7196 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7197 Moving2Blocked(x, y, &newx, &newy);
7199 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7200 MovDelay[x][y] = 8 + 8 * !RND(3);
7202 MovDelay[x][y] = 16;
7204 else if (element == EL_PENGUIN)
7210 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7212 boolean first_horiz = RND(2);
7213 int new_move_dir = MovDir[x][y];
7216 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7217 Moving2Blocked(x, y, &newx, &newy);
7219 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7223 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7224 Moving2Blocked(x, y, &newx, &newy);
7226 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7229 MovDir[x][y] = old_move_dir;
7233 else if (element == EL_SATELLITE)
7239 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7241 boolean first_horiz = RND(2);
7242 int new_move_dir = MovDir[x][y];
7245 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7246 Moving2Blocked(x, y, &newx, &newy);
7248 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7252 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7253 Moving2Blocked(x, y, &newx, &newy);
7255 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7258 MovDir[x][y] = old_move_dir;
7262 else if (element == EL_EMC_ANDROID)
7264 static int check_pos[16] =
7266 -1, // 0 => (invalid)
7269 -1, // 3 => (invalid)
7271 0, // 5 => MV_LEFT | MV_UP
7272 2, // 6 => MV_RIGHT | MV_UP
7273 -1, // 7 => (invalid)
7275 6, // 9 => MV_LEFT | MV_DOWN
7276 4, // 10 => MV_RIGHT | MV_DOWN
7277 -1, // 11 => (invalid)
7278 -1, // 12 => (invalid)
7279 -1, // 13 => (invalid)
7280 -1, // 14 => (invalid)
7281 -1, // 15 => (invalid)
7289 { -1, -1, MV_LEFT | MV_UP },
7291 { +1, -1, MV_RIGHT | MV_UP },
7292 { +1, 0, MV_RIGHT },
7293 { +1, +1, MV_RIGHT | MV_DOWN },
7295 { -1, +1, MV_LEFT | MV_DOWN },
7298 int start_pos, check_order;
7299 boolean can_clone = FALSE;
7302 // check if there is any free field around current position
7303 for (i = 0; i < 8; i++)
7305 int newx = x + check_xy[i].dx;
7306 int newy = y + check_xy[i].dy;
7308 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7316 if (can_clone) // randomly find an element to clone
7320 start_pos = check_pos[RND(8)];
7321 check_order = (RND(2) ? -1 : +1);
7323 for (i = 0; i < 8; i++)
7325 int pos_raw = start_pos + i * check_order;
7326 int pos = (pos_raw + 8) % 8;
7327 int newx = x + check_xy[pos].dx;
7328 int newy = y + check_xy[pos].dy;
7330 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7332 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7333 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7335 Store[x][y] = Tile[newx][newy];
7344 if (can_clone) // randomly find a direction to move
7348 start_pos = check_pos[RND(8)];
7349 check_order = (RND(2) ? -1 : +1);
7351 for (i = 0; i < 8; i++)
7353 int pos_raw = start_pos + i * check_order;
7354 int pos = (pos_raw + 8) % 8;
7355 int newx = x + check_xy[pos].dx;
7356 int newy = y + check_xy[pos].dy;
7357 int new_move_dir = check_xy[pos].dir;
7359 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7361 MovDir[x][y] = new_move_dir;
7362 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7371 if (can_clone) // cloning and moving successful
7374 // cannot clone -- try to move towards player
7376 start_pos = check_pos[MovDir[x][y] & 0x0f];
7377 check_order = (RND(2) ? -1 : +1);
7379 for (i = 0; i < 3; i++)
7381 // first check start_pos, then previous/next or (next/previous) pos
7382 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7383 int pos = (pos_raw + 8) % 8;
7384 int newx = x + check_xy[pos].dx;
7385 int newy = y + check_xy[pos].dy;
7386 int new_move_dir = check_xy[pos].dir;
7388 if (IS_PLAYER(newx, newy))
7391 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7393 MovDir[x][y] = new_move_dir;
7394 MovDelay[x][y] = level.android_move_time * 8 + 1;
7401 else if (move_pattern == MV_TURNING_LEFT ||
7402 move_pattern == MV_TURNING_RIGHT ||
7403 move_pattern == MV_TURNING_LEFT_RIGHT ||
7404 move_pattern == MV_TURNING_RIGHT_LEFT ||
7405 move_pattern == MV_TURNING_RANDOM ||
7406 move_pattern == MV_ALL_DIRECTIONS)
7408 boolean can_turn_left =
7409 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7410 boolean can_turn_right =
7411 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7413 if (element_info[element].move_stepsize == 0) // "not moving"
7416 if (move_pattern == MV_TURNING_LEFT)
7417 MovDir[x][y] = left_dir;
7418 else if (move_pattern == MV_TURNING_RIGHT)
7419 MovDir[x][y] = right_dir;
7420 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7421 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7422 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7423 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7424 else if (move_pattern == MV_TURNING_RANDOM)
7425 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7426 can_turn_right && !can_turn_left ? right_dir :
7427 RND(2) ? left_dir : right_dir);
7428 else if (can_turn_left && can_turn_right)
7429 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7430 else if (can_turn_left)
7431 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7432 else if (can_turn_right)
7433 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7435 MovDir[x][y] = back_dir;
7437 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7439 else if (move_pattern == MV_HORIZONTAL ||
7440 move_pattern == MV_VERTICAL)
7442 if (move_pattern & old_move_dir)
7443 MovDir[x][y] = back_dir;
7444 else if (move_pattern == MV_HORIZONTAL)
7445 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7446 else if (move_pattern == MV_VERTICAL)
7447 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7449 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7451 else if (move_pattern & MV_ANY_DIRECTION)
7453 MovDir[x][y] = move_pattern;
7454 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7456 else if (move_pattern & MV_WIND_DIRECTION)
7458 MovDir[x][y] = game.wind_direction;
7459 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7461 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7463 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7464 MovDir[x][y] = left_dir;
7465 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7466 MovDir[x][y] = right_dir;
7468 if (MovDir[x][y] != old_move_dir)
7469 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7471 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7473 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7474 MovDir[x][y] = right_dir;
7475 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7476 MovDir[x][y] = left_dir;
7478 if (MovDir[x][y] != old_move_dir)
7479 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7481 else if (move_pattern == MV_TOWARDS_PLAYER ||
7482 move_pattern == MV_AWAY_FROM_PLAYER)
7484 int attr_x = -1, attr_y = -1;
7486 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7488 if (game.all_players_gone)
7490 attr_x = game.exit_x;
7491 attr_y = game.exit_y;
7497 for (i = 0; i < MAX_PLAYERS; i++)
7499 struct PlayerInfo *player = &stored_player[i];
7500 int jx = player->jx, jy = player->jy;
7502 if (!player->active)
7506 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7514 MovDir[x][y] = MV_NONE;
7516 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7517 else if (attr_x > x)
7518 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7520 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7521 else if (attr_y > y)
7522 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7524 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7526 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7528 boolean first_horiz = RND(2);
7529 int new_move_dir = MovDir[x][y];
7531 if (element_info[element].move_stepsize == 0) // "not moving"
7533 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7534 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7540 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7541 Moving2Blocked(x, y, &newx, &newy);
7543 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7547 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7548 Moving2Blocked(x, y, &newx, &newy);
7550 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7553 MovDir[x][y] = old_move_dir;
7556 else if (move_pattern == MV_WHEN_PUSHED ||
7557 move_pattern == MV_WHEN_DROPPED)
7559 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7560 MovDir[x][y] = MV_NONE;
7564 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7566 static int test_xy[7][2] =
7576 static int test_dir[7] =
7586 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7587 int move_preference = -1000000; // start with very low preference
7588 int new_move_dir = MV_NONE;
7589 int start_test = RND(4);
7592 for (i = 0; i < NUM_DIRECTIONS; i++)
7594 int move_dir = test_dir[start_test + i];
7595 int move_dir_preference;
7597 xx = x + test_xy[start_test + i][0];
7598 yy = y + test_xy[start_test + i][1];
7600 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7601 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7603 new_move_dir = move_dir;
7608 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7611 move_dir_preference = -1 * RunnerVisit[xx][yy];
7612 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7613 move_dir_preference = PlayerVisit[xx][yy];
7615 if (move_dir_preference > move_preference)
7617 // prefer field that has not been visited for the longest time
7618 move_preference = move_dir_preference;
7619 new_move_dir = move_dir;
7621 else if (move_dir_preference == move_preference &&
7622 move_dir == old_move_dir)
7624 // prefer last direction when all directions are preferred equally
7625 move_preference = move_dir_preference;
7626 new_move_dir = move_dir;
7630 MovDir[x][y] = new_move_dir;
7631 if (old_move_dir != new_move_dir)
7632 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7636 static void TurnRound(int x, int y)
7638 int direction = MovDir[x][y];
7642 GfxDir[x][y] = MovDir[x][y];
7644 if (direction != MovDir[x][y])
7648 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7650 ResetGfxFrame(x, y);
7653 static boolean JustBeingPushed(int x, int y)
7657 for (i = 0; i < MAX_PLAYERS; i++)
7659 struct PlayerInfo *player = &stored_player[i];
7661 if (player->active && player->is_pushing && player->MovPos)
7663 int next_jx = player->jx + (player->jx - player->last_jx);
7664 int next_jy = player->jy + (player->jy - player->last_jy);
7666 if (x == next_jx && y == next_jy)
7674 static void StartMoving(int x, int y)
7676 boolean started_moving = FALSE; // some elements can fall _and_ move
7677 int element = Tile[x][y];
7682 if (MovDelay[x][y] == 0)
7683 GfxAction[x][y] = ACTION_DEFAULT;
7685 if (CAN_FALL(element) && y < lev_fieldy - 1)
7687 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7688 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7689 if (JustBeingPushed(x, y))
7692 if (element == EL_QUICKSAND_FULL)
7694 if (IS_FREE(x, y + 1))
7696 InitMovingField(x, y, MV_DOWN);
7697 started_moving = TRUE;
7699 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7700 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7701 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7702 Store[x][y] = EL_ROCK;
7704 Store[x][y] = EL_ROCK;
7707 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7709 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7711 if (!MovDelay[x][y])
7713 MovDelay[x][y] = TILEY + 1;
7715 ResetGfxAnimation(x, y);
7716 ResetGfxAnimation(x, y + 1);
7721 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7722 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7729 Tile[x][y] = EL_QUICKSAND_EMPTY;
7730 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7731 Store[x][y + 1] = Store[x][y];
7734 PlayLevelSoundAction(x, y, ACTION_FILLING);
7736 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7738 if (!MovDelay[x][y])
7740 MovDelay[x][y] = TILEY + 1;
7742 ResetGfxAnimation(x, y);
7743 ResetGfxAnimation(x, y + 1);
7748 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7749 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7756 Tile[x][y] = EL_QUICKSAND_EMPTY;
7757 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7758 Store[x][y + 1] = Store[x][y];
7761 PlayLevelSoundAction(x, y, ACTION_FILLING);
7764 else if (element == EL_QUICKSAND_FAST_FULL)
7766 if (IS_FREE(x, y + 1))
7768 InitMovingField(x, y, MV_DOWN);
7769 started_moving = TRUE;
7771 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7772 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7773 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7774 Store[x][y] = EL_ROCK;
7776 Store[x][y] = EL_ROCK;
7779 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7781 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7783 if (!MovDelay[x][y])
7785 MovDelay[x][y] = TILEY + 1;
7787 ResetGfxAnimation(x, y);
7788 ResetGfxAnimation(x, y + 1);
7793 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7794 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7801 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7802 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7803 Store[x][y + 1] = Store[x][y];
7806 PlayLevelSoundAction(x, y, ACTION_FILLING);
7808 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7810 if (!MovDelay[x][y])
7812 MovDelay[x][y] = TILEY + 1;
7814 ResetGfxAnimation(x, y);
7815 ResetGfxAnimation(x, y + 1);
7820 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7821 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7828 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7829 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7830 Store[x][y + 1] = Store[x][y];
7833 PlayLevelSoundAction(x, y, ACTION_FILLING);
7836 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7837 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7839 InitMovingField(x, y, MV_DOWN);
7840 started_moving = TRUE;
7842 Tile[x][y] = EL_QUICKSAND_FILLING;
7843 Store[x][y] = element;
7845 PlayLevelSoundAction(x, y, ACTION_FILLING);
7847 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7848 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7850 InitMovingField(x, y, MV_DOWN);
7851 started_moving = TRUE;
7853 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7854 Store[x][y] = element;
7856 PlayLevelSoundAction(x, y, ACTION_FILLING);
7858 else if (element == EL_MAGIC_WALL_FULL)
7860 if (IS_FREE(x, y + 1))
7862 InitMovingField(x, y, MV_DOWN);
7863 started_moving = TRUE;
7865 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7866 Store[x][y] = EL_CHANGED(Store[x][y]);
7868 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7870 if (!MovDelay[x][y])
7871 MovDelay[x][y] = TILEY / 4 + 1;
7880 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7881 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7882 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7886 else if (element == EL_BD_MAGIC_WALL_FULL)
7888 if (IS_FREE(x, y + 1))
7890 InitMovingField(x, y, MV_DOWN);
7891 started_moving = TRUE;
7893 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7894 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7896 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7898 if (!MovDelay[x][y])
7899 MovDelay[x][y] = TILEY / 4 + 1;
7908 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7909 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7910 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7914 else if (element == EL_DC_MAGIC_WALL_FULL)
7916 if (IS_FREE(x, y + 1))
7918 InitMovingField(x, y, MV_DOWN);
7919 started_moving = TRUE;
7921 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7922 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7924 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7926 if (!MovDelay[x][y])
7927 MovDelay[x][y] = TILEY / 4 + 1;
7936 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7937 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7938 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7942 else if ((CAN_PASS_MAGIC_WALL(element) &&
7943 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7944 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7945 (CAN_PASS_DC_MAGIC_WALL(element) &&
7946 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7949 InitMovingField(x, y, MV_DOWN);
7950 started_moving = TRUE;
7953 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7954 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7955 EL_DC_MAGIC_WALL_FILLING);
7956 Store[x][y] = element;
7958 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7960 SplashAcid(x, y + 1);
7962 InitMovingField(x, y, MV_DOWN);
7963 started_moving = TRUE;
7965 Store[x][y] = EL_ACID;
7968 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
7969 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
7970 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
7971 CAN_FALL(element) && WasJustFalling[x][y] &&
7972 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
7974 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
7975 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
7976 (Tile[x][y + 1] == EL_BLOCKED)))
7978 /* this is needed for a special case not covered by calling "Impact()"
7979 from "ContinueMoving()": if an element moves to a tile directly below
7980 another element which was just falling on that tile (which was empty
7981 in the previous frame), the falling element above would just stop
7982 instead of smashing the element below (in previous version, the above
7983 element was just checked for "moving" instead of "falling", resulting
7984 in incorrect smashes caused by horizontal movement of the above
7985 element; also, the case of the player being the element to smash was
7986 simply not covered here... :-/ ) */
7988 CheckCollision[x][y] = 0;
7989 CheckImpact[x][y] = 0;
7993 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
7995 if (MovDir[x][y] == MV_NONE)
7997 InitMovingField(x, y, MV_DOWN);
7998 started_moving = TRUE;
8001 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8003 if (WasJustFalling[x][y]) // prevent animation from being restarted
8004 MovDir[x][y] = MV_DOWN;
8006 InitMovingField(x, y, MV_DOWN);
8007 started_moving = TRUE;
8009 else if (element == EL_AMOEBA_DROP)
8011 Tile[x][y] = EL_AMOEBA_GROWING;
8012 Store[x][y] = EL_AMOEBA_WET;
8014 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8015 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8016 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8017 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8019 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8020 (IS_FREE(x - 1, y + 1) ||
8021 Tile[x - 1][y + 1] == EL_ACID));
8022 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8023 (IS_FREE(x + 1, y + 1) ||
8024 Tile[x + 1][y + 1] == EL_ACID));
8025 boolean can_fall_any = (can_fall_left || can_fall_right);
8026 boolean can_fall_both = (can_fall_left && can_fall_right);
8027 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8029 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8031 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8032 can_fall_right = FALSE;
8033 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8034 can_fall_left = FALSE;
8035 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8036 can_fall_right = FALSE;
8037 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8038 can_fall_left = FALSE;
8040 can_fall_any = (can_fall_left || can_fall_right);
8041 can_fall_both = FALSE;
8046 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8047 can_fall_right = FALSE; // slip down on left side
8049 can_fall_left = !(can_fall_right = RND(2));
8051 can_fall_both = FALSE;
8056 // if not determined otherwise, prefer left side for slipping down
8057 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8058 started_moving = TRUE;
8061 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8063 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8064 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8065 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8066 int belt_dir = game.belt_dir[belt_nr];
8068 if ((belt_dir == MV_LEFT && left_is_free) ||
8069 (belt_dir == MV_RIGHT && right_is_free))
8071 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8073 InitMovingField(x, y, belt_dir);
8074 started_moving = TRUE;
8076 Pushed[x][y] = TRUE;
8077 Pushed[nextx][y] = TRUE;
8079 GfxAction[x][y] = ACTION_DEFAULT;
8083 MovDir[x][y] = 0; // if element was moving, stop it
8088 // not "else if" because of elements that can fall and move (EL_SPRING)
8089 if (CAN_MOVE(element) && !started_moving)
8091 int move_pattern = element_info[element].move_pattern;
8094 Moving2Blocked(x, y, &newx, &newy);
8096 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8099 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8100 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8102 WasJustMoving[x][y] = 0;
8103 CheckCollision[x][y] = 0;
8105 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8107 if (Tile[x][y] != element) // element has changed
8111 if (!MovDelay[x][y]) // start new movement phase
8113 // all objects that can change their move direction after each step
8114 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8116 if (element != EL_YAMYAM &&
8117 element != EL_DARK_YAMYAM &&
8118 element != EL_PACMAN &&
8119 !(move_pattern & MV_ANY_DIRECTION) &&
8120 move_pattern != MV_TURNING_LEFT &&
8121 move_pattern != MV_TURNING_RIGHT &&
8122 move_pattern != MV_TURNING_LEFT_RIGHT &&
8123 move_pattern != MV_TURNING_RIGHT_LEFT &&
8124 move_pattern != MV_TURNING_RANDOM)
8128 if (MovDelay[x][y] && (element == EL_BUG ||
8129 element == EL_SPACESHIP ||
8130 element == EL_SP_SNIKSNAK ||
8131 element == EL_SP_ELECTRON ||
8132 element == EL_MOLE))
8133 TEST_DrawLevelField(x, y);
8137 if (MovDelay[x][y]) // wait some time before next movement
8141 if (element == EL_ROBOT ||
8142 element == EL_YAMYAM ||
8143 element == EL_DARK_YAMYAM)
8145 DrawLevelElementAnimationIfNeeded(x, y, element);
8146 PlayLevelSoundAction(x, y, ACTION_WAITING);
8148 else if (element == EL_SP_ELECTRON)
8149 DrawLevelElementAnimationIfNeeded(x, y, element);
8150 else if (element == EL_DRAGON)
8153 int dir = MovDir[x][y];
8154 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8155 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8156 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8157 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8158 dir == MV_UP ? IMG_FLAMES_1_UP :
8159 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8160 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8162 GfxAction[x][y] = ACTION_ATTACKING;
8164 if (IS_PLAYER(x, y))
8165 DrawPlayerField(x, y);
8167 TEST_DrawLevelField(x, y);
8169 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8171 for (i = 1; i <= 3; i++)
8173 int xx = x + i * dx;
8174 int yy = y + i * dy;
8175 int sx = SCREENX(xx);
8176 int sy = SCREENY(yy);
8177 int flame_graphic = graphic + (i - 1);
8179 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8184 int flamed = MovingOrBlocked2Element(xx, yy);
8186 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8189 RemoveMovingField(xx, yy);
8191 ChangeDelay[xx][yy] = 0;
8193 Tile[xx][yy] = EL_FLAMES;
8195 if (IN_SCR_FIELD(sx, sy))
8197 TEST_DrawLevelFieldCrumbled(xx, yy);
8198 DrawGraphic(sx, sy, flame_graphic, frame);
8203 if (Tile[xx][yy] == EL_FLAMES)
8204 Tile[xx][yy] = EL_EMPTY;
8205 TEST_DrawLevelField(xx, yy);
8210 if (MovDelay[x][y]) // element still has to wait some time
8212 PlayLevelSoundAction(x, y, ACTION_WAITING);
8218 // now make next step
8220 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8222 if (DONT_COLLIDE_WITH(element) &&
8223 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8224 !PLAYER_ENEMY_PROTECTED(newx, newy))
8226 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8231 else if (CAN_MOVE_INTO_ACID(element) &&
8232 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8233 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8234 (MovDir[x][y] == MV_DOWN ||
8235 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8237 SplashAcid(newx, newy);
8238 Store[x][y] = EL_ACID;
8240 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8242 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8243 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8244 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8245 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8248 TEST_DrawLevelField(x, y);
8250 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8251 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8252 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8254 game.friends_still_needed--;
8255 if (!game.friends_still_needed &&
8257 game.all_players_gone)
8262 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8264 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8265 TEST_DrawLevelField(newx, newy);
8267 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8269 else if (!IS_FREE(newx, newy))
8271 GfxAction[x][y] = ACTION_WAITING;
8273 if (IS_PLAYER(x, y))
8274 DrawPlayerField(x, y);
8276 TEST_DrawLevelField(x, y);
8281 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8283 if (IS_FOOD_PIG(Tile[newx][newy]))
8285 if (IS_MOVING(newx, newy))
8286 RemoveMovingField(newx, newy);
8289 Tile[newx][newy] = EL_EMPTY;
8290 TEST_DrawLevelField(newx, newy);
8293 PlayLevelSound(x, y, SND_PIG_DIGGING);
8295 else if (!IS_FREE(newx, newy))
8297 if (IS_PLAYER(x, y))
8298 DrawPlayerField(x, y);
8300 TEST_DrawLevelField(x, y);
8305 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8307 if (Store[x][y] != EL_EMPTY)
8309 boolean can_clone = FALSE;
8312 // check if element to clone is still there
8313 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8315 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8323 // cannot clone or target field not free anymore -- do not clone
8324 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8325 Store[x][y] = EL_EMPTY;
8328 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8330 if (IS_MV_DIAGONAL(MovDir[x][y]))
8332 int diagonal_move_dir = MovDir[x][y];
8333 int stored = Store[x][y];
8334 int change_delay = 8;
8337 // android is moving diagonally
8339 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8341 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8342 GfxElement[x][y] = EL_EMC_ANDROID;
8343 GfxAction[x][y] = ACTION_SHRINKING;
8344 GfxDir[x][y] = diagonal_move_dir;
8345 ChangeDelay[x][y] = change_delay;
8347 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8350 DrawLevelGraphicAnimation(x, y, graphic);
8351 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8353 if (Tile[newx][newy] == EL_ACID)
8355 SplashAcid(newx, newy);
8360 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8362 Store[newx][newy] = EL_EMC_ANDROID;
8363 GfxElement[newx][newy] = EL_EMC_ANDROID;
8364 GfxAction[newx][newy] = ACTION_GROWING;
8365 GfxDir[newx][newy] = diagonal_move_dir;
8366 ChangeDelay[newx][newy] = change_delay;
8368 graphic = el_act_dir2img(GfxElement[newx][newy],
8369 GfxAction[newx][newy], GfxDir[newx][newy]);
8371 DrawLevelGraphicAnimation(newx, newy, graphic);
8372 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8378 Tile[newx][newy] = EL_EMPTY;
8379 TEST_DrawLevelField(newx, newy);
8381 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8384 else if (!IS_FREE(newx, newy))
8389 else if (IS_CUSTOM_ELEMENT(element) &&
8390 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8392 if (!DigFieldByCE(newx, newy, element))
8395 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8397 RunnerVisit[x][y] = FrameCounter;
8398 PlayerVisit[x][y] /= 8; // expire player visit path
8401 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8403 if (!IS_FREE(newx, newy))
8405 if (IS_PLAYER(x, y))
8406 DrawPlayerField(x, y);
8408 TEST_DrawLevelField(x, y);
8414 boolean wanna_flame = !RND(10);
8415 int dx = newx - x, dy = newy - y;
8416 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8417 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8418 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8419 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8420 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8421 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8424 IS_CLASSIC_ENEMY(element1) ||
8425 IS_CLASSIC_ENEMY(element2)) &&
8426 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8427 element1 != EL_FLAMES && element2 != EL_FLAMES)
8429 ResetGfxAnimation(x, y);
8430 GfxAction[x][y] = ACTION_ATTACKING;
8432 if (IS_PLAYER(x, y))
8433 DrawPlayerField(x, y);
8435 TEST_DrawLevelField(x, y);
8437 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8439 MovDelay[x][y] = 50;
8441 Tile[newx][newy] = EL_FLAMES;
8442 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8443 Tile[newx1][newy1] = EL_FLAMES;
8444 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8445 Tile[newx2][newy2] = EL_FLAMES;
8451 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8452 Tile[newx][newy] == EL_DIAMOND)
8454 if (IS_MOVING(newx, newy))
8455 RemoveMovingField(newx, newy);
8458 Tile[newx][newy] = EL_EMPTY;
8459 TEST_DrawLevelField(newx, newy);
8462 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8464 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8465 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8467 if (AmoebaNr[newx][newy])
8469 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8470 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8471 Tile[newx][newy] == EL_BD_AMOEBA)
8472 AmoebaCnt[AmoebaNr[newx][newy]]--;
8475 if (IS_MOVING(newx, newy))
8477 RemoveMovingField(newx, newy);
8481 Tile[newx][newy] = EL_EMPTY;
8482 TEST_DrawLevelField(newx, newy);
8485 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8487 else if ((element == EL_PACMAN || element == EL_MOLE)
8488 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8490 if (AmoebaNr[newx][newy])
8492 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8493 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8494 Tile[newx][newy] == EL_BD_AMOEBA)
8495 AmoebaCnt[AmoebaNr[newx][newy]]--;
8498 if (element == EL_MOLE)
8500 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8501 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8503 ResetGfxAnimation(x, y);
8504 GfxAction[x][y] = ACTION_DIGGING;
8505 TEST_DrawLevelField(x, y);
8507 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8509 return; // wait for shrinking amoeba
8511 else // element == EL_PACMAN
8513 Tile[newx][newy] = EL_EMPTY;
8514 TEST_DrawLevelField(newx, newy);
8515 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8518 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8519 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8520 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8522 // wait for shrinking amoeba to completely disappear
8525 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8527 // object was running against a wall
8531 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8532 DrawLevelElementAnimation(x, y, element);
8534 if (DONT_TOUCH(element))
8535 TestIfBadThingTouchesPlayer(x, y);
8540 InitMovingField(x, y, MovDir[x][y]);
8542 PlayLevelSoundAction(x, y, ACTION_MOVING);
8546 ContinueMoving(x, y);
8549 void ContinueMoving(int x, int y)
8551 int element = Tile[x][y];
8552 struct ElementInfo *ei = &element_info[element];
8553 int direction = MovDir[x][y];
8554 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8555 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8556 int newx = x + dx, newy = y + dy;
8557 int stored = Store[x][y];
8558 int stored_new = Store[newx][newy];
8559 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8560 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8561 boolean last_line = (newy == lev_fieldy - 1);
8562 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8564 if (pushed_by_player) // special case: moving object pushed by player
8566 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8568 else if (use_step_delay) // special case: moving object has step delay
8570 if (!MovDelay[x][y])
8571 MovPos[x][y] += getElementMoveStepsize(x, y);
8576 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8580 TEST_DrawLevelField(x, y);
8582 return; // element is still waiting
8585 else // normal case: generically moving object
8587 MovPos[x][y] += getElementMoveStepsize(x, y);
8590 if (ABS(MovPos[x][y]) < TILEX)
8592 TEST_DrawLevelField(x, y);
8594 return; // element is still moving
8597 // element reached destination field
8599 Tile[x][y] = EL_EMPTY;
8600 Tile[newx][newy] = element;
8601 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8603 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8605 element = Tile[newx][newy] = EL_ACID;
8607 else if (element == EL_MOLE)
8609 Tile[x][y] = EL_SAND;
8611 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8613 else if (element == EL_QUICKSAND_FILLING)
8615 element = Tile[newx][newy] = get_next_element(element);
8616 Store[newx][newy] = Store[x][y];
8618 else if (element == EL_QUICKSAND_EMPTYING)
8620 Tile[x][y] = get_next_element(element);
8621 element = Tile[newx][newy] = Store[x][y];
8623 else if (element == EL_QUICKSAND_FAST_FILLING)
8625 element = Tile[newx][newy] = get_next_element(element);
8626 Store[newx][newy] = Store[x][y];
8628 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8630 Tile[x][y] = get_next_element(element);
8631 element = Tile[newx][newy] = Store[x][y];
8633 else if (element == EL_MAGIC_WALL_FILLING)
8635 element = Tile[newx][newy] = get_next_element(element);
8636 if (!game.magic_wall_active)
8637 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8638 Store[newx][newy] = Store[x][y];
8640 else if (element == EL_MAGIC_WALL_EMPTYING)
8642 Tile[x][y] = get_next_element(element);
8643 if (!game.magic_wall_active)
8644 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8645 element = Tile[newx][newy] = Store[x][y];
8647 InitField(newx, newy, FALSE);
8649 else if (element == EL_BD_MAGIC_WALL_FILLING)
8651 element = Tile[newx][newy] = get_next_element(element);
8652 if (!game.magic_wall_active)
8653 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8654 Store[newx][newy] = Store[x][y];
8656 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8658 Tile[x][y] = get_next_element(element);
8659 if (!game.magic_wall_active)
8660 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8661 element = Tile[newx][newy] = Store[x][y];
8663 InitField(newx, newy, FALSE);
8665 else if (element == EL_DC_MAGIC_WALL_FILLING)
8667 element = Tile[newx][newy] = get_next_element(element);
8668 if (!game.magic_wall_active)
8669 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8670 Store[newx][newy] = Store[x][y];
8672 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8674 Tile[x][y] = get_next_element(element);
8675 if (!game.magic_wall_active)
8676 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8677 element = Tile[newx][newy] = Store[x][y];
8679 InitField(newx, newy, FALSE);
8681 else if (element == EL_AMOEBA_DROPPING)
8683 Tile[x][y] = get_next_element(element);
8684 element = Tile[newx][newy] = Store[x][y];
8686 else if (element == EL_SOKOBAN_OBJECT)
8689 Tile[x][y] = Back[x][y];
8691 if (Back[newx][newy])
8692 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8694 Back[x][y] = Back[newx][newy] = 0;
8697 Store[x][y] = EL_EMPTY;
8702 MovDelay[newx][newy] = 0;
8704 if (CAN_CHANGE_OR_HAS_ACTION(element))
8706 // copy element change control values to new field
8707 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8708 ChangePage[newx][newy] = ChangePage[x][y];
8709 ChangeCount[newx][newy] = ChangeCount[x][y];
8710 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8713 CustomValue[newx][newy] = CustomValue[x][y];
8715 ChangeDelay[x][y] = 0;
8716 ChangePage[x][y] = -1;
8717 ChangeCount[x][y] = 0;
8718 ChangeEvent[x][y] = -1;
8720 CustomValue[x][y] = 0;
8722 // copy animation control values to new field
8723 GfxFrame[newx][newy] = GfxFrame[x][y];
8724 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8725 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8726 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8728 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8730 // some elements can leave other elements behind after moving
8731 if (ei->move_leave_element != EL_EMPTY &&
8732 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8733 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8735 int move_leave_element = ei->move_leave_element;
8737 // this makes it possible to leave the removed element again
8738 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8739 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8741 Tile[x][y] = move_leave_element;
8743 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8744 MovDir[x][y] = direction;
8746 InitField(x, y, FALSE);
8748 if (GFX_CRUMBLED(Tile[x][y]))
8749 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8751 if (ELEM_IS_PLAYER(move_leave_element))
8752 RelocatePlayer(x, y, move_leave_element);
8755 // do this after checking for left-behind element
8756 ResetGfxAnimation(x, y); // reset animation values for old field
8758 if (!CAN_MOVE(element) ||
8759 (CAN_FALL(element) && direction == MV_DOWN &&
8760 (element == EL_SPRING ||
8761 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8762 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8763 GfxDir[x][y] = MovDir[newx][newy] = 0;
8765 TEST_DrawLevelField(x, y);
8766 TEST_DrawLevelField(newx, newy);
8768 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8770 // prevent pushed element from moving on in pushed direction
8771 if (pushed_by_player && CAN_MOVE(element) &&
8772 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8773 !(element_info[element].move_pattern & direction))
8774 TurnRound(newx, newy);
8776 // prevent elements on conveyor belt from moving on in last direction
8777 if (pushed_by_conveyor && CAN_FALL(element) &&
8778 direction & MV_HORIZONTAL)
8779 MovDir[newx][newy] = 0;
8781 if (!pushed_by_player)
8783 int nextx = newx + dx, nexty = newy + dy;
8784 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8786 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8788 if (CAN_FALL(element) && direction == MV_DOWN)
8789 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8791 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8792 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8794 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8795 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8798 if (DONT_TOUCH(element)) // object may be nasty to player or others
8800 TestIfBadThingTouchesPlayer(newx, newy);
8801 TestIfBadThingTouchesFriend(newx, newy);
8803 if (!IS_CUSTOM_ELEMENT(element))
8804 TestIfBadThingTouchesOtherBadThing(newx, newy);
8806 else if (element == EL_PENGUIN)
8807 TestIfFriendTouchesBadThing(newx, newy);
8809 if (DONT_GET_HIT_BY(element))
8811 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8814 // give the player one last chance (one more frame) to move away
8815 if (CAN_FALL(element) && direction == MV_DOWN &&
8816 (last_line || (!IS_FREE(x, newy + 1) &&
8817 (!IS_PLAYER(x, newy + 1) ||
8818 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8821 if (pushed_by_player && !game.use_change_when_pushing_bug)
8823 int push_side = MV_DIR_OPPOSITE(direction);
8824 struct PlayerInfo *player = PLAYERINFO(x, y);
8826 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8827 player->index_bit, push_side);
8828 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8829 player->index_bit, push_side);
8832 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8833 MovDelay[newx][newy] = 1;
8835 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8837 TestIfElementTouchesCustomElement(x, y); // empty or new element
8838 TestIfElementHitsCustomElement(newx, newy, direction);
8839 TestIfPlayerTouchesCustomElement(newx, newy);
8840 TestIfElementTouchesCustomElement(newx, newy);
8842 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8843 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8844 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8845 MV_DIR_OPPOSITE(direction));
8848 int AmoebaNeighbourNr(int ax, int ay)
8851 int element = Tile[ax][ay];
8853 static int xy[4][2] =
8861 for (i = 0; i < NUM_DIRECTIONS; i++)
8863 int x = ax + xy[i][0];
8864 int y = ay + xy[i][1];
8866 if (!IN_LEV_FIELD(x, y))
8869 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8870 group_nr = AmoebaNr[x][y];
8876 static void AmoebaMerge(int ax, int ay)
8878 int i, x, y, xx, yy;
8879 int new_group_nr = AmoebaNr[ax][ay];
8880 static int xy[4][2] =
8888 if (new_group_nr == 0)
8891 for (i = 0; i < NUM_DIRECTIONS; i++)
8896 if (!IN_LEV_FIELD(x, y))
8899 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8900 Tile[x][y] == EL_BD_AMOEBA ||
8901 Tile[x][y] == EL_AMOEBA_DEAD) &&
8902 AmoebaNr[x][y] != new_group_nr)
8904 int old_group_nr = AmoebaNr[x][y];
8906 if (old_group_nr == 0)
8909 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8910 AmoebaCnt[old_group_nr] = 0;
8911 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8912 AmoebaCnt2[old_group_nr] = 0;
8914 SCAN_PLAYFIELD(xx, yy)
8916 if (AmoebaNr[xx][yy] == old_group_nr)
8917 AmoebaNr[xx][yy] = new_group_nr;
8923 void AmoebaToDiamond(int ax, int ay)
8927 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8929 int group_nr = AmoebaNr[ax][ay];
8934 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8935 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8941 SCAN_PLAYFIELD(x, y)
8943 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8946 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8950 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8951 SND_AMOEBA_TURNING_TO_GEM :
8952 SND_AMOEBA_TURNING_TO_ROCK));
8957 static int xy[4][2] =
8965 for (i = 0; i < NUM_DIRECTIONS; i++)
8970 if (!IN_LEV_FIELD(x, y))
8973 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
8975 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
8976 SND_AMOEBA_TURNING_TO_GEM :
8977 SND_AMOEBA_TURNING_TO_ROCK));
8984 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
8987 int group_nr = AmoebaNr[ax][ay];
8988 boolean done = FALSE;
8993 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
8994 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9000 SCAN_PLAYFIELD(x, y)
9002 if (AmoebaNr[x][y] == group_nr &&
9003 (Tile[x][y] == EL_AMOEBA_DEAD ||
9004 Tile[x][y] == EL_BD_AMOEBA ||
9005 Tile[x][y] == EL_AMOEBA_GROWING))
9008 Tile[x][y] = new_element;
9009 InitField(x, y, FALSE);
9010 TEST_DrawLevelField(x, y);
9016 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9017 SND_BD_AMOEBA_TURNING_TO_ROCK :
9018 SND_BD_AMOEBA_TURNING_TO_GEM));
9021 static void AmoebaGrowing(int x, int y)
9023 static unsigned int sound_delay = 0;
9024 static unsigned int sound_delay_value = 0;
9026 if (!MovDelay[x][y]) // start new growing cycle
9030 if (DelayReached(&sound_delay, sound_delay_value))
9032 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9033 sound_delay_value = 30;
9037 if (MovDelay[x][y]) // wait some time before growing bigger
9040 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9042 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9043 6 - MovDelay[x][y]);
9045 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9048 if (!MovDelay[x][y])
9050 Tile[x][y] = Store[x][y];
9052 TEST_DrawLevelField(x, y);
9057 static void AmoebaShrinking(int x, int y)
9059 static unsigned int sound_delay = 0;
9060 static unsigned int sound_delay_value = 0;
9062 if (!MovDelay[x][y]) // start new shrinking cycle
9066 if (DelayReached(&sound_delay, sound_delay_value))
9067 sound_delay_value = 30;
9070 if (MovDelay[x][y]) // wait some time before shrinking
9073 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9075 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9076 6 - MovDelay[x][y]);
9078 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9081 if (!MovDelay[x][y])
9083 Tile[x][y] = EL_EMPTY;
9084 TEST_DrawLevelField(x, y);
9086 // don't let mole enter this field in this cycle;
9087 // (give priority to objects falling to this field from above)
9093 static void AmoebaReproduce(int ax, int ay)
9096 int element = Tile[ax][ay];
9097 int graphic = el2img(element);
9098 int newax = ax, neway = ay;
9099 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9100 static int xy[4][2] =
9108 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9110 Tile[ax][ay] = EL_AMOEBA_DEAD;
9111 TEST_DrawLevelField(ax, ay);
9115 if (IS_ANIMATED(graphic))
9116 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9118 if (!MovDelay[ax][ay]) // start making new amoeba field
9119 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9121 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9124 if (MovDelay[ax][ay])
9128 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9131 int x = ax + xy[start][0];
9132 int y = ay + xy[start][1];
9134 if (!IN_LEV_FIELD(x, y))
9137 if (IS_FREE(x, y) ||
9138 CAN_GROW_INTO(Tile[x][y]) ||
9139 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9140 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9146 if (newax == ax && neway == ay)
9149 else // normal or "filled" (BD style) amoeba
9152 boolean waiting_for_player = FALSE;
9154 for (i = 0; i < NUM_DIRECTIONS; i++)
9156 int j = (start + i) % 4;
9157 int x = ax + xy[j][0];
9158 int y = ay + xy[j][1];
9160 if (!IN_LEV_FIELD(x, y))
9163 if (IS_FREE(x, y) ||
9164 CAN_GROW_INTO(Tile[x][y]) ||
9165 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9166 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9172 else if (IS_PLAYER(x, y))
9173 waiting_for_player = TRUE;
9176 if (newax == ax && neway == ay) // amoeba cannot grow
9178 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9180 Tile[ax][ay] = EL_AMOEBA_DEAD;
9181 TEST_DrawLevelField(ax, ay);
9182 AmoebaCnt[AmoebaNr[ax][ay]]--;
9184 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9186 if (element == EL_AMOEBA_FULL)
9187 AmoebaToDiamond(ax, ay);
9188 else if (element == EL_BD_AMOEBA)
9189 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9194 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9196 // amoeba gets larger by growing in some direction
9198 int new_group_nr = AmoebaNr[ax][ay];
9201 if (new_group_nr == 0)
9203 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9205 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9211 AmoebaNr[newax][neway] = new_group_nr;
9212 AmoebaCnt[new_group_nr]++;
9213 AmoebaCnt2[new_group_nr]++;
9215 // if amoeba touches other amoeba(s) after growing, unify them
9216 AmoebaMerge(newax, neway);
9218 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9220 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9226 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9227 (neway == lev_fieldy - 1 && newax != ax))
9229 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9230 Store[newax][neway] = element;
9232 else if (neway == ay || element == EL_EMC_DRIPPER)
9234 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9236 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9240 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9241 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9242 Store[ax][ay] = EL_AMOEBA_DROP;
9243 ContinueMoving(ax, ay);
9247 TEST_DrawLevelField(newax, neway);
9250 static void Life(int ax, int ay)
9254 int element = Tile[ax][ay];
9255 int graphic = el2img(element);
9256 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9258 boolean changed = FALSE;
9260 if (IS_ANIMATED(graphic))
9261 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9266 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9267 MovDelay[ax][ay] = life_time;
9269 if (MovDelay[ax][ay]) // wait some time before next cycle
9272 if (MovDelay[ax][ay])
9276 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9278 int xx = ax+x1, yy = ay+y1;
9279 int old_element = Tile[xx][yy];
9280 int num_neighbours = 0;
9282 if (!IN_LEV_FIELD(xx, yy))
9285 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9287 int x = xx+x2, y = yy+y2;
9289 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9292 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9293 boolean is_neighbour = FALSE;
9295 if (level.use_life_bugs)
9297 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9298 (IS_FREE(x, y) && Stop[x][y]));
9301 (Last[x][y] == element || is_player_cell);
9307 boolean is_free = FALSE;
9309 if (level.use_life_bugs)
9310 is_free = (IS_FREE(xx, yy));
9312 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9314 if (xx == ax && yy == ay) // field in the middle
9316 if (num_neighbours < life_parameter[0] ||
9317 num_neighbours > life_parameter[1])
9319 Tile[xx][yy] = EL_EMPTY;
9320 if (Tile[xx][yy] != old_element)
9321 TEST_DrawLevelField(xx, yy);
9322 Stop[xx][yy] = TRUE;
9326 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9327 { // free border field
9328 if (num_neighbours >= life_parameter[2] &&
9329 num_neighbours <= life_parameter[3])
9331 Tile[xx][yy] = element;
9332 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9333 if (Tile[xx][yy] != old_element)
9334 TEST_DrawLevelField(xx, yy);
9335 Stop[xx][yy] = TRUE;
9342 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9343 SND_GAME_OF_LIFE_GROWING);
9346 static void InitRobotWheel(int x, int y)
9348 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9351 static void RunRobotWheel(int x, int y)
9353 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9356 static void StopRobotWheel(int x, int y)
9358 if (game.robot_wheel_x == x &&
9359 game.robot_wheel_y == y)
9361 game.robot_wheel_x = -1;
9362 game.robot_wheel_y = -1;
9363 game.robot_wheel_active = FALSE;
9367 static void InitTimegateWheel(int x, int y)
9369 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9372 static void RunTimegateWheel(int x, int y)
9374 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9377 static void InitMagicBallDelay(int x, int y)
9379 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9382 static void ActivateMagicBall(int bx, int by)
9386 if (level.ball_random)
9388 int pos_border = RND(8); // select one of the eight border elements
9389 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9390 int xx = pos_content % 3;
9391 int yy = pos_content / 3;
9396 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9397 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9401 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9403 int xx = x - bx + 1;
9404 int yy = y - by + 1;
9406 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9407 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9411 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9414 static void CheckExit(int x, int y)
9416 if (game.gems_still_needed > 0 ||
9417 game.sokoban_fields_still_needed > 0 ||
9418 game.sokoban_objects_still_needed > 0 ||
9419 game.lights_still_needed > 0)
9421 int element = Tile[x][y];
9422 int graphic = el2img(element);
9424 if (IS_ANIMATED(graphic))
9425 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9430 // do not re-open exit door closed after last player
9431 if (game.all_players_gone)
9434 Tile[x][y] = EL_EXIT_OPENING;
9436 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9439 static void CheckExitEM(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_EM_EXIT_OPENING;
9461 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9464 static void CheckExitSteel(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_STEEL_EXIT_OPENING;
9486 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9489 static void CheckExitSteelEM(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_EM_STEEL_EXIT_OPENING;
9511 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9514 static void CheckExitSP(int x, int y)
9516 if (game.gems_still_needed > 0)
9518 int element = Tile[x][y];
9519 int graphic = el2img(element);
9521 if (IS_ANIMATED(graphic))
9522 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9527 // do not re-open exit door closed after last player
9528 if (game.all_players_gone)
9531 Tile[x][y] = EL_SP_EXIT_OPENING;
9533 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9536 static void CloseAllOpenTimegates(void)
9540 SCAN_PLAYFIELD(x, y)
9542 int element = Tile[x][y];
9544 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9546 Tile[x][y] = EL_TIMEGATE_CLOSING;
9548 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9553 static void DrawTwinkleOnField(int x, int y)
9555 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9558 if (Tile[x][y] == EL_BD_DIAMOND)
9561 if (MovDelay[x][y] == 0) // next animation frame
9562 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9564 if (MovDelay[x][y] != 0) // wait some time before next frame
9568 DrawLevelElementAnimation(x, y, Tile[x][y]);
9570 if (MovDelay[x][y] != 0)
9572 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9573 10 - MovDelay[x][y]);
9575 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9580 static void MauerWaechst(int x, int y)
9584 if (!MovDelay[x][y]) // next animation frame
9585 MovDelay[x][y] = 3 * delay;
9587 if (MovDelay[x][y]) // wait some time before next frame
9591 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9593 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9594 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9596 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9599 if (!MovDelay[x][y])
9601 if (MovDir[x][y] == MV_LEFT)
9603 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9604 TEST_DrawLevelField(x - 1, y);
9606 else if (MovDir[x][y] == MV_RIGHT)
9608 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9609 TEST_DrawLevelField(x + 1, y);
9611 else if (MovDir[x][y] == MV_UP)
9613 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9614 TEST_DrawLevelField(x, y - 1);
9618 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9619 TEST_DrawLevelField(x, y + 1);
9622 Tile[x][y] = Store[x][y];
9624 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9625 TEST_DrawLevelField(x, y);
9630 static void MauerAbleger(int ax, int ay)
9632 int element = Tile[ax][ay];
9633 int graphic = el2img(element);
9634 boolean oben_frei = FALSE, unten_frei = FALSE;
9635 boolean links_frei = FALSE, rechts_frei = FALSE;
9636 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9637 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9638 boolean new_wall = FALSE;
9640 if (IS_ANIMATED(graphic))
9641 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9643 if (!MovDelay[ax][ay]) // start building new wall
9644 MovDelay[ax][ay] = 6;
9646 if (MovDelay[ax][ay]) // wait some time before building new wall
9649 if (MovDelay[ax][ay])
9653 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9655 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9657 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9659 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9662 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9663 element == EL_EXPANDABLE_WALL_ANY)
9667 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9668 Store[ax][ay-1] = element;
9669 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9670 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9671 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9672 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9677 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9678 Store[ax][ay+1] = element;
9679 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9680 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9681 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9682 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9687 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9688 element == EL_EXPANDABLE_WALL_ANY ||
9689 element == EL_EXPANDABLE_WALL ||
9690 element == EL_BD_EXPANDABLE_WALL)
9694 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9695 Store[ax-1][ay] = element;
9696 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9697 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9698 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9699 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9705 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9706 Store[ax+1][ay] = element;
9707 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9708 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9709 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9710 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9715 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9716 TEST_DrawLevelField(ax, ay);
9718 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9720 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9721 unten_massiv = TRUE;
9722 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9723 links_massiv = TRUE;
9724 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9725 rechts_massiv = TRUE;
9727 if (((oben_massiv && unten_massiv) ||
9728 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9729 element == EL_EXPANDABLE_WALL) &&
9730 ((links_massiv && rechts_massiv) ||
9731 element == EL_EXPANDABLE_WALL_VERTICAL))
9732 Tile[ax][ay] = EL_WALL;
9735 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9738 static void MauerAblegerStahl(int ax, int ay)
9740 int element = Tile[ax][ay];
9741 int graphic = el2img(element);
9742 boolean oben_frei = FALSE, unten_frei = FALSE;
9743 boolean links_frei = FALSE, rechts_frei = FALSE;
9744 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9745 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9746 boolean new_wall = FALSE;
9748 if (IS_ANIMATED(graphic))
9749 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9751 if (!MovDelay[ax][ay]) // start building new wall
9752 MovDelay[ax][ay] = 6;
9754 if (MovDelay[ax][ay]) // wait some time before building new wall
9757 if (MovDelay[ax][ay])
9761 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9763 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9765 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9767 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9770 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9771 element == EL_EXPANDABLE_STEELWALL_ANY)
9775 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9776 Store[ax][ay-1] = element;
9777 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9778 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9779 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9780 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9785 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9786 Store[ax][ay+1] = element;
9787 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9788 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9789 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9790 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9795 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9796 element == EL_EXPANDABLE_STEELWALL_ANY)
9800 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9801 Store[ax-1][ay] = element;
9802 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9803 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9804 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9805 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9811 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9812 Store[ax+1][ay] = element;
9813 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9814 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9815 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9816 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9821 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9823 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9824 unten_massiv = TRUE;
9825 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9826 links_massiv = TRUE;
9827 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9828 rechts_massiv = TRUE;
9830 if (((oben_massiv && unten_massiv) ||
9831 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9832 ((links_massiv && rechts_massiv) ||
9833 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9834 Tile[ax][ay] = EL_STEELWALL;
9837 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9840 static void CheckForDragon(int x, int y)
9843 boolean dragon_found = FALSE;
9844 static int xy[4][2] =
9852 for (i = 0; i < NUM_DIRECTIONS; i++)
9854 for (j = 0; j < 4; j++)
9856 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9858 if (IN_LEV_FIELD(xx, yy) &&
9859 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9861 if (Tile[xx][yy] == EL_DRAGON)
9862 dragon_found = TRUE;
9871 for (i = 0; i < NUM_DIRECTIONS; i++)
9873 for (j = 0; j < 3; j++)
9875 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9877 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9879 Tile[xx][yy] = EL_EMPTY;
9880 TEST_DrawLevelField(xx, yy);
9889 static void InitBuggyBase(int x, int y)
9891 int element = Tile[x][y];
9892 int activating_delay = FRAMES_PER_SECOND / 4;
9895 (element == EL_SP_BUGGY_BASE ?
9896 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9897 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9899 element == EL_SP_BUGGY_BASE_ACTIVE ?
9900 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9903 static void WarnBuggyBase(int x, int y)
9906 static int xy[4][2] =
9914 for (i = 0; i < NUM_DIRECTIONS; i++)
9916 int xx = x + xy[i][0];
9917 int yy = y + xy[i][1];
9919 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9921 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9928 static void InitTrap(int x, int y)
9930 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9933 static void ActivateTrap(int x, int y)
9935 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9938 static void ChangeActiveTrap(int x, int y)
9940 int graphic = IMG_TRAP_ACTIVE;
9942 // if new animation frame was drawn, correct crumbled sand border
9943 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9944 TEST_DrawLevelFieldCrumbled(x, y);
9947 static int getSpecialActionElement(int element, int number, int base_element)
9949 return (element != EL_EMPTY ? element :
9950 number != -1 ? base_element + number - 1 :
9954 static int getModifiedActionNumber(int value_old, int operator, int operand,
9955 int value_min, int value_max)
9957 int value_new = (operator == CA_MODE_SET ? operand :
9958 operator == CA_MODE_ADD ? value_old + operand :
9959 operator == CA_MODE_SUBTRACT ? value_old - operand :
9960 operator == CA_MODE_MULTIPLY ? value_old * operand :
9961 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9962 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9965 return (value_new < value_min ? value_min :
9966 value_new > value_max ? value_max :
9970 static void ExecuteCustomElementAction(int x, int y, int element, int page)
9972 struct ElementInfo *ei = &element_info[element];
9973 struct ElementChangeInfo *change = &ei->change_page[page];
9974 int target_element = change->target_element;
9975 int action_type = change->action_type;
9976 int action_mode = change->action_mode;
9977 int action_arg = change->action_arg;
9978 int action_element = change->action_element;
9981 if (!change->has_action)
9984 // ---------- determine action paramater values -----------------------------
9986 int level_time_value =
9987 (level.time > 0 ? TimeLeft :
9990 int action_arg_element_raw =
9991 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
9992 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
9993 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
9994 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
9995 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
9996 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
9997 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
9999 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10001 int action_arg_direction =
10002 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10003 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10004 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10005 change->actual_trigger_side :
10006 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10007 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10010 int action_arg_number_min =
10011 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10014 int action_arg_number_max =
10015 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10016 action_type == CA_SET_LEVEL_GEMS ? 999 :
10017 action_type == CA_SET_LEVEL_TIME ? 9999 :
10018 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10019 action_type == CA_SET_CE_VALUE ? 9999 :
10020 action_type == CA_SET_CE_SCORE ? 9999 :
10023 int action_arg_number_reset =
10024 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10025 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10026 action_type == CA_SET_LEVEL_TIME ? level.time :
10027 action_type == CA_SET_LEVEL_SCORE ? 0 :
10028 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10029 action_type == CA_SET_CE_SCORE ? 0 :
10032 int action_arg_number =
10033 (action_arg <= CA_ARG_MAX ? action_arg :
10034 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10035 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10036 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10037 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10038 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10039 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10040 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10041 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10042 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10043 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10044 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10045 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10046 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10047 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10048 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10049 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10050 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10051 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10052 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10053 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10054 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10057 int action_arg_number_old =
10058 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10059 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10060 action_type == CA_SET_LEVEL_SCORE ? game.score :
10061 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10062 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10065 int action_arg_number_new =
10066 getModifiedActionNumber(action_arg_number_old,
10067 action_mode, action_arg_number,
10068 action_arg_number_min, action_arg_number_max);
10070 int trigger_player_bits =
10071 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10072 change->actual_trigger_player_bits : change->trigger_player);
10074 int action_arg_player_bits =
10075 (action_arg >= CA_ARG_PLAYER_1 &&
10076 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10077 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10078 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10081 // ---------- execute action -----------------------------------------------
10083 switch (action_type)
10090 // ---------- level actions ----------------------------------------------
10092 case CA_RESTART_LEVEL:
10094 game.restart_level = TRUE;
10099 case CA_SHOW_ENVELOPE:
10101 int element = getSpecialActionElement(action_arg_element,
10102 action_arg_number, EL_ENVELOPE_1);
10104 if (IS_ENVELOPE(element))
10105 local_player->show_envelope = element;
10110 case CA_SET_LEVEL_TIME:
10112 if (level.time > 0) // only modify limited time value
10114 TimeLeft = action_arg_number_new;
10116 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10118 DisplayGameControlValues();
10120 if (!TimeLeft && setup.time_limit)
10121 for (i = 0; i < MAX_PLAYERS; i++)
10122 KillPlayer(&stored_player[i]);
10128 case CA_SET_LEVEL_SCORE:
10130 game.score = action_arg_number_new;
10132 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10134 DisplayGameControlValues();
10139 case CA_SET_LEVEL_GEMS:
10141 game.gems_still_needed = action_arg_number_new;
10143 game.snapshot.collected_item = TRUE;
10145 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10147 DisplayGameControlValues();
10152 case CA_SET_LEVEL_WIND:
10154 game.wind_direction = action_arg_direction;
10159 case CA_SET_LEVEL_RANDOM_SEED:
10161 // ensure that setting a new random seed while playing is predictable
10162 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10167 // ---------- player actions ---------------------------------------------
10169 case CA_MOVE_PLAYER:
10170 case CA_MOVE_PLAYER_NEW:
10172 // automatically move to the next field in specified direction
10173 for (i = 0; i < MAX_PLAYERS; i++)
10174 if (trigger_player_bits & (1 << i))
10175 if (action_type == CA_MOVE_PLAYER ||
10176 stored_player[i].MovPos == 0)
10177 stored_player[i].programmed_action = action_arg_direction;
10182 case CA_EXIT_PLAYER:
10184 for (i = 0; i < MAX_PLAYERS; i++)
10185 if (action_arg_player_bits & (1 << i))
10186 ExitPlayer(&stored_player[i]);
10188 if (game.players_still_needed == 0)
10194 case CA_KILL_PLAYER:
10196 for (i = 0; i < MAX_PLAYERS; i++)
10197 if (action_arg_player_bits & (1 << i))
10198 KillPlayer(&stored_player[i]);
10203 case CA_SET_PLAYER_KEYS:
10205 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10206 int element = getSpecialActionElement(action_arg_element,
10207 action_arg_number, EL_KEY_1);
10209 if (IS_KEY(element))
10211 for (i = 0; i < MAX_PLAYERS; i++)
10213 if (trigger_player_bits & (1 << i))
10215 stored_player[i].key[KEY_NR(element)] = key_state;
10217 DrawGameDoorValues();
10225 case CA_SET_PLAYER_SPEED:
10227 for (i = 0; i < MAX_PLAYERS; i++)
10229 if (trigger_player_bits & (1 << i))
10231 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10233 if (action_arg == CA_ARG_SPEED_FASTER &&
10234 stored_player[i].cannot_move)
10236 action_arg_number = STEPSIZE_VERY_SLOW;
10238 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10239 action_arg == CA_ARG_SPEED_FASTER)
10241 action_arg_number = 2;
10242 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10245 else if (action_arg == CA_ARG_NUMBER_RESET)
10247 action_arg_number = level.initial_player_stepsize[i];
10251 getModifiedActionNumber(move_stepsize,
10254 action_arg_number_min,
10255 action_arg_number_max);
10257 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10264 case CA_SET_PLAYER_SHIELD:
10266 for (i = 0; i < MAX_PLAYERS; i++)
10268 if (trigger_player_bits & (1 << i))
10270 if (action_arg == CA_ARG_SHIELD_OFF)
10272 stored_player[i].shield_normal_time_left = 0;
10273 stored_player[i].shield_deadly_time_left = 0;
10275 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10277 stored_player[i].shield_normal_time_left = 999999;
10279 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10281 stored_player[i].shield_normal_time_left = 999999;
10282 stored_player[i].shield_deadly_time_left = 999999;
10290 case CA_SET_PLAYER_GRAVITY:
10292 for (i = 0; i < MAX_PLAYERS; i++)
10294 if (trigger_player_bits & (1 << i))
10296 stored_player[i].gravity =
10297 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10298 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10299 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10300 stored_player[i].gravity);
10307 case CA_SET_PLAYER_ARTWORK:
10309 for (i = 0; i < MAX_PLAYERS; i++)
10311 if (trigger_player_bits & (1 << i))
10313 int artwork_element = action_arg_element;
10315 if (action_arg == CA_ARG_ELEMENT_RESET)
10317 (level.use_artwork_element[i] ? level.artwork_element[i] :
10318 stored_player[i].element_nr);
10320 if (stored_player[i].artwork_element != artwork_element)
10321 stored_player[i].Frame = 0;
10323 stored_player[i].artwork_element = artwork_element;
10325 SetPlayerWaiting(&stored_player[i], FALSE);
10327 // set number of special actions for bored and sleeping animation
10328 stored_player[i].num_special_action_bored =
10329 get_num_special_action(artwork_element,
10330 ACTION_BORING_1, ACTION_BORING_LAST);
10331 stored_player[i].num_special_action_sleeping =
10332 get_num_special_action(artwork_element,
10333 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10340 case CA_SET_PLAYER_INVENTORY:
10342 for (i = 0; i < MAX_PLAYERS; i++)
10344 struct PlayerInfo *player = &stored_player[i];
10347 if (trigger_player_bits & (1 << i))
10349 int inventory_element = action_arg_element;
10351 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10352 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10353 action_arg == CA_ARG_ELEMENT_ACTION)
10355 int element = inventory_element;
10356 int collect_count = element_info[element].collect_count_initial;
10358 if (!IS_CUSTOM_ELEMENT(element))
10361 if (collect_count == 0)
10362 player->inventory_infinite_element = element;
10364 for (k = 0; k < collect_count; k++)
10365 if (player->inventory_size < MAX_INVENTORY_SIZE)
10366 player->inventory_element[player->inventory_size++] =
10369 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10370 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10371 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10373 if (player->inventory_infinite_element != EL_UNDEFINED &&
10374 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10375 action_arg_element_raw))
10376 player->inventory_infinite_element = EL_UNDEFINED;
10378 for (k = 0, j = 0; j < player->inventory_size; j++)
10380 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10381 action_arg_element_raw))
10382 player->inventory_element[k++] = player->inventory_element[j];
10385 player->inventory_size = k;
10387 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10389 if (player->inventory_size > 0)
10391 for (j = 0; j < player->inventory_size - 1; j++)
10392 player->inventory_element[j] = player->inventory_element[j + 1];
10394 player->inventory_size--;
10397 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10399 if (player->inventory_size > 0)
10400 player->inventory_size--;
10402 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10404 player->inventory_infinite_element = EL_UNDEFINED;
10405 player->inventory_size = 0;
10407 else if (action_arg == CA_ARG_INVENTORY_RESET)
10409 player->inventory_infinite_element = EL_UNDEFINED;
10410 player->inventory_size = 0;
10412 if (level.use_initial_inventory[i])
10414 for (j = 0; j < level.initial_inventory_size[i]; j++)
10416 int element = level.initial_inventory_content[i][j];
10417 int collect_count = element_info[element].collect_count_initial;
10419 if (!IS_CUSTOM_ELEMENT(element))
10422 if (collect_count == 0)
10423 player->inventory_infinite_element = element;
10425 for (k = 0; k < collect_count; k++)
10426 if (player->inventory_size < MAX_INVENTORY_SIZE)
10427 player->inventory_element[player->inventory_size++] =
10438 // ---------- CE actions -------------------------------------------------
10440 case CA_SET_CE_VALUE:
10442 int last_ce_value = CustomValue[x][y];
10444 CustomValue[x][y] = action_arg_number_new;
10446 if (CustomValue[x][y] != last_ce_value)
10448 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10449 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10451 if (CustomValue[x][y] == 0)
10453 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10454 ChangeCount[x][y] = 0; // allow at least one more change
10456 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10457 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10464 case CA_SET_CE_SCORE:
10466 int last_ce_score = ei->collect_score;
10468 ei->collect_score = action_arg_number_new;
10470 if (ei->collect_score != last_ce_score)
10472 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10473 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10475 if (ei->collect_score == 0)
10479 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10480 ChangeCount[x][y] = 0; // allow at least one more change
10482 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10483 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10486 This is a very special case that seems to be a mixture between
10487 CheckElementChange() and CheckTriggeredElementChange(): while
10488 the first one only affects single elements that are triggered
10489 directly, the second one affects multiple elements in the playfield
10490 that are triggered indirectly by another element. This is a third
10491 case: Changing the CE score always affects multiple identical CEs,
10492 so every affected CE must be checked, not only the single CE for
10493 which the CE score was changed in the first place (as every instance
10494 of that CE shares the same CE score, and therefore also can change)!
10496 SCAN_PLAYFIELD(xx, yy)
10498 if (Tile[xx][yy] == element)
10499 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10500 CE_SCORE_GETS_ZERO);
10508 case CA_SET_CE_ARTWORK:
10510 int artwork_element = action_arg_element;
10511 boolean reset_frame = FALSE;
10514 if (action_arg == CA_ARG_ELEMENT_RESET)
10515 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10518 if (ei->gfx_element != artwork_element)
10519 reset_frame = TRUE;
10521 ei->gfx_element = artwork_element;
10523 SCAN_PLAYFIELD(xx, yy)
10525 if (Tile[xx][yy] == element)
10529 ResetGfxAnimation(xx, yy);
10530 ResetRandomAnimationValue(xx, yy);
10533 TEST_DrawLevelField(xx, yy);
10540 // ---------- engine actions ---------------------------------------------
10542 case CA_SET_ENGINE_SCAN_MODE:
10544 InitPlayfieldScanMode(action_arg);
10554 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10556 int old_element = Tile[x][y];
10557 int new_element = GetElementFromGroupElement(element);
10558 int previous_move_direction = MovDir[x][y];
10559 int last_ce_value = CustomValue[x][y];
10560 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10561 boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
10562 boolean add_player_onto_element = (new_element_is_player &&
10563 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10564 IS_WALKABLE(old_element));
10566 if (!add_player_onto_element)
10568 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10569 RemoveMovingField(x, y);
10573 Tile[x][y] = new_element;
10575 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10576 MovDir[x][y] = previous_move_direction;
10578 if (element_info[new_element].use_last_ce_value)
10579 CustomValue[x][y] = last_ce_value;
10581 InitField_WithBug1(x, y, FALSE);
10583 new_element = Tile[x][y]; // element may have changed
10585 ResetGfxAnimation(x, y);
10586 ResetRandomAnimationValue(x, y);
10588 TEST_DrawLevelField(x, y);
10590 if (GFX_CRUMBLED(new_element))
10591 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10594 // check if element under the player changes from accessible to unaccessible
10595 // (needed for special case of dropping element which then changes)
10596 // (must be checked after creating new element for walkable group elements)
10597 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10598 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10605 // "ChangeCount" not set yet to allow "entered by player" change one time
10606 if (new_element_is_player)
10607 RelocatePlayer(x, y, new_element);
10610 ChangeCount[x][y]++; // count number of changes in the same frame
10612 TestIfBadThingTouchesPlayer(x, y);
10613 TestIfPlayerTouchesCustomElement(x, y);
10614 TestIfElementTouchesCustomElement(x, y);
10617 static void CreateField(int x, int y, int element)
10619 CreateFieldExt(x, y, element, FALSE);
10622 static void CreateElementFromChange(int x, int y, int element)
10624 element = GET_VALID_RUNTIME_ELEMENT(element);
10626 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10628 int old_element = Tile[x][y];
10630 // prevent changed element from moving in same engine frame
10631 // unless both old and new element can either fall or move
10632 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10633 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10637 CreateFieldExt(x, y, element, TRUE);
10640 static boolean ChangeElement(int x, int y, int element, int page)
10642 struct ElementInfo *ei = &element_info[element];
10643 struct ElementChangeInfo *change = &ei->change_page[page];
10644 int ce_value = CustomValue[x][y];
10645 int ce_score = ei->collect_score;
10646 int target_element;
10647 int old_element = Tile[x][y];
10649 // always use default change event to prevent running into a loop
10650 if (ChangeEvent[x][y] == -1)
10651 ChangeEvent[x][y] = CE_DELAY;
10653 if (ChangeEvent[x][y] == CE_DELAY)
10655 // reset actual trigger element, trigger player and action element
10656 change->actual_trigger_element = EL_EMPTY;
10657 change->actual_trigger_player = EL_EMPTY;
10658 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10659 change->actual_trigger_side = CH_SIDE_NONE;
10660 change->actual_trigger_ce_value = 0;
10661 change->actual_trigger_ce_score = 0;
10664 // do not change elements more than a specified maximum number of changes
10665 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10668 ChangeCount[x][y]++; // count number of changes in the same frame
10670 if (change->explode)
10677 if (change->use_target_content)
10679 boolean complete_replace = TRUE;
10680 boolean can_replace[3][3];
10683 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10686 boolean is_walkable;
10687 boolean is_diggable;
10688 boolean is_collectible;
10689 boolean is_removable;
10690 boolean is_destructible;
10691 int ex = x + xx - 1;
10692 int ey = y + yy - 1;
10693 int content_element = change->target_content.e[xx][yy];
10696 can_replace[xx][yy] = TRUE;
10698 if (ex == x && ey == y) // do not check changing element itself
10701 if (content_element == EL_EMPTY_SPACE)
10703 can_replace[xx][yy] = FALSE; // do not replace border with space
10708 if (!IN_LEV_FIELD(ex, ey))
10710 can_replace[xx][yy] = FALSE;
10711 complete_replace = FALSE;
10718 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10719 e = MovingOrBlocked2Element(ex, ey);
10721 is_empty = (IS_FREE(ex, ey) ||
10722 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10724 is_walkable = (is_empty || IS_WALKABLE(e));
10725 is_diggable = (is_empty || IS_DIGGABLE(e));
10726 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10727 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10728 is_removable = (is_diggable || is_collectible);
10730 can_replace[xx][yy] =
10731 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10732 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10733 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10734 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10735 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10736 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10737 !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
10739 if (!can_replace[xx][yy])
10740 complete_replace = FALSE;
10743 if (!change->only_if_complete || complete_replace)
10745 boolean something_has_changed = FALSE;
10747 if (change->only_if_complete && change->use_random_replace &&
10748 RND(100) < change->random_percentage)
10751 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10753 int ex = x + xx - 1;
10754 int ey = y + yy - 1;
10755 int content_element;
10757 if (can_replace[xx][yy] && (!change->use_random_replace ||
10758 RND(100) < change->random_percentage))
10760 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10761 RemoveMovingField(ex, ey);
10763 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10765 content_element = change->target_content.e[xx][yy];
10766 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10767 ce_value, ce_score);
10769 CreateElementFromChange(ex, ey, target_element);
10771 something_has_changed = TRUE;
10773 // for symmetry reasons, freeze newly created border elements
10774 if (ex != x || ey != y)
10775 Stop[ex][ey] = TRUE; // no more moving in this frame
10779 if (something_has_changed)
10781 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10782 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10788 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10789 ce_value, ce_score);
10791 if (element == EL_DIAGONAL_GROWING ||
10792 element == EL_DIAGONAL_SHRINKING)
10794 target_element = Store[x][y];
10796 Store[x][y] = EL_EMPTY;
10799 CreateElementFromChange(x, y, target_element);
10801 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10802 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10805 // this uses direct change before indirect change
10806 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10811 static void HandleElementChange(int x, int y, int page)
10813 int element = MovingOrBlocked2Element(x, y);
10814 struct ElementInfo *ei = &element_info[element];
10815 struct ElementChangeInfo *change = &ei->change_page[page];
10816 boolean handle_action_before_change = FALSE;
10819 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10820 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10822 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10823 x, y, element, element_info[element].token_name);
10824 Debug("game:playing:HandleElementChange", "This should never happen!");
10828 // this can happen with classic bombs on walkable, changing elements
10829 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10834 if (ChangeDelay[x][y] == 0) // initialize element change
10836 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10838 if (change->can_change)
10840 // !!! not clear why graphic animation should be reset at all here !!!
10841 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10842 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10845 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10847 When using an animation frame delay of 1 (this only happens with
10848 "sp_zonk.moving.left/right" in the classic graphics), the default
10849 (non-moving) animation shows wrong animation frames (while the
10850 moving animation, like "sp_zonk.moving.left/right", is correct,
10851 so this graphical bug never shows up with the classic graphics).
10852 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10853 be drawn instead of the correct frames 0,1,2,3. This is caused by
10854 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10855 an element change: First when the change delay ("ChangeDelay[][]")
10856 counter has reached zero after decrementing, then a second time in
10857 the next frame (after "GfxFrame[][]" was already incremented) when
10858 "ChangeDelay[][]" is reset to the initial delay value again.
10860 This causes frame 0 to be drawn twice, while the last frame won't
10861 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10863 As some animations may already be cleverly designed around this bug
10864 (at least the "Snake Bite" snake tail animation does this), it cannot
10865 simply be fixed here without breaking such existing animations.
10866 Unfortunately, it cannot easily be detected if a graphics set was
10867 designed "before" or "after" the bug was fixed. As a workaround,
10868 a new graphics set option "game.graphics_engine_version" was added
10869 to be able to specify the game's major release version for which the
10870 graphics set was designed, which can then be used to decide if the
10871 bugfix should be used (version 4 and above) or not (version 3 or
10872 below, or if no version was specified at all, as with old sets).
10874 (The wrong/fixed animation frames can be tested with the test level set
10875 "test_gfxframe" and level "000", which contains a specially prepared
10876 custom element at level position (x/y) == (11/9) which uses the zonk
10877 animation mentioned above. Using "game.graphics_engine_version: 4"
10878 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10879 This can also be seen from the debug output for this test element.)
10882 // when a custom element is about to change (for example by change delay),
10883 // do not reset graphic animation when the custom element is moving
10884 if (game.graphics_engine_version < 4 &&
10887 ResetGfxAnimation(x, y);
10888 ResetRandomAnimationValue(x, y);
10891 if (change->pre_change_function)
10892 change->pre_change_function(x, y);
10896 ChangeDelay[x][y]--;
10898 if (ChangeDelay[x][y] != 0) // continue element change
10900 if (change->can_change)
10902 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10904 if (IS_ANIMATED(graphic))
10905 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10907 if (change->change_function)
10908 change->change_function(x, y);
10911 else // finish element change
10913 if (ChangePage[x][y] != -1) // remember page from delayed change
10915 page = ChangePage[x][y];
10916 ChangePage[x][y] = -1;
10918 change = &ei->change_page[page];
10921 if (IS_MOVING(x, y)) // never change a running system ;-)
10923 ChangeDelay[x][y] = 1; // try change after next move step
10924 ChangePage[x][y] = page; // remember page to use for change
10929 // special case: set new level random seed before changing element
10930 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10931 handle_action_before_change = TRUE;
10933 if (change->has_action && handle_action_before_change)
10934 ExecuteCustomElementAction(x, y, element, page);
10936 if (change->can_change)
10938 if (ChangeElement(x, y, element, page))
10940 if (change->post_change_function)
10941 change->post_change_function(x, y);
10945 if (change->has_action && !handle_action_before_change)
10946 ExecuteCustomElementAction(x, y, element, page);
10950 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10951 int trigger_element,
10953 int trigger_player,
10957 boolean change_done_any = FALSE;
10958 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10961 if (!(trigger_events[trigger_element][trigger_event]))
10964 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
10966 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
10968 int element = EL_CUSTOM_START + i;
10969 boolean change_done = FALSE;
10972 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
10973 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
10976 for (p = 0; p < element_info[element].num_change_pages; p++)
10978 struct ElementChangeInfo *change = &element_info[element].change_page[p];
10980 if (change->can_change_or_has_action &&
10981 change->has_event[trigger_event] &&
10982 change->trigger_side & trigger_side &&
10983 change->trigger_player & trigger_player &&
10984 change->trigger_page & trigger_page_bits &&
10985 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
10987 change->actual_trigger_element = trigger_element;
10988 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
10989 change->actual_trigger_player_bits = trigger_player;
10990 change->actual_trigger_side = trigger_side;
10991 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
10992 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
10994 if ((change->can_change && !change_done) || change->has_action)
10998 SCAN_PLAYFIELD(x, y)
11000 if (Tile[x][y] == element)
11002 if (change->can_change && !change_done)
11004 // if element already changed in this frame, not only prevent
11005 // another element change (checked in ChangeElement()), but
11006 // also prevent additional element actions for this element
11008 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11009 !level.use_action_after_change_bug)
11012 ChangeDelay[x][y] = 1;
11013 ChangeEvent[x][y] = trigger_event;
11015 HandleElementChange(x, y, p);
11017 else if (change->has_action)
11019 // if element already changed in this frame, not only prevent
11020 // another element change (checked in ChangeElement()), but
11021 // also prevent additional element actions for this element
11023 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11024 !level.use_action_after_change_bug)
11027 ExecuteCustomElementAction(x, y, element, p);
11028 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11033 if (change->can_change)
11035 change_done = TRUE;
11036 change_done_any = TRUE;
11043 RECURSION_LOOP_DETECTION_END();
11045 return change_done_any;
11048 static boolean CheckElementChangeExt(int x, int y,
11050 int trigger_element,
11052 int trigger_player,
11055 boolean change_done = FALSE;
11058 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11059 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11062 if (Tile[x][y] == EL_BLOCKED)
11064 Blocked2Moving(x, y, &x, &y);
11065 element = Tile[x][y];
11068 // check if element has already changed or is about to change after moving
11069 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11070 Tile[x][y] != element) ||
11072 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11073 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11074 ChangePage[x][y] != -1)))
11077 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11079 for (p = 0; p < element_info[element].num_change_pages; p++)
11081 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11083 /* check trigger element for all events where the element that is checked
11084 for changing interacts with a directly adjacent element -- this is
11085 different to element changes that affect other elements to change on the
11086 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11087 boolean check_trigger_element =
11088 (trigger_event == CE_TOUCHING_X ||
11089 trigger_event == CE_HITTING_X ||
11090 trigger_event == CE_HIT_BY_X ||
11091 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11093 if (change->can_change_or_has_action &&
11094 change->has_event[trigger_event] &&
11095 change->trigger_side & trigger_side &&
11096 change->trigger_player & trigger_player &&
11097 (!check_trigger_element ||
11098 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11100 change->actual_trigger_element = trigger_element;
11101 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11102 change->actual_trigger_player_bits = trigger_player;
11103 change->actual_trigger_side = trigger_side;
11104 change->actual_trigger_ce_value = CustomValue[x][y];
11105 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11107 // special case: trigger element not at (x,y) position for some events
11108 if (check_trigger_element)
11120 { 0, 0 }, { 0, 0 }, { 0, 0 },
11124 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11125 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11127 change->actual_trigger_ce_value = CustomValue[xx][yy];
11128 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11131 if (change->can_change && !change_done)
11133 ChangeDelay[x][y] = 1;
11134 ChangeEvent[x][y] = trigger_event;
11136 HandleElementChange(x, y, p);
11138 change_done = TRUE;
11140 else if (change->has_action)
11142 ExecuteCustomElementAction(x, y, element, p);
11143 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11148 RECURSION_LOOP_DETECTION_END();
11150 return change_done;
11153 static void PlayPlayerSound(struct PlayerInfo *player)
11155 int jx = player->jx, jy = player->jy;
11156 int sound_element = player->artwork_element;
11157 int last_action = player->last_action_waiting;
11158 int action = player->action_waiting;
11160 if (player->is_waiting)
11162 if (action != last_action)
11163 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11165 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11169 if (action != last_action)
11170 StopSound(element_info[sound_element].sound[last_action]);
11172 if (last_action == ACTION_SLEEPING)
11173 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11177 static void PlayAllPlayersSound(void)
11181 for (i = 0; i < MAX_PLAYERS; i++)
11182 if (stored_player[i].active)
11183 PlayPlayerSound(&stored_player[i]);
11186 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11188 boolean last_waiting = player->is_waiting;
11189 int move_dir = player->MovDir;
11191 player->dir_waiting = move_dir;
11192 player->last_action_waiting = player->action_waiting;
11196 if (!last_waiting) // not waiting -> waiting
11198 player->is_waiting = TRUE;
11200 player->frame_counter_bored =
11202 game.player_boring_delay_fixed +
11203 GetSimpleRandom(game.player_boring_delay_random);
11204 player->frame_counter_sleeping =
11206 game.player_sleeping_delay_fixed +
11207 GetSimpleRandom(game.player_sleeping_delay_random);
11209 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11212 if (game.player_sleeping_delay_fixed +
11213 game.player_sleeping_delay_random > 0 &&
11214 player->anim_delay_counter == 0 &&
11215 player->post_delay_counter == 0 &&
11216 FrameCounter >= player->frame_counter_sleeping)
11217 player->is_sleeping = TRUE;
11218 else if (game.player_boring_delay_fixed +
11219 game.player_boring_delay_random > 0 &&
11220 FrameCounter >= player->frame_counter_bored)
11221 player->is_bored = TRUE;
11223 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11224 player->is_bored ? ACTION_BORING :
11227 if (player->is_sleeping && player->use_murphy)
11229 // special case for sleeping Murphy when leaning against non-free tile
11231 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11232 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11233 !IS_MOVING(player->jx - 1, player->jy)))
11234 move_dir = MV_LEFT;
11235 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11236 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11237 !IS_MOVING(player->jx + 1, player->jy)))
11238 move_dir = MV_RIGHT;
11240 player->is_sleeping = FALSE;
11242 player->dir_waiting = move_dir;
11245 if (player->is_sleeping)
11247 if (player->num_special_action_sleeping > 0)
11249 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11251 int last_special_action = player->special_action_sleeping;
11252 int num_special_action = player->num_special_action_sleeping;
11253 int special_action =
11254 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11255 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11256 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11257 last_special_action + 1 : ACTION_SLEEPING);
11258 int special_graphic =
11259 el_act_dir2img(player->artwork_element, special_action, move_dir);
11261 player->anim_delay_counter =
11262 graphic_info[special_graphic].anim_delay_fixed +
11263 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11264 player->post_delay_counter =
11265 graphic_info[special_graphic].post_delay_fixed +
11266 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11268 player->special_action_sleeping = special_action;
11271 if (player->anim_delay_counter > 0)
11273 player->action_waiting = player->special_action_sleeping;
11274 player->anim_delay_counter--;
11276 else if (player->post_delay_counter > 0)
11278 player->post_delay_counter--;
11282 else if (player->is_bored)
11284 if (player->num_special_action_bored > 0)
11286 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11288 int special_action =
11289 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11290 int special_graphic =
11291 el_act_dir2img(player->artwork_element, special_action, move_dir);
11293 player->anim_delay_counter =
11294 graphic_info[special_graphic].anim_delay_fixed +
11295 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11296 player->post_delay_counter =
11297 graphic_info[special_graphic].post_delay_fixed +
11298 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11300 player->special_action_bored = special_action;
11303 if (player->anim_delay_counter > 0)
11305 player->action_waiting = player->special_action_bored;
11306 player->anim_delay_counter--;
11308 else if (player->post_delay_counter > 0)
11310 player->post_delay_counter--;
11315 else if (last_waiting) // waiting -> not waiting
11317 player->is_waiting = FALSE;
11318 player->is_bored = FALSE;
11319 player->is_sleeping = FALSE;
11321 player->frame_counter_bored = -1;
11322 player->frame_counter_sleeping = -1;
11324 player->anim_delay_counter = 0;
11325 player->post_delay_counter = 0;
11327 player->dir_waiting = player->MovDir;
11328 player->action_waiting = ACTION_DEFAULT;
11330 player->special_action_bored = ACTION_DEFAULT;
11331 player->special_action_sleeping = ACTION_DEFAULT;
11335 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11337 if ((!player->is_moving && player->was_moving) ||
11338 (player->MovPos == 0 && player->was_moving) ||
11339 (player->is_snapping && !player->was_snapping) ||
11340 (player->is_dropping && !player->was_dropping))
11342 if (!CheckSaveEngineSnapshotToList())
11345 player->was_moving = FALSE;
11346 player->was_snapping = TRUE;
11347 player->was_dropping = TRUE;
11351 if (player->is_moving)
11352 player->was_moving = TRUE;
11354 if (!player->is_snapping)
11355 player->was_snapping = FALSE;
11357 if (!player->is_dropping)
11358 player->was_dropping = FALSE;
11361 static struct MouseActionInfo mouse_action_last = { 0 };
11362 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11363 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11366 CheckSaveEngineSnapshotToList();
11368 mouse_action_last = mouse_action;
11371 static void CheckSingleStepMode(struct PlayerInfo *player)
11373 if (tape.single_step && tape.recording && !tape.pausing)
11375 // as it is called "single step mode", just return to pause mode when the
11376 // player stopped moving after one tile (or never starts moving at all)
11377 // (reverse logic needed here in case single step mode used in team mode)
11378 if (player->is_moving ||
11379 player->is_pushing ||
11380 player->is_dropping_pressed ||
11381 player->effective_mouse_action.button)
11382 game.enter_single_step_mode = FALSE;
11385 CheckSaveEngineSnapshot(player);
11388 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11390 int left = player_action & JOY_LEFT;
11391 int right = player_action & JOY_RIGHT;
11392 int up = player_action & JOY_UP;
11393 int down = player_action & JOY_DOWN;
11394 int button1 = player_action & JOY_BUTTON_1;
11395 int button2 = player_action & JOY_BUTTON_2;
11396 int dx = (left ? -1 : right ? 1 : 0);
11397 int dy = (up ? -1 : down ? 1 : 0);
11399 if (!player->active || tape.pausing)
11405 SnapField(player, dx, dy);
11409 DropElement(player);
11411 MovePlayer(player, dx, dy);
11414 CheckSingleStepMode(player);
11416 SetPlayerWaiting(player, FALSE);
11418 return player_action;
11422 // no actions for this player (no input at player's configured device)
11424 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11425 SnapField(player, 0, 0);
11426 CheckGravityMovementWhenNotMoving(player);
11428 if (player->MovPos == 0)
11429 SetPlayerWaiting(player, TRUE);
11431 if (player->MovPos == 0) // needed for tape.playing
11432 player->is_moving = FALSE;
11434 player->is_dropping = FALSE;
11435 player->is_dropping_pressed = FALSE;
11436 player->drop_pressed_delay = 0;
11438 CheckSingleStepMode(player);
11444 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11447 if (!tape.use_mouse_actions)
11450 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11451 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11452 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11455 static void SetTapeActionFromMouseAction(byte *tape_action,
11456 struct MouseActionInfo *mouse_action)
11458 if (!tape.use_mouse_actions)
11461 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11462 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11463 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11466 static void CheckLevelSolved(void)
11468 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11470 if (game_em.level_solved &&
11471 !game_em.game_over) // game won
11475 game_em.game_over = TRUE;
11477 game.all_players_gone = TRUE;
11480 if (game_em.game_over) // game lost
11481 game.all_players_gone = TRUE;
11483 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11485 if (game_sp.level_solved &&
11486 !game_sp.game_over) // game won
11490 game_sp.game_over = TRUE;
11492 game.all_players_gone = TRUE;
11495 if (game_sp.game_over) // game lost
11496 game.all_players_gone = TRUE;
11498 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11500 if (game_mm.level_solved &&
11501 !game_mm.game_over) // game won
11505 game_mm.game_over = TRUE;
11507 game.all_players_gone = TRUE;
11510 if (game_mm.game_over) // game lost
11511 game.all_players_gone = TRUE;
11515 static void CheckLevelTime(void)
11519 if (TimeFrames >= FRAMES_PER_SECOND)
11524 for (i = 0; i < MAX_PLAYERS; i++)
11526 struct PlayerInfo *player = &stored_player[i];
11528 if (SHIELD_ON(player))
11530 player->shield_normal_time_left--;
11532 if (player->shield_deadly_time_left > 0)
11533 player->shield_deadly_time_left--;
11537 if (!game.LevelSolved && !level.use_step_counter)
11545 if (TimeLeft <= 10 && setup.time_limit)
11546 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11548 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11549 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11551 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11553 if (!TimeLeft && setup.time_limit)
11555 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11556 game_em.lev->killed_out_of_time = TRUE;
11558 for (i = 0; i < MAX_PLAYERS; i++)
11559 KillPlayer(&stored_player[i]);
11562 else if (game.no_time_limit && !game.all_players_gone)
11564 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11567 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11570 if (tape.recording || tape.playing)
11571 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11574 if (tape.recording || tape.playing)
11575 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11577 UpdateAndDisplayGameControlValues();
11580 void AdvanceFrameAndPlayerCounters(int player_nr)
11584 // advance frame counters (global frame counter and time frame counter)
11588 // advance player counters (counters for move delay, move animation etc.)
11589 for (i = 0; i < MAX_PLAYERS; i++)
11591 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11592 int move_delay_value = stored_player[i].move_delay_value;
11593 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11595 if (!advance_player_counters) // not all players may be affected
11598 if (move_frames == 0) // less than one move per game frame
11600 int stepsize = TILEX / move_delay_value;
11601 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11602 int count = (stored_player[i].is_moving ?
11603 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11605 if (count % delay == 0)
11609 stored_player[i].Frame += move_frames;
11611 if (stored_player[i].MovPos != 0)
11612 stored_player[i].StepFrame += move_frames;
11614 if (stored_player[i].move_delay > 0)
11615 stored_player[i].move_delay--;
11617 // due to bugs in previous versions, counter must count up, not down
11618 if (stored_player[i].push_delay != -1)
11619 stored_player[i].push_delay++;
11621 if (stored_player[i].drop_delay > 0)
11622 stored_player[i].drop_delay--;
11624 if (stored_player[i].is_dropping_pressed)
11625 stored_player[i].drop_pressed_delay++;
11629 void StartGameActions(boolean init_network_game, boolean record_tape,
11632 unsigned int new_random_seed = InitRND(random_seed);
11635 TapeStartRecording(new_random_seed);
11637 if (init_network_game)
11639 SendToServer_LevelFile();
11640 SendToServer_StartPlaying();
11648 static void GameActionsExt(void)
11651 static unsigned int game_frame_delay = 0;
11653 unsigned int game_frame_delay_value;
11654 byte *recorded_player_action;
11655 byte summarized_player_action = 0;
11656 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11659 // detect endless loops, caused by custom element programming
11660 if (recursion_loop_detected && recursion_loop_depth == 0)
11662 char *message = getStringCat3("Internal Error! Element ",
11663 EL_NAME(recursion_loop_element),
11664 " caused endless loop! Quit the game?");
11666 Warn("element '%s' caused endless loop in game engine",
11667 EL_NAME(recursion_loop_element));
11669 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11671 recursion_loop_detected = FALSE; // if game should be continued
11678 if (game.restart_level)
11679 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11681 CheckLevelSolved();
11683 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11686 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11689 if (game_status != GAME_MODE_PLAYING) // status might have changed
11692 game_frame_delay_value =
11693 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11695 if (tape.playing && tape.warp_forward && !tape.pausing)
11696 game_frame_delay_value = 0;
11698 SetVideoFrameDelay(game_frame_delay_value);
11700 // (de)activate virtual buttons depending on current game status
11701 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11703 if (game.all_players_gone) // if no players there to be controlled anymore
11704 SetOverlayActive(FALSE);
11705 else if (!tape.playing) // if game continues after tape stopped playing
11706 SetOverlayActive(TRUE);
11711 // ---------- main game synchronization point ----------
11713 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11715 Debug("game:playing:skip", "skip == %d", skip);
11718 // ---------- main game synchronization point ----------
11720 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11724 if (network_playing && !network_player_action_received)
11726 // try to get network player actions in time
11728 // last chance to get network player actions without main loop delay
11729 HandleNetworking();
11731 // game was quit by network peer
11732 if (game_status != GAME_MODE_PLAYING)
11735 // check if network player actions still missing and game still running
11736 if (!network_player_action_received && !checkGameEnded())
11737 return; // failed to get network player actions in time
11739 // do not yet reset "network_player_action_received" (for tape.pausing)
11745 // at this point we know that we really continue executing the game
11747 network_player_action_received = FALSE;
11749 // when playing tape, read previously recorded player input from tape data
11750 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11752 local_player->effective_mouse_action = local_player->mouse_action;
11754 if (recorded_player_action != NULL)
11755 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11756 recorded_player_action);
11758 // TapePlayAction() may return NULL when toggling to "pause before death"
11762 if (tape.set_centered_player)
11764 game.centered_player_nr_next = tape.centered_player_nr_next;
11765 game.set_centered_player = TRUE;
11768 for (i = 0; i < MAX_PLAYERS; i++)
11770 summarized_player_action |= stored_player[i].action;
11772 if (!network_playing && (game.team_mode || tape.playing))
11773 stored_player[i].effective_action = stored_player[i].action;
11776 if (network_playing && !checkGameEnded())
11777 SendToServer_MovePlayer(summarized_player_action);
11779 // summarize all actions at local players mapped input device position
11780 // (this allows using different input devices in single player mode)
11781 if (!network.enabled && !game.team_mode)
11782 stored_player[map_player_action[local_player->index_nr]].effective_action =
11783 summarized_player_action;
11785 // summarize all actions at centered player in local team mode
11786 if (tape.recording &&
11787 setup.team_mode && !network.enabled &&
11788 setup.input_on_focus &&
11789 game.centered_player_nr != -1)
11791 for (i = 0; i < MAX_PLAYERS; i++)
11792 stored_player[map_player_action[i]].effective_action =
11793 (i == game.centered_player_nr ? summarized_player_action : 0);
11796 if (recorded_player_action != NULL)
11797 for (i = 0; i < MAX_PLAYERS; i++)
11798 stored_player[i].effective_action = recorded_player_action[i];
11800 for (i = 0; i < MAX_PLAYERS; i++)
11802 tape_action[i] = stored_player[i].effective_action;
11804 /* (this may happen in the RND game engine if a player was not present on
11805 the playfield on level start, but appeared later from a custom element */
11806 if (setup.team_mode &&
11809 !tape.player_participates[i])
11810 tape.player_participates[i] = TRUE;
11813 SetTapeActionFromMouseAction(tape_action,
11814 &local_player->effective_mouse_action);
11816 // only record actions from input devices, but not programmed actions
11817 if (tape.recording)
11818 TapeRecordAction(tape_action);
11820 // remember if game was played (especially after tape stopped playing)
11821 if (!tape.playing && summarized_player_action)
11822 game.GamePlayed = TRUE;
11824 #if USE_NEW_PLAYER_ASSIGNMENTS
11825 // !!! also map player actions in single player mode !!!
11826 // if (game.team_mode)
11829 byte mapped_action[MAX_PLAYERS];
11831 #if DEBUG_PLAYER_ACTIONS
11832 for (i = 0; i < MAX_PLAYERS; i++)
11833 DebugContinued("", "%d, ", stored_player[i].effective_action);
11836 for (i = 0; i < MAX_PLAYERS; i++)
11837 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11839 for (i = 0; i < MAX_PLAYERS; i++)
11840 stored_player[i].effective_action = mapped_action[i];
11842 #if DEBUG_PLAYER_ACTIONS
11843 DebugContinued("", "=> ");
11844 for (i = 0; i < MAX_PLAYERS; i++)
11845 DebugContinued("", "%d, ", stored_player[i].effective_action);
11846 DebugContinued("game:playing:player", "\n");
11849 #if DEBUG_PLAYER_ACTIONS
11852 for (i = 0; i < MAX_PLAYERS; i++)
11853 DebugContinued("", "%d, ", stored_player[i].effective_action);
11854 DebugContinued("game:playing:player", "\n");
11859 for (i = 0; i < MAX_PLAYERS; i++)
11861 // allow engine snapshot in case of changed movement attempt
11862 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11863 (stored_player[i].effective_action & KEY_MOTION))
11864 game.snapshot.changed_action = TRUE;
11866 // allow engine snapshot in case of snapping/dropping attempt
11867 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11868 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11869 game.snapshot.changed_action = TRUE;
11871 game.snapshot.last_action[i] = stored_player[i].effective_action;
11874 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11876 GameActions_EM_Main();
11878 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11880 GameActions_SP_Main();
11882 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11884 GameActions_MM_Main();
11888 GameActions_RND_Main();
11891 BlitScreenToBitmap(backbuffer);
11893 CheckLevelSolved();
11896 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11898 if (global.show_frames_per_second)
11900 static unsigned int fps_counter = 0;
11901 static int fps_frames = 0;
11902 unsigned int fps_delay_ms = Counter() - fps_counter;
11906 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11908 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11911 fps_counter = Counter();
11913 // always draw FPS to screen after FPS value was updated
11914 redraw_mask |= REDRAW_FPS;
11917 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11918 if (GetDrawDeactivationMask() == REDRAW_NONE)
11919 redraw_mask |= REDRAW_FPS;
11923 static void GameActions_CheckSaveEngineSnapshot(void)
11925 if (!game.snapshot.save_snapshot)
11928 // clear flag for saving snapshot _before_ saving snapshot
11929 game.snapshot.save_snapshot = FALSE;
11931 SaveEngineSnapshotToList();
11934 void GameActions(void)
11938 GameActions_CheckSaveEngineSnapshot();
11941 void GameActions_EM_Main(void)
11943 byte effective_action[MAX_PLAYERS];
11944 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11947 for (i = 0; i < MAX_PLAYERS; i++)
11948 effective_action[i] = stored_player[i].effective_action;
11950 GameActions_EM(effective_action, warp_mode);
11953 void GameActions_SP_Main(void)
11955 byte effective_action[MAX_PLAYERS];
11956 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11959 for (i = 0; i < MAX_PLAYERS; i++)
11960 effective_action[i] = stored_player[i].effective_action;
11962 GameActions_SP(effective_action, warp_mode);
11964 for (i = 0; i < MAX_PLAYERS; i++)
11966 if (stored_player[i].force_dropping)
11967 stored_player[i].action |= KEY_BUTTON_DROP;
11969 stored_player[i].force_dropping = FALSE;
11973 void GameActions_MM_Main(void)
11975 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11977 GameActions_MM(local_player->effective_mouse_action, warp_mode);
11980 void GameActions_RND_Main(void)
11985 void GameActions_RND(void)
11987 static struct MouseActionInfo mouse_action_last = { 0 };
11988 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
11989 int magic_wall_x = 0, magic_wall_y = 0;
11990 int i, x, y, element, graphic, last_gfx_frame;
11992 InitPlayfieldScanModeVars();
11994 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
11996 SCAN_PLAYFIELD(x, y)
11998 ChangeCount[x][y] = 0;
11999 ChangeEvent[x][y] = -1;
12003 if (game.set_centered_player)
12005 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12007 // switching to "all players" only possible if all players fit to screen
12008 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12010 game.centered_player_nr_next = game.centered_player_nr;
12011 game.set_centered_player = FALSE;
12014 // do not switch focus to non-existing (or non-active) player
12015 if (game.centered_player_nr_next >= 0 &&
12016 !stored_player[game.centered_player_nr_next].active)
12018 game.centered_player_nr_next = game.centered_player_nr;
12019 game.set_centered_player = FALSE;
12023 if (game.set_centered_player &&
12024 ScreenMovPos == 0) // screen currently aligned at tile position
12028 if (game.centered_player_nr_next == -1)
12030 setScreenCenteredToAllPlayers(&sx, &sy);
12034 sx = stored_player[game.centered_player_nr_next].jx;
12035 sy = stored_player[game.centered_player_nr_next].jy;
12038 game.centered_player_nr = game.centered_player_nr_next;
12039 game.set_centered_player = FALSE;
12041 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12042 DrawGameDoorValues();
12045 // check single step mode (set flag and clear again if any player is active)
12046 game.enter_single_step_mode =
12047 (tape.single_step && tape.recording && !tape.pausing);
12049 for (i = 0; i < MAX_PLAYERS; i++)
12051 int actual_player_action = stored_player[i].effective_action;
12054 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12055 - rnd_equinox_tetrachloride 048
12056 - rnd_equinox_tetrachloride_ii 096
12057 - rnd_emanuel_schmieg 002
12058 - doctor_sloan_ww 001, 020
12060 if (stored_player[i].MovPos == 0)
12061 CheckGravityMovement(&stored_player[i]);
12064 // overwrite programmed action with tape action
12065 if (stored_player[i].programmed_action)
12066 actual_player_action = stored_player[i].programmed_action;
12068 PlayerActions(&stored_player[i], actual_player_action);
12070 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12073 // single step pause mode may already have been toggled by "ScrollPlayer()"
12074 if (game.enter_single_step_mode && !tape.pausing)
12075 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12077 ScrollScreen(NULL, SCROLL_GO_ON);
12079 /* for backwards compatibility, the following code emulates a fixed bug that
12080 occured when pushing elements (causing elements that just made their last
12081 pushing step to already (if possible) make their first falling step in the
12082 same game frame, which is bad); this code is also needed to use the famous
12083 "spring push bug" which is used in older levels and might be wanted to be
12084 used also in newer levels, but in this case the buggy pushing code is only
12085 affecting the "spring" element and no other elements */
12087 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12089 for (i = 0; i < MAX_PLAYERS; i++)
12091 struct PlayerInfo *player = &stored_player[i];
12092 int x = player->jx;
12093 int y = player->jy;
12095 if (player->active && player->is_pushing && player->is_moving &&
12097 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12098 Tile[x][y] == EL_SPRING))
12100 ContinueMoving(x, y);
12102 // continue moving after pushing (this is actually a bug)
12103 if (!IS_MOVING(x, y))
12104 Stop[x][y] = FALSE;
12109 SCAN_PLAYFIELD(x, y)
12111 Last[x][y] = Tile[x][y];
12113 ChangeCount[x][y] = 0;
12114 ChangeEvent[x][y] = -1;
12116 // this must be handled before main playfield loop
12117 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12120 if (MovDelay[x][y] <= 0)
12124 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12127 if (MovDelay[x][y] <= 0)
12129 int element = Store[x][y];
12130 int move_direction = MovDir[x][y];
12131 int player_index_bit = Store2[x][y];
12137 TEST_DrawLevelField(x, y);
12139 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12144 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12146 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12148 Debug("game:playing:GameActions_RND", "This should never happen!");
12150 ChangePage[x][y] = -1;
12154 Stop[x][y] = FALSE;
12155 if (WasJustMoving[x][y] > 0)
12156 WasJustMoving[x][y]--;
12157 if (WasJustFalling[x][y] > 0)
12158 WasJustFalling[x][y]--;
12159 if (CheckCollision[x][y] > 0)
12160 CheckCollision[x][y]--;
12161 if (CheckImpact[x][y] > 0)
12162 CheckImpact[x][y]--;
12166 /* reset finished pushing action (not done in ContinueMoving() to allow
12167 continuous pushing animation for elements with zero push delay) */
12168 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12170 ResetGfxAnimation(x, y);
12171 TEST_DrawLevelField(x, y);
12175 if (IS_BLOCKED(x, y))
12179 Blocked2Moving(x, y, &oldx, &oldy);
12180 if (!IS_MOVING(oldx, oldy))
12182 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12183 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12184 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12185 Debug("game:playing:GameActions_RND", "This should never happen!");
12191 if (mouse_action.button)
12193 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12194 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12196 x = mouse_action.lx;
12197 y = mouse_action.ly;
12198 element = Tile[x][y];
12202 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12203 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12207 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12208 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12212 SCAN_PLAYFIELD(x, y)
12214 element = Tile[x][y];
12215 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12216 last_gfx_frame = GfxFrame[x][y];
12218 ResetGfxFrame(x, y);
12220 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12221 DrawLevelGraphicAnimation(x, y, graphic);
12223 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12224 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12225 ResetRandomAnimationValue(x, y);
12227 SetRandomAnimationValue(x, y);
12229 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12231 if (IS_INACTIVE(element))
12233 if (IS_ANIMATED(graphic))
12234 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12239 // this may take place after moving, so 'element' may have changed
12240 if (IS_CHANGING(x, y) &&
12241 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12243 int page = element_info[element].event_page_nr[CE_DELAY];
12245 HandleElementChange(x, y, page);
12247 element = Tile[x][y];
12248 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12251 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12255 element = Tile[x][y];
12256 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12258 if (IS_ANIMATED(graphic) &&
12259 !IS_MOVING(x, y) &&
12261 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12263 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12264 TEST_DrawTwinkleOnField(x, y);
12266 else if (element == EL_ACID)
12269 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12271 else if ((element == EL_EXIT_OPEN ||
12272 element == EL_EM_EXIT_OPEN ||
12273 element == EL_SP_EXIT_OPEN ||
12274 element == EL_STEEL_EXIT_OPEN ||
12275 element == EL_EM_STEEL_EXIT_OPEN ||
12276 element == EL_SP_TERMINAL ||
12277 element == EL_SP_TERMINAL_ACTIVE ||
12278 element == EL_EXTRA_TIME ||
12279 element == EL_SHIELD_NORMAL ||
12280 element == EL_SHIELD_DEADLY) &&
12281 IS_ANIMATED(graphic))
12282 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12283 else if (IS_MOVING(x, y))
12284 ContinueMoving(x, y);
12285 else if (IS_ACTIVE_BOMB(element))
12286 CheckDynamite(x, y);
12287 else if (element == EL_AMOEBA_GROWING)
12288 AmoebaGrowing(x, y);
12289 else if (element == EL_AMOEBA_SHRINKING)
12290 AmoebaShrinking(x, y);
12292 #if !USE_NEW_AMOEBA_CODE
12293 else if (IS_AMOEBALIVE(element))
12294 AmoebaReproduce(x, y);
12297 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12299 else if (element == EL_EXIT_CLOSED)
12301 else if (element == EL_EM_EXIT_CLOSED)
12303 else if (element == EL_STEEL_EXIT_CLOSED)
12304 CheckExitSteel(x, y);
12305 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12306 CheckExitSteelEM(x, y);
12307 else if (element == EL_SP_EXIT_CLOSED)
12309 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12310 element == EL_EXPANDABLE_STEELWALL_GROWING)
12311 MauerWaechst(x, y);
12312 else if (element == EL_EXPANDABLE_WALL ||
12313 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12314 element == EL_EXPANDABLE_WALL_VERTICAL ||
12315 element == EL_EXPANDABLE_WALL_ANY ||
12316 element == EL_BD_EXPANDABLE_WALL)
12317 MauerAbleger(x, y);
12318 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12319 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12320 element == EL_EXPANDABLE_STEELWALL_ANY)
12321 MauerAblegerStahl(x, y);
12322 else if (element == EL_FLAMES)
12323 CheckForDragon(x, y);
12324 else if (element == EL_EXPLOSION)
12325 ; // drawing of correct explosion animation is handled separately
12326 else if (element == EL_ELEMENT_SNAPPING ||
12327 element == EL_DIAGONAL_SHRINKING ||
12328 element == EL_DIAGONAL_GROWING)
12330 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12332 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12334 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12335 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12337 if (IS_BELT_ACTIVE(element))
12338 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12340 if (game.magic_wall_active)
12342 int jx = local_player->jx, jy = local_player->jy;
12344 // play the element sound at the position nearest to the player
12345 if ((element == EL_MAGIC_WALL_FULL ||
12346 element == EL_MAGIC_WALL_ACTIVE ||
12347 element == EL_MAGIC_WALL_EMPTYING ||
12348 element == EL_BD_MAGIC_WALL_FULL ||
12349 element == EL_BD_MAGIC_WALL_ACTIVE ||
12350 element == EL_BD_MAGIC_WALL_EMPTYING ||
12351 element == EL_DC_MAGIC_WALL_FULL ||
12352 element == EL_DC_MAGIC_WALL_ACTIVE ||
12353 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12354 ABS(x - jx) + ABS(y - jy) <
12355 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12363 #if USE_NEW_AMOEBA_CODE
12364 // new experimental amoeba growth stuff
12365 if (!(FrameCounter % 8))
12367 static unsigned int random = 1684108901;
12369 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12371 x = RND(lev_fieldx);
12372 y = RND(lev_fieldy);
12373 element = Tile[x][y];
12375 if (!IS_PLAYER(x,y) &&
12376 (element == EL_EMPTY ||
12377 CAN_GROW_INTO(element) ||
12378 element == EL_QUICKSAND_EMPTY ||
12379 element == EL_QUICKSAND_FAST_EMPTY ||
12380 element == EL_ACID_SPLASH_LEFT ||
12381 element == EL_ACID_SPLASH_RIGHT))
12383 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12384 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12385 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12386 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12387 Tile[x][y] = EL_AMOEBA_DROP;
12390 random = random * 129 + 1;
12395 game.explosions_delayed = FALSE;
12397 SCAN_PLAYFIELD(x, y)
12399 element = Tile[x][y];
12401 if (ExplodeField[x][y])
12402 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12403 else if (element == EL_EXPLOSION)
12404 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12406 ExplodeField[x][y] = EX_TYPE_NONE;
12409 game.explosions_delayed = TRUE;
12411 if (game.magic_wall_active)
12413 if (!(game.magic_wall_time_left % 4))
12415 int element = Tile[magic_wall_x][magic_wall_y];
12417 if (element == EL_BD_MAGIC_WALL_FULL ||
12418 element == EL_BD_MAGIC_WALL_ACTIVE ||
12419 element == EL_BD_MAGIC_WALL_EMPTYING)
12420 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12421 else if (element == EL_DC_MAGIC_WALL_FULL ||
12422 element == EL_DC_MAGIC_WALL_ACTIVE ||
12423 element == EL_DC_MAGIC_WALL_EMPTYING)
12424 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12426 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12429 if (game.magic_wall_time_left > 0)
12431 game.magic_wall_time_left--;
12433 if (!game.magic_wall_time_left)
12435 SCAN_PLAYFIELD(x, y)
12437 element = Tile[x][y];
12439 if (element == EL_MAGIC_WALL_ACTIVE ||
12440 element == EL_MAGIC_WALL_FULL)
12442 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12443 TEST_DrawLevelField(x, y);
12445 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12446 element == EL_BD_MAGIC_WALL_FULL)
12448 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12449 TEST_DrawLevelField(x, y);
12451 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12452 element == EL_DC_MAGIC_WALL_FULL)
12454 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12455 TEST_DrawLevelField(x, y);
12459 game.magic_wall_active = FALSE;
12464 if (game.light_time_left > 0)
12466 game.light_time_left--;
12468 if (game.light_time_left == 0)
12469 RedrawAllLightSwitchesAndInvisibleElements();
12472 if (game.timegate_time_left > 0)
12474 game.timegate_time_left--;
12476 if (game.timegate_time_left == 0)
12477 CloseAllOpenTimegates();
12480 if (game.lenses_time_left > 0)
12482 game.lenses_time_left--;
12484 if (game.lenses_time_left == 0)
12485 RedrawAllInvisibleElementsForLenses();
12488 if (game.magnify_time_left > 0)
12490 game.magnify_time_left--;
12492 if (game.magnify_time_left == 0)
12493 RedrawAllInvisibleElementsForMagnifier();
12496 for (i = 0; i < MAX_PLAYERS; i++)
12498 struct PlayerInfo *player = &stored_player[i];
12500 if (SHIELD_ON(player))
12502 if (player->shield_deadly_time_left)
12503 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12504 else if (player->shield_normal_time_left)
12505 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12509 #if USE_DELAYED_GFX_REDRAW
12510 SCAN_PLAYFIELD(x, y)
12512 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12514 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12515 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12517 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12518 DrawLevelField(x, y);
12520 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12521 DrawLevelFieldCrumbled(x, y);
12523 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12524 DrawLevelFieldCrumbledNeighbours(x, y);
12526 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12527 DrawTwinkleOnField(x, y);
12530 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12535 PlayAllPlayersSound();
12537 for (i = 0; i < MAX_PLAYERS; i++)
12539 struct PlayerInfo *player = &stored_player[i];
12541 if (player->show_envelope != 0 && (!player->active ||
12542 player->MovPos == 0))
12544 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12546 player->show_envelope = 0;
12550 // use random number generator in every frame to make it less predictable
12551 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12554 mouse_action_last = mouse_action;
12557 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12559 int min_x = x, min_y = y, max_x = x, max_y = y;
12560 int scr_fieldx = getScreenFieldSizeX();
12561 int scr_fieldy = getScreenFieldSizeY();
12564 for (i = 0; i < MAX_PLAYERS; i++)
12566 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12568 if (!stored_player[i].active || &stored_player[i] == player)
12571 min_x = MIN(min_x, jx);
12572 min_y = MIN(min_y, jy);
12573 max_x = MAX(max_x, jx);
12574 max_y = MAX(max_y, jy);
12577 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12580 static boolean AllPlayersInVisibleScreen(void)
12584 for (i = 0; i < MAX_PLAYERS; i++)
12586 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12588 if (!stored_player[i].active)
12591 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12598 void ScrollLevel(int dx, int dy)
12600 int scroll_offset = 2 * TILEX_VAR;
12603 BlitBitmap(drawto_field, drawto_field,
12604 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12605 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12606 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12607 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12608 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12609 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12613 x = (dx == 1 ? BX1 : BX2);
12614 for (y = BY1; y <= BY2; y++)
12615 DrawScreenField(x, y);
12620 y = (dy == 1 ? BY1 : BY2);
12621 for (x = BX1; x <= BX2; x++)
12622 DrawScreenField(x, y);
12625 redraw_mask |= REDRAW_FIELD;
12628 static boolean canFallDown(struct PlayerInfo *player)
12630 int jx = player->jx, jy = player->jy;
12632 return (IN_LEV_FIELD(jx, jy + 1) &&
12633 (IS_FREE(jx, jy + 1) ||
12634 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12635 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12636 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12639 static boolean canPassField(int x, int y, int move_dir)
12641 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12642 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12643 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12644 int nextx = x + dx;
12645 int nexty = y + dy;
12646 int element = Tile[x][y];
12648 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12649 !CAN_MOVE(element) &&
12650 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12651 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12652 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12655 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12657 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12658 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12659 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12663 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12664 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12665 (IS_DIGGABLE(Tile[newx][newy]) ||
12666 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12667 canPassField(newx, newy, move_dir)));
12670 static void CheckGravityMovement(struct PlayerInfo *player)
12672 if (player->gravity && !player->programmed_action)
12674 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12675 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12676 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12677 int jx = player->jx, jy = player->jy;
12678 boolean player_is_moving_to_valid_field =
12679 (!player_is_snapping &&
12680 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12681 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12682 boolean player_can_fall_down = canFallDown(player);
12684 if (player_can_fall_down &&
12685 !player_is_moving_to_valid_field)
12686 player->programmed_action = MV_DOWN;
12690 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12692 return CheckGravityMovement(player);
12694 if (player->gravity && !player->programmed_action)
12696 int jx = player->jx, jy = player->jy;
12697 boolean field_under_player_is_free =
12698 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12699 boolean player_is_standing_on_valid_field =
12700 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12701 (IS_WALKABLE(Tile[jx][jy]) &&
12702 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12704 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12705 player->programmed_action = MV_DOWN;
12710 MovePlayerOneStep()
12711 -----------------------------------------------------------------------------
12712 dx, dy: direction (non-diagonal) to try to move the player to
12713 real_dx, real_dy: direction as read from input device (can be diagonal)
12716 boolean MovePlayerOneStep(struct PlayerInfo *player,
12717 int dx, int dy, int real_dx, int real_dy)
12719 int jx = player->jx, jy = player->jy;
12720 int new_jx = jx + dx, new_jy = jy + dy;
12722 boolean player_can_move = !player->cannot_move;
12724 if (!player->active || (!dx && !dy))
12725 return MP_NO_ACTION;
12727 player->MovDir = (dx < 0 ? MV_LEFT :
12728 dx > 0 ? MV_RIGHT :
12730 dy > 0 ? MV_DOWN : MV_NONE);
12732 if (!IN_LEV_FIELD(new_jx, new_jy))
12733 return MP_NO_ACTION;
12735 if (!player_can_move)
12737 if (player->MovPos == 0)
12739 player->is_moving = FALSE;
12740 player->is_digging = FALSE;
12741 player->is_collecting = FALSE;
12742 player->is_snapping = FALSE;
12743 player->is_pushing = FALSE;
12747 if (!network.enabled && game.centered_player_nr == -1 &&
12748 !AllPlayersInSight(player, new_jx, new_jy))
12749 return MP_NO_ACTION;
12751 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12752 if (can_move != MP_MOVING)
12755 // check if DigField() has caused relocation of the player
12756 if (player->jx != jx || player->jy != jy)
12757 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12759 StorePlayer[jx][jy] = 0;
12760 player->last_jx = jx;
12761 player->last_jy = jy;
12762 player->jx = new_jx;
12763 player->jy = new_jy;
12764 StorePlayer[new_jx][new_jy] = player->element_nr;
12766 if (player->move_delay_value_next != -1)
12768 player->move_delay_value = player->move_delay_value_next;
12769 player->move_delay_value_next = -1;
12773 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12775 player->step_counter++;
12777 PlayerVisit[jx][jy] = FrameCounter;
12779 player->is_moving = TRUE;
12782 // should better be called in MovePlayer(), but this breaks some tapes
12783 ScrollPlayer(player, SCROLL_INIT);
12789 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12791 int jx = player->jx, jy = player->jy;
12792 int old_jx = jx, old_jy = jy;
12793 int moved = MP_NO_ACTION;
12795 if (!player->active)
12800 if (player->MovPos == 0)
12802 player->is_moving = FALSE;
12803 player->is_digging = FALSE;
12804 player->is_collecting = FALSE;
12805 player->is_snapping = FALSE;
12806 player->is_pushing = FALSE;
12812 if (player->move_delay > 0)
12815 player->move_delay = -1; // set to "uninitialized" value
12817 // store if player is automatically moved to next field
12818 player->is_auto_moving = (player->programmed_action != MV_NONE);
12820 // remove the last programmed player action
12821 player->programmed_action = 0;
12823 if (player->MovPos)
12825 // should only happen if pre-1.2 tape recordings are played
12826 // this is only for backward compatibility
12828 int original_move_delay_value = player->move_delay_value;
12831 Debug("game:playing:MovePlayer",
12832 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12836 // scroll remaining steps with finest movement resolution
12837 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12839 while (player->MovPos)
12841 ScrollPlayer(player, SCROLL_GO_ON);
12842 ScrollScreen(NULL, SCROLL_GO_ON);
12844 AdvanceFrameAndPlayerCounters(player->index_nr);
12847 BackToFront_WithFrameDelay(0);
12850 player->move_delay_value = original_move_delay_value;
12853 player->is_active = FALSE;
12855 if (player->last_move_dir & MV_HORIZONTAL)
12857 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12858 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12862 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12863 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12866 if (!moved && !player->is_active)
12868 player->is_moving = FALSE;
12869 player->is_digging = FALSE;
12870 player->is_collecting = FALSE;
12871 player->is_snapping = FALSE;
12872 player->is_pushing = FALSE;
12878 if (moved & MP_MOVING && !ScreenMovPos &&
12879 (player->index_nr == game.centered_player_nr ||
12880 game.centered_player_nr == -1))
12882 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12884 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12886 // actual player has left the screen -- scroll in that direction
12887 if (jx != old_jx) // player has moved horizontally
12888 scroll_x += (jx - old_jx);
12889 else // player has moved vertically
12890 scroll_y += (jy - old_jy);
12894 int offset_raw = game.scroll_delay_value;
12896 if (jx != old_jx) // player has moved horizontally
12898 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12899 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12900 int new_scroll_x = jx - MIDPOSX + offset_x;
12902 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12903 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12904 scroll_x = new_scroll_x;
12906 // don't scroll over playfield boundaries
12907 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12909 // don't scroll more than one field at a time
12910 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12912 // don't scroll against the player's moving direction
12913 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12914 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12915 scroll_x = old_scroll_x;
12917 else // player has moved vertically
12919 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12920 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12921 int new_scroll_y = jy - MIDPOSY + offset_y;
12923 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12924 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12925 scroll_y = new_scroll_y;
12927 // don't scroll over playfield boundaries
12928 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12930 // don't scroll more than one field at a time
12931 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12933 // don't scroll against the player's moving direction
12934 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12935 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12936 scroll_y = old_scroll_y;
12940 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12942 if (!network.enabled && game.centered_player_nr == -1 &&
12943 !AllPlayersInVisibleScreen())
12945 scroll_x = old_scroll_x;
12946 scroll_y = old_scroll_y;
12950 ScrollScreen(player, SCROLL_INIT);
12951 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12956 player->StepFrame = 0;
12958 if (moved & MP_MOVING)
12960 if (old_jx != jx && old_jy == jy)
12961 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
12962 else if (old_jx == jx && old_jy != jy)
12963 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
12965 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
12967 player->last_move_dir = player->MovDir;
12968 player->is_moving = TRUE;
12969 player->is_snapping = FALSE;
12970 player->is_switching = FALSE;
12971 player->is_dropping = FALSE;
12972 player->is_dropping_pressed = FALSE;
12973 player->drop_pressed_delay = 0;
12976 // should better be called here than above, but this breaks some tapes
12977 ScrollPlayer(player, SCROLL_INIT);
12982 CheckGravityMovementWhenNotMoving(player);
12984 player->is_moving = FALSE;
12986 /* at this point, the player is allowed to move, but cannot move right now
12987 (e.g. because of something blocking the way) -- ensure that the player
12988 is also allowed to move in the next frame (in old versions before 3.1.1,
12989 the player was forced to wait again for eight frames before next try) */
12991 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12992 player->move_delay = 0; // allow direct movement in the next frame
12995 if (player->move_delay == -1) // not yet initialized by DigField()
12996 player->move_delay = player->move_delay_value;
12998 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13000 TestIfPlayerTouchesBadThing(jx, jy);
13001 TestIfPlayerTouchesCustomElement(jx, jy);
13004 if (!player->active)
13005 RemovePlayer(player);
13010 void ScrollPlayer(struct PlayerInfo *player, int mode)
13012 int jx = player->jx, jy = player->jy;
13013 int last_jx = player->last_jx, last_jy = player->last_jy;
13014 int move_stepsize = TILEX / player->move_delay_value;
13016 if (!player->active)
13019 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13022 if (mode == SCROLL_INIT)
13024 player->actual_frame_counter = FrameCounter;
13025 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13027 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13028 Tile[last_jx][last_jy] == EL_EMPTY)
13030 int last_field_block_delay = 0; // start with no blocking at all
13031 int block_delay_adjustment = player->block_delay_adjustment;
13033 // if player blocks last field, add delay for exactly one move
13034 if (player->block_last_field)
13036 last_field_block_delay += player->move_delay_value;
13038 // when blocking enabled, prevent moving up despite gravity
13039 if (player->gravity && player->MovDir == MV_UP)
13040 block_delay_adjustment = -1;
13043 // add block delay adjustment (also possible when not blocking)
13044 last_field_block_delay += block_delay_adjustment;
13046 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13047 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13050 if (player->MovPos != 0) // player has not yet reached destination
13053 else if (!FrameReached(&player->actual_frame_counter, 1))
13056 if (player->MovPos != 0)
13058 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13059 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13061 // before DrawPlayer() to draw correct player graphic for this case
13062 if (player->MovPos == 0)
13063 CheckGravityMovement(player);
13066 if (player->MovPos == 0) // player reached destination field
13068 if (player->move_delay_reset_counter > 0)
13070 player->move_delay_reset_counter--;
13072 if (player->move_delay_reset_counter == 0)
13074 // continue with normal speed after quickly moving through gate
13075 HALVE_PLAYER_SPEED(player);
13077 // be able to make the next move without delay
13078 player->move_delay = 0;
13082 player->last_jx = jx;
13083 player->last_jy = jy;
13085 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13086 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13087 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13088 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13089 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13090 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13091 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13092 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13094 ExitPlayer(player);
13096 if (game.players_still_needed == 0 &&
13097 (game.friends_still_needed == 0 ||
13098 IS_SP_ELEMENT(Tile[jx][jy])))
13102 // this breaks one level: "machine", level 000
13104 int move_direction = player->MovDir;
13105 int enter_side = MV_DIR_OPPOSITE(move_direction);
13106 int leave_side = move_direction;
13107 int old_jx = last_jx;
13108 int old_jy = last_jy;
13109 int old_element = Tile[old_jx][old_jy];
13110 int new_element = Tile[jx][jy];
13112 if (IS_CUSTOM_ELEMENT(old_element))
13113 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13115 player->index_bit, leave_side);
13117 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13118 CE_PLAYER_LEAVES_X,
13119 player->index_bit, leave_side);
13121 if (IS_CUSTOM_ELEMENT(new_element))
13122 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13123 player->index_bit, enter_side);
13125 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13126 CE_PLAYER_ENTERS_X,
13127 player->index_bit, enter_side);
13129 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13130 CE_MOVE_OF_X, move_direction);
13133 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13135 TestIfPlayerTouchesBadThing(jx, jy);
13136 TestIfPlayerTouchesCustomElement(jx, jy);
13138 /* needed because pushed element has not yet reached its destination,
13139 so it would trigger a change event at its previous field location */
13140 if (!player->is_pushing)
13141 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13143 if (level.finish_dig_collect &&
13144 (player->is_digging || player->is_collecting))
13146 int last_element = player->last_removed_element;
13147 int move_direction = player->MovDir;
13148 int enter_side = MV_DIR_OPPOSITE(move_direction);
13149 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13150 CE_PLAYER_COLLECTS_X);
13152 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13153 player->index_bit, enter_side);
13155 player->last_removed_element = EL_UNDEFINED;
13158 if (!player->active)
13159 RemovePlayer(player);
13162 if (level.use_step_counter)
13172 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13173 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13175 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13177 DisplayGameControlValues();
13179 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13180 for (i = 0; i < MAX_PLAYERS; i++)
13181 KillPlayer(&stored_player[i]);
13183 else if (game.no_time_limit && !game.all_players_gone)
13185 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13187 DisplayGameControlValues();
13191 if (tape.single_step && tape.recording && !tape.pausing &&
13192 !player->programmed_action)
13193 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13195 if (!player->programmed_action)
13196 CheckSaveEngineSnapshot(player);
13200 void ScrollScreen(struct PlayerInfo *player, int mode)
13202 static unsigned int screen_frame_counter = 0;
13204 if (mode == SCROLL_INIT)
13206 // set scrolling step size according to actual player's moving speed
13207 ScrollStepSize = TILEX / player->move_delay_value;
13209 screen_frame_counter = FrameCounter;
13210 ScreenMovDir = player->MovDir;
13211 ScreenMovPos = player->MovPos;
13212 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13215 else if (!FrameReached(&screen_frame_counter, 1))
13220 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13221 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13222 redraw_mask |= REDRAW_FIELD;
13225 ScreenMovDir = MV_NONE;
13228 void TestIfPlayerTouchesCustomElement(int x, int y)
13230 static int xy[4][2] =
13237 static int trigger_sides[4][2] =
13239 // center side border side
13240 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13241 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13242 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13243 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13245 static int touch_dir[4] =
13247 MV_LEFT | MV_RIGHT,
13252 int center_element = Tile[x][y]; // should always be non-moving!
13255 for (i = 0; i < NUM_DIRECTIONS; i++)
13257 int xx = x + xy[i][0];
13258 int yy = y + xy[i][1];
13259 int center_side = trigger_sides[i][0];
13260 int border_side = trigger_sides[i][1];
13261 int border_element;
13263 if (!IN_LEV_FIELD(xx, yy))
13266 if (IS_PLAYER(x, y)) // player found at center element
13268 struct PlayerInfo *player = PLAYERINFO(x, y);
13270 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13271 border_element = Tile[xx][yy]; // may be moving!
13272 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13273 border_element = Tile[xx][yy];
13274 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13275 border_element = MovingOrBlocked2Element(xx, yy);
13277 continue; // center and border element do not touch
13279 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13280 player->index_bit, border_side);
13281 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13282 CE_PLAYER_TOUCHES_X,
13283 player->index_bit, border_side);
13286 /* use player element that is initially defined in the level playfield,
13287 not the player element that corresponds to the runtime player number
13288 (example: a level that contains EL_PLAYER_3 as the only player would
13289 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13290 int player_element = PLAYERINFO(x, y)->initial_element;
13292 CheckElementChangeBySide(xx, yy, border_element, player_element,
13293 CE_TOUCHING_X, border_side);
13296 else if (IS_PLAYER(xx, yy)) // player found at border element
13298 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13300 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13302 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13303 continue; // center and border element do not touch
13306 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13307 player->index_bit, center_side);
13308 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13309 CE_PLAYER_TOUCHES_X,
13310 player->index_bit, center_side);
13313 /* use player element that is initially defined in the level playfield,
13314 not the player element that corresponds to the runtime player number
13315 (example: a level that contains EL_PLAYER_3 as the only player would
13316 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13317 int player_element = PLAYERINFO(xx, yy)->initial_element;
13319 CheckElementChangeBySide(x, y, center_element, player_element,
13320 CE_TOUCHING_X, center_side);
13328 void TestIfElementTouchesCustomElement(int x, int y)
13330 static int xy[4][2] =
13337 static int trigger_sides[4][2] =
13339 // center side border side
13340 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13341 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13342 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13343 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13345 static int touch_dir[4] =
13347 MV_LEFT | MV_RIGHT,
13352 boolean change_center_element = FALSE;
13353 int center_element = Tile[x][y]; // should always be non-moving!
13354 int border_element_old[NUM_DIRECTIONS];
13357 for (i = 0; i < NUM_DIRECTIONS; i++)
13359 int xx = x + xy[i][0];
13360 int yy = y + xy[i][1];
13361 int border_element;
13363 border_element_old[i] = -1;
13365 if (!IN_LEV_FIELD(xx, yy))
13368 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13369 border_element = Tile[xx][yy]; // may be moving!
13370 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13371 border_element = Tile[xx][yy];
13372 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13373 border_element = MovingOrBlocked2Element(xx, yy);
13375 continue; // center and border element do not touch
13377 border_element_old[i] = border_element;
13380 for (i = 0; i < NUM_DIRECTIONS; i++)
13382 int xx = x + xy[i][0];
13383 int yy = y + xy[i][1];
13384 int center_side = trigger_sides[i][0];
13385 int border_element = border_element_old[i];
13387 if (border_element == -1)
13390 // check for change of border element
13391 CheckElementChangeBySide(xx, yy, border_element, center_element,
13392 CE_TOUCHING_X, center_side);
13394 // (center element cannot be player, so we dont have to check this here)
13397 for (i = 0; i < NUM_DIRECTIONS; i++)
13399 int xx = x + xy[i][0];
13400 int yy = y + xy[i][1];
13401 int border_side = trigger_sides[i][1];
13402 int border_element = border_element_old[i];
13404 if (border_element == -1)
13407 // check for change of center element (but change it only once)
13408 if (!change_center_element)
13409 change_center_element =
13410 CheckElementChangeBySide(x, y, center_element, border_element,
13411 CE_TOUCHING_X, border_side);
13413 if (IS_PLAYER(xx, yy))
13415 /* use player element that is initially defined in the level playfield,
13416 not the player element that corresponds to the runtime player number
13417 (example: a level that contains EL_PLAYER_3 as the only player would
13418 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13419 int player_element = PLAYERINFO(xx, yy)->initial_element;
13421 CheckElementChangeBySide(x, y, center_element, player_element,
13422 CE_TOUCHING_X, border_side);
13427 void TestIfElementHitsCustomElement(int x, int y, int direction)
13429 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13430 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13431 int hitx = x + dx, hity = y + dy;
13432 int hitting_element = Tile[x][y];
13433 int touched_element;
13435 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13438 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13439 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13441 if (IN_LEV_FIELD(hitx, hity))
13443 int opposite_direction = MV_DIR_OPPOSITE(direction);
13444 int hitting_side = direction;
13445 int touched_side = opposite_direction;
13446 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13447 MovDir[hitx][hity] != direction ||
13448 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13454 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13455 CE_HITTING_X, touched_side);
13457 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13458 CE_HIT_BY_X, hitting_side);
13460 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13461 CE_HIT_BY_SOMETHING, opposite_direction);
13463 if (IS_PLAYER(hitx, hity))
13465 /* use player element that is initially defined in the level playfield,
13466 not the player element that corresponds to the runtime player number
13467 (example: a level that contains EL_PLAYER_3 as the only player would
13468 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13469 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13471 CheckElementChangeBySide(x, y, hitting_element, player_element,
13472 CE_HITTING_X, touched_side);
13477 // "hitting something" is also true when hitting the playfield border
13478 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13479 CE_HITTING_SOMETHING, direction);
13482 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13484 int i, kill_x = -1, kill_y = -1;
13486 int bad_element = -1;
13487 static int test_xy[4][2] =
13494 static int test_dir[4] =
13502 for (i = 0; i < NUM_DIRECTIONS; i++)
13504 int test_x, test_y, test_move_dir, test_element;
13506 test_x = good_x + test_xy[i][0];
13507 test_y = good_y + test_xy[i][1];
13509 if (!IN_LEV_FIELD(test_x, test_y))
13513 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13515 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13517 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13518 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13520 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13521 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13525 bad_element = test_element;
13531 if (kill_x != -1 || kill_y != -1)
13533 if (IS_PLAYER(good_x, good_y))
13535 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13537 if (player->shield_deadly_time_left > 0 &&
13538 !IS_INDESTRUCTIBLE(bad_element))
13539 Bang(kill_x, kill_y);
13540 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13541 KillPlayer(player);
13544 Bang(good_x, good_y);
13548 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13550 int i, kill_x = -1, kill_y = -1;
13551 int bad_element = Tile[bad_x][bad_y];
13552 static int test_xy[4][2] =
13559 static int touch_dir[4] =
13561 MV_LEFT | MV_RIGHT,
13566 static int test_dir[4] =
13574 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13577 for (i = 0; i < NUM_DIRECTIONS; i++)
13579 int test_x, test_y, test_move_dir, test_element;
13581 test_x = bad_x + test_xy[i][0];
13582 test_y = bad_y + test_xy[i][1];
13584 if (!IN_LEV_FIELD(test_x, test_y))
13588 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13590 test_element = Tile[test_x][test_y];
13592 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13593 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13595 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13596 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13598 // good thing is player or penguin that does not move away
13599 if (IS_PLAYER(test_x, test_y))
13601 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13603 if (bad_element == EL_ROBOT && player->is_moving)
13604 continue; // robot does not kill player if he is moving
13606 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13608 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13609 continue; // center and border element do not touch
13617 else if (test_element == EL_PENGUIN)
13627 if (kill_x != -1 || kill_y != -1)
13629 if (IS_PLAYER(kill_x, kill_y))
13631 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13633 if (player->shield_deadly_time_left > 0 &&
13634 !IS_INDESTRUCTIBLE(bad_element))
13635 Bang(bad_x, bad_y);
13636 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13637 KillPlayer(player);
13640 Bang(kill_x, kill_y);
13644 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13646 int bad_element = Tile[bad_x][bad_y];
13647 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13648 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13649 int test_x = bad_x + dx, test_y = bad_y + dy;
13650 int test_move_dir, test_element;
13651 int kill_x = -1, kill_y = -1;
13653 if (!IN_LEV_FIELD(test_x, test_y))
13657 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13659 test_element = Tile[test_x][test_y];
13661 if (test_move_dir != bad_move_dir)
13663 // good thing can be player or penguin that does not move away
13664 if (IS_PLAYER(test_x, test_y))
13666 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13668 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13669 player as being hit when he is moving towards the bad thing, because
13670 the "get hit by" condition would be lost after the player stops) */
13671 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13672 return; // player moves away from bad thing
13677 else if (test_element == EL_PENGUIN)
13684 if (kill_x != -1 || kill_y != -1)
13686 if (IS_PLAYER(kill_x, kill_y))
13688 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13690 if (player->shield_deadly_time_left > 0 &&
13691 !IS_INDESTRUCTIBLE(bad_element))
13692 Bang(bad_x, bad_y);
13693 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13694 KillPlayer(player);
13697 Bang(kill_x, kill_y);
13701 void TestIfPlayerTouchesBadThing(int x, int y)
13703 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13706 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13708 TestIfGoodThingHitsBadThing(x, y, move_dir);
13711 void TestIfBadThingTouchesPlayer(int x, int y)
13713 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13716 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13718 TestIfBadThingHitsGoodThing(x, y, move_dir);
13721 void TestIfFriendTouchesBadThing(int x, int y)
13723 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13726 void TestIfBadThingTouchesFriend(int x, int y)
13728 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13731 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13733 int i, kill_x = bad_x, kill_y = bad_y;
13734 static int xy[4][2] =
13742 for (i = 0; i < NUM_DIRECTIONS; i++)
13746 x = bad_x + xy[i][0];
13747 y = bad_y + xy[i][1];
13748 if (!IN_LEV_FIELD(x, y))
13751 element = Tile[x][y];
13752 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13753 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13761 if (kill_x != bad_x || kill_y != bad_y)
13762 Bang(bad_x, bad_y);
13765 void KillPlayer(struct PlayerInfo *player)
13767 int jx = player->jx, jy = player->jy;
13769 if (!player->active)
13773 Debug("game:playing:KillPlayer",
13774 "0: killed == %d, active == %d, reanimated == %d",
13775 player->killed, player->active, player->reanimated);
13778 /* the following code was introduced to prevent an infinite loop when calling
13780 -> CheckTriggeredElementChangeExt()
13781 -> ExecuteCustomElementAction()
13783 -> (infinitely repeating the above sequence of function calls)
13784 which occurs when killing the player while having a CE with the setting
13785 "kill player X when explosion of <player X>"; the solution using a new
13786 field "player->killed" was chosen for backwards compatibility, although
13787 clever use of the fields "player->active" etc. would probably also work */
13789 if (player->killed)
13793 player->killed = TRUE;
13795 // remove accessible field at the player's position
13796 Tile[jx][jy] = EL_EMPTY;
13798 // deactivate shield (else Bang()/Explode() would not work right)
13799 player->shield_normal_time_left = 0;
13800 player->shield_deadly_time_left = 0;
13803 Debug("game:playing:KillPlayer",
13804 "1: killed == %d, active == %d, reanimated == %d",
13805 player->killed, player->active, player->reanimated);
13811 Debug("game:playing:KillPlayer",
13812 "2: killed == %d, active == %d, reanimated == %d",
13813 player->killed, player->active, player->reanimated);
13816 if (player->reanimated) // killed player may have been reanimated
13817 player->killed = player->reanimated = FALSE;
13819 BuryPlayer(player);
13822 static void KillPlayerUnlessEnemyProtected(int x, int y)
13824 if (!PLAYER_ENEMY_PROTECTED(x, y))
13825 KillPlayer(PLAYERINFO(x, y));
13828 static void KillPlayerUnlessExplosionProtected(int x, int y)
13830 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13831 KillPlayer(PLAYERINFO(x, y));
13834 void BuryPlayer(struct PlayerInfo *player)
13836 int jx = player->jx, jy = player->jy;
13838 if (!player->active)
13841 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13842 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13844 RemovePlayer(player);
13846 player->buried = TRUE;
13848 if (game.all_players_gone)
13849 game.GameOver = TRUE;
13852 void RemovePlayer(struct PlayerInfo *player)
13854 int jx = player->jx, jy = player->jy;
13855 int i, found = FALSE;
13857 player->present = FALSE;
13858 player->active = FALSE;
13860 // required for some CE actions (even if the player is not active anymore)
13861 player->MovPos = 0;
13863 if (!ExplodeField[jx][jy])
13864 StorePlayer[jx][jy] = 0;
13866 if (player->is_moving)
13867 TEST_DrawLevelField(player->last_jx, player->last_jy);
13869 for (i = 0; i < MAX_PLAYERS; i++)
13870 if (stored_player[i].active)
13875 game.all_players_gone = TRUE;
13876 game.GameOver = TRUE;
13879 game.exit_x = game.robot_wheel_x = jx;
13880 game.exit_y = game.robot_wheel_y = jy;
13883 void ExitPlayer(struct PlayerInfo *player)
13885 DrawPlayer(player); // needed here only to cleanup last field
13886 RemovePlayer(player);
13888 if (game.players_still_needed > 0)
13889 game.players_still_needed--;
13892 static void SetFieldForSnapping(int x, int y, int element, int direction,
13893 int player_index_bit)
13895 struct ElementInfo *ei = &element_info[element];
13896 int direction_bit = MV_DIR_TO_BIT(direction);
13897 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13898 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13899 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13901 Tile[x][y] = EL_ELEMENT_SNAPPING;
13902 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13903 MovDir[x][y] = direction;
13904 Store[x][y] = element;
13905 Store2[x][y] = player_index_bit;
13907 ResetGfxAnimation(x, y);
13909 GfxElement[x][y] = element;
13910 GfxAction[x][y] = action;
13911 GfxDir[x][y] = direction;
13912 GfxFrame[x][y] = -1;
13915 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13916 int player_index_bit)
13918 TestIfElementTouchesCustomElement(x, y); // for empty space
13920 if (level.finish_dig_collect)
13922 int dig_side = MV_DIR_OPPOSITE(direction);
13924 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13925 player_index_bit, dig_side);
13930 =============================================================================
13931 checkDiagonalPushing()
13932 -----------------------------------------------------------------------------
13933 check if diagonal input device direction results in pushing of object
13934 (by checking if the alternative direction is walkable, diggable, ...)
13935 =============================================================================
13938 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13939 int x, int y, int real_dx, int real_dy)
13941 int jx, jy, dx, dy, xx, yy;
13943 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13946 // diagonal direction: check alternative direction
13951 xx = jx + (dx == 0 ? real_dx : 0);
13952 yy = jy + (dy == 0 ? real_dy : 0);
13954 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13958 =============================================================================
13960 -----------------------------------------------------------------------------
13961 x, y: field next to player (non-diagonal) to try to dig to
13962 real_dx, real_dy: direction as read from input device (can be diagonal)
13963 =============================================================================
13966 static int DigField(struct PlayerInfo *player,
13967 int oldx, int oldy, int x, int y,
13968 int real_dx, int real_dy, int mode)
13970 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
13971 boolean player_was_pushing = player->is_pushing;
13972 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
13973 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
13974 int jx = oldx, jy = oldy;
13975 int dx = x - jx, dy = y - jy;
13976 int nextx = x + dx, nexty = y + dy;
13977 int move_direction = (dx == -1 ? MV_LEFT :
13978 dx == +1 ? MV_RIGHT :
13980 dy == +1 ? MV_DOWN : MV_NONE);
13981 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
13982 int dig_side = MV_DIR_OPPOSITE(move_direction);
13983 int old_element = Tile[jx][jy];
13984 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
13987 if (is_player) // function can also be called by EL_PENGUIN
13989 if (player->MovPos == 0)
13991 player->is_digging = FALSE;
13992 player->is_collecting = FALSE;
13995 if (player->MovPos == 0) // last pushing move finished
13996 player->is_pushing = FALSE;
13998 if (mode == DF_NO_PUSH) // player just stopped pushing
14000 player->is_switching = FALSE;
14001 player->push_delay = -1;
14003 return MP_NO_ACTION;
14007 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14008 old_element = Back[jx][jy];
14010 // in case of element dropped at player position, check background
14011 else if (Back[jx][jy] != EL_EMPTY &&
14012 game.engine_version >= VERSION_IDENT(2,2,0,0))
14013 old_element = Back[jx][jy];
14015 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14016 return MP_NO_ACTION; // field has no opening in this direction
14018 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14019 return MP_NO_ACTION; // field has no opening in this direction
14021 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14025 Tile[jx][jy] = player->artwork_element;
14026 InitMovingField(jx, jy, MV_DOWN);
14027 Store[jx][jy] = EL_ACID;
14028 ContinueMoving(jx, jy);
14029 BuryPlayer(player);
14031 return MP_DONT_RUN_INTO;
14034 if (player_can_move && DONT_RUN_INTO(element))
14036 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14038 return MP_DONT_RUN_INTO;
14041 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14042 return MP_NO_ACTION;
14044 collect_count = element_info[element].collect_count_initial;
14046 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14047 return MP_NO_ACTION;
14049 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14050 player_can_move = player_can_move_or_snap;
14052 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14053 game.engine_version >= VERSION_IDENT(2,2,0,0))
14055 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14056 player->index_bit, dig_side);
14057 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14058 player->index_bit, dig_side);
14060 if (element == EL_DC_LANDMINE)
14063 if (Tile[x][y] != element) // field changed by snapping
14066 return MP_NO_ACTION;
14069 if (player->gravity && is_player && !player->is_auto_moving &&
14070 canFallDown(player) && move_direction != MV_DOWN &&
14071 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14072 return MP_NO_ACTION; // player cannot walk here due to gravity
14074 if (player_can_move &&
14075 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14077 int sound_element = SND_ELEMENT(element);
14078 int sound_action = ACTION_WALKING;
14080 if (IS_RND_GATE(element))
14082 if (!player->key[RND_GATE_NR(element)])
14083 return MP_NO_ACTION;
14085 else if (IS_RND_GATE_GRAY(element))
14087 if (!player->key[RND_GATE_GRAY_NR(element)])
14088 return MP_NO_ACTION;
14090 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14092 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14093 return MP_NO_ACTION;
14095 else if (element == EL_EXIT_OPEN ||
14096 element == EL_EM_EXIT_OPEN ||
14097 element == EL_EM_EXIT_OPENING ||
14098 element == EL_STEEL_EXIT_OPEN ||
14099 element == EL_EM_STEEL_EXIT_OPEN ||
14100 element == EL_EM_STEEL_EXIT_OPENING ||
14101 element == EL_SP_EXIT_OPEN ||
14102 element == EL_SP_EXIT_OPENING)
14104 sound_action = ACTION_PASSING; // player is passing exit
14106 else if (element == EL_EMPTY)
14108 sound_action = ACTION_MOVING; // nothing to walk on
14111 // play sound from background or player, whatever is available
14112 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14113 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14115 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14117 else if (player_can_move &&
14118 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14120 if (!ACCESS_FROM(element, opposite_direction))
14121 return MP_NO_ACTION; // field not accessible from this direction
14123 if (CAN_MOVE(element)) // only fixed elements can be passed!
14124 return MP_NO_ACTION;
14126 if (IS_EM_GATE(element))
14128 if (!player->key[EM_GATE_NR(element)])
14129 return MP_NO_ACTION;
14131 else if (IS_EM_GATE_GRAY(element))
14133 if (!player->key[EM_GATE_GRAY_NR(element)])
14134 return MP_NO_ACTION;
14136 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14138 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14139 return MP_NO_ACTION;
14141 else if (IS_EMC_GATE(element))
14143 if (!player->key[EMC_GATE_NR(element)])
14144 return MP_NO_ACTION;
14146 else if (IS_EMC_GATE_GRAY(element))
14148 if (!player->key[EMC_GATE_GRAY_NR(element)])
14149 return MP_NO_ACTION;
14151 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14153 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14154 return MP_NO_ACTION;
14156 else if (element == EL_DC_GATE_WHITE ||
14157 element == EL_DC_GATE_WHITE_GRAY ||
14158 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14160 if (player->num_white_keys == 0)
14161 return MP_NO_ACTION;
14163 player->num_white_keys--;
14165 else if (IS_SP_PORT(element))
14167 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14168 element == EL_SP_GRAVITY_PORT_RIGHT ||
14169 element == EL_SP_GRAVITY_PORT_UP ||
14170 element == EL_SP_GRAVITY_PORT_DOWN)
14171 player->gravity = !player->gravity;
14172 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14173 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14174 element == EL_SP_GRAVITY_ON_PORT_UP ||
14175 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14176 player->gravity = TRUE;
14177 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14178 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14179 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14180 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14181 player->gravity = FALSE;
14184 // automatically move to the next field with double speed
14185 player->programmed_action = move_direction;
14187 if (player->move_delay_reset_counter == 0)
14189 player->move_delay_reset_counter = 2; // two double speed steps
14191 DOUBLE_PLAYER_SPEED(player);
14194 PlayLevelSoundAction(x, y, ACTION_PASSING);
14196 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14200 if (mode != DF_SNAP)
14202 GfxElement[x][y] = GFX_ELEMENT(element);
14203 player->is_digging = TRUE;
14206 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14208 // use old behaviour for old levels (digging)
14209 if (!level.finish_dig_collect)
14211 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14212 player->index_bit, dig_side);
14214 // if digging triggered player relocation, finish digging tile
14215 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14216 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14219 if (mode == DF_SNAP)
14221 if (level.block_snap_field)
14222 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14224 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14226 // use old behaviour for old levels (snapping)
14227 if (!level.finish_dig_collect)
14228 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14229 player->index_bit, dig_side);
14232 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14236 if (is_player && mode != DF_SNAP)
14238 GfxElement[x][y] = element;
14239 player->is_collecting = TRUE;
14242 if (element == EL_SPEED_PILL)
14244 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14246 else if (element == EL_EXTRA_TIME && level.time > 0)
14248 TimeLeft += level.extra_time;
14250 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14252 DisplayGameControlValues();
14254 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14256 player->shield_normal_time_left += level.shield_normal_time;
14257 if (element == EL_SHIELD_DEADLY)
14258 player->shield_deadly_time_left += level.shield_deadly_time;
14260 else if (element == EL_DYNAMITE ||
14261 element == EL_EM_DYNAMITE ||
14262 element == EL_SP_DISK_RED)
14264 if (player->inventory_size < MAX_INVENTORY_SIZE)
14265 player->inventory_element[player->inventory_size++] = element;
14267 DrawGameDoorValues();
14269 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14271 player->dynabomb_count++;
14272 player->dynabombs_left++;
14274 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14276 player->dynabomb_size++;
14278 else if (element == EL_DYNABOMB_INCREASE_POWER)
14280 player->dynabomb_xl = TRUE;
14282 else if (IS_KEY(element))
14284 player->key[KEY_NR(element)] = TRUE;
14286 DrawGameDoorValues();
14288 else if (element == EL_DC_KEY_WHITE)
14290 player->num_white_keys++;
14292 // display white keys?
14293 // DrawGameDoorValues();
14295 else if (IS_ENVELOPE(element))
14297 player->show_envelope = element;
14299 else if (element == EL_EMC_LENSES)
14301 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14303 RedrawAllInvisibleElementsForLenses();
14305 else if (element == EL_EMC_MAGNIFIER)
14307 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14309 RedrawAllInvisibleElementsForMagnifier();
14311 else if (IS_DROPPABLE(element) ||
14312 IS_THROWABLE(element)) // can be collected and dropped
14316 if (collect_count == 0)
14317 player->inventory_infinite_element = element;
14319 for (i = 0; i < collect_count; i++)
14320 if (player->inventory_size < MAX_INVENTORY_SIZE)
14321 player->inventory_element[player->inventory_size++] = element;
14323 DrawGameDoorValues();
14325 else if (collect_count > 0)
14327 game.gems_still_needed -= collect_count;
14328 if (game.gems_still_needed < 0)
14329 game.gems_still_needed = 0;
14331 game.snapshot.collected_item = TRUE;
14333 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14335 DisplayGameControlValues();
14338 RaiseScoreElement(element);
14339 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14341 // use old behaviour for old levels (collecting)
14342 if (!level.finish_dig_collect && is_player)
14344 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14345 player->index_bit, dig_side);
14347 // if collecting triggered player relocation, finish collecting tile
14348 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14349 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14352 if (mode == DF_SNAP)
14354 if (level.block_snap_field)
14355 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14357 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14359 // use old behaviour for old levels (snapping)
14360 if (!level.finish_dig_collect)
14361 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14362 player->index_bit, dig_side);
14365 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14367 if (mode == DF_SNAP && element != EL_BD_ROCK)
14368 return MP_NO_ACTION;
14370 if (CAN_FALL(element) && dy)
14371 return MP_NO_ACTION;
14373 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14374 !(element == EL_SPRING && level.use_spring_bug))
14375 return MP_NO_ACTION;
14377 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14378 ((move_direction & MV_VERTICAL &&
14379 ((element_info[element].move_pattern & MV_LEFT &&
14380 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14381 (element_info[element].move_pattern & MV_RIGHT &&
14382 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14383 (move_direction & MV_HORIZONTAL &&
14384 ((element_info[element].move_pattern & MV_UP &&
14385 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14386 (element_info[element].move_pattern & MV_DOWN &&
14387 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14388 return MP_NO_ACTION;
14390 // do not push elements already moving away faster than player
14391 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14392 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14393 return MP_NO_ACTION;
14395 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14397 if (player->push_delay_value == -1 || !player_was_pushing)
14398 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14400 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14402 if (player->push_delay_value == -1)
14403 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14405 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14407 if (!player->is_pushing)
14408 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14411 player->is_pushing = TRUE;
14412 player->is_active = TRUE;
14414 if (!(IN_LEV_FIELD(nextx, nexty) &&
14415 (IS_FREE(nextx, nexty) ||
14416 (IS_SB_ELEMENT(element) &&
14417 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14418 (IS_CUSTOM_ELEMENT(element) &&
14419 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14420 return MP_NO_ACTION;
14422 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14423 return MP_NO_ACTION;
14425 if (player->push_delay == -1) // new pushing; restart delay
14426 player->push_delay = 0;
14428 if (player->push_delay < player->push_delay_value &&
14429 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14430 element != EL_SPRING && element != EL_BALLOON)
14432 // make sure that there is no move delay before next try to push
14433 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14434 player->move_delay = 0;
14436 return MP_NO_ACTION;
14439 if (IS_CUSTOM_ELEMENT(element) &&
14440 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14442 if (!DigFieldByCE(nextx, nexty, element))
14443 return MP_NO_ACTION;
14446 if (IS_SB_ELEMENT(element))
14448 boolean sokoban_task_solved = FALSE;
14450 if (element == EL_SOKOBAN_FIELD_FULL)
14452 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14454 IncrementSokobanFieldsNeeded();
14455 IncrementSokobanObjectsNeeded();
14458 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14460 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14462 DecrementSokobanFieldsNeeded();
14463 DecrementSokobanObjectsNeeded();
14465 // sokoban object was pushed from empty field to sokoban field
14466 if (Back[x][y] == EL_EMPTY)
14467 sokoban_task_solved = TRUE;
14470 Tile[x][y] = EL_SOKOBAN_OBJECT;
14472 if (Back[x][y] == Back[nextx][nexty])
14473 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14474 else if (Back[x][y] != 0)
14475 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14478 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14481 if (sokoban_task_solved &&
14482 game.sokoban_fields_still_needed == 0 &&
14483 game.sokoban_objects_still_needed == 0 &&
14484 (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
14486 game.players_still_needed = 0;
14490 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14494 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14496 InitMovingField(x, y, move_direction);
14497 GfxAction[x][y] = ACTION_PUSHING;
14499 if (mode == DF_SNAP)
14500 ContinueMoving(x, y);
14502 MovPos[x][y] = (dx != 0 ? dx : dy);
14504 Pushed[x][y] = TRUE;
14505 Pushed[nextx][nexty] = TRUE;
14507 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14508 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14510 player->push_delay_value = -1; // get new value later
14512 // check for element change _after_ element has been pushed
14513 if (game.use_change_when_pushing_bug)
14515 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14516 player->index_bit, dig_side);
14517 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14518 player->index_bit, dig_side);
14521 else if (IS_SWITCHABLE(element))
14523 if (PLAYER_SWITCHING(player, x, y))
14525 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14526 player->index_bit, dig_side);
14531 player->is_switching = TRUE;
14532 player->switch_x = x;
14533 player->switch_y = y;
14535 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14537 if (element == EL_ROBOT_WHEEL)
14539 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14541 game.robot_wheel_x = x;
14542 game.robot_wheel_y = y;
14543 game.robot_wheel_active = TRUE;
14545 TEST_DrawLevelField(x, y);
14547 else if (element == EL_SP_TERMINAL)
14551 SCAN_PLAYFIELD(xx, yy)
14553 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14557 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14559 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14561 ResetGfxAnimation(xx, yy);
14562 TEST_DrawLevelField(xx, yy);
14566 else if (IS_BELT_SWITCH(element))
14568 ToggleBeltSwitch(x, y);
14570 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14571 element == EL_SWITCHGATE_SWITCH_DOWN ||
14572 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14573 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14575 ToggleSwitchgateSwitch(x, y);
14577 else if (element == EL_LIGHT_SWITCH ||
14578 element == EL_LIGHT_SWITCH_ACTIVE)
14580 ToggleLightSwitch(x, y);
14582 else if (element == EL_TIMEGATE_SWITCH ||
14583 element == EL_DC_TIMEGATE_SWITCH)
14585 ActivateTimegateSwitch(x, y);
14587 else if (element == EL_BALLOON_SWITCH_LEFT ||
14588 element == EL_BALLOON_SWITCH_RIGHT ||
14589 element == EL_BALLOON_SWITCH_UP ||
14590 element == EL_BALLOON_SWITCH_DOWN ||
14591 element == EL_BALLOON_SWITCH_NONE ||
14592 element == EL_BALLOON_SWITCH_ANY)
14594 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14595 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14596 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14597 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14598 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14601 else if (element == EL_LAMP)
14603 Tile[x][y] = EL_LAMP_ACTIVE;
14604 game.lights_still_needed--;
14606 ResetGfxAnimation(x, y);
14607 TEST_DrawLevelField(x, y);
14609 else if (element == EL_TIME_ORB_FULL)
14611 Tile[x][y] = EL_TIME_ORB_EMPTY;
14613 if (level.time > 0 || level.use_time_orb_bug)
14615 TimeLeft += level.time_orb_time;
14616 game.no_time_limit = FALSE;
14618 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14620 DisplayGameControlValues();
14623 ResetGfxAnimation(x, y);
14624 TEST_DrawLevelField(x, y);
14626 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14627 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14631 game.ball_active = !game.ball_active;
14633 SCAN_PLAYFIELD(xx, yy)
14635 int e = Tile[xx][yy];
14637 if (game.ball_active)
14639 if (e == EL_EMC_MAGIC_BALL)
14640 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14641 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14642 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14646 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14647 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14648 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14649 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14654 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14655 player->index_bit, dig_side);
14657 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14658 player->index_bit, dig_side);
14660 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14661 player->index_bit, dig_side);
14667 if (!PLAYER_SWITCHING(player, x, y))
14669 player->is_switching = TRUE;
14670 player->switch_x = x;
14671 player->switch_y = y;
14673 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14674 player->index_bit, dig_side);
14675 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14676 player->index_bit, dig_side);
14678 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14679 player->index_bit, dig_side);
14680 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14681 player->index_bit, dig_side);
14684 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14685 player->index_bit, dig_side);
14686 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14687 player->index_bit, dig_side);
14689 return MP_NO_ACTION;
14692 player->push_delay = -1;
14694 if (is_player) // function can also be called by EL_PENGUIN
14696 if (Tile[x][y] != element) // really digged/collected something
14698 player->is_collecting = !player->is_digging;
14699 player->is_active = TRUE;
14701 player->last_removed_element = element;
14708 static boolean DigFieldByCE(int x, int y, int digging_element)
14710 int element = Tile[x][y];
14712 if (!IS_FREE(x, y))
14714 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14715 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14718 // no element can dig solid indestructible elements
14719 if (IS_INDESTRUCTIBLE(element) &&
14720 !IS_DIGGABLE(element) &&
14721 !IS_COLLECTIBLE(element))
14724 if (AmoebaNr[x][y] &&
14725 (element == EL_AMOEBA_FULL ||
14726 element == EL_BD_AMOEBA ||
14727 element == EL_AMOEBA_GROWING))
14729 AmoebaCnt[AmoebaNr[x][y]]--;
14730 AmoebaCnt2[AmoebaNr[x][y]]--;
14733 if (IS_MOVING(x, y))
14734 RemoveMovingField(x, y);
14738 TEST_DrawLevelField(x, y);
14741 // if digged element was about to explode, prevent the explosion
14742 ExplodeField[x][y] = EX_TYPE_NONE;
14744 PlayLevelSoundAction(x, y, action);
14747 Store[x][y] = EL_EMPTY;
14749 // this makes it possible to leave the removed element again
14750 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14751 Store[x][y] = element;
14756 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14758 int jx = player->jx, jy = player->jy;
14759 int x = jx + dx, y = jy + dy;
14760 int snap_direction = (dx == -1 ? MV_LEFT :
14761 dx == +1 ? MV_RIGHT :
14763 dy == +1 ? MV_DOWN : MV_NONE);
14764 boolean can_continue_snapping = (level.continuous_snapping &&
14765 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14767 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14770 if (!player->active || !IN_LEV_FIELD(x, y))
14778 if (player->MovPos == 0)
14779 player->is_pushing = FALSE;
14781 player->is_snapping = FALSE;
14783 if (player->MovPos == 0)
14785 player->is_moving = FALSE;
14786 player->is_digging = FALSE;
14787 player->is_collecting = FALSE;
14793 // prevent snapping with already pressed snap key when not allowed
14794 if (player->is_snapping && !can_continue_snapping)
14797 player->MovDir = snap_direction;
14799 if (player->MovPos == 0)
14801 player->is_moving = FALSE;
14802 player->is_digging = FALSE;
14803 player->is_collecting = FALSE;
14806 player->is_dropping = FALSE;
14807 player->is_dropping_pressed = FALSE;
14808 player->drop_pressed_delay = 0;
14810 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14813 player->is_snapping = TRUE;
14814 player->is_active = TRUE;
14816 if (player->MovPos == 0)
14818 player->is_moving = FALSE;
14819 player->is_digging = FALSE;
14820 player->is_collecting = FALSE;
14823 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14824 TEST_DrawLevelField(player->last_jx, player->last_jy);
14826 TEST_DrawLevelField(x, y);
14831 static boolean DropElement(struct PlayerInfo *player)
14833 int old_element, new_element;
14834 int dropx = player->jx, dropy = player->jy;
14835 int drop_direction = player->MovDir;
14836 int drop_side = drop_direction;
14837 int drop_element = get_next_dropped_element(player);
14839 /* do not drop an element on top of another element; when holding drop key
14840 pressed without moving, dropped element must move away before the next
14841 element can be dropped (this is especially important if the next element
14842 is dynamite, which can be placed on background for historical reasons) */
14843 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14846 if (IS_THROWABLE(drop_element))
14848 dropx += GET_DX_FROM_DIR(drop_direction);
14849 dropy += GET_DY_FROM_DIR(drop_direction);
14851 if (!IN_LEV_FIELD(dropx, dropy))
14855 old_element = Tile[dropx][dropy]; // old element at dropping position
14856 new_element = drop_element; // default: no change when dropping
14858 // check if player is active, not moving and ready to drop
14859 if (!player->active || player->MovPos || player->drop_delay > 0)
14862 // check if player has anything that can be dropped
14863 if (new_element == EL_UNDEFINED)
14866 // only set if player has anything that can be dropped
14867 player->is_dropping_pressed = TRUE;
14869 // check if drop key was pressed long enough for EM style dynamite
14870 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14873 // check if anything can be dropped at the current position
14874 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14877 // collected custom elements can only be dropped on empty fields
14878 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14881 if (old_element != EL_EMPTY)
14882 Back[dropx][dropy] = old_element; // store old element on this field
14884 ResetGfxAnimation(dropx, dropy);
14885 ResetRandomAnimationValue(dropx, dropy);
14887 if (player->inventory_size > 0 ||
14888 player->inventory_infinite_element != EL_UNDEFINED)
14890 if (player->inventory_size > 0)
14892 player->inventory_size--;
14894 DrawGameDoorValues();
14896 if (new_element == EL_DYNAMITE)
14897 new_element = EL_DYNAMITE_ACTIVE;
14898 else if (new_element == EL_EM_DYNAMITE)
14899 new_element = EL_EM_DYNAMITE_ACTIVE;
14900 else if (new_element == EL_SP_DISK_RED)
14901 new_element = EL_SP_DISK_RED_ACTIVE;
14904 Tile[dropx][dropy] = new_element;
14906 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14907 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14908 el2img(Tile[dropx][dropy]), 0);
14910 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14912 // needed if previous element just changed to "empty" in the last frame
14913 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14915 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14916 player->index_bit, drop_side);
14917 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14919 player->index_bit, drop_side);
14921 TestIfElementTouchesCustomElement(dropx, dropy);
14923 else // player is dropping a dyna bomb
14925 player->dynabombs_left--;
14927 Tile[dropx][dropy] = new_element;
14929 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14930 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14931 el2img(Tile[dropx][dropy]), 0);
14933 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14936 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14937 InitField_WithBug1(dropx, dropy, FALSE);
14939 new_element = Tile[dropx][dropy]; // element might have changed
14941 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14942 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14944 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14945 MovDir[dropx][dropy] = drop_direction;
14947 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14949 // do not cause impact style collision by dropping elements that can fall
14950 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14953 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14954 player->is_dropping = TRUE;
14956 player->drop_pressed_delay = 0;
14957 player->is_dropping_pressed = FALSE;
14959 player->drop_x = dropx;
14960 player->drop_y = dropy;
14965 // ----------------------------------------------------------------------------
14966 // game sound playing functions
14967 // ----------------------------------------------------------------------------
14969 static int *loop_sound_frame = NULL;
14970 static int *loop_sound_volume = NULL;
14972 void InitPlayLevelSound(void)
14974 int num_sounds = getSoundListSize();
14976 checked_free(loop_sound_frame);
14977 checked_free(loop_sound_volume);
14979 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
14980 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
14983 static void PlayLevelSound(int x, int y, int nr)
14985 int sx = SCREENX(x), sy = SCREENY(y);
14986 int volume, stereo_position;
14987 int max_distance = 8;
14988 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
14990 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
14991 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
14994 if (!IN_LEV_FIELD(x, y) ||
14995 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
14996 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
14999 volume = SOUND_MAX_VOLUME;
15001 if (!IN_SCR_FIELD(sx, sy))
15003 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15004 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15006 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15009 stereo_position = (SOUND_MAX_LEFT +
15010 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15011 (SCR_FIELDX + 2 * max_distance));
15013 if (IS_LOOP_SOUND(nr))
15015 /* This assures that quieter loop sounds do not overwrite louder ones,
15016 while restarting sound volume comparison with each new game frame. */
15018 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15021 loop_sound_volume[nr] = volume;
15022 loop_sound_frame[nr] = FrameCounter;
15025 PlaySoundExt(nr, volume, stereo_position, type);
15028 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15030 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15031 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15032 y < LEVELY(BY1) ? LEVELY(BY1) :
15033 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15037 static void PlayLevelSoundAction(int x, int y, int action)
15039 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15042 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15044 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15046 if (sound_effect != SND_UNDEFINED)
15047 PlayLevelSound(x, y, sound_effect);
15050 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15053 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15055 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15056 PlayLevelSound(x, y, sound_effect);
15059 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15061 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15063 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15064 PlayLevelSound(x, y, sound_effect);
15067 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15069 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15071 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15072 StopSound(sound_effect);
15075 static int getLevelMusicNr(void)
15077 if (levelset.music[level_nr] != MUS_UNDEFINED)
15078 return levelset.music[level_nr]; // from config file
15080 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15083 static void FadeLevelSounds(void)
15088 static void FadeLevelMusic(void)
15090 int music_nr = getLevelMusicNr();
15091 char *curr_music = getCurrentlyPlayingMusicFilename();
15092 char *next_music = getMusicInfoEntryFilename(music_nr);
15094 if (!strEqual(curr_music, next_music))
15098 void FadeLevelSoundsAndMusic(void)
15104 static void PlayLevelMusic(void)
15106 int music_nr = getLevelMusicNr();
15107 char *curr_music = getCurrentlyPlayingMusicFilename();
15108 char *next_music = getMusicInfoEntryFilename(music_nr);
15110 if (!strEqual(curr_music, next_music))
15111 PlayMusicLoop(music_nr);
15114 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15116 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15118 int x = xx - offset;
15119 int y = yy - offset;
15124 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15128 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15132 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15136 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15140 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15144 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15148 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15151 case SOUND_android_clone:
15152 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15155 case SOUND_android_move:
15156 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15160 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15164 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15168 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15171 case SOUND_eater_eat:
15172 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15176 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15179 case SOUND_collect:
15180 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15183 case SOUND_diamond:
15184 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15188 // !!! CHECK THIS !!!
15190 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15192 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15196 case SOUND_wonderfall:
15197 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15201 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15205 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15209 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15213 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15217 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15221 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15225 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15229 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15232 case SOUND_exit_open:
15233 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15236 case SOUND_exit_leave:
15237 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15240 case SOUND_dynamite:
15241 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15245 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15249 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15253 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15257 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15261 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15265 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15269 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15274 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15276 int element = map_element_SP_to_RND(element_sp);
15277 int action = map_action_SP_to_RND(action_sp);
15278 int offset = (setup.sp_show_border_elements ? 0 : 1);
15279 int x = xx - offset;
15280 int y = yy - offset;
15282 PlayLevelSoundElementAction(x, y, element, action);
15285 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15287 int element = map_element_MM_to_RND(element_mm);
15288 int action = map_action_MM_to_RND(action_mm);
15290 int x = xx - offset;
15291 int y = yy - offset;
15293 if (!IS_MM_ELEMENT(element))
15294 element = EL_MM_DEFAULT;
15296 PlayLevelSoundElementAction(x, y, element, action);
15299 void PlaySound_MM(int sound_mm)
15301 int sound = map_sound_MM_to_RND(sound_mm);
15303 if (sound == SND_UNDEFINED)
15309 void PlaySoundLoop_MM(int sound_mm)
15311 int sound = map_sound_MM_to_RND(sound_mm);
15313 if (sound == SND_UNDEFINED)
15316 PlaySoundLoop(sound);
15319 void StopSound_MM(int sound_mm)
15321 int sound = map_sound_MM_to_RND(sound_mm);
15323 if (sound == SND_UNDEFINED)
15329 void RaiseScore(int value)
15331 game.score += value;
15333 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15335 DisplayGameControlValues();
15338 void RaiseScoreElement(int element)
15343 case EL_BD_DIAMOND:
15344 case EL_EMERALD_YELLOW:
15345 case EL_EMERALD_RED:
15346 case EL_EMERALD_PURPLE:
15347 case EL_SP_INFOTRON:
15348 RaiseScore(level.score[SC_EMERALD]);
15351 RaiseScore(level.score[SC_DIAMOND]);
15354 RaiseScore(level.score[SC_CRYSTAL]);
15357 RaiseScore(level.score[SC_PEARL]);
15360 case EL_BD_BUTTERFLY:
15361 case EL_SP_ELECTRON:
15362 RaiseScore(level.score[SC_BUG]);
15365 case EL_BD_FIREFLY:
15366 case EL_SP_SNIKSNAK:
15367 RaiseScore(level.score[SC_SPACESHIP]);
15370 case EL_DARK_YAMYAM:
15371 RaiseScore(level.score[SC_YAMYAM]);
15374 RaiseScore(level.score[SC_ROBOT]);
15377 RaiseScore(level.score[SC_PACMAN]);
15380 RaiseScore(level.score[SC_NUT]);
15383 case EL_EM_DYNAMITE:
15384 case EL_SP_DISK_RED:
15385 case EL_DYNABOMB_INCREASE_NUMBER:
15386 case EL_DYNABOMB_INCREASE_SIZE:
15387 case EL_DYNABOMB_INCREASE_POWER:
15388 RaiseScore(level.score[SC_DYNAMITE]);
15390 case EL_SHIELD_NORMAL:
15391 case EL_SHIELD_DEADLY:
15392 RaiseScore(level.score[SC_SHIELD]);
15394 case EL_EXTRA_TIME:
15395 RaiseScore(level.extra_time_score);
15409 case EL_DC_KEY_WHITE:
15410 RaiseScore(level.score[SC_KEY]);
15413 RaiseScore(element_info[element].collect_score);
15418 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15420 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15424 // prevent short reactivation of overlay buttons while closing door
15425 SetOverlayActive(FALSE);
15427 // door may still be open due to skipped or envelope style request
15428 CloseDoor(DOOR_CLOSE_1);
15431 if (network.enabled)
15432 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15436 FadeSkipNextFadeIn();
15438 SetGameStatus(GAME_MODE_MAIN);
15443 else // continue playing the game
15445 if (tape.playing && tape.deactivate_display)
15446 TapeDeactivateDisplayOff(TRUE);
15448 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15450 if (tape.playing && tape.deactivate_display)
15451 TapeDeactivateDisplayOn();
15455 void RequestQuitGame(boolean escape_key_pressed)
15457 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15458 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15459 level_editor_test_game);
15460 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15463 RequestQuitGameExt(skip_request, quick_quit,
15464 "Do you really want to quit the game?");
15467 void RequestRestartGame(char *message)
15469 game.restart_game_message = NULL;
15471 boolean has_started_game = hasStartedNetworkGame();
15472 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15474 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15476 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15480 // needed in case of envelope request to close game panel
15481 CloseDoor(DOOR_CLOSE_1);
15483 SetGameStatus(GAME_MODE_MAIN);
15489 void CheckGameOver(void)
15491 static boolean last_game_over = FALSE;
15492 static int game_over_delay = 0;
15493 int game_over_delay_value = 50;
15494 boolean game_over = checkGameFailed();
15496 // do not handle game over if request dialog is already active
15497 if (game.request_active)
15500 // do not ask to play again if game was never actually played
15501 if (!game.GamePlayed)
15506 last_game_over = FALSE;
15507 game_over_delay = game_over_delay_value;
15512 if (game_over_delay > 0)
15519 if (last_game_over != game_over)
15520 game.restart_game_message = (hasStartedNetworkGame() ?
15521 "Game over! Play it again?" :
15524 last_game_over = game_over;
15527 boolean checkGameSolved(void)
15529 // set for all game engines if level was solved
15530 return game.LevelSolved_GameEnd;
15533 boolean checkGameFailed(void)
15535 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15536 return (game_em.game_over && !game_em.level_solved);
15537 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15538 return (game_sp.game_over && !game_sp.level_solved);
15539 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15540 return (game_mm.game_over && !game_mm.level_solved);
15541 else // GAME_ENGINE_TYPE_RND
15542 return (game.GameOver && !game.LevelSolved);
15545 boolean checkGameEnded(void)
15547 return (checkGameSolved() || checkGameFailed());
15551 // ----------------------------------------------------------------------------
15552 // random generator functions
15553 // ----------------------------------------------------------------------------
15555 unsigned int InitEngineRandom_RND(int seed)
15557 game.num_random_calls = 0;
15559 return InitEngineRandom(seed);
15562 unsigned int RND(int max)
15566 game.num_random_calls++;
15568 return GetEngineRandom(max);
15575 // ----------------------------------------------------------------------------
15576 // game engine snapshot handling functions
15577 // ----------------------------------------------------------------------------
15579 struct EngineSnapshotInfo
15581 // runtime values for custom element collect score
15582 int collect_score[NUM_CUSTOM_ELEMENTS];
15584 // runtime values for group element choice position
15585 int choice_pos[NUM_GROUP_ELEMENTS];
15587 // runtime values for belt position animations
15588 int belt_graphic[4][NUM_BELT_PARTS];
15589 int belt_anim_mode[4][NUM_BELT_PARTS];
15592 static struct EngineSnapshotInfo engine_snapshot_rnd;
15593 static char *snapshot_level_identifier = NULL;
15594 static int snapshot_level_nr = -1;
15596 static void SaveEngineSnapshotValues_RND(void)
15598 static int belt_base_active_element[4] =
15600 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15601 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15602 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15603 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15607 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15609 int element = EL_CUSTOM_START + i;
15611 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15614 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15616 int element = EL_GROUP_START + i;
15618 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15621 for (i = 0; i < 4; i++)
15623 for (j = 0; j < NUM_BELT_PARTS; j++)
15625 int element = belt_base_active_element[i] + j;
15626 int graphic = el2img(element);
15627 int anim_mode = graphic_info[graphic].anim_mode;
15629 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15630 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15635 static void LoadEngineSnapshotValues_RND(void)
15637 unsigned int num_random_calls = game.num_random_calls;
15640 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15642 int element = EL_CUSTOM_START + i;
15644 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15647 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15649 int element = EL_GROUP_START + i;
15651 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15654 for (i = 0; i < 4; i++)
15656 for (j = 0; j < NUM_BELT_PARTS; j++)
15658 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15659 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15661 graphic_info[graphic].anim_mode = anim_mode;
15665 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15667 InitRND(tape.random_seed);
15668 for (i = 0; i < num_random_calls; i++)
15672 if (game.num_random_calls != num_random_calls)
15674 Error("number of random calls out of sync");
15675 Error("number of random calls should be %d", num_random_calls);
15676 Error("number of random calls is %d", game.num_random_calls);
15678 Fail("this should not happen -- please debug");
15682 void FreeEngineSnapshotSingle(void)
15684 FreeSnapshotSingle();
15686 setString(&snapshot_level_identifier, NULL);
15687 snapshot_level_nr = -1;
15690 void FreeEngineSnapshotList(void)
15692 FreeSnapshotList();
15695 static ListNode *SaveEngineSnapshotBuffers(void)
15697 ListNode *buffers = NULL;
15699 // copy some special values to a structure better suited for the snapshot
15701 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15702 SaveEngineSnapshotValues_RND();
15703 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15704 SaveEngineSnapshotValues_EM();
15705 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15706 SaveEngineSnapshotValues_SP(&buffers);
15707 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15708 SaveEngineSnapshotValues_MM(&buffers);
15710 // save values stored in special snapshot structure
15712 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15713 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15714 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15715 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15716 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15717 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15718 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15719 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15721 // save further RND engine values
15723 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15724 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15725 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15727 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15728 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15729 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15730 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15731 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15733 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15734 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15735 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15737 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15739 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15740 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15742 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15743 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15744 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15745 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15746 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15747 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15748 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15749 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15750 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15751 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15752 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15753 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15754 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15755 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15756 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15757 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15758 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15759 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15761 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15762 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15764 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15765 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15766 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15768 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15769 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15771 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15772 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15773 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15774 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15775 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15777 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15778 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15781 ListNode *node = engine_snapshot_list_rnd;
15784 while (node != NULL)
15786 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15791 Debug("game:playing:SaveEngineSnapshotBuffers",
15792 "size of engine snapshot: %d bytes", num_bytes);
15798 void SaveEngineSnapshotSingle(void)
15800 ListNode *buffers = SaveEngineSnapshotBuffers();
15802 // finally save all snapshot buffers to single snapshot
15803 SaveSnapshotSingle(buffers);
15805 // save level identification information
15806 setString(&snapshot_level_identifier, leveldir_current->identifier);
15807 snapshot_level_nr = level_nr;
15810 boolean CheckSaveEngineSnapshotToList(void)
15812 boolean save_snapshot =
15813 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15814 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15815 game.snapshot.changed_action) ||
15816 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15817 game.snapshot.collected_item));
15819 game.snapshot.changed_action = FALSE;
15820 game.snapshot.collected_item = FALSE;
15821 game.snapshot.save_snapshot = save_snapshot;
15823 return save_snapshot;
15826 void SaveEngineSnapshotToList(void)
15828 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15832 ListNode *buffers = SaveEngineSnapshotBuffers();
15834 // finally save all snapshot buffers to snapshot list
15835 SaveSnapshotToList(buffers);
15838 void SaveEngineSnapshotToListInitial(void)
15840 FreeEngineSnapshotList();
15842 SaveEngineSnapshotToList();
15845 static void LoadEngineSnapshotValues(void)
15847 // restore special values from snapshot structure
15849 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15850 LoadEngineSnapshotValues_RND();
15851 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15852 LoadEngineSnapshotValues_EM();
15853 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15854 LoadEngineSnapshotValues_SP();
15855 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15856 LoadEngineSnapshotValues_MM();
15859 void LoadEngineSnapshotSingle(void)
15861 LoadSnapshotSingle();
15863 LoadEngineSnapshotValues();
15866 static void LoadEngineSnapshot_Undo(int steps)
15868 LoadSnapshotFromList_Older(steps);
15870 LoadEngineSnapshotValues();
15873 static void LoadEngineSnapshot_Redo(int steps)
15875 LoadSnapshotFromList_Newer(steps);
15877 LoadEngineSnapshotValues();
15880 boolean CheckEngineSnapshotSingle(void)
15882 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15883 snapshot_level_nr == level_nr);
15886 boolean CheckEngineSnapshotList(void)
15888 return CheckSnapshotList();
15892 // ---------- new game button stuff -------------------------------------------
15899 boolean *setup_value;
15900 boolean allowed_on_tape;
15901 boolean is_touch_button;
15903 } gamebutton_info[NUM_GAME_BUTTONS] =
15906 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15907 GAME_CTRL_ID_STOP, NULL,
15908 TRUE, FALSE, "stop game"
15911 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15912 GAME_CTRL_ID_PAUSE, NULL,
15913 TRUE, FALSE, "pause game"
15916 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15917 GAME_CTRL_ID_PLAY, NULL,
15918 TRUE, FALSE, "play game"
15921 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15922 GAME_CTRL_ID_UNDO, NULL,
15923 TRUE, FALSE, "undo step"
15926 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15927 GAME_CTRL_ID_REDO, NULL,
15928 TRUE, FALSE, "redo step"
15931 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15932 GAME_CTRL_ID_SAVE, NULL,
15933 TRUE, FALSE, "save game"
15936 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15937 GAME_CTRL_ID_PAUSE2, NULL,
15938 TRUE, FALSE, "pause game"
15941 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15942 GAME_CTRL_ID_LOAD, NULL,
15943 TRUE, FALSE, "load game"
15946 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15947 GAME_CTRL_ID_PANEL_STOP, NULL,
15948 FALSE, FALSE, "stop game"
15951 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15952 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15953 FALSE, FALSE, "pause game"
15956 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15957 GAME_CTRL_ID_PANEL_PLAY, NULL,
15958 FALSE, FALSE, "play game"
15961 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
15962 GAME_CTRL_ID_TOUCH_STOP, NULL,
15963 FALSE, TRUE, "stop game"
15966 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
15967 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
15968 FALSE, TRUE, "pause game"
15971 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
15972 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
15973 TRUE, FALSE, "background music on/off"
15976 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
15977 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
15978 TRUE, FALSE, "sound loops on/off"
15981 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
15982 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
15983 TRUE, FALSE, "normal sounds on/off"
15986 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
15987 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
15988 FALSE, FALSE, "background music on/off"
15991 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
15992 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
15993 FALSE, FALSE, "sound loops on/off"
15996 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
15997 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
15998 FALSE, FALSE, "normal sounds on/off"
16002 void CreateGameButtons(void)
16006 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16008 int graphic = gamebutton_info[i].graphic;
16009 struct GraphicInfo *gfx = &graphic_info[graphic];
16010 struct XY *pos = gamebutton_info[i].pos;
16011 struct GadgetInfo *gi;
16014 unsigned int event_mask;
16015 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16016 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16017 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16018 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16019 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16020 int gd_x = gfx->src_x;
16021 int gd_y = gfx->src_y;
16022 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16023 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16024 int gd_xa = gfx->src_x + gfx->active_xoffset;
16025 int gd_ya = gfx->src_y + gfx->active_yoffset;
16026 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16027 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16028 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16029 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16032 if (gfx->bitmap == NULL)
16034 game_gadget[id] = NULL;
16039 if (id == GAME_CTRL_ID_STOP ||
16040 id == GAME_CTRL_ID_PANEL_STOP ||
16041 id == GAME_CTRL_ID_TOUCH_STOP ||
16042 id == GAME_CTRL_ID_PLAY ||
16043 id == GAME_CTRL_ID_PANEL_PLAY ||
16044 id == GAME_CTRL_ID_SAVE ||
16045 id == GAME_CTRL_ID_LOAD)
16047 button_type = GD_TYPE_NORMAL_BUTTON;
16049 event_mask = GD_EVENT_RELEASED;
16051 else if (id == GAME_CTRL_ID_UNDO ||
16052 id == GAME_CTRL_ID_REDO)
16054 button_type = GD_TYPE_NORMAL_BUTTON;
16056 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16060 button_type = GD_TYPE_CHECK_BUTTON;
16061 checked = (gamebutton_info[i].setup_value != NULL ?
16062 *gamebutton_info[i].setup_value : FALSE);
16063 event_mask = GD_EVENT_PRESSED;
16066 gi = CreateGadget(GDI_CUSTOM_ID, id,
16067 GDI_IMAGE_ID, graphic,
16068 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16071 GDI_WIDTH, gfx->width,
16072 GDI_HEIGHT, gfx->height,
16073 GDI_TYPE, button_type,
16074 GDI_STATE, GD_BUTTON_UNPRESSED,
16075 GDI_CHECKED, checked,
16076 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16077 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16078 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16079 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16080 GDI_DIRECT_DRAW, FALSE,
16081 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16082 GDI_EVENT_MASK, event_mask,
16083 GDI_CALLBACK_ACTION, HandleGameButtons,
16087 Fail("cannot create gadget");
16089 game_gadget[id] = gi;
16093 void FreeGameButtons(void)
16097 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16098 FreeGadget(game_gadget[i]);
16101 static void UnmapGameButtonsAtSamePosition(int id)
16105 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16107 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16108 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16109 UnmapGadget(game_gadget[i]);
16112 static void UnmapGameButtonsAtSamePosition_All(void)
16114 if (setup.show_snapshot_buttons)
16116 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16117 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16118 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16122 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16123 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16124 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16126 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16127 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16128 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16132 static void MapGameButtonsAtSamePosition(int id)
16136 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16138 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16139 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16140 MapGadget(game_gadget[i]);
16142 UnmapGameButtonsAtSamePosition_All();
16145 void MapUndoRedoButtons(void)
16147 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16148 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16150 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16151 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16154 void UnmapUndoRedoButtons(void)
16156 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16157 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16159 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16160 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16163 void ModifyPauseButtons(void)
16167 GAME_CTRL_ID_PAUSE,
16168 GAME_CTRL_ID_PAUSE2,
16169 GAME_CTRL_ID_PANEL_PAUSE,
16170 GAME_CTRL_ID_TOUCH_PAUSE,
16175 for (i = 0; ids[i] > -1; i++)
16176 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16179 static void MapGameButtonsExt(boolean on_tape)
16183 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16184 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16185 i != GAME_CTRL_ID_UNDO &&
16186 i != GAME_CTRL_ID_REDO)
16187 MapGadget(game_gadget[i]);
16189 UnmapGameButtonsAtSamePosition_All();
16191 RedrawGameButtons();
16194 static void UnmapGameButtonsExt(boolean on_tape)
16198 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16199 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16200 UnmapGadget(game_gadget[i]);
16203 static void RedrawGameButtonsExt(boolean on_tape)
16207 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16208 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16209 RedrawGadget(game_gadget[i]);
16212 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16217 gi->checked = state;
16220 static void RedrawSoundButtonGadget(int id)
16222 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16223 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16224 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16225 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16226 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16227 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16230 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16231 RedrawGadget(game_gadget[id2]);
16234 void MapGameButtons(void)
16236 MapGameButtonsExt(FALSE);
16239 void UnmapGameButtons(void)
16241 UnmapGameButtonsExt(FALSE);
16244 void RedrawGameButtons(void)
16246 RedrawGameButtonsExt(FALSE);
16249 void MapGameButtonsOnTape(void)
16251 MapGameButtonsExt(TRUE);
16254 void UnmapGameButtonsOnTape(void)
16256 UnmapGameButtonsExt(TRUE);
16259 void RedrawGameButtonsOnTape(void)
16261 RedrawGameButtonsExt(TRUE);
16264 static void GameUndoRedoExt(void)
16266 ClearPlayerAction();
16268 tape.pausing = TRUE;
16271 UpdateAndDisplayGameControlValues();
16273 DrawCompleteVideoDisplay();
16274 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16275 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16276 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16281 static void GameUndo(int steps)
16283 if (!CheckEngineSnapshotList())
16286 LoadEngineSnapshot_Undo(steps);
16291 static void GameRedo(int steps)
16293 if (!CheckEngineSnapshotList())
16296 LoadEngineSnapshot_Redo(steps);
16301 static void HandleGameButtonsExt(int id, int button)
16303 static boolean game_undo_executed = FALSE;
16304 int steps = BUTTON_STEPSIZE(button);
16305 boolean handle_game_buttons =
16306 (game_status == GAME_MODE_PLAYING ||
16307 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16309 if (!handle_game_buttons)
16314 case GAME_CTRL_ID_STOP:
16315 case GAME_CTRL_ID_PANEL_STOP:
16316 case GAME_CTRL_ID_TOUCH_STOP:
16317 if (game_status == GAME_MODE_MAIN)
16323 RequestQuitGame(FALSE);
16327 case GAME_CTRL_ID_PAUSE:
16328 case GAME_CTRL_ID_PAUSE2:
16329 case GAME_CTRL_ID_PANEL_PAUSE:
16330 case GAME_CTRL_ID_TOUCH_PAUSE:
16331 if (network.enabled && game_status == GAME_MODE_PLAYING)
16334 SendToServer_ContinuePlaying();
16336 SendToServer_PausePlaying();
16339 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16341 game_undo_executed = FALSE;
16345 case GAME_CTRL_ID_PLAY:
16346 case GAME_CTRL_ID_PANEL_PLAY:
16347 if (game_status == GAME_MODE_MAIN)
16349 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16351 else if (tape.pausing)
16353 if (network.enabled)
16354 SendToServer_ContinuePlaying();
16356 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16360 case GAME_CTRL_ID_UNDO:
16361 // Important: When using "save snapshot when collecting an item" mode,
16362 // load last (current) snapshot for first "undo" after pressing "pause"
16363 // (else the last-but-one snapshot would be loaded, because the snapshot
16364 // pointer already points to the last snapshot when pressing "pause",
16365 // which is fine for "every step/move" mode, but not for "every collect")
16366 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16367 !game_undo_executed)
16370 game_undo_executed = TRUE;
16375 case GAME_CTRL_ID_REDO:
16379 case GAME_CTRL_ID_SAVE:
16383 case GAME_CTRL_ID_LOAD:
16387 case SOUND_CTRL_ID_MUSIC:
16388 case SOUND_CTRL_ID_PANEL_MUSIC:
16389 if (setup.sound_music)
16391 setup.sound_music = FALSE;
16395 else if (audio.music_available)
16397 setup.sound = setup.sound_music = TRUE;
16399 SetAudioMode(setup.sound);
16401 if (game_status == GAME_MODE_PLAYING)
16405 RedrawSoundButtonGadget(id);
16409 case SOUND_CTRL_ID_LOOPS:
16410 case SOUND_CTRL_ID_PANEL_LOOPS:
16411 if (setup.sound_loops)
16412 setup.sound_loops = FALSE;
16413 else if (audio.loops_available)
16415 setup.sound = setup.sound_loops = TRUE;
16417 SetAudioMode(setup.sound);
16420 RedrawSoundButtonGadget(id);
16424 case SOUND_CTRL_ID_SIMPLE:
16425 case SOUND_CTRL_ID_PANEL_SIMPLE:
16426 if (setup.sound_simple)
16427 setup.sound_simple = FALSE;
16428 else if (audio.sound_available)
16430 setup.sound = setup.sound_simple = TRUE;
16432 SetAudioMode(setup.sound);
16435 RedrawSoundButtonGadget(id);
16444 static void HandleGameButtons(struct GadgetInfo *gi)
16446 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16449 void HandleSoundButtonKeys(Key key)
16451 if (key == setup.shortcut.sound_simple)
16452 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16453 else if (key == setup.shortcut.sound_loops)
16454 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16455 else if (key == setup.shortcut.sound_music)
16456 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);