1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_INIT_PLAYER 1
27 #define DEBUG_PLAYER_ACTIONS 0
30 #define USE_NEW_AMOEBA_CODE FALSE
33 #define USE_QUICKSAND_BD_ROCK_BUGFIX 0
34 #define USE_QUICKSAND_IMPACT_BUGFIX 0
35 #define USE_DELAYED_GFX_REDRAW 0
36 #define USE_NEW_PLAYER_ASSIGNMENTS 1
38 #if USE_DELAYED_GFX_REDRAW
39 #define TEST_DrawLevelField(x, y) \
40 GfxRedraw[x][y] |= GFX_REDRAW_TILE
41 #define TEST_DrawLevelFieldCrumbled(x, y) \
42 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED
43 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
44 GfxRedraw[x][y] |= GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS
45 #define TEST_DrawTwinkleOnField(x, y) \
46 GfxRedraw[x][y] |= GFX_REDRAW_TILE_TWINKLED
48 #define TEST_DrawLevelField(x, y) \
50 #define TEST_DrawLevelFieldCrumbled(x, y) \
51 DrawLevelFieldCrumbled(x, y)
52 #define TEST_DrawLevelFieldCrumbledNeighbours(x, y) \
53 DrawLevelFieldCrumbledNeighbours(x, y)
54 #define TEST_DrawTwinkleOnField(x, y) \
55 DrawTwinkleOnField(x, y)
65 #define MP_NO_ACTION 0
68 #define MP_DONT_RUN_INTO (MP_MOVING | MP_ACTION)
72 #define SCROLL_GO_ON 1
74 // for Bang()/Explode()
75 #define EX_PHASE_START 0
76 #define EX_TYPE_NONE 0
77 #define EX_TYPE_NORMAL (1 << 0)
78 #define EX_TYPE_CENTER (1 << 1)
79 #define EX_TYPE_BORDER (1 << 2)
80 #define EX_TYPE_CROSS (1 << 3)
81 #define EX_TYPE_DYNA (1 << 4)
82 #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER)
84 #define PANEL_OFF() (game.panel.active == FALSE)
85 #define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF())
86 #define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p))
87 #define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p))
89 // game panel display and control definitions
90 #define GAME_PANEL_LEVEL_NUMBER 0
91 #define GAME_PANEL_GEMS 1
92 #define GAME_PANEL_INVENTORY_COUNT 2
93 #define GAME_PANEL_INVENTORY_FIRST_1 3
94 #define GAME_PANEL_INVENTORY_FIRST_2 4
95 #define GAME_PANEL_INVENTORY_FIRST_3 5
96 #define GAME_PANEL_INVENTORY_FIRST_4 6
97 #define GAME_PANEL_INVENTORY_FIRST_5 7
98 #define GAME_PANEL_INVENTORY_FIRST_6 8
99 #define GAME_PANEL_INVENTORY_FIRST_7 9
100 #define GAME_PANEL_INVENTORY_FIRST_8 10
101 #define GAME_PANEL_INVENTORY_LAST_1 11
102 #define GAME_PANEL_INVENTORY_LAST_2 12
103 #define GAME_PANEL_INVENTORY_LAST_3 13
104 #define GAME_PANEL_INVENTORY_LAST_4 14
105 #define GAME_PANEL_INVENTORY_LAST_5 15
106 #define GAME_PANEL_INVENTORY_LAST_6 16
107 #define GAME_PANEL_INVENTORY_LAST_7 17
108 #define GAME_PANEL_INVENTORY_LAST_8 18
109 #define GAME_PANEL_KEY_1 19
110 #define GAME_PANEL_KEY_2 20
111 #define GAME_PANEL_KEY_3 21
112 #define GAME_PANEL_KEY_4 22
113 #define GAME_PANEL_KEY_5 23
114 #define GAME_PANEL_KEY_6 24
115 #define GAME_PANEL_KEY_7 25
116 #define GAME_PANEL_KEY_8 26
117 #define GAME_PANEL_KEY_WHITE 27
118 #define GAME_PANEL_KEY_WHITE_COUNT 28
119 #define GAME_PANEL_SCORE 29
120 #define GAME_PANEL_HIGHSCORE 30
121 #define GAME_PANEL_TIME 31
122 #define GAME_PANEL_TIME_HH 32
123 #define GAME_PANEL_TIME_MM 33
124 #define GAME_PANEL_TIME_SS 34
125 #define GAME_PANEL_TIME_ANIM 35
126 #define GAME_PANEL_HEALTH 36
127 #define GAME_PANEL_HEALTH_ANIM 37
128 #define GAME_PANEL_FRAME 38
129 #define GAME_PANEL_SHIELD_NORMAL 39
130 #define GAME_PANEL_SHIELD_NORMAL_TIME 40
131 #define GAME_PANEL_SHIELD_DEADLY 41
132 #define GAME_PANEL_SHIELD_DEADLY_TIME 42
133 #define GAME_PANEL_EXIT 43
134 #define GAME_PANEL_EMC_MAGIC_BALL 44
135 #define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45
136 #define GAME_PANEL_LIGHT_SWITCH 46
137 #define GAME_PANEL_LIGHT_SWITCH_TIME 47
138 #define GAME_PANEL_TIMEGATE_SWITCH 48
139 #define GAME_PANEL_TIMEGATE_SWITCH_TIME 49
140 #define GAME_PANEL_SWITCHGATE_SWITCH 50
141 #define GAME_PANEL_EMC_LENSES 51
142 #define GAME_PANEL_EMC_LENSES_TIME 52
143 #define GAME_PANEL_EMC_MAGNIFIER 53
144 #define GAME_PANEL_EMC_MAGNIFIER_TIME 54
145 #define GAME_PANEL_BALLOON_SWITCH 55
146 #define GAME_PANEL_DYNABOMB_NUMBER 56
147 #define GAME_PANEL_DYNABOMB_SIZE 57
148 #define GAME_PANEL_DYNABOMB_POWER 58
149 #define GAME_PANEL_PENGUINS 59
150 #define GAME_PANEL_SOKOBAN_OBJECTS 60
151 #define GAME_PANEL_SOKOBAN_FIELDS 61
152 #define GAME_PANEL_ROBOT_WHEEL 62
153 #define GAME_PANEL_CONVEYOR_BELT_1 63
154 #define GAME_PANEL_CONVEYOR_BELT_2 64
155 #define GAME_PANEL_CONVEYOR_BELT_3 65
156 #define GAME_PANEL_CONVEYOR_BELT_4 66
157 #define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67
158 #define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68
159 #define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69
160 #define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70
161 #define GAME_PANEL_MAGIC_WALL 71
162 #define GAME_PANEL_MAGIC_WALL_TIME 72
163 #define GAME_PANEL_GRAVITY_STATE 73
164 #define GAME_PANEL_GRAPHIC_1 74
165 #define GAME_PANEL_GRAPHIC_2 75
166 #define GAME_PANEL_GRAPHIC_3 76
167 #define GAME_PANEL_GRAPHIC_4 77
168 #define GAME_PANEL_GRAPHIC_5 78
169 #define GAME_PANEL_GRAPHIC_6 79
170 #define GAME_PANEL_GRAPHIC_7 80
171 #define GAME_PANEL_GRAPHIC_8 81
172 #define GAME_PANEL_ELEMENT_1 82
173 #define GAME_PANEL_ELEMENT_2 83
174 #define GAME_PANEL_ELEMENT_3 84
175 #define GAME_PANEL_ELEMENT_4 85
176 #define GAME_PANEL_ELEMENT_5 86
177 #define GAME_PANEL_ELEMENT_6 87
178 #define GAME_PANEL_ELEMENT_7 88
179 #define GAME_PANEL_ELEMENT_8 89
180 #define GAME_PANEL_ELEMENT_COUNT_1 90
181 #define GAME_PANEL_ELEMENT_COUNT_2 91
182 #define GAME_PANEL_ELEMENT_COUNT_3 92
183 #define GAME_PANEL_ELEMENT_COUNT_4 93
184 #define GAME_PANEL_ELEMENT_COUNT_5 94
185 #define GAME_PANEL_ELEMENT_COUNT_6 95
186 #define GAME_PANEL_ELEMENT_COUNT_7 96
187 #define GAME_PANEL_ELEMENT_COUNT_8 97
188 #define GAME_PANEL_CE_SCORE_1 98
189 #define GAME_PANEL_CE_SCORE_2 99
190 #define GAME_PANEL_CE_SCORE_3 100
191 #define GAME_PANEL_CE_SCORE_4 101
192 #define GAME_PANEL_CE_SCORE_5 102
193 #define GAME_PANEL_CE_SCORE_6 103
194 #define GAME_PANEL_CE_SCORE_7 104
195 #define GAME_PANEL_CE_SCORE_8 105
196 #define GAME_PANEL_CE_SCORE_1_ELEMENT 106
197 #define GAME_PANEL_CE_SCORE_2_ELEMENT 107
198 #define GAME_PANEL_CE_SCORE_3_ELEMENT 108
199 #define GAME_PANEL_CE_SCORE_4_ELEMENT 109
200 #define GAME_PANEL_CE_SCORE_5_ELEMENT 110
201 #define GAME_PANEL_CE_SCORE_6_ELEMENT 111
202 #define GAME_PANEL_CE_SCORE_7_ELEMENT 112
203 #define GAME_PANEL_CE_SCORE_8_ELEMENT 113
204 #define GAME_PANEL_PLAYER_NAME 114
205 #define GAME_PANEL_LEVEL_NAME 115
206 #define GAME_PANEL_LEVEL_AUTHOR 116
208 #define NUM_GAME_PANEL_CONTROLS 117
210 struct GamePanelOrderInfo
216 static struct GamePanelOrderInfo game_panel_order[NUM_GAME_PANEL_CONTROLS];
218 struct GamePanelControlInfo
222 struct TextPosInfo *pos;
225 int graphic, graphic_active;
227 int value, last_value;
228 int frame, last_frame;
233 static struct GamePanelControlInfo game_panel_controls[] =
236 GAME_PANEL_LEVEL_NUMBER,
237 &game.panel.level_number,
246 GAME_PANEL_INVENTORY_COUNT,
247 &game.panel.inventory_count,
251 GAME_PANEL_INVENTORY_FIRST_1,
252 &game.panel.inventory_first[0],
256 GAME_PANEL_INVENTORY_FIRST_2,
257 &game.panel.inventory_first[1],
261 GAME_PANEL_INVENTORY_FIRST_3,
262 &game.panel.inventory_first[2],
266 GAME_PANEL_INVENTORY_FIRST_4,
267 &game.panel.inventory_first[3],
271 GAME_PANEL_INVENTORY_FIRST_5,
272 &game.panel.inventory_first[4],
276 GAME_PANEL_INVENTORY_FIRST_6,
277 &game.panel.inventory_first[5],
281 GAME_PANEL_INVENTORY_FIRST_7,
282 &game.panel.inventory_first[6],
286 GAME_PANEL_INVENTORY_FIRST_8,
287 &game.panel.inventory_first[7],
291 GAME_PANEL_INVENTORY_LAST_1,
292 &game.panel.inventory_last[0],
296 GAME_PANEL_INVENTORY_LAST_2,
297 &game.panel.inventory_last[1],
301 GAME_PANEL_INVENTORY_LAST_3,
302 &game.panel.inventory_last[2],
306 GAME_PANEL_INVENTORY_LAST_4,
307 &game.panel.inventory_last[3],
311 GAME_PANEL_INVENTORY_LAST_5,
312 &game.panel.inventory_last[4],
316 GAME_PANEL_INVENTORY_LAST_6,
317 &game.panel.inventory_last[5],
321 GAME_PANEL_INVENTORY_LAST_7,
322 &game.panel.inventory_last[6],
326 GAME_PANEL_INVENTORY_LAST_8,
327 &game.panel.inventory_last[7],
371 GAME_PANEL_KEY_WHITE,
372 &game.panel.key_white,
376 GAME_PANEL_KEY_WHITE_COUNT,
377 &game.panel.key_white_count,
386 GAME_PANEL_HIGHSCORE,
387 &game.panel.highscore,
411 GAME_PANEL_TIME_ANIM,
412 &game.panel.time_anim,
415 IMG_GFX_GAME_PANEL_TIME_ANIM,
416 IMG_GFX_GAME_PANEL_TIME_ANIM_ACTIVE
424 GAME_PANEL_HEALTH_ANIM,
425 &game.panel.health_anim,
428 IMG_GFX_GAME_PANEL_HEALTH_ANIM,
429 IMG_GFX_GAME_PANEL_HEALTH_ANIM_ACTIVE
437 GAME_PANEL_SHIELD_NORMAL,
438 &game.panel.shield_normal,
442 GAME_PANEL_SHIELD_NORMAL_TIME,
443 &game.panel.shield_normal_time,
447 GAME_PANEL_SHIELD_DEADLY,
448 &game.panel.shield_deadly,
452 GAME_PANEL_SHIELD_DEADLY_TIME,
453 &game.panel.shield_deadly_time,
462 GAME_PANEL_EMC_MAGIC_BALL,
463 &game.panel.emc_magic_ball,
467 GAME_PANEL_EMC_MAGIC_BALL_SWITCH,
468 &game.panel.emc_magic_ball_switch,
472 GAME_PANEL_LIGHT_SWITCH,
473 &game.panel.light_switch,
477 GAME_PANEL_LIGHT_SWITCH_TIME,
478 &game.panel.light_switch_time,
482 GAME_PANEL_TIMEGATE_SWITCH,
483 &game.panel.timegate_switch,
487 GAME_PANEL_TIMEGATE_SWITCH_TIME,
488 &game.panel.timegate_switch_time,
492 GAME_PANEL_SWITCHGATE_SWITCH,
493 &game.panel.switchgate_switch,
497 GAME_PANEL_EMC_LENSES,
498 &game.panel.emc_lenses,
502 GAME_PANEL_EMC_LENSES_TIME,
503 &game.panel.emc_lenses_time,
507 GAME_PANEL_EMC_MAGNIFIER,
508 &game.panel.emc_magnifier,
512 GAME_PANEL_EMC_MAGNIFIER_TIME,
513 &game.panel.emc_magnifier_time,
517 GAME_PANEL_BALLOON_SWITCH,
518 &game.panel.balloon_switch,
522 GAME_PANEL_DYNABOMB_NUMBER,
523 &game.panel.dynabomb_number,
527 GAME_PANEL_DYNABOMB_SIZE,
528 &game.panel.dynabomb_size,
532 GAME_PANEL_DYNABOMB_POWER,
533 &game.panel.dynabomb_power,
538 &game.panel.penguins,
542 GAME_PANEL_SOKOBAN_OBJECTS,
543 &game.panel.sokoban_objects,
547 GAME_PANEL_SOKOBAN_FIELDS,
548 &game.panel.sokoban_fields,
552 GAME_PANEL_ROBOT_WHEEL,
553 &game.panel.robot_wheel,
557 GAME_PANEL_CONVEYOR_BELT_1,
558 &game.panel.conveyor_belt[0],
562 GAME_PANEL_CONVEYOR_BELT_2,
563 &game.panel.conveyor_belt[1],
567 GAME_PANEL_CONVEYOR_BELT_3,
568 &game.panel.conveyor_belt[2],
572 GAME_PANEL_CONVEYOR_BELT_4,
573 &game.panel.conveyor_belt[3],
577 GAME_PANEL_CONVEYOR_BELT_1_SWITCH,
578 &game.panel.conveyor_belt_switch[0],
582 GAME_PANEL_CONVEYOR_BELT_2_SWITCH,
583 &game.panel.conveyor_belt_switch[1],
587 GAME_PANEL_CONVEYOR_BELT_3_SWITCH,
588 &game.panel.conveyor_belt_switch[2],
592 GAME_PANEL_CONVEYOR_BELT_4_SWITCH,
593 &game.panel.conveyor_belt_switch[3],
597 GAME_PANEL_MAGIC_WALL,
598 &game.panel.magic_wall,
602 GAME_PANEL_MAGIC_WALL_TIME,
603 &game.panel.magic_wall_time,
607 GAME_PANEL_GRAVITY_STATE,
608 &game.panel.gravity_state,
612 GAME_PANEL_GRAPHIC_1,
613 &game.panel.graphic[0],
617 GAME_PANEL_GRAPHIC_2,
618 &game.panel.graphic[1],
622 GAME_PANEL_GRAPHIC_3,
623 &game.panel.graphic[2],
627 GAME_PANEL_GRAPHIC_4,
628 &game.panel.graphic[3],
632 GAME_PANEL_GRAPHIC_5,
633 &game.panel.graphic[4],
637 GAME_PANEL_GRAPHIC_6,
638 &game.panel.graphic[5],
642 GAME_PANEL_GRAPHIC_7,
643 &game.panel.graphic[6],
647 GAME_PANEL_GRAPHIC_8,
648 &game.panel.graphic[7],
652 GAME_PANEL_ELEMENT_1,
653 &game.panel.element[0],
657 GAME_PANEL_ELEMENT_2,
658 &game.panel.element[1],
662 GAME_PANEL_ELEMENT_3,
663 &game.panel.element[2],
667 GAME_PANEL_ELEMENT_4,
668 &game.panel.element[3],
672 GAME_PANEL_ELEMENT_5,
673 &game.panel.element[4],
677 GAME_PANEL_ELEMENT_6,
678 &game.panel.element[5],
682 GAME_PANEL_ELEMENT_7,
683 &game.panel.element[6],
687 GAME_PANEL_ELEMENT_8,
688 &game.panel.element[7],
692 GAME_PANEL_ELEMENT_COUNT_1,
693 &game.panel.element_count[0],
697 GAME_PANEL_ELEMENT_COUNT_2,
698 &game.panel.element_count[1],
702 GAME_PANEL_ELEMENT_COUNT_3,
703 &game.panel.element_count[2],
707 GAME_PANEL_ELEMENT_COUNT_4,
708 &game.panel.element_count[3],
712 GAME_PANEL_ELEMENT_COUNT_5,
713 &game.panel.element_count[4],
717 GAME_PANEL_ELEMENT_COUNT_6,
718 &game.panel.element_count[5],
722 GAME_PANEL_ELEMENT_COUNT_7,
723 &game.panel.element_count[6],
727 GAME_PANEL_ELEMENT_COUNT_8,
728 &game.panel.element_count[7],
732 GAME_PANEL_CE_SCORE_1,
733 &game.panel.ce_score[0],
737 GAME_PANEL_CE_SCORE_2,
738 &game.panel.ce_score[1],
742 GAME_PANEL_CE_SCORE_3,
743 &game.panel.ce_score[2],
747 GAME_PANEL_CE_SCORE_4,
748 &game.panel.ce_score[3],
752 GAME_PANEL_CE_SCORE_5,
753 &game.panel.ce_score[4],
757 GAME_PANEL_CE_SCORE_6,
758 &game.panel.ce_score[5],
762 GAME_PANEL_CE_SCORE_7,
763 &game.panel.ce_score[6],
767 GAME_PANEL_CE_SCORE_8,
768 &game.panel.ce_score[7],
772 GAME_PANEL_CE_SCORE_1_ELEMENT,
773 &game.panel.ce_score_element[0],
777 GAME_PANEL_CE_SCORE_2_ELEMENT,
778 &game.panel.ce_score_element[1],
782 GAME_PANEL_CE_SCORE_3_ELEMENT,
783 &game.panel.ce_score_element[2],
787 GAME_PANEL_CE_SCORE_4_ELEMENT,
788 &game.panel.ce_score_element[3],
792 GAME_PANEL_CE_SCORE_5_ELEMENT,
793 &game.panel.ce_score_element[4],
797 GAME_PANEL_CE_SCORE_6_ELEMENT,
798 &game.panel.ce_score_element[5],
802 GAME_PANEL_CE_SCORE_7_ELEMENT,
803 &game.panel.ce_score_element[6],
807 GAME_PANEL_CE_SCORE_8_ELEMENT,
808 &game.panel.ce_score_element[7],
812 GAME_PANEL_PLAYER_NAME,
813 &game.panel.player_name,
817 GAME_PANEL_LEVEL_NAME,
818 &game.panel.level_name,
822 GAME_PANEL_LEVEL_AUTHOR,
823 &game.panel.level_author,
834 // values for delayed check of falling and moving elements and for collision
835 #define CHECK_DELAY_MOVING 3
836 #define CHECK_DELAY_FALLING CHECK_DELAY_MOVING
837 #define CHECK_DELAY_COLLISION 2
838 #define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION
840 // values for initial player move delay (initial delay counter value)
841 #define INITIAL_MOVE_DELAY_OFF -1
842 #define INITIAL_MOVE_DELAY_ON 0
844 // values for player movement speed (which is in fact a delay value)
845 #define MOVE_DELAY_MIN_SPEED 32
846 #define MOVE_DELAY_NORMAL_SPEED 8
847 #define MOVE_DELAY_HIGH_SPEED 4
848 #define MOVE_DELAY_MAX_SPEED 1
850 #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x))
851 #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x))
853 #define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value))
854 #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value))
856 // values for scroll positions
857 #define SCROLL_POSITION_X(x) ((x) < SBX_Left + MIDPOSX ? SBX_Left : \
858 (x) > SBX_Right + MIDPOSX ? SBX_Right :\
860 #define SCROLL_POSITION_Y(y) ((y) < SBY_Upper + MIDPOSY ? SBY_Upper :\
861 (y) > SBY_Lower + MIDPOSY ? SBY_Lower :\
864 // values for other actions
865 #define MOVE_STEPSIZE_NORMAL (TILEX / MOVE_DELAY_NORMAL_SPEED)
866 #define MOVE_STEPSIZE_MIN (1)
867 #define MOVE_STEPSIZE_MAX (TILEX)
869 #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0)
870 #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0)
872 #define INIT_GFX_RANDOM() (GetSimpleRandom(1000000))
874 #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \
875 RND(element_info[e].push_delay_random))
876 #define GET_NEW_DROP_DELAY(e) ( (element_info[e].drop_delay_fixed) + \
877 RND(element_info[e].drop_delay_random))
878 #define GET_NEW_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
879 RND(element_info[e].move_delay_random))
880 #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \
881 (element_info[e].move_delay_random))
882 #define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
883 RND(element_info[e].step_delay_random))
884 #define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \
885 (element_info[e].step_delay_random))
886 #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\
887 RND(element_info[e].ce_value_random_initial))
888 #define GET_CE_SCORE(e) ( (element_info[e].collect_score))
889 #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \
890 RND((c)->delay_random * (c)->delay_frames))
891 #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \
892 RND((c)->delay_random))
895 #define GET_VALID_RUNTIME_ELEMENT(e) \
896 ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e))
898 #define RESOLVED_REFERENCE_ELEMENT(be, e) \
899 ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \
900 (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \
901 (be) + (e) - EL_SELF)
903 #define GET_PLAYER_FROM_BITS(p) \
904 (EL_PLAYER_1 + ((p) != PLAYER_BITS_ANY ? log_2(p) : 0))
906 #define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \
907 ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \
908 (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \
909 (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \
910 (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \
911 (e) == EL_CURRENT_CE_VALUE ? (cv) : \
912 (e) == EL_CURRENT_CE_SCORE ? (cs) : \
913 (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \
914 RESOLVED_REFERENCE_ELEMENT(be, e) : \
917 #define CAN_GROW_INTO(e) \
918 ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable))
920 #define ELEMENT_CAN_ENTER_FIELD_BASE_X(x, y, condition) \
921 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
924 #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \
925 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
926 (CAN_MOVE_INTO_ACID(e) && \
927 Tile[x][y] == EL_ACID) || \
930 #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \
931 (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \
932 (CAN_MOVE_INTO_ACID(e) && \
933 Tile[x][y] == EL_ACID) || \
936 #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \
937 (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \
939 (CAN_MOVE_INTO_ACID(e) && \
940 Tile[x][y] == EL_ACID) || \
941 (DONT_COLLIDE_WITH(e) && \
943 !PLAYER_ENEMY_PROTECTED(x, y))))
945 #define ELEMENT_CAN_ENTER_FIELD(e, x, y) \
946 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, 0)
948 #define SATELLITE_CAN_ENTER_FIELD(x, y) \
949 ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0)
951 #define ANDROID_CAN_ENTER_FIELD(e, x, y) \
952 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT)
954 #define ANDROID_CAN_CLONE_FIELD(x, y) \
955 (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \
956 CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT)))
958 #define ENEMY_CAN_ENTER_FIELD(e, x, y) \
959 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
961 #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \
962 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND)
964 #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \
965 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y]))
967 #define PACMAN_CAN_ENTER_FIELD(e, x, y) \
968 ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y]))
970 #define PIG_CAN_ENTER_FIELD(e, x, y) \
971 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y]))
973 #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \
974 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \
975 Tile[x][y] == EL_EM_EXIT_OPEN || \
976 Tile[x][y] == EL_STEEL_EXIT_OPEN || \
977 Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \
978 IS_FOOD_PENGUIN(Tile[x][y])))
979 #define DRAGON_CAN_ENTER_FIELD(e, x, y) \
980 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
982 #define MOLE_CAN_ENTER_FIELD(e, x, y, condition) \
983 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (condition))
985 #define SPRING_CAN_ENTER_FIELD(e, x, y) \
986 ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0)
988 #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \
989 (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \
990 Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE))
992 #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element)
994 #define CE_ENTER_FIELD_COND(e, x, y) \
995 (!IS_PLAYER(x, y) && \
996 IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e)))
998 #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \
999 ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y))
1001 #define IN_LEV_FIELD_AND_IS_FREE(x, y) (IN_LEV_FIELD(x, y) && IS_FREE(x, y))
1002 #define IN_LEV_FIELD_AND_NOT_FREE(x, y) (IN_LEV_FIELD(x, y) && !IS_FREE(x, y))
1004 #define ACCESS_FROM(e, d) (element_info[e].access_direction &(d))
1005 #define IS_WALKABLE_FROM(e, d) (IS_WALKABLE(e) && ACCESS_FROM(e, d))
1006 #define IS_PASSABLE_FROM(e, d) (IS_PASSABLE(e) && ACCESS_FROM(e, d))
1007 #define IS_ACCESSIBLE_FROM(e, d) (IS_ACCESSIBLE(e) && ACCESS_FROM(e, d))
1009 #define MM_HEALTH(x) (MIN(MAX(0, MAX_HEALTH - (x)), MAX_HEALTH))
1011 // game button identifiers
1012 #define GAME_CTRL_ID_STOP 0
1013 #define GAME_CTRL_ID_PAUSE 1
1014 #define GAME_CTRL_ID_PLAY 2
1015 #define GAME_CTRL_ID_UNDO 3
1016 #define GAME_CTRL_ID_REDO 4
1017 #define GAME_CTRL_ID_SAVE 5
1018 #define GAME_CTRL_ID_PAUSE2 6
1019 #define GAME_CTRL_ID_LOAD 7
1020 #define GAME_CTRL_ID_PANEL_STOP 8
1021 #define GAME_CTRL_ID_PANEL_PAUSE 9
1022 #define GAME_CTRL_ID_PANEL_PLAY 10
1023 #define GAME_CTRL_ID_TOUCH_STOP 11
1024 #define GAME_CTRL_ID_TOUCH_PAUSE 12
1025 #define SOUND_CTRL_ID_MUSIC 13
1026 #define SOUND_CTRL_ID_LOOPS 14
1027 #define SOUND_CTRL_ID_SIMPLE 15
1028 #define SOUND_CTRL_ID_PANEL_MUSIC 16
1029 #define SOUND_CTRL_ID_PANEL_LOOPS 17
1030 #define SOUND_CTRL_ID_PANEL_SIMPLE 18
1032 #define NUM_GAME_BUTTONS 19
1035 // forward declaration for internal use
1037 static void CreateField(int, int, int);
1039 static void ResetGfxAnimation(int, int);
1041 static void SetPlayerWaiting(struct PlayerInfo *, boolean);
1042 static void AdvanceFrameAndPlayerCounters(int);
1044 static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int);
1045 static boolean MovePlayer(struct PlayerInfo *, int, int);
1046 static void ScrollPlayer(struct PlayerInfo *, int);
1047 static void ScrollScreen(struct PlayerInfo *, int);
1049 static int DigField(struct PlayerInfo *, int, int, int, int, int, int, int);
1050 static boolean DigFieldByCE(int, int, int);
1051 static boolean SnapField(struct PlayerInfo *, int, int);
1052 static boolean DropElement(struct PlayerInfo *);
1054 static void InitBeltMovement(void);
1055 static void CloseAllOpenTimegates(void);
1056 static void CheckGravityMovement(struct PlayerInfo *);
1057 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *);
1058 static void KillPlayerUnlessEnemyProtected(int, int);
1059 static void KillPlayerUnlessExplosionProtected(int, int);
1061 static void TestIfPlayerTouchesCustomElement(int, int);
1062 static void TestIfElementTouchesCustomElement(int, int);
1063 static void TestIfElementHitsCustomElement(int, int, int);
1065 static void HandleElementChange(int, int, int);
1066 static void ExecuteCustomElementAction(int, int, int, int);
1067 static boolean ChangeElement(int, int, int, int);
1069 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1070 #define CheckTriggeredElementChange(x, y, e, ev) \
1071 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1072 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1073 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1074 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1075 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1076 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1077 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1078 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1079 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1081 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1082 #define CheckElementChange(x, y, e, te, ev) \
1083 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1084 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1085 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1086 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1087 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1088 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1089 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1091 static void PlayLevelSound(int, int, int);
1092 static void PlayLevelSoundNearest(int, int, int);
1093 static void PlayLevelSoundAction(int, int, int);
1094 static void PlayLevelSoundElementAction(int, int, int, int);
1095 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1096 static void PlayLevelSoundActionIfLoop(int, int, int);
1097 static void StopLevelSoundActionIfLoop(int, int, int);
1098 static void PlayLevelMusic(void);
1099 static void FadeLevelSoundsAndMusic(void);
1101 static void HandleGameButtons(struct GadgetInfo *);
1103 int AmoebaNeighbourNr(int, int);
1104 void AmoebaToDiamond(int, int);
1105 void ContinueMoving(int, int);
1106 void Bang(int, int);
1107 void InitMovDir(int, int);
1108 void InitAmoebaNr(int, int);
1109 void NewHighScore(int);
1111 void TestIfGoodThingHitsBadThing(int, int, int);
1112 void TestIfBadThingHitsGoodThing(int, int, int);
1113 void TestIfPlayerTouchesBadThing(int, int);
1114 void TestIfPlayerRunsIntoBadThing(int, int, int);
1115 void TestIfBadThingTouchesPlayer(int, int);
1116 void TestIfBadThingRunsIntoPlayer(int, int, int);
1117 void TestIfFriendTouchesBadThing(int, int);
1118 void TestIfBadThingTouchesFriend(int, int);
1119 void TestIfBadThingTouchesOtherBadThing(int, int);
1120 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1122 void KillPlayer(struct PlayerInfo *);
1123 void BuryPlayer(struct PlayerInfo *);
1124 void RemovePlayer(struct PlayerInfo *);
1125 void ExitPlayer(struct PlayerInfo *);
1127 static int getInvisibleActiveFromInvisibleElement(int);
1128 static int getInvisibleFromInvisibleActiveElement(int);
1130 static void TestFieldAfterSnapping(int, int, int, int, int);
1132 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1134 // for detection of endless loops, caused by custom element programming
1135 // (using maximal playfield width x 10 is just a rough approximation)
1136 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1138 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1140 if (recursion_loop_detected) \
1143 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1145 recursion_loop_detected = TRUE; \
1146 recursion_loop_element = (e); \
1149 recursion_loop_depth++; \
1152 #define RECURSION_LOOP_DETECTION_END() \
1154 recursion_loop_depth--; \
1157 static int recursion_loop_depth;
1158 static boolean recursion_loop_detected;
1159 static boolean recursion_loop_element;
1161 static int map_player_action[MAX_PLAYERS];
1164 // ----------------------------------------------------------------------------
1165 // definition of elements that automatically change to other elements after
1166 // a specified time, eventually calling a function when changing
1167 // ----------------------------------------------------------------------------
1169 // forward declaration for changer functions
1170 static void InitBuggyBase(int, int);
1171 static void WarnBuggyBase(int, int);
1173 static void InitTrap(int, int);
1174 static void ActivateTrap(int, int);
1175 static void ChangeActiveTrap(int, int);
1177 static void InitRobotWheel(int, int);
1178 static void RunRobotWheel(int, int);
1179 static void StopRobotWheel(int, int);
1181 static void InitTimegateWheel(int, int);
1182 static void RunTimegateWheel(int, int);
1184 static void InitMagicBallDelay(int, int);
1185 static void ActivateMagicBall(int, int);
1187 struct ChangingElementInfo
1192 void (*pre_change_function)(int x, int y);
1193 void (*change_function)(int x, int y);
1194 void (*post_change_function)(int x, int y);
1197 static struct ChangingElementInfo change_delay_list[] =
1232 EL_STEEL_EXIT_OPENING,
1240 EL_STEEL_EXIT_CLOSING,
1241 EL_STEEL_EXIT_CLOSED,
1264 EL_EM_STEEL_EXIT_OPENING,
1265 EL_EM_STEEL_EXIT_OPEN,
1272 EL_EM_STEEL_EXIT_CLOSING,
1296 EL_SWITCHGATE_OPENING,
1304 EL_SWITCHGATE_CLOSING,
1305 EL_SWITCHGATE_CLOSED,
1312 EL_TIMEGATE_OPENING,
1320 EL_TIMEGATE_CLOSING,
1329 EL_ACID_SPLASH_LEFT,
1337 EL_ACID_SPLASH_RIGHT,
1346 EL_SP_BUGGY_BASE_ACTIVATING,
1353 EL_SP_BUGGY_BASE_ACTIVATING,
1354 EL_SP_BUGGY_BASE_ACTIVE,
1361 EL_SP_BUGGY_BASE_ACTIVE,
1385 EL_ROBOT_WHEEL_ACTIVE,
1393 EL_TIMEGATE_SWITCH_ACTIVE,
1401 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1402 EL_DC_TIMEGATE_SWITCH,
1409 EL_EMC_MAGIC_BALL_ACTIVE,
1410 EL_EMC_MAGIC_BALL_ACTIVE,
1417 EL_EMC_SPRING_BUMPER_ACTIVE,
1418 EL_EMC_SPRING_BUMPER,
1425 EL_DIAGONAL_SHRINKING,
1433 EL_DIAGONAL_GROWING,
1454 int push_delay_fixed, push_delay_random;
1458 { EL_SPRING, 0, 0 },
1459 { EL_BALLOON, 0, 0 },
1461 { EL_SOKOBAN_OBJECT, 2, 0 },
1462 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1463 { EL_SATELLITE, 2, 0 },
1464 { EL_SP_DISK_YELLOW, 2, 0 },
1466 { EL_UNDEFINED, 0, 0 },
1474 move_stepsize_list[] =
1476 { EL_AMOEBA_DROP, 2 },
1477 { EL_AMOEBA_DROPPING, 2 },
1478 { EL_QUICKSAND_FILLING, 1 },
1479 { EL_QUICKSAND_EMPTYING, 1 },
1480 { EL_QUICKSAND_FAST_FILLING, 2 },
1481 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1482 { EL_MAGIC_WALL_FILLING, 2 },
1483 { EL_MAGIC_WALL_EMPTYING, 2 },
1484 { EL_BD_MAGIC_WALL_FILLING, 2 },
1485 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1486 { EL_DC_MAGIC_WALL_FILLING, 2 },
1487 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_UNDEFINED, 0 },
1497 collect_count_list[] =
1500 { EL_BD_DIAMOND, 1 },
1501 { EL_EMERALD_YELLOW, 1 },
1502 { EL_EMERALD_RED, 1 },
1503 { EL_EMERALD_PURPLE, 1 },
1505 { EL_SP_INFOTRON, 1 },
1509 { EL_UNDEFINED, 0 },
1517 access_direction_list[] =
1519 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1520 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1521 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1522 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1525 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1526 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1527 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1528 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1529 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1531 { EL_SP_PORT_LEFT, MV_RIGHT },
1532 { EL_SP_PORT_RIGHT, MV_LEFT },
1533 { EL_SP_PORT_UP, MV_DOWN },
1534 { EL_SP_PORT_DOWN, MV_UP },
1535 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1536 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1537 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1538 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1539 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1540 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1542 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1543 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1544 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1545 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1546 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1547 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1548 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1549 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1551 { EL_UNDEFINED, MV_NONE }
1554 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1556 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1557 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1558 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1559 IS_JUST_CHANGING(x, y))
1561 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1563 // static variables for playfield scan mode (scanning forward or backward)
1564 static int playfield_scan_start_x = 0;
1565 static int playfield_scan_start_y = 0;
1566 static int playfield_scan_delta_x = 1;
1567 static int playfield_scan_delta_y = 1;
1569 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1570 (y) >= 0 && (y) <= lev_fieldy - 1; \
1571 (y) += playfield_scan_delta_y) \
1572 for ((x) = playfield_scan_start_x; \
1573 (x) >= 0 && (x) <= lev_fieldx - 1; \
1574 (x) += playfield_scan_delta_x)
1577 void DEBUG_SetMaximumDynamite(void)
1581 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1582 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1583 local_player->inventory_element[local_player->inventory_size++] =
1588 static void InitPlayfieldScanModeVars(void)
1590 if (game.use_reverse_scan_direction)
1592 playfield_scan_start_x = lev_fieldx - 1;
1593 playfield_scan_start_y = lev_fieldy - 1;
1595 playfield_scan_delta_x = -1;
1596 playfield_scan_delta_y = -1;
1600 playfield_scan_start_x = 0;
1601 playfield_scan_start_y = 0;
1603 playfield_scan_delta_x = 1;
1604 playfield_scan_delta_y = 1;
1608 static void InitPlayfieldScanMode(int mode)
1610 game.use_reverse_scan_direction =
1611 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1613 InitPlayfieldScanModeVars();
1616 static int get_move_delay_from_stepsize(int move_stepsize)
1619 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1621 // make sure that stepsize value is always a power of 2
1622 move_stepsize = (1 << log_2(move_stepsize));
1624 return TILEX / move_stepsize;
1627 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1630 int player_nr = player->index_nr;
1631 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1632 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1634 // do no immediately change move delay -- the player might just be moving
1635 player->move_delay_value_next = move_delay;
1637 // information if player can move must be set separately
1638 player->cannot_move = cannot_move;
1642 player->move_delay = game.initial_move_delay[player_nr];
1643 player->move_delay_value = game.initial_move_delay_value[player_nr];
1645 player->move_delay_value_next = -1;
1647 player->move_delay_reset_counter = 0;
1651 void GetPlayerConfig(void)
1653 GameFrameDelay = setup.game_frame_delay;
1655 if (!audio.sound_available)
1656 setup.sound_simple = FALSE;
1658 if (!audio.loops_available)
1659 setup.sound_loops = FALSE;
1661 if (!audio.music_available)
1662 setup.sound_music = FALSE;
1664 if (!video.fullscreen_available)
1665 setup.fullscreen = FALSE;
1667 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1669 SetAudioMode(setup.sound);
1672 int GetElementFromGroupElement(int element)
1674 if (IS_GROUP_ELEMENT(element))
1676 struct ElementGroupInfo *group = element_info[element].group;
1677 int last_anim_random_frame = gfx.anim_random_frame;
1680 if (group->choice_mode == ANIM_RANDOM)
1681 gfx.anim_random_frame = RND(group->num_elements_resolved);
1683 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1684 group->choice_mode, 0,
1687 if (group->choice_mode == ANIM_RANDOM)
1688 gfx.anim_random_frame = last_anim_random_frame;
1690 group->choice_pos++;
1692 element = group->element_resolved[element_pos];
1698 static void IncrementSokobanFieldsNeeded(void)
1700 if (level.sb_fields_needed)
1701 game.sokoban_fields_still_needed++;
1704 static void IncrementSokobanObjectsNeeded(void)
1706 if (level.sb_objects_needed)
1707 game.sokoban_objects_still_needed++;
1710 static void DecrementSokobanFieldsNeeded(void)
1712 if (game.sokoban_fields_still_needed > 0)
1713 game.sokoban_fields_still_needed--;
1716 static void DecrementSokobanObjectsNeeded(void)
1718 if (game.sokoban_objects_still_needed > 0)
1719 game.sokoban_objects_still_needed--;
1722 static void InitPlayerField(int x, int y, int element, boolean init_game)
1724 if (element == EL_SP_MURPHY)
1728 if (stored_player[0].present)
1730 Tile[x][y] = EL_SP_MURPHY_CLONE;
1736 stored_player[0].initial_element = element;
1737 stored_player[0].use_murphy = TRUE;
1739 if (!level.use_artwork_element[0])
1740 stored_player[0].artwork_element = EL_SP_MURPHY;
1743 Tile[x][y] = EL_PLAYER_1;
1749 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1750 int jx = player->jx, jy = player->jy;
1752 player->present = TRUE;
1754 player->block_last_field = (element == EL_SP_MURPHY ?
1755 level.sp_block_last_field :
1756 level.block_last_field);
1758 // ---------- initialize player's last field block delay ------------------
1760 // always start with reliable default value (no adjustment needed)
1761 player->block_delay_adjustment = 0;
1763 // special case 1: in Supaplex, Murphy blocks last field one more frame
1764 if (player->block_last_field && element == EL_SP_MURPHY)
1765 player->block_delay_adjustment = 1;
1767 // special case 2: in game engines before 3.1.1, blocking was different
1768 if (game.use_block_last_field_bug)
1769 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1771 if (!network.enabled || player->connected_network)
1773 player->active = TRUE;
1775 // remove potentially duplicate players
1776 if (StorePlayer[jx][jy] == Tile[x][y])
1777 StorePlayer[jx][jy] = 0;
1779 StorePlayer[x][y] = Tile[x][y];
1781 #if DEBUG_INIT_PLAYER
1782 Debug("game:init:player", "- player element %d activated",
1783 player->element_nr);
1784 Debug("game:init:player", " (local player is %d and currently %s)",
1785 local_player->element_nr,
1786 local_player->active ? "active" : "not active");
1790 Tile[x][y] = EL_EMPTY;
1792 player->jx = player->last_jx = x;
1793 player->jy = player->last_jy = y;
1796 // always check if player was just killed and should be reanimated
1798 int player_nr = GET_PLAYER_NR(element);
1799 struct PlayerInfo *player = &stored_player[player_nr];
1801 if (player->active && player->killed)
1802 player->reanimated = TRUE; // if player was just killed, reanimate him
1806 static void InitField(int x, int y, boolean init_game)
1808 int element = Tile[x][y];
1817 InitPlayerField(x, y, element, init_game);
1820 case EL_SOKOBAN_FIELD_PLAYER:
1821 element = Tile[x][y] = EL_PLAYER_1;
1822 InitField(x, y, init_game);
1824 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1825 InitField(x, y, init_game);
1828 case EL_SOKOBAN_FIELD_EMPTY:
1829 IncrementSokobanFieldsNeeded();
1832 case EL_SOKOBAN_OBJECT:
1833 IncrementSokobanObjectsNeeded();
1837 if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID)
1838 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1839 else if (x > 0 && Tile[x-1][y] == EL_ACID)
1840 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1841 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT)
1842 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1843 else if (y > 0 && Tile[x][y-1] == EL_ACID)
1844 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1845 else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT)
1846 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1855 case EL_SPACESHIP_RIGHT:
1856 case EL_SPACESHIP_UP:
1857 case EL_SPACESHIP_LEFT:
1858 case EL_SPACESHIP_DOWN:
1859 case EL_BD_BUTTERFLY:
1860 case EL_BD_BUTTERFLY_RIGHT:
1861 case EL_BD_BUTTERFLY_UP:
1862 case EL_BD_BUTTERFLY_LEFT:
1863 case EL_BD_BUTTERFLY_DOWN:
1865 case EL_BD_FIREFLY_RIGHT:
1866 case EL_BD_FIREFLY_UP:
1867 case EL_BD_FIREFLY_LEFT:
1868 case EL_BD_FIREFLY_DOWN:
1869 case EL_PACMAN_RIGHT:
1871 case EL_PACMAN_LEFT:
1872 case EL_PACMAN_DOWN:
1874 case EL_YAMYAM_LEFT:
1875 case EL_YAMYAM_RIGHT:
1877 case EL_YAMYAM_DOWN:
1878 case EL_DARK_YAMYAM:
1881 case EL_SP_SNIKSNAK:
1882 case EL_SP_ELECTRON:
1888 case EL_SPRING_LEFT:
1889 case EL_SPRING_RIGHT:
1893 case EL_AMOEBA_FULL:
1898 case EL_AMOEBA_DROP:
1899 if (y == lev_fieldy - 1)
1901 Tile[x][y] = EL_AMOEBA_GROWING;
1902 Store[x][y] = EL_AMOEBA_WET;
1906 case EL_DYNAMITE_ACTIVE:
1907 case EL_SP_DISK_RED_ACTIVE:
1908 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1909 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1910 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1912 MovDelay[x][y] = 96;
1915 case EL_EM_DYNAMITE_ACTIVE:
1916 MovDelay[x][y] = 32;
1920 game.lights_still_needed++;
1924 game.friends_still_needed++;
1929 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1932 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1933 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1934 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1935 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1946 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1947 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1948 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1950 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1952 game.belt_dir[belt_nr] = belt_dir;
1953 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1955 else // more than one switch -- set it like the first switch
1957 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1962 case EL_LIGHT_SWITCH_ACTIVE:
1964 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1967 case EL_INVISIBLE_STEELWALL:
1968 case EL_INVISIBLE_WALL:
1969 case EL_INVISIBLE_SAND:
1970 if (game.light_time_left > 0 ||
1971 game.lenses_time_left > 0)
1972 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1975 case EL_EMC_MAGIC_BALL:
1976 if (game.ball_active)
1977 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1980 case EL_EMC_MAGIC_BALL_SWITCH:
1981 if (game.ball_active)
1982 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1985 case EL_TRIGGER_PLAYER:
1986 case EL_TRIGGER_ELEMENT:
1987 case EL_TRIGGER_CE_VALUE:
1988 case EL_TRIGGER_CE_SCORE:
1990 case EL_ANY_ELEMENT:
1991 case EL_CURRENT_CE_VALUE:
1992 case EL_CURRENT_CE_SCORE:
2009 // reference elements should not be used on the playfield
2010 Tile[x][y] = EL_EMPTY;
2014 if (IS_CUSTOM_ELEMENT(element))
2016 if (CAN_MOVE(element))
2019 if (!element_info[element].use_last_ce_value || init_game)
2020 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2022 else if (IS_GROUP_ELEMENT(element))
2024 Tile[x][y] = GetElementFromGroupElement(element);
2026 InitField(x, y, init_game);
2033 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2036 static void InitField_WithBug1(int x, int y, boolean init_game)
2038 InitField(x, y, init_game);
2040 // not needed to call InitMovDir() -- already done by InitField()!
2041 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2042 CAN_MOVE(Tile[x][y]))
2046 static void InitField_WithBug2(int x, int y, boolean init_game)
2048 int old_element = Tile[x][y];
2050 InitField(x, y, init_game);
2052 // not needed to call InitMovDir() -- already done by InitField()!
2053 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2054 CAN_MOVE(old_element) &&
2055 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2058 /* this case is in fact a combination of not less than three bugs:
2059 first, it calls InitMovDir() for elements that can move, although this is
2060 already done by InitField(); then, it checks the element that was at this
2061 field _before_ the call to InitField() (which can change it); lastly, it
2062 was not called for "mole with direction" elements, which were treated as
2063 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2067 static int get_key_element_from_nr(int key_nr)
2069 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2070 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2071 EL_EM_KEY_1 : EL_KEY_1);
2073 return key_base_element + key_nr;
2076 static int get_next_dropped_element(struct PlayerInfo *player)
2078 return (player->inventory_size > 0 ?
2079 player->inventory_element[player->inventory_size - 1] :
2080 player->inventory_infinite_element != EL_UNDEFINED ?
2081 player->inventory_infinite_element :
2082 player->dynabombs_left > 0 ?
2083 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2087 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2089 // pos >= 0: get element from bottom of the stack;
2090 // pos < 0: get element from top of the stack
2094 int min_inventory_size = -pos;
2095 int inventory_pos = player->inventory_size - min_inventory_size;
2096 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2098 return (player->inventory_size >= min_inventory_size ?
2099 player->inventory_element[inventory_pos] :
2100 player->inventory_infinite_element != EL_UNDEFINED ?
2101 player->inventory_infinite_element :
2102 player->dynabombs_left >= min_dynabombs_left ?
2103 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2108 int min_dynabombs_left = pos + 1;
2109 int min_inventory_size = pos + 1 - player->dynabombs_left;
2110 int inventory_pos = pos - player->dynabombs_left;
2112 return (player->inventory_infinite_element != EL_UNDEFINED ?
2113 player->inventory_infinite_element :
2114 player->dynabombs_left >= min_dynabombs_left ?
2115 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2116 player->inventory_size >= min_inventory_size ?
2117 player->inventory_element[inventory_pos] :
2122 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2124 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2125 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2128 if (gpo1->sort_priority != gpo2->sort_priority)
2129 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2131 compare_result = gpo1->nr - gpo2->nr;
2133 return compare_result;
2136 int getPlayerInventorySize(int player_nr)
2138 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2139 return game_em.ply[player_nr]->dynamite;
2140 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2141 return game_sp.red_disk_count;
2143 return stored_player[player_nr].inventory_size;
2146 static void InitGameControlValues(void)
2150 for (i = 0; game_panel_controls[i].nr != -1; i++)
2152 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2153 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2154 struct TextPosInfo *pos = gpc->pos;
2156 int type = gpc->type;
2160 Error("'game_panel_controls' structure corrupted at %d", i);
2162 Fail("this should not happen -- please debug");
2165 // force update of game controls after initialization
2166 gpc->value = gpc->last_value = -1;
2167 gpc->frame = gpc->last_frame = -1;
2168 gpc->gfx_frame = -1;
2170 // determine panel value width for later calculation of alignment
2171 if (type == TYPE_INTEGER || type == TYPE_STRING)
2173 pos->width = pos->size * getFontWidth(pos->font);
2174 pos->height = getFontHeight(pos->font);
2176 else if (type == TYPE_ELEMENT)
2178 pos->width = pos->size;
2179 pos->height = pos->size;
2182 // fill structure for game panel draw order
2184 gpo->sort_priority = pos->sort_priority;
2187 // sort game panel controls according to sort_priority and control number
2188 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2189 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2192 static void UpdatePlayfieldElementCount(void)
2194 boolean use_element_count = FALSE;
2197 // first check if it is needed at all to calculate playfield element count
2198 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2199 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2200 use_element_count = TRUE;
2202 if (!use_element_count)
2205 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2206 element_info[i].element_count = 0;
2208 SCAN_PLAYFIELD(x, y)
2210 element_info[Tile[x][y]].element_count++;
2213 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2214 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2215 if (IS_IN_GROUP(j, i))
2216 element_info[EL_GROUP_START + i].element_count +=
2217 element_info[j].element_count;
2220 static void UpdateGameControlValues(void)
2223 int time = (game.LevelSolved ?
2224 game.LevelSolved_CountingTime :
2225 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2227 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2228 game_sp.time_played :
2229 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2230 game_mm.energy_left :
2231 game.no_time_limit ? TimePlayed : TimeLeft);
2232 int score = (game.LevelSolved ?
2233 game.LevelSolved_CountingScore :
2234 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2235 game_em.lev->score :
2236 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2242 game_em.lev->gems_needed :
2243 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2244 game_sp.infotrons_still_needed :
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2246 game_mm.kettles_still_needed :
2247 game.gems_still_needed);
2248 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2249 game_em.lev->gems_needed > 0 :
2250 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2251 game_sp.infotrons_still_needed > 0 :
2252 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2253 game_mm.kettles_still_needed > 0 ||
2254 game_mm.lights_still_needed > 0 :
2255 game.gems_still_needed > 0 ||
2256 game.sokoban_fields_still_needed > 0 ||
2257 game.sokoban_objects_still_needed > 0 ||
2258 game.lights_still_needed > 0);
2259 int health = (game.LevelSolved ?
2260 game.LevelSolved_CountingHealth :
2261 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2262 MM_HEALTH(game_mm.laser_overload_value) :
2264 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2266 UpdatePlayfieldElementCount();
2268 // update game panel control values
2270 // used instead of "level_nr" (for network games)
2271 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2272 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2274 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2275 for (i = 0; i < MAX_NUM_KEYS; i++)
2276 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2277 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2278 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2280 if (game.centered_player_nr == -1)
2282 for (i = 0; i < MAX_PLAYERS; i++)
2284 // only one player in Supaplex game engine
2285 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2288 for (k = 0; k < MAX_NUM_KEYS; k++)
2290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2292 if (game_em.ply[i]->keys & (1 << k))
2293 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2294 get_key_element_from_nr(k);
2296 else if (stored_player[i].key[k])
2297 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2298 get_key_element_from_nr(k);
2301 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2302 getPlayerInventorySize(i);
2304 if (stored_player[i].num_white_keys > 0)
2305 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2308 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2309 stored_player[i].num_white_keys;
2314 int player_nr = game.centered_player_nr;
2316 for (k = 0; k < MAX_NUM_KEYS; k++)
2318 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2320 if (game_em.ply[player_nr]->keys & (1 << k))
2321 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2322 get_key_element_from_nr(k);
2324 else if (stored_player[player_nr].key[k])
2325 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2326 get_key_element_from_nr(k);
2329 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2330 getPlayerInventorySize(player_nr);
2332 if (stored_player[player_nr].num_white_keys > 0)
2333 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2335 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2336 stored_player[player_nr].num_white_keys;
2339 // re-arrange keys on game panel, if needed or if defined by style settings
2340 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2342 int nr = GAME_PANEL_KEY_1 + i;
2343 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2344 struct TextPosInfo *pos = gpc->pos;
2346 // skip check if key is not in the player's inventory
2347 if (gpc->value == EL_EMPTY)
2350 // check if keys should be arranged on panel from left to right
2351 if (pos->style == STYLE_LEFTMOST_POSITION)
2353 // check previous key positions (left from current key)
2354 for (k = 0; k < i; k++)
2356 int nr_new = GAME_PANEL_KEY_1 + k;
2358 if (game_panel_controls[nr_new].value == EL_EMPTY)
2360 game_panel_controls[nr_new].value = gpc->value;
2361 gpc->value = EL_EMPTY;
2368 // check if "undefined" keys can be placed at some other position
2369 if (pos->x == -1 && pos->y == -1)
2371 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2373 // 1st try: display key at the same position as normal or EM keys
2374 if (game_panel_controls[nr_new].value == EL_EMPTY)
2376 game_panel_controls[nr_new].value = gpc->value;
2380 // 2nd try: display key at the next free position in the key panel
2381 for (k = 0; k < STD_NUM_KEYS; k++)
2383 nr_new = GAME_PANEL_KEY_1 + k;
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2396 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2398 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2399 get_inventory_element_from_pos(local_player, i);
2400 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2401 get_inventory_element_from_pos(local_player, -i - 1);
2404 game_panel_controls[GAME_PANEL_SCORE].value = score;
2405 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2407 game_panel_controls[GAME_PANEL_TIME].value = time;
2409 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2410 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2411 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2413 if (level.time == 0)
2414 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2416 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2418 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2419 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2421 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2423 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2424 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2426 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2427 local_player->shield_normal_time_left;
2428 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2429 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2431 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2432 local_player->shield_deadly_time_left;
2434 game_panel_controls[GAME_PANEL_EXIT].value =
2435 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2437 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2438 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2439 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2440 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2441 EL_EMC_MAGIC_BALL_SWITCH);
2443 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2444 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2445 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2446 game.light_time_left;
2448 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2449 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2450 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2451 game.timegate_time_left;
2453 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2454 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2456 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2457 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2458 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2459 game.lenses_time_left;
2461 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2462 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2463 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2464 game.magnify_time_left;
2466 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2467 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2468 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2469 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2470 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2471 EL_BALLOON_SWITCH_NONE);
2473 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2474 local_player->dynabomb_count;
2475 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2476 local_player->dynabomb_size;
2477 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2478 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2480 game_panel_controls[GAME_PANEL_PENGUINS].value =
2481 game.friends_still_needed;
2483 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2484 game.sokoban_objects_still_needed;
2485 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2486 game.sokoban_fields_still_needed;
2488 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2489 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2491 for (i = 0; i < NUM_BELTS; i++)
2493 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2494 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2495 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2496 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2497 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2500 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2501 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2502 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2503 game.magic_wall_time_left;
2505 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2506 local_player->gravity;
2508 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2509 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2511 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2512 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2513 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2514 game.panel.element[i].id : EL_UNDEFINED);
2516 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2517 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2518 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2519 element_info[game.panel.element_count[i].id].element_count : 0);
2521 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2522 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2523 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2524 element_info[game.panel.ce_score[i].id].collect_score : 0);
2526 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2527 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2528 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2529 element_info[game.panel.ce_score_element[i].id].collect_score :
2532 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2533 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2534 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2536 // update game panel control frames
2538 for (i = 0; game_panel_controls[i].nr != -1; i++)
2540 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2542 if (gpc->type == TYPE_ELEMENT)
2544 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2546 int last_anim_random_frame = gfx.anim_random_frame;
2547 int element = gpc->value;
2548 int graphic = el2panelimg(element);
2549 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2550 sync_random_frame : INIT_GFX_RANDOM());
2552 if (gpc->value != gpc->last_value)
2555 gpc->gfx_random = init_gfx_random;
2561 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2562 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2563 gpc->gfx_random = init_gfx_random;
2566 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2567 gfx.anim_random_frame = gpc->gfx_random;
2569 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2570 gpc->gfx_frame = element_info[element].collect_score;
2572 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2574 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2575 gfx.anim_random_frame = last_anim_random_frame;
2578 else if (gpc->type == TYPE_GRAPHIC)
2580 if (gpc->graphic != IMG_UNDEFINED)
2582 int last_anim_random_frame = gfx.anim_random_frame;
2583 int graphic = gpc->graphic;
2584 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2585 sync_random_frame : INIT_GFX_RANDOM());
2587 if (gpc->value != gpc->last_value)
2590 gpc->gfx_random = init_gfx_random;
2596 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2597 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2598 gpc->gfx_random = init_gfx_random;
2601 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2602 gfx.anim_random_frame = gpc->gfx_random;
2604 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2606 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2607 gfx.anim_random_frame = last_anim_random_frame;
2613 static void DisplayGameControlValues(void)
2615 boolean redraw_panel = FALSE;
2618 for (i = 0; game_panel_controls[i].nr != -1; i++)
2620 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2622 if (PANEL_DEACTIVATED(gpc->pos))
2625 if (gpc->value == gpc->last_value &&
2626 gpc->frame == gpc->last_frame)
2629 redraw_panel = TRUE;
2635 // copy default game door content to main double buffer
2637 // !!! CHECK AGAIN !!!
2638 SetPanelBackground();
2639 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2640 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2642 // redraw game control buttons
2643 RedrawGameButtons();
2645 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2647 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2649 int nr = game_panel_order[i].nr;
2650 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2651 struct TextPosInfo *pos = gpc->pos;
2652 int type = gpc->type;
2653 int value = gpc->value;
2654 int frame = gpc->frame;
2655 int size = pos->size;
2656 int font = pos->font;
2657 boolean draw_masked = pos->draw_masked;
2658 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2660 if (PANEL_DEACTIVATED(pos))
2663 if (pos->class == get_hash_from_key("extra_panel_items") &&
2664 !setup.prefer_extra_panel_items)
2667 gpc->last_value = value;
2668 gpc->last_frame = frame;
2670 if (type == TYPE_INTEGER)
2672 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2673 nr == GAME_PANEL_TIME)
2675 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2677 if (use_dynamic_size) // use dynamic number of digits
2679 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000);
2680 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3);
2681 int size2 = size1 + 1;
2682 int font1 = pos->font;
2683 int font2 = pos->font_alt;
2685 size = (value < value_change ? size1 : size2);
2686 font = (value < value_change ? font1 : font2);
2690 // correct text size if "digits" is zero or less
2692 size = strlen(int2str(value, size));
2694 // dynamically correct text alignment
2695 pos->width = size * getFontWidth(font);
2697 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2698 int2str(value, size), font, mask_mode);
2700 else if (type == TYPE_ELEMENT)
2702 int element, graphic;
2706 int dst_x = PANEL_XPOS(pos);
2707 int dst_y = PANEL_YPOS(pos);
2709 if (value != EL_UNDEFINED && value != EL_EMPTY)
2712 graphic = el2panelimg(value);
2715 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2716 element, EL_NAME(element), size);
2719 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2722 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2725 width = graphic_info[graphic].width * size / TILESIZE;
2726 height = graphic_info[graphic].height * size / TILESIZE;
2729 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2732 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2736 else if (type == TYPE_GRAPHIC)
2738 int graphic = gpc->graphic;
2739 int graphic_active = gpc->graphic_active;
2743 int dst_x = PANEL_XPOS(pos);
2744 int dst_y = PANEL_YPOS(pos);
2745 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2746 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2748 if (graphic != IMG_UNDEFINED && !skip)
2750 if (pos->style == STYLE_REVERSE)
2751 value = 100 - value;
2753 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2755 if (pos->direction & MV_HORIZONTAL)
2757 width = graphic_info[graphic_active].width * value / 100;
2758 height = graphic_info[graphic_active].height;
2760 if (pos->direction == MV_LEFT)
2762 src_x += graphic_info[graphic_active].width - width;
2763 dst_x += graphic_info[graphic_active].width - width;
2768 width = graphic_info[graphic_active].width;
2769 height = graphic_info[graphic_active].height * value / 100;
2771 if (pos->direction == MV_UP)
2773 src_y += graphic_info[graphic_active].height - height;
2774 dst_y += graphic_info[graphic_active].height - height;
2779 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2782 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2785 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2787 if (pos->direction & MV_HORIZONTAL)
2789 if (pos->direction == MV_RIGHT)
2796 dst_x = PANEL_XPOS(pos);
2799 width = graphic_info[graphic].width - width;
2803 if (pos->direction == MV_DOWN)
2810 dst_y = PANEL_YPOS(pos);
2813 height = graphic_info[graphic].height - height;
2817 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2820 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2824 else if (type == TYPE_STRING)
2826 boolean active = (value != 0);
2827 char *state_normal = "off";
2828 char *state_active = "on";
2829 char *state = (active ? state_active : state_normal);
2830 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2831 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2832 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2833 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2835 if (nr == GAME_PANEL_GRAVITY_STATE)
2837 int font1 = pos->font; // (used for normal state)
2838 int font2 = pos->font_alt; // (used for active state)
2840 font = (active ? font2 : font1);
2849 // don't truncate output if "chars" is zero or less
2852 // dynamically correct text alignment
2853 pos->width = size * getFontWidth(font);
2856 s_cut = getStringCopyN(s, size);
2858 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2859 s_cut, font, mask_mode);
2865 redraw_mask |= REDRAW_DOOR_1;
2868 SetGameStatus(GAME_MODE_PLAYING);
2871 void UpdateAndDisplayGameControlValues(void)
2873 if (tape.deactivate_display)
2876 UpdateGameControlValues();
2877 DisplayGameControlValues();
2880 void UpdateGameDoorValues(void)
2882 UpdateGameControlValues();
2885 void DrawGameDoorValues(void)
2887 DisplayGameControlValues();
2891 // ============================================================================
2893 // ----------------------------------------------------------------------------
2894 // initialize game engine due to level / tape version number
2895 // ============================================================================
2897 static void InitGameEngine(void)
2899 int i, j, k, l, x, y;
2901 // set game engine from tape file when re-playing, else from level file
2902 game.engine_version = (tape.playing ? tape.engine_version :
2903 level.game_version);
2905 // set single or multi-player game mode (needed for re-playing tapes)
2906 game.team_mode = setup.team_mode;
2910 int num_players = 0;
2912 for (i = 0; i < MAX_PLAYERS; i++)
2913 if (tape.player_participates[i])
2916 // multi-player tapes contain input data for more than one player
2917 game.team_mode = (num_players > 1);
2921 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2922 level.game_version);
2923 Debug("game:init:level", " tape.file_version == %06d",
2925 Debug("game:init:level", " tape.game_version == %06d",
2927 Debug("game:init:level", " tape.engine_version == %06d",
2928 tape.engine_version);
2929 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2930 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2933 // --------------------------------------------------------------------------
2934 // set flags for bugs and changes according to active game engine version
2935 // --------------------------------------------------------------------------
2939 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2941 Bug was introduced in version:
2944 Bug was fixed in version:
2948 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2949 but the property "can fall" was missing, which caused some levels to be
2950 unsolvable. This was fixed in version 4.2.0.0.
2952 Affected levels/tapes:
2953 An example for a tape that was fixed by this bugfix is tape 029 from the
2954 level set "rnd_sam_bateman".
2955 The wrong behaviour will still be used for all levels or tapes that were
2956 created/recorded with it. An example for this is tape 023 from the level
2957 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2960 boolean use_amoeba_dropping_cannot_fall_bug =
2961 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2962 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2964 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2965 tape.game_version < VERSION_IDENT(4,2,0,0)));
2968 Summary of bugfix/change:
2969 Fixed move speed of elements entering or leaving magic wall.
2971 Fixed/changed in version:
2975 Before 2.0.1, move speed of elements entering or leaving magic wall was
2976 twice as fast as it is now.
2977 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
2979 Affected levels/tapes:
2980 The first condition is generally needed for all levels/tapes before version
2981 2.0.1, which might use the old behaviour before it was changed; known tapes
2982 that are affected: Tape 014 from the level set "rnd_conor_mancone".
2983 The second condition is an exception from the above case and is needed for
2984 the special case of tapes recorded with game (not engine!) version 2.0.1 or
2985 above, but before it was known that this change would break tapes like the
2986 above and was fixed in 4.2.0.0, so that the changed behaviour was active
2987 although the engine version while recording maybe was before 2.0.1. There
2988 are a lot of tapes that are affected by this exception, like tape 006 from
2989 the level set "rnd_conor_mancone".
2992 boolean use_old_move_stepsize_for_magic_wall =
2993 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
2995 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2996 tape.game_version < VERSION_IDENT(4,2,0,0)));
2999 Summary of bugfix/change:
3000 Fixed handling for custom elements that change when pushed by the player.
3002 Fixed/changed in version:
3006 Before 3.1.0, custom elements that "change when pushing" changed directly
3007 after the player started pushing them (until then handled in "DigField()").
3008 Since 3.1.0, these custom elements are not changed until the "pushing"
3009 move of the element is finished (now handled in "ContinueMoving()").
3011 Affected levels/tapes:
3012 The first condition is generally needed for all levels/tapes before version
3013 3.1.0, which might use the old behaviour before it was changed; known tapes
3014 that are affected are some tapes from the level set "Walpurgis Gardens" by
3016 The second condition is an exception from the above case and is needed for
3017 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3018 above (including some development versions of 3.1.0), but before it was
3019 known that this change would break tapes like the above and was fixed in
3020 3.1.1, so that the changed behaviour was active although the engine version
3021 while recording maybe was before 3.1.0. There is at least one tape that is
3022 affected by this exception, which is the tape for the one-level set "Bug
3023 Machine" by Juergen Bonhagen.
3026 game.use_change_when_pushing_bug =
3027 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3029 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3030 tape.game_version < VERSION_IDENT(3,1,1,0)));
3033 Summary of bugfix/change:
3034 Fixed handling for blocking the field the player leaves when moving.
3036 Fixed/changed in version:
3040 Before 3.1.1, when "block last field when moving" was enabled, the field
3041 the player is leaving when moving was blocked for the time of the move,
3042 and was directly unblocked afterwards. This resulted in the last field
3043 being blocked for exactly one less than the number of frames of one player
3044 move. Additionally, even when blocking was disabled, the last field was
3045 blocked for exactly one frame.
3046 Since 3.1.1, due to changes in player movement handling, the last field
3047 is not blocked at all when blocking is disabled. When blocking is enabled,
3048 the last field is blocked for exactly the number of frames of one player
3049 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3050 last field is blocked for exactly one more than the number of frames of
3053 Affected levels/tapes:
3054 (!!! yet to be determined -- probably many !!!)
3057 game.use_block_last_field_bug =
3058 (game.engine_version < VERSION_IDENT(3,1,1,0));
3060 /* various special flags and settings for native Emerald Mine game engine */
3062 game_em.use_single_button =
3063 (game.engine_version > VERSION_IDENT(4,0,0,2));
3065 game_em.use_snap_key_bug =
3066 (game.engine_version < VERSION_IDENT(4,0,1,0));
3068 game_em.use_random_bug =
3069 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3071 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3073 game_em.use_old_explosions = use_old_em_engine;
3074 game_em.use_old_android = use_old_em_engine;
3075 game_em.use_old_push_elements = use_old_em_engine;
3076 game_em.use_old_push_into_acid = use_old_em_engine;
3078 game_em.use_wrap_around = !use_old_em_engine;
3080 // --------------------------------------------------------------------------
3082 // set maximal allowed number of custom element changes per game frame
3083 game.max_num_changes_per_frame = 1;
3085 // default scan direction: scan playfield from top/left to bottom/right
3086 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3088 // dynamically adjust element properties according to game engine version
3089 InitElementPropertiesEngine(game.engine_version);
3091 // ---------- initialize special element properties -------------------------
3093 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3094 if (use_amoeba_dropping_cannot_fall_bug)
3095 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3097 // ---------- initialize player's initial move delay ------------------------
3099 // dynamically adjust player properties according to level information
3100 for (i = 0; i < MAX_PLAYERS; i++)
3101 game.initial_move_delay_value[i] =
3102 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3104 // dynamically adjust player properties according to game engine version
3105 for (i = 0; i < MAX_PLAYERS; i++)
3106 game.initial_move_delay[i] =
3107 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3108 game.initial_move_delay_value[i] : 0);
3110 // ---------- initialize player's initial push delay ------------------------
3112 // dynamically adjust player properties according to game engine version
3113 game.initial_push_delay_value =
3114 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3116 // ---------- initialize changing elements ----------------------------------
3118 // initialize changing elements information
3119 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3121 struct ElementInfo *ei = &element_info[i];
3123 // this pointer might have been changed in the level editor
3124 ei->change = &ei->change_page[0];
3126 if (!IS_CUSTOM_ELEMENT(i))
3128 ei->change->target_element = EL_EMPTY_SPACE;
3129 ei->change->delay_fixed = 0;
3130 ei->change->delay_random = 0;
3131 ei->change->delay_frames = 1;
3134 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3136 ei->has_change_event[j] = FALSE;
3138 ei->event_page_nr[j] = 0;
3139 ei->event_page[j] = &ei->change_page[0];
3143 // add changing elements from pre-defined list
3144 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3146 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3147 struct ElementInfo *ei = &element_info[ch_delay->element];
3149 ei->change->target_element = ch_delay->target_element;
3150 ei->change->delay_fixed = ch_delay->change_delay;
3152 ei->change->pre_change_function = ch_delay->pre_change_function;
3153 ei->change->change_function = ch_delay->change_function;
3154 ei->change->post_change_function = ch_delay->post_change_function;
3156 ei->change->can_change = TRUE;
3157 ei->change->can_change_or_has_action = TRUE;
3159 ei->has_change_event[CE_DELAY] = TRUE;
3161 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3162 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3165 // ---------- initialize internal run-time variables ------------------------
3167 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3169 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3171 for (j = 0; j < ei->num_change_pages; j++)
3173 ei->change_page[j].can_change_or_has_action =
3174 (ei->change_page[j].can_change |
3175 ei->change_page[j].has_action);
3179 // add change events from custom element configuration
3180 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3182 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3184 for (j = 0; j < ei->num_change_pages; j++)
3186 if (!ei->change_page[j].can_change_or_has_action)
3189 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3191 // only add event page for the first page found with this event
3192 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3194 ei->has_change_event[k] = TRUE;
3196 ei->event_page_nr[k] = j;
3197 ei->event_page[k] = &ei->change_page[j];
3203 // ---------- initialize reference elements in change conditions ------------
3205 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3207 int element = EL_CUSTOM_START + i;
3208 struct ElementInfo *ei = &element_info[element];
3210 for (j = 0; j < ei->num_change_pages; j++)
3212 int trigger_element = ei->change_page[j].initial_trigger_element;
3214 if (trigger_element >= EL_PREV_CE_8 &&
3215 trigger_element <= EL_NEXT_CE_8)
3216 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3218 ei->change_page[j].trigger_element = trigger_element;
3222 // ---------- initialize run-time trigger player and element ----------------
3224 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3226 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3228 for (j = 0; j < ei->num_change_pages; j++)
3230 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3231 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3232 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3233 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3234 ei->change_page[j].actual_trigger_ce_value = 0;
3235 ei->change_page[j].actual_trigger_ce_score = 0;
3239 // ---------- initialize trigger events -------------------------------------
3241 // initialize trigger events information
3242 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3243 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3244 trigger_events[i][j] = FALSE;
3246 // add trigger events from element change event properties
3247 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3249 struct ElementInfo *ei = &element_info[i];
3251 for (j = 0; j < ei->num_change_pages; j++)
3253 if (!ei->change_page[j].can_change_or_has_action)
3256 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3258 int trigger_element = ei->change_page[j].trigger_element;
3260 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3262 if (ei->change_page[j].has_event[k])
3264 if (IS_GROUP_ELEMENT(trigger_element))
3266 struct ElementGroupInfo *group =
3267 element_info[trigger_element].group;
3269 for (l = 0; l < group->num_elements_resolved; l++)
3270 trigger_events[group->element_resolved[l]][k] = TRUE;
3272 else if (trigger_element == EL_ANY_ELEMENT)
3273 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3274 trigger_events[l][k] = TRUE;
3276 trigger_events[trigger_element][k] = TRUE;
3283 // ---------- initialize push delay -----------------------------------------
3285 // initialize push delay values to default
3286 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3288 if (!IS_CUSTOM_ELEMENT(i))
3290 // set default push delay values (corrected since version 3.0.7-1)
3291 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3293 element_info[i].push_delay_fixed = 2;
3294 element_info[i].push_delay_random = 8;
3298 element_info[i].push_delay_fixed = 8;
3299 element_info[i].push_delay_random = 8;
3304 // set push delay value for certain elements from pre-defined list
3305 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3307 int e = push_delay_list[i].element;
3309 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3310 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3313 // set push delay value for Supaplex elements for newer engine versions
3314 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3316 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3318 if (IS_SP_ELEMENT(i))
3320 // set SP push delay to just enough to push under a falling zonk
3321 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3323 element_info[i].push_delay_fixed = delay;
3324 element_info[i].push_delay_random = 0;
3329 // ---------- initialize move stepsize --------------------------------------
3331 // initialize move stepsize values to default
3332 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3333 if (!IS_CUSTOM_ELEMENT(i))
3334 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3336 // set move stepsize value for certain elements from pre-defined list
3337 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3339 int e = move_stepsize_list[i].element;
3341 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3343 // set move stepsize value for certain elements for older engine versions
3344 if (use_old_move_stepsize_for_magic_wall)
3346 if (e == EL_MAGIC_WALL_FILLING ||
3347 e == EL_MAGIC_WALL_EMPTYING ||
3348 e == EL_BD_MAGIC_WALL_FILLING ||
3349 e == EL_BD_MAGIC_WALL_EMPTYING)
3350 element_info[e].move_stepsize *= 2;
3354 // ---------- initialize collect score --------------------------------------
3356 // initialize collect score values for custom elements from initial value
3357 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3358 if (IS_CUSTOM_ELEMENT(i))
3359 element_info[i].collect_score = element_info[i].collect_score_initial;
3361 // ---------- initialize collect count --------------------------------------
3363 // initialize collect count values for non-custom elements
3364 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3365 if (!IS_CUSTOM_ELEMENT(i))
3366 element_info[i].collect_count_initial = 0;
3368 // add collect count values for all elements from pre-defined list
3369 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3370 element_info[collect_count_list[i].element].collect_count_initial =
3371 collect_count_list[i].count;
3373 // ---------- initialize access direction -----------------------------------
3375 // initialize access direction values to default (access from every side)
3376 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3377 if (!IS_CUSTOM_ELEMENT(i))
3378 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3380 // set access direction value for certain elements from pre-defined list
3381 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3382 element_info[access_direction_list[i].element].access_direction =
3383 access_direction_list[i].direction;
3385 // ---------- initialize explosion content ----------------------------------
3386 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3388 if (IS_CUSTOM_ELEMENT(i))
3391 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3393 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3395 element_info[i].content.e[x][y] =
3396 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3397 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3398 i == EL_PLAYER_3 ? EL_EMERALD :
3399 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3400 i == EL_MOLE ? EL_EMERALD_RED :
3401 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3402 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3403 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3404 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3405 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3406 i == EL_WALL_EMERALD ? EL_EMERALD :
3407 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3408 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3409 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3410 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3411 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3412 i == EL_WALL_PEARL ? EL_PEARL :
3413 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3418 // ---------- initialize recursion detection --------------------------------
3419 recursion_loop_depth = 0;
3420 recursion_loop_detected = FALSE;
3421 recursion_loop_element = EL_UNDEFINED;
3423 // ---------- initialize graphics engine ------------------------------------
3424 game.scroll_delay_value =
3425 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3426 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3427 !setup.forced_scroll_delay ? 0 :
3428 setup.scroll_delay ? setup.scroll_delay_value : 0);
3429 game.scroll_delay_value =
3430 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3432 // ---------- initialize game engine snapshots ------------------------------
3433 for (i = 0; i < MAX_PLAYERS; i++)
3434 game.snapshot.last_action[i] = 0;
3435 game.snapshot.changed_action = FALSE;
3436 game.snapshot.collected_item = FALSE;
3437 game.snapshot.mode =
3438 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3439 SNAPSHOT_MODE_EVERY_STEP :
3440 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3441 SNAPSHOT_MODE_EVERY_MOVE :
3442 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3443 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3444 game.snapshot.save_snapshot = FALSE;
3446 // ---------- initialize level time for Supaplex engine ---------------------
3447 // Supaplex levels with time limit currently unsupported -- should be added
3448 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3451 // ---------- initialize flags for handling game actions --------------------
3453 // set flags for game actions to default values
3454 game.use_key_actions = TRUE;
3455 game.use_mouse_actions = FALSE;
3457 // when using Mirror Magic game engine, handle mouse events only
3458 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3460 game.use_key_actions = FALSE;
3461 game.use_mouse_actions = TRUE;
3464 // check for custom elements with mouse click events
3465 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3467 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3469 int element = EL_CUSTOM_START + i;
3471 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3472 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3473 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3474 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3475 game.use_mouse_actions = TRUE;
3480 static int get_num_special_action(int element, int action_first,
3483 int num_special_action = 0;
3486 for (i = action_first; i <= action_last; i++)
3488 boolean found = FALSE;
3490 for (j = 0; j < NUM_DIRECTIONS; j++)
3491 if (el_act_dir2img(element, i, j) !=
3492 el_act_dir2img(element, ACTION_DEFAULT, j))
3496 num_special_action++;
3501 return num_special_action;
3505 // ============================================================================
3507 // ----------------------------------------------------------------------------
3508 // initialize and start new game
3509 // ============================================================================
3511 #if DEBUG_INIT_PLAYER
3512 static void DebugPrintPlayerStatus(char *message)
3519 Debug("game:init:player", "%s:", message);
3521 for (i = 0; i < MAX_PLAYERS; i++)
3523 struct PlayerInfo *player = &stored_player[i];
3525 Debug("game:init:player",
3526 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3530 player->connected_locally,
3531 player->connected_network,
3533 (local_player == player ? " (local player)" : ""));
3540 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3541 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3542 int fade_mask = REDRAW_FIELD;
3544 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3545 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3546 int initial_move_dir = MV_DOWN;
3549 // required here to update video display before fading (FIX THIS)
3550 DrawMaskedBorder(REDRAW_DOOR_2);
3552 if (!game.restart_level)
3553 CloseDoor(DOOR_CLOSE_1);
3555 SetGameStatus(GAME_MODE_PLAYING);
3557 if (level_editor_test_game)
3558 FadeSkipNextFadeOut();
3560 FadeSetEnterScreen();
3563 fade_mask = REDRAW_ALL;
3565 FadeLevelSoundsAndMusic();
3567 ExpireSoundLoops(TRUE);
3571 if (level_editor_test_game)
3572 FadeSkipNextFadeIn();
3574 // needed if different viewport properties defined for playing
3575 ChangeViewportPropertiesIfNeeded();
3579 DrawCompleteVideoDisplay();
3581 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3584 InitGameControlValues();
3588 // initialize tape actions from game when recording tape
3589 tape.use_key_actions = game.use_key_actions;
3590 tape.use_mouse_actions = game.use_mouse_actions;
3592 // initialize visible playfield size when recording tape (for team mode)
3593 tape.scr_fieldx = SCR_FIELDX;
3594 tape.scr_fieldy = SCR_FIELDY;
3597 // don't play tapes over network
3598 network_playing = (network.enabled && !tape.playing);
3600 for (i = 0; i < MAX_PLAYERS; i++)
3602 struct PlayerInfo *player = &stored_player[i];
3604 player->index_nr = i;
3605 player->index_bit = (1 << i);
3606 player->element_nr = EL_PLAYER_1 + i;
3608 player->present = FALSE;
3609 player->active = FALSE;
3610 player->mapped = FALSE;
3612 player->killed = FALSE;
3613 player->reanimated = FALSE;
3614 player->buried = FALSE;
3617 player->effective_action = 0;
3618 player->programmed_action = 0;
3619 player->snap_action = 0;
3621 player->mouse_action.lx = 0;
3622 player->mouse_action.ly = 0;
3623 player->mouse_action.button = 0;
3624 player->mouse_action.button_hint = 0;
3626 player->effective_mouse_action.lx = 0;
3627 player->effective_mouse_action.ly = 0;
3628 player->effective_mouse_action.button = 0;
3629 player->effective_mouse_action.button_hint = 0;
3631 for (j = 0; j < MAX_NUM_KEYS; j++)
3632 player->key[j] = FALSE;
3634 player->num_white_keys = 0;
3636 player->dynabomb_count = 0;
3637 player->dynabomb_size = 1;
3638 player->dynabombs_left = 0;
3639 player->dynabomb_xl = FALSE;
3641 player->MovDir = initial_move_dir;
3644 player->GfxDir = initial_move_dir;
3645 player->GfxAction = ACTION_DEFAULT;
3647 player->StepFrame = 0;
3649 player->initial_element = player->element_nr;
3650 player->artwork_element =
3651 (level.use_artwork_element[i] ? level.artwork_element[i] :
3652 player->element_nr);
3653 player->use_murphy = FALSE;
3655 player->block_last_field = FALSE; // initialized in InitPlayerField()
3656 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3658 player->gravity = level.initial_player_gravity[i];
3660 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3662 player->actual_frame_counter = 0;
3664 player->step_counter = 0;
3666 player->last_move_dir = initial_move_dir;
3668 player->is_active = FALSE;
3670 player->is_waiting = FALSE;
3671 player->is_moving = FALSE;
3672 player->is_auto_moving = FALSE;
3673 player->is_digging = FALSE;
3674 player->is_snapping = FALSE;
3675 player->is_collecting = FALSE;
3676 player->is_pushing = FALSE;
3677 player->is_switching = FALSE;
3678 player->is_dropping = FALSE;
3679 player->is_dropping_pressed = FALSE;
3681 player->is_bored = FALSE;
3682 player->is_sleeping = FALSE;
3684 player->was_waiting = TRUE;
3685 player->was_moving = FALSE;
3686 player->was_snapping = FALSE;
3687 player->was_dropping = FALSE;
3689 player->force_dropping = FALSE;
3691 player->frame_counter_bored = -1;
3692 player->frame_counter_sleeping = -1;
3694 player->anim_delay_counter = 0;
3695 player->post_delay_counter = 0;
3697 player->dir_waiting = initial_move_dir;
3698 player->action_waiting = ACTION_DEFAULT;
3699 player->last_action_waiting = ACTION_DEFAULT;
3700 player->special_action_bored = ACTION_DEFAULT;
3701 player->special_action_sleeping = ACTION_DEFAULT;
3703 player->switch_x = -1;
3704 player->switch_y = -1;
3706 player->drop_x = -1;
3707 player->drop_y = -1;
3709 player->show_envelope = 0;
3711 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3713 player->push_delay = -1; // initialized when pushing starts
3714 player->push_delay_value = game.initial_push_delay_value;
3716 player->drop_delay = 0;
3717 player->drop_pressed_delay = 0;
3719 player->last_jx = -1;
3720 player->last_jy = -1;
3724 player->shield_normal_time_left = 0;
3725 player->shield_deadly_time_left = 0;
3727 player->last_removed_element = EL_UNDEFINED;
3729 player->inventory_infinite_element = EL_UNDEFINED;
3730 player->inventory_size = 0;
3732 if (level.use_initial_inventory[i])
3734 for (j = 0; j < level.initial_inventory_size[i]; j++)
3736 int element = level.initial_inventory_content[i][j];
3737 int collect_count = element_info[element].collect_count_initial;
3740 if (!IS_CUSTOM_ELEMENT(element))
3743 if (collect_count == 0)
3744 player->inventory_infinite_element = element;
3746 for (k = 0; k < collect_count; k++)
3747 if (player->inventory_size < MAX_INVENTORY_SIZE)
3748 player->inventory_element[player->inventory_size++] = element;
3752 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3753 SnapField(player, 0, 0);
3755 map_player_action[i] = i;
3758 network_player_action_received = FALSE;
3760 // initial null action
3761 if (network_playing)
3762 SendToServer_MovePlayer(MV_NONE);
3767 TimeLeft = level.time;
3770 ScreenMovDir = MV_NONE;
3774 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3776 game.robot_wheel_x = -1;
3777 game.robot_wheel_y = -1;
3782 game.all_players_gone = FALSE;
3784 game.LevelSolved = FALSE;
3785 game.GameOver = FALSE;
3787 game.GamePlayed = !tape.playing;
3789 game.LevelSolved_GameWon = FALSE;
3790 game.LevelSolved_GameEnd = FALSE;
3791 game.LevelSolved_SaveTape = FALSE;
3792 game.LevelSolved_SaveScore = FALSE;
3794 game.LevelSolved_CountingTime = 0;
3795 game.LevelSolved_CountingScore = 0;
3796 game.LevelSolved_CountingHealth = 0;
3798 game.panel.active = TRUE;
3800 game.no_time_limit = (level.time == 0);
3802 game.yamyam_content_nr = 0;
3803 game.robot_wheel_active = FALSE;
3804 game.magic_wall_active = FALSE;
3805 game.magic_wall_time_left = 0;
3806 game.light_time_left = 0;
3807 game.timegate_time_left = 0;
3808 game.switchgate_pos = 0;
3809 game.wind_direction = level.wind_direction_initial;
3811 game.time_final = 0;
3812 game.score_time_final = 0;
3815 game.score_final = 0;
3817 game.health = MAX_HEALTH;
3818 game.health_final = MAX_HEALTH;
3820 game.gems_still_needed = level.gems_needed;
3821 game.sokoban_fields_still_needed = 0;
3822 game.sokoban_objects_still_needed = 0;
3823 game.lights_still_needed = 0;
3824 game.players_still_needed = 0;
3825 game.friends_still_needed = 0;
3827 game.lenses_time_left = 0;
3828 game.magnify_time_left = 0;
3830 game.ball_active = level.ball_active_initial;
3831 game.ball_content_nr = 0;
3833 game.explosions_delayed = TRUE;
3835 game.envelope_active = FALSE;
3837 for (i = 0; i < NUM_BELTS; i++)
3839 game.belt_dir[i] = MV_NONE;
3840 game.belt_dir_nr[i] = 3; // not moving, next moving left
3843 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3844 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3846 #if DEBUG_INIT_PLAYER
3847 DebugPrintPlayerStatus("Player status at level initialization");
3850 SCAN_PLAYFIELD(x, y)
3852 Tile[x][y] = Last[x][y] = level.field[x][y];
3853 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3854 ChangeDelay[x][y] = 0;
3855 ChangePage[x][y] = -1;
3856 CustomValue[x][y] = 0; // initialized in InitField()
3857 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3859 WasJustMoving[x][y] = 0;
3860 WasJustFalling[x][y] = 0;
3861 CheckCollision[x][y] = 0;
3862 CheckImpact[x][y] = 0;
3864 Pushed[x][y] = FALSE;
3866 ChangeCount[x][y] = 0;
3867 ChangeEvent[x][y] = -1;
3869 ExplodePhase[x][y] = 0;
3870 ExplodeDelay[x][y] = 0;
3871 ExplodeField[x][y] = EX_TYPE_NONE;
3873 RunnerVisit[x][y] = 0;
3874 PlayerVisit[x][y] = 0;
3877 GfxRandom[x][y] = INIT_GFX_RANDOM();
3878 GfxElement[x][y] = EL_UNDEFINED;
3879 GfxAction[x][y] = ACTION_DEFAULT;
3880 GfxDir[x][y] = MV_NONE;
3881 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3884 SCAN_PLAYFIELD(x, y)
3886 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3888 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3891 InitField(x, y, TRUE);
3893 ResetGfxAnimation(x, y);
3898 for (i = 0; i < MAX_PLAYERS; i++)
3900 struct PlayerInfo *player = &stored_player[i];
3902 // set number of special actions for bored and sleeping animation
3903 player->num_special_action_bored =
3904 get_num_special_action(player->artwork_element,
3905 ACTION_BORING_1, ACTION_BORING_LAST);
3906 player->num_special_action_sleeping =
3907 get_num_special_action(player->artwork_element,
3908 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3911 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3912 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3914 // initialize type of slippery elements
3915 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3917 if (!IS_CUSTOM_ELEMENT(i))
3919 // default: elements slip down either to the left or right randomly
3920 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3922 // SP style elements prefer to slip down on the left side
3923 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3924 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3926 // BD style elements prefer to slip down on the left side
3927 if (game.emulation == EMU_BOULDERDASH)
3928 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3932 // initialize explosion and ignition delay
3933 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3935 if (!IS_CUSTOM_ELEMENT(i))
3938 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3939 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3940 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3941 int last_phase = (num_phase + 1) * delay;
3942 int half_phase = (num_phase / 2) * delay;
3944 element_info[i].explosion_delay = last_phase - 1;
3945 element_info[i].ignition_delay = half_phase;
3947 if (i == EL_BLACK_ORB)
3948 element_info[i].ignition_delay = 1;
3952 // correct non-moving belts to start moving left
3953 for (i = 0; i < NUM_BELTS; i++)
3954 if (game.belt_dir[i] == MV_NONE)
3955 game.belt_dir_nr[i] = 3; // not moving, next moving left
3957 #if USE_NEW_PLAYER_ASSIGNMENTS
3958 // use preferred player also in local single-player mode
3959 if (!network.enabled && !game.team_mode)
3961 int new_index_nr = setup.network_player_nr;
3963 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3965 for (i = 0; i < MAX_PLAYERS; i++)
3966 stored_player[i].connected_locally = FALSE;
3968 stored_player[new_index_nr].connected_locally = TRUE;
3972 for (i = 0; i < MAX_PLAYERS; i++)
3974 stored_player[i].connected = FALSE;
3976 // in network game mode, the local player might not be the first player
3977 if (stored_player[i].connected_locally)
3978 local_player = &stored_player[i];
3981 if (!network.enabled)
3982 local_player->connected = TRUE;
3986 for (i = 0; i < MAX_PLAYERS; i++)
3987 stored_player[i].connected = tape.player_participates[i];
3989 else if (network.enabled)
3991 // add team mode players connected over the network (needed for correct
3992 // assignment of player figures from level to locally playing players)
3994 for (i = 0; i < MAX_PLAYERS; i++)
3995 if (stored_player[i].connected_network)
3996 stored_player[i].connected = TRUE;
3998 else if (game.team_mode)
4000 // try to guess locally connected team mode players (needed for correct
4001 // assignment of player figures from level to locally playing players)
4003 for (i = 0; i < MAX_PLAYERS; i++)
4004 if (setup.input[i].use_joystick ||
4005 setup.input[i].key.left != KSYM_UNDEFINED)
4006 stored_player[i].connected = TRUE;
4009 #if DEBUG_INIT_PLAYER
4010 DebugPrintPlayerStatus("Player status after level initialization");
4013 #if DEBUG_INIT_PLAYER
4014 Debug("game:init:player", "Reassigning players ...");
4017 // check if any connected player was not found in playfield
4018 for (i = 0; i < MAX_PLAYERS; i++)
4020 struct PlayerInfo *player = &stored_player[i];
4022 if (player->connected && !player->present)
4024 struct PlayerInfo *field_player = NULL;
4026 #if DEBUG_INIT_PLAYER
4027 Debug("game:init:player",
4028 "- looking for field player for player %d ...", i + 1);
4031 // assign first free player found that is present in the playfield
4033 // first try: look for unmapped playfield player that is not connected
4034 for (j = 0; j < MAX_PLAYERS; j++)
4035 if (field_player == NULL &&
4036 stored_player[j].present &&
4037 !stored_player[j].mapped &&
4038 !stored_player[j].connected)
4039 field_player = &stored_player[j];
4041 // second try: look for *any* unmapped playfield player
4042 for (j = 0; j < MAX_PLAYERS; j++)
4043 if (field_player == NULL &&
4044 stored_player[j].present &&
4045 !stored_player[j].mapped)
4046 field_player = &stored_player[j];
4048 if (field_player != NULL)
4050 int jx = field_player->jx, jy = field_player->jy;
4052 #if DEBUG_INIT_PLAYER
4053 Debug("game:init:player", "- found player %d",
4054 field_player->index_nr + 1);
4057 player->present = FALSE;
4058 player->active = FALSE;
4060 field_player->present = TRUE;
4061 field_player->active = TRUE;
4064 player->initial_element = field_player->initial_element;
4065 player->artwork_element = field_player->artwork_element;
4067 player->block_last_field = field_player->block_last_field;
4068 player->block_delay_adjustment = field_player->block_delay_adjustment;
4071 StorePlayer[jx][jy] = field_player->element_nr;
4073 field_player->jx = field_player->last_jx = jx;
4074 field_player->jy = field_player->last_jy = jy;
4076 if (local_player == player)
4077 local_player = field_player;
4079 map_player_action[field_player->index_nr] = i;
4081 field_player->mapped = TRUE;
4083 #if DEBUG_INIT_PLAYER
4084 Debug("game:init:player", "- map_player_action[%d] == %d",
4085 field_player->index_nr + 1, i + 1);
4090 if (player->connected && player->present)
4091 player->mapped = TRUE;
4094 #if DEBUG_INIT_PLAYER
4095 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4100 // check if any connected player was not found in playfield
4101 for (i = 0; i < MAX_PLAYERS; i++)
4103 struct PlayerInfo *player = &stored_player[i];
4105 if (player->connected && !player->present)
4107 for (j = 0; j < MAX_PLAYERS; j++)
4109 struct PlayerInfo *field_player = &stored_player[j];
4110 int jx = field_player->jx, jy = field_player->jy;
4112 // assign first free player found that is present in the playfield
4113 if (field_player->present && !field_player->connected)
4115 player->present = TRUE;
4116 player->active = TRUE;
4118 field_player->present = FALSE;
4119 field_player->active = FALSE;
4121 player->initial_element = field_player->initial_element;
4122 player->artwork_element = field_player->artwork_element;
4124 player->block_last_field = field_player->block_last_field;
4125 player->block_delay_adjustment = field_player->block_delay_adjustment;
4127 StorePlayer[jx][jy] = player->element_nr;
4129 player->jx = player->last_jx = jx;
4130 player->jy = player->last_jy = jy;
4140 Debug("game:init:player", "local_player->present == %d",
4141 local_player->present);
4144 // set focus to local player for network games, else to all players
4145 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4146 game.centered_player_nr_next = game.centered_player_nr;
4147 game.set_centered_player = FALSE;
4148 game.set_centered_player_wrap = FALSE;
4150 if (network_playing && tape.recording)
4152 // store client dependent player focus when recording network games
4153 tape.centered_player_nr_next = game.centered_player_nr_next;
4154 tape.set_centered_player = TRUE;
4159 // when playing a tape, eliminate all players who do not participate
4161 #if USE_NEW_PLAYER_ASSIGNMENTS
4163 if (!game.team_mode)
4165 for (i = 0; i < MAX_PLAYERS; i++)
4167 if (stored_player[i].active &&
4168 !tape.player_participates[map_player_action[i]])
4170 struct PlayerInfo *player = &stored_player[i];
4171 int jx = player->jx, jy = player->jy;
4173 #if DEBUG_INIT_PLAYER
4174 Debug("game:init:player", "Removing player %d at (%d, %d)",
4178 player->active = FALSE;
4179 StorePlayer[jx][jy] = 0;
4180 Tile[jx][jy] = EL_EMPTY;
4187 for (i = 0; i < MAX_PLAYERS; i++)
4189 if (stored_player[i].active &&
4190 !tape.player_participates[i])
4192 struct PlayerInfo *player = &stored_player[i];
4193 int jx = player->jx, jy = player->jy;
4195 player->active = FALSE;
4196 StorePlayer[jx][jy] = 0;
4197 Tile[jx][jy] = EL_EMPTY;
4202 else if (!network.enabled && !game.team_mode) // && !tape.playing
4204 // when in single player mode, eliminate all but the local player
4206 for (i = 0; i < MAX_PLAYERS; i++)
4208 struct PlayerInfo *player = &stored_player[i];
4210 if (player->active && player != local_player)
4212 int jx = player->jx, jy = player->jy;
4214 player->active = FALSE;
4215 player->present = FALSE;
4217 StorePlayer[jx][jy] = 0;
4218 Tile[jx][jy] = EL_EMPTY;
4223 for (i = 0; i < MAX_PLAYERS; i++)
4224 if (stored_player[i].active)
4225 game.players_still_needed++;
4227 if (level.solved_by_one_player)
4228 game.players_still_needed = 1;
4230 // when recording the game, store which players take part in the game
4233 #if USE_NEW_PLAYER_ASSIGNMENTS
4234 for (i = 0; i < MAX_PLAYERS; i++)
4235 if (stored_player[i].connected)
4236 tape.player_participates[i] = TRUE;
4238 for (i = 0; i < MAX_PLAYERS; i++)
4239 if (stored_player[i].active)
4240 tape.player_participates[i] = TRUE;
4244 #if DEBUG_INIT_PLAYER
4245 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4248 if (BorderElement == EL_EMPTY)
4251 SBX_Right = lev_fieldx - SCR_FIELDX;
4253 SBY_Lower = lev_fieldy - SCR_FIELDY;
4258 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4260 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4263 if (full_lev_fieldx <= SCR_FIELDX)
4264 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4265 if (full_lev_fieldy <= SCR_FIELDY)
4266 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4268 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4270 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4273 // if local player not found, look for custom element that might create
4274 // the player (make some assumptions about the right custom element)
4275 if (!local_player->present)
4277 int start_x = 0, start_y = 0;
4278 int found_rating = 0;
4279 int found_element = EL_UNDEFINED;
4280 int player_nr = local_player->index_nr;
4282 SCAN_PLAYFIELD(x, y)
4284 int element = Tile[x][y];
4289 if (level.use_start_element[player_nr] &&
4290 level.start_element[player_nr] == element &&
4297 found_element = element;
4300 if (!IS_CUSTOM_ELEMENT(element))
4303 if (CAN_CHANGE(element))
4305 for (i = 0; i < element_info[element].num_change_pages; i++)
4307 // check for player created from custom element as single target
4308 content = element_info[element].change_page[i].target_element;
4309 is_player = IS_PLAYER_ELEMENT(content);
4311 if (is_player && (found_rating < 3 ||
4312 (found_rating == 3 && element < found_element)))
4318 found_element = element;
4323 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4325 // check for player created from custom element as explosion content
4326 content = element_info[element].content.e[xx][yy];
4327 is_player = IS_PLAYER_ELEMENT(content);
4329 if (is_player && (found_rating < 2 ||
4330 (found_rating == 2 && element < found_element)))
4332 start_x = x + xx - 1;
4333 start_y = y + yy - 1;
4336 found_element = element;
4339 if (!CAN_CHANGE(element))
4342 for (i = 0; i < element_info[element].num_change_pages; i++)
4344 // check for player created from custom element as extended target
4346 element_info[element].change_page[i].target_content.e[xx][yy];
4348 is_player = IS_PLAYER_ELEMENT(content);
4350 if (is_player && (found_rating < 1 ||
4351 (found_rating == 1 && element < found_element)))
4353 start_x = x + xx - 1;
4354 start_y = y + yy - 1;
4357 found_element = element;
4363 scroll_x = SCROLL_POSITION_X(start_x);
4364 scroll_y = SCROLL_POSITION_Y(start_y);
4368 scroll_x = SCROLL_POSITION_X(local_player->jx);
4369 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4372 // !!! FIX THIS (START) !!!
4373 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4375 InitGameEngine_EM();
4377 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4379 InitGameEngine_SP();
4381 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4383 InitGameEngine_MM();
4387 DrawLevel(REDRAW_FIELD);
4390 // after drawing the level, correct some elements
4391 if (game.timegate_time_left == 0)
4392 CloseAllOpenTimegates();
4395 // blit playfield from scroll buffer to normal back buffer for fading in
4396 BlitScreenToBitmap(backbuffer);
4397 // !!! FIX THIS (END) !!!
4399 DrawMaskedBorder(fade_mask);
4404 // full screen redraw is required at this point in the following cases:
4405 // - special editor door undrawn when game was started from level editor
4406 // - drawing area (playfield) was changed and has to be removed completely
4407 redraw_mask = REDRAW_ALL;
4411 if (!game.restart_level)
4413 // copy default game door content to main double buffer
4415 // !!! CHECK AGAIN !!!
4416 SetPanelBackground();
4417 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4418 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4421 SetPanelBackground();
4422 SetDrawBackgroundMask(REDRAW_DOOR_1);
4424 UpdateAndDisplayGameControlValues();
4426 if (!game.restart_level)
4432 CreateGameButtons();
4437 // copy actual game door content to door double buffer for OpenDoor()
4438 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4440 OpenDoor(DOOR_OPEN_ALL);
4442 KeyboardAutoRepeatOffUnlessAutoplay();
4444 #if DEBUG_INIT_PLAYER
4445 DebugPrintPlayerStatus("Player status (final)");
4454 if (!game.restart_level && !tape.playing)
4456 LevelStats_incPlayed(level_nr);
4458 SaveLevelSetup_SeriesInfo();
4461 game.restart_level = FALSE;
4462 game.restart_game_message = NULL;
4464 game.request_active = FALSE;
4465 game.request_active_or_moving = FALSE;
4467 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4468 InitGameActions_MM();
4470 SaveEngineSnapshotToListInitial();
4472 if (!game.restart_level)
4474 PlaySound(SND_GAME_STARTING);
4476 if (setup.sound_music)
4481 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4482 int actual_player_x, int actual_player_y)
4484 // this is used for non-R'n'D game engines to update certain engine values
4486 // needed to determine if sounds are played within the visible screen area
4487 scroll_x = actual_scroll_x;
4488 scroll_y = actual_scroll_y;
4490 // needed to get player position for "follow finger" playing input method
4491 local_player->jx = actual_player_x;
4492 local_player->jy = actual_player_y;
4495 void InitMovDir(int x, int y)
4497 int i, element = Tile[x][y];
4498 static int xy[4][2] =
4505 static int direction[3][4] =
4507 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4508 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4509 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4518 Tile[x][y] = EL_BUG;
4519 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4522 case EL_SPACESHIP_RIGHT:
4523 case EL_SPACESHIP_UP:
4524 case EL_SPACESHIP_LEFT:
4525 case EL_SPACESHIP_DOWN:
4526 Tile[x][y] = EL_SPACESHIP;
4527 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4530 case EL_BD_BUTTERFLY_RIGHT:
4531 case EL_BD_BUTTERFLY_UP:
4532 case EL_BD_BUTTERFLY_LEFT:
4533 case EL_BD_BUTTERFLY_DOWN:
4534 Tile[x][y] = EL_BD_BUTTERFLY;
4535 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4538 case EL_BD_FIREFLY_RIGHT:
4539 case EL_BD_FIREFLY_UP:
4540 case EL_BD_FIREFLY_LEFT:
4541 case EL_BD_FIREFLY_DOWN:
4542 Tile[x][y] = EL_BD_FIREFLY;
4543 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4546 case EL_PACMAN_RIGHT:
4548 case EL_PACMAN_LEFT:
4549 case EL_PACMAN_DOWN:
4550 Tile[x][y] = EL_PACMAN;
4551 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4554 case EL_YAMYAM_LEFT:
4555 case EL_YAMYAM_RIGHT:
4557 case EL_YAMYAM_DOWN:
4558 Tile[x][y] = EL_YAMYAM;
4559 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4562 case EL_SP_SNIKSNAK:
4563 MovDir[x][y] = MV_UP;
4566 case EL_SP_ELECTRON:
4567 MovDir[x][y] = MV_LEFT;
4574 Tile[x][y] = EL_MOLE;
4575 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4578 case EL_SPRING_LEFT:
4579 case EL_SPRING_RIGHT:
4580 Tile[x][y] = EL_SPRING;
4581 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4585 if (IS_CUSTOM_ELEMENT(element))
4587 struct ElementInfo *ei = &element_info[element];
4588 int move_direction_initial = ei->move_direction_initial;
4589 int move_pattern = ei->move_pattern;
4591 if (move_direction_initial == MV_START_PREVIOUS)
4593 if (MovDir[x][y] != MV_NONE)
4596 move_direction_initial = MV_START_AUTOMATIC;
4599 if (move_direction_initial == MV_START_RANDOM)
4600 MovDir[x][y] = 1 << RND(4);
4601 else if (move_direction_initial & MV_ANY_DIRECTION)
4602 MovDir[x][y] = move_direction_initial;
4603 else if (move_pattern == MV_ALL_DIRECTIONS ||
4604 move_pattern == MV_TURNING_LEFT ||
4605 move_pattern == MV_TURNING_RIGHT ||
4606 move_pattern == MV_TURNING_LEFT_RIGHT ||
4607 move_pattern == MV_TURNING_RIGHT_LEFT ||
4608 move_pattern == MV_TURNING_RANDOM)
4609 MovDir[x][y] = 1 << RND(4);
4610 else if (move_pattern == MV_HORIZONTAL)
4611 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4612 else if (move_pattern == MV_VERTICAL)
4613 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4614 else if (move_pattern & MV_ANY_DIRECTION)
4615 MovDir[x][y] = element_info[element].move_pattern;
4616 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4617 move_pattern == MV_ALONG_RIGHT_SIDE)
4619 // use random direction as default start direction
4620 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4621 MovDir[x][y] = 1 << RND(4);
4623 for (i = 0; i < NUM_DIRECTIONS; i++)
4625 int x1 = x + xy[i][0];
4626 int y1 = y + xy[i][1];
4628 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4630 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4631 MovDir[x][y] = direction[0][i];
4633 MovDir[x][y] = direction[1][i];
4642 MovDir[x][y] = 1 << RND(4);
4644 if (element != EL_BUG &&
4645 element != EL_SPACESHIP &&
4646 element != EL_BD_BUTTERFLY &&
4647 element != EL_BD_FIREFLY)
4650 for (i = 0; i < NUM_DIRECTIONS; i++)
4652 int x1 = x + xy[i][0];
4653 int y1 = y + xy[i][1];
4655 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4657 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4659 MovDir[x][y] = direction[0][i];
4662 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4663 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4665 MovDir[x][y] = direction[1][i];
4674 GfxDir[x][y] = MovDir[x][y];
4677 void InitAmoebaNr(int x, int y)
4680 int group_nr = AmoebaNeighbourNr(x, y);
4684 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4686 if (AmoebaCnt[i] == 0)
4694 AmoebaNr[x][y] = group_nr;
4695 AmoebaCnt[group_nr]++;
4696 AmoebaCnt2[group_nr]++;
4699 static void LevelSolved(void)
4701 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4702 game.players_still_needed > 0)
4705 game.LevelSolved = TRUE;
4706 game.GameOver = TRUE;
4711 static int time_count_steps;
4712 static int time, time_final;
4713 static float score, score_final; // needed for time score < 10 for 10 seconds
4714 static int health, health_final;
4715 static int game_over_delay_1 = 0;
4716 static int game_over_delay_2 = 0;
4717 static int game_over_delay_3 = 0;
4718 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4719 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4721 if (!game.LevelSolved_GameWon)
4725 // do not start end game actions before the player stops moving (to exit)
4726 if (local_player->active && local_player->MovPos)
4729 game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
4730 game.score_time_final = (level.use_step_counter ? TimePlayed :
4731 TimePlayed * FRAMES_PER_SECOND + TimeFrames);
4733 game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
4734 game_em.lev->score :
4735 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4739 game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
4740 MM_HEALTH(game_mm.laser_overload_value) :
4743 game.LevelSolved_CountingTime = game.time_final;
4744 game.LevelSolved_CountingScore = game.score_final;
4745 game.LevelSolved_CountingHealth = game.health_final;
4747 game.LevelSolved_GameWon = TRUE;
4748 game.LevelSolved_SaveTape = tape.recording;
4749 game.LevelSolved_SaveScore = !tape.playing;
4753 LevelStats_incSolved(level_nr);
4755 SaveLevelSetup_SeriesInfo();
4758 if (tape.auto_play) // tape might already be stopped here
4759 tape.auto_play_level_solved = TRUE;
4763 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4764 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4765 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4767 time = time_final = game.time_final;
4768 score = score_final = game.score_final;
4769 health = health_final = game.health_final;
4773 int time_final_max = 999;
4774 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4775 int time_frames = 0;
4776 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4777 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4782 time_frames = time_frames_left;
4784 else if (game.no_time_limit && TimePlayed < time_final_max)
4786 time_final = time_final_max;
4787 time_frames = time_frames_final_max - time_frames_played;
4790 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4792 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4794 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4797 score_final += health * time_score;
4800 game.score_final = score_final;
4801 game.health_final = health_final;
4804 if (level_editor_test_game || !setup.count_score_after_game)
4807 score = score_final;
4809 game.LevelSolved_CountingTime = time;
4810 game.LevelSolved_CountingScore = score;
4812 game_panel_controls[GAME_PANEL_TIME].value = time;
4813 game_panel_controls[GAME_PANEL_SCORE].value = score;
4815 DisplayGameControlValues();
4818 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4820 // check if last player has left the level
4821 if (game.exit_x >= 0 &&
4824 int x = game.exit_x;
4825 int y = game.exit_y;
4826 int element = Tile[x][y];
4828 // close exit door after last player
4829 if ((game.all_players_gone &&
4830 (element == EL_EXIT_OPEN ||
4831 element == EL_SP_EXIT_OPEN ||
4832 element == EL_STEEL_EXIT_OPEN)) ||
4833 element == EL_EM_EXIT_OPEN ||
4834 element == EL_EM_STEEL_EXIT_OPEN)
4838 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4839 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4840 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4841 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4842 EL_EM_STEEL_EXIT_CLOSING);
4844 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4847 // player disappears
4848 DrawLevelField(x, y);
4851 for (i = 0; i < MAX_PLAYERS; i++)
4853 struct PlayerInfo *player = &stored_player[i];
4855 if (player->present)
4857 RemovePlayer(player);
4859 // player disappears
4860 DrawLevelField(player->jx, player->jy);
4865 PlaySound(SND_GAME_WINNING);
4868 if (setup.count_score_after_game)
4870 if (time != time_final)
4872 if (game_over_delay_1 > 0)
4874 game_over_delay_1--;
4879 int time_to_go = ABS(time_final - time);
4880 int time_count_dir = (time < time_final ? +1 : -1);
4882 if (time_to_go < time_count_steps)
4883 time_count_steps = 1;
4885 time += time_count_steps * time_count_dir;
4886 score += time_count_steps * time_score;
4888 // set final score to correct rounding differences after counting score
4889 if (time == time_final)
4890 score = score_final;
4892 game.LevelSolved_CountingTime = time;
4893 game.LevelSolved_CountingScore = score;
4895 game_panel_controls[GAME_PANEL_TIME].value = time;
4896 game_panel_controls[GAME_PANEL_SCORE].value = score;
4898 DisplayGameControlValues();
4900 if (time == time_final)
4901 StopSound(SND_GAME_LEVELTIME_BONUS);
4902 else if (setup.sound_loops)
4903 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4905 PlaySound(SND_GAME_LEVELTIME_BONUS);
4910 if (health != health_final)
4912 if (game_over_delay_2 > 0)
4914 game_over_delay_2--;
4919 int health_count_dir = (health < health_final ? +1 : -1);
4921 health += health_count_dir;
4922 score += time_score;
4924 game.LevelSolved_CountingHealth = health;
4925 game.LevelSolved_CountingScore = score;
4927 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4928 game_panel_controls[GAME_PANEL_SCORE].value = score;
4930 DisplayGameControlValues();
4932 if (health == health_final)
4933 StopSound(SND_GAME_LEVELTIME_BONUS);
4934 else if (setup.sound_loops)
4935 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4937 PlaySound(SND_GAME_LEVELTIME_BONUS);
4943 game.panel.active = FALSE;
4945 if (game_over_delay_3 > 0)
4947 game_over_delay_3--;
4957 // used instead of "level_nr" (needed for network games)
4958 int last_level_nr = levelset.level_nr;
4960 game.LevelSolved_GameEnd = TRUE;
4962 if (game.LevelSolved_SaveTape)
4964 // make sure that request dialog to save tape does not open door again
4965 if (!global.use_envelope_request)
4966 CloseDoor(DOOR_CLOSE_1);
4968 SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape
4970 // set unique basename for score tape (also saved in high score table)
4971 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
4974 // if no tape is to be saved, close both doors simultaneously
4975 CloseDoor(DOOR_CLOSE_ALL);
4977 if (level_editor_test_game)
4979 SetGameStatus(GAME_MODE_MAIN);
4986 if (!game.LevelSolved_SaveScore)
4988 SetGameStatus(GAME_MODE_MAIN);
4995 if (level_nr == leveldir_current->handicap_level)
4997 leveldir_current->handicap_level++;
4999 SaveLevelSetup_SeriesInfo();
5002 // save score and score tape before potentially erasing tape below
5003 NewHighScore(last_level_nr);
5005 if (setup.increment_levels &&
5006 level_nr < leveldir_current->last_level &&
5009 level_nr++; // advance to next level
5010 TapeErase(); // start with empty tape
5012 if (setup.auto_play_next_level)
5014 LoadLevel(level_nr);
5016 SaveLevelSetup_SeriesInfo();
5020 if (scores.last_added >= 0 && setup.show_scores_after_game)
5022 SetGameStatus(GAME_MODE_SCORES);
5024 DrawHallOfFame(last_level_nr);
5026 else if (setup.auto_play_next_level && setup.increment_levels &&
5027 last_level_nr < leveldir_current->last_level &&
5030 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5034 SetGameStatus(GAME_MODE_MAIN);
5040 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5041 boolean one_score_entry_per_name)
5045 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5048 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5050 struct ScoreEntry *entry = &list->entry[i];
5051 boolean score_is_better = (new_entry->score > entry->score);
5052 boolean score_is_equal = (new_entry->score == entry->score);
5053 boolean time_is_better = (new_entry->time < entry->time);
5054 boolean time_is_equal = (new_entry->time == entry->time);
5055 boolean better_by_score = (score_is_better ||
5056 (score_is_equal && time_is_better));
5057 boolean better_by_time = (time_is_better ||
5058 (time_is_equal && score_is_better));
5059 boolean is_better = (level.rate_time_over_score ? better_by_time :
5061 boolean entry_is_empty = (entry->score == 0 &&
5064 // prevent adding server score entries if also existing in local score file
5065 // (special case: historic score entries have an empty tape basename entry)
5066 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5067 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5070 if (is_better || entry_is_empty)
5072 // player has made it to the hall of fame
5074 if (i < MAX_SCORE_ENTRIES - 1)
5076 int m = MAX_SCORE_ENTRIES - 1;
5079 if (one_score_entry_per_name)
5081 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5082 if (strEqual(list->entry[l].name, new_entry->name))
5085 if (m == i) // player's new highscore overwrites his old one
5089 for (l = m; l > i; l--)
5090 list->entry[l] = list->entry[l - 1];
5095 *entry = *new_entry;
5099 else if (one_score_entry_per_name &&
5100 strEqual(entry->name, new_entry->name))
5102 // player already in high score list with better score or time
5111 void NewHighScore(int level_nr)
5113 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5114 boolean one_per_name = FALSE;
5116 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5117 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5119 new_entry.score = game.score_final;
5120 new_entry.time = game.score_time_final;
5122 LoadScore(level_nr);
5124 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5126 if (scores.last_added >= 0)
5128 SaveScore(level_nr);
5130 // store last added local score entry (before merging server scores)
5131 scores.last_added_local = scores.last_added;
5134 if (game.LevelSolved_SaveTape)
5136 SaveScoreTape(level_nr);
5137 SaveServerScore(level_nr);
5141 void MergeServerScore(void)
5143 struct ScoreEntry last_added_entry;
5144 boolean one_per_name = FALSE;
5147 if (scores.last_added >= 0)
5148 last_added_entry = scores.entry[scores.last_added];
5150 for (i = 0; i < server_scores.num_entries; i++)
5152 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5154 if (pos >= 0 && pos <= scores.last_added)
5155 scores.last_added++;
5158 if (scores.last_added >= MAX_SCORE_ENTRIES)
5160 scores.last_added = MAX_SCORE_ENTRIES - 1;
5161 scores.force_last_added = TRUE;
5163 scores.entry[scores.last_added] = last_added_entry;
5167 static int getElementMoveStepsizeExt(int x, int y, int direction)
5169 int element = Tile[x][y];
5170 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5171 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5172 int horiz_move = (dx != 0);
5173 int sign = (horiz_move ? dx : dy);
5174 int step = sign * element_info[element].move_stepsize;
5176 // special values for move stepsize for spring and things on conveyor belt
5179 if (CAN_FALL(element) &&
5180 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5181 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5182 else if (element == EL_SPRING)
5183 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5189 static int getElementMoveStepsize(int x, int y)
5191 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5194 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5196 if (player->GfxAction != action || player->GfxDir != dir)
5198 player->GfxAction = action;
5199 player->GfxDir = dir;
5201 player->StepFrame = 0;
5205 static void ResetGfxFrame(int x, int y)
5207 // profiling showed that "autotest" spends 10~20% of its time in this function
5208 if (DrawingDeactivatedField())
5211 int element = Tile[x][y];
5212 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5214 if (graphic_info[graphic].anim_global_sync)
5215 GfxFrame[x][y] = FrameCounter;
5216 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5217 GfxFrame[x][y] = CustomValue[x][y];
5218 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5219 GfxFrame[x][y] = element_info[element].collect_score;
5220 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5221 GfxFrame[x][y] = ChangeDelay[x][y];
5224 static void ResetGfxAnimation(int x, int y)
5226 GfxAction[x][y] = ACTION_DEFAULT;
5227 GfxDir[x][y] = MovDir[x][y];
5230 ResetGfxFrame(x, y);
5233 static void ResetRandomAnimationValue(int x, int y)
5235 GfxRandom[x][y] = INIT_GFX_RANDOM();
5238 static void InitMovingField(int x, int y, int direction)
5240 int element = Tile[x][y];
5241 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5242 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5245 boolean is_moving_before, is_moving_after;
5247 // check if element was/is moving or being moved before/after mode change
5248 is_moving_before = (WasJustMoving[x][y] != 0);
5249 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5251 // reset animation only for moving elements which change direction of moving
5252 // or which just started or stopped moving
5253 // (else CEs with property "can move" / "not moving" are reset each frame)
5254 if (is_moving_before != is_moving_after ||
5255 direction != MovDir[x][y])
5256 ResetGfxAnimation(x, y);
5258 MovDir[x][y] = direction;
5259 GfxDir[x][y] = direction;
5261 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5262 direction == MV_DOWN && CAN_FALL(element) ?
5263 ACTION_FALLING : ACTION_MOVING);
5265 // this is needed for CEs with property "can move" / "not moving"
5267 if (is_moving_after)
5269 if (Tile[newx][newy] == EL_EMPTY)
5270 Tile[newx][newy] = EL_BLOCKED;
5272 MovDir[newx][newy] = MovDir[x][y];
5274 CustomValue[newx][newy] = CustomValue[x][y];
5276 GfxFrame[newx][newy] = GfxFrame[x][y];
5277 GfxRandom[newx][newy] = GfxRandom[x][y];
5278 GfxAction[newx][newy] = GfxAction[x][y];
5279 GfxDir[newx][newy] = GfxDir[x][y];
5283 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5285 int direction = MovDir[x][y];
5286 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5287 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5293 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5295 int oldx = x, oldy = y;
5296 int direction = MovDir[x][y];
5298 if (direction == MV_LEFT)
5300 else if (direction == MV_RIGHT)
5302 else if (direction == MV_UP)
5304 else if (direction == MV_DOWN)
5307 *comes_from_x = oldx;
5308 *comes_from_y = oldy;
5311 static int MovingOrBlocked2Element(int x, int y)
5313 int element = Tile[x][y];
5315 if (element == EL_BLOCKED)
5319 Blocked2Moving(x, y, &oldx, &oldy);
5320 return Tile[oldx][oldy];
5326 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5328 // like MovingOrBlocked2Element(), but if element is moving
5329 // and (x,y) is the field the moving element is just leaving,
5330 // return EL_BLOCKED instead of the element value
5331 int element = Tile[x][y];
5333 if (IS_MOVING(x, y))
5335 if (element == EL_BLOCKED)
5339 Blocked2Moving(x, y, &oldx, &oldy);
5340 return Tile[oldx][oldy];
5349 static void RemoveField(int x, int y)
5351 Tile[x][y] = EL_EMPTY;
5357 CustomValue[x][y] = 0;
5360 ChangeDelay[x][y] = 0;
5361 ChangePage[x][y] = -1;
5362 Pushed[x][y] = FALSE;
5364 GfxElement[x][y] = EL_UNDEFINED;
5365 GfxAction[x][y] = ACTION_DEFAULT;
5366 GfxDir[x][y] = MV_NONE;
5369 static void RemoveMovingField(int x, int y)
5371 int oldx = x, oldy = y, newx = x, newy = y;
5372 int element = Tile[x][y];
5373 int next_element = EL_UNDEFINED;
5375 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5378 if (IS_MOVING(x, y))
5380 Moving2Blocked(x, y, &newx, &newy);
5382 if (Tile[newx][newy] != EL_BLOCKED)
5384 // element is moving, but target field is not free (blocked), but
5385 // already occupied by something different (example: acid pool);
5386 // in this case, only remove the moving field, but not the target
5388 RemoveField(oldx, oldy);
5390 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5392 TEST_DrawLevelField(oldx, oldy);
5397 else if (element == EL_BLOCKED)
5399 Blocked2Moving(x, y, &oldx, &oldy);
5400 if (!IS_MOVING(oldx, oldy))
5404 if (element == EL_BLOCKED &&
5405 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5406 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5407 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5408 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5409 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5410 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5411 next_element = get_next_element(Tile[oldx][oldy]);
5413 RemoveField(oldx, oldy);
5414 RemoveField(newx, newy);
5416 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5418 if (next_element != EL_UNDEFINED)
5419 Tile[oldx][oldy] = next_element;
5421 TEST_DrawLevelField(oldx, oldy);
5422 TEST_DrawLevelField(newx, newy);
5425 void DrawDynamite(int x, int y)
5427 int sx = SCREENX(x), sy = SCREENY(y);
5428 int graphic = el2img(Tile[x][y]);
5431 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5434 if (IS_WALKABLE_INSIDE(Back[x][y]))
5438 DrawLevelElement(x, y, Back[x][y]);
5439 else if (Store[x][y])
5440 DrawLevelElement(x, y, Store[x][y]);
5441 else if (game.use_masked_elements)
5442 DrawLevelElement(x, y, EL_EMPTY);
5444 frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
5446 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5447 DrawGraphicThruMask(sx, sy, graphic, frame);
5449 DrawGraphic(sx, sy, graphic, frame);
5452 static void CheckDynamite(int x, int y)
5454 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5458 if (MovDelay[x][y] != 0)
5461 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5467 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5472 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5474 boolean num_checked_players = 0;
5477 for (i = 0; i < MAX_PLAYERS; i++)
5479 if (stored_player[i].active)
5481 int sx = stored_player[i].jx;
5482 int sy = stored_player[i].jy;
5484 if (num_checked_players == 0)
5491 *sx1 = MIN(*sx1, sx);
5492 *sy1 = MIN(*sy1, sy);
5493 *sx2 = MAX(*sx2, sx);
5494 *sy2 = MAX(*sy2, sy);
5497 num_checked_players++;
5502 static boolean checkIfAllPlayersFitToScreen_RND(void)
5504 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5506 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5508 return (sx2 - sx1 < SCR_FIELDX &&
5509 sy2 - sy1 < SCR_FIELDY);
5512 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5514 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5516 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5518 *sx = (sx1 + sx2) / 2;
5519 *sy = (sy1 + sy2) / 2;
5522 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5523 boolean center_screen, boolean quick_relocation)
5525 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5526 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5527 boolean no_delay = (tape.warp_forward);
5528 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5529 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5530 int new_scroll_x, new_scroll_y;
5532 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5534 // case 1: quick relocation inside visible screen (without scrolling)
5541 if (!level.shifted_relocation || center_screen)
5543 // relocation _with_ centering of screen
5545 new_scroll_x = SCROLL_POSITION_X(x);
5546 new_scroll_y = SCROLL_POSITION_Y(y);
5550 // relocation _without_ centering of screen
5552 int center_scroll_x = SCROLL_POSITION_X(old_x);
5553 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5554 int offset_x = x + (scroll_x - center_scroll_x);
5555 int offset_y = y + (scroll_y - center_scroll_y);
5557 // for new screen position, apply previous offset to center position
5558 new_scroll_x = SCROLL_POSITION_X(offset_x);
5559 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5562 if (quick_relocation)
5564 // case 2: quick relocation (redraw without visible scrolling)
5566 scroll_x = new_scroll_x;
5567 scroll_y = new_scroll_y;
5574 // case 3: visible relocation (with scrolling to new position)
5576 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5578 SetVideoFrameDelay(wait_delay_value);
5580 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5582 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5583 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5585 if (dx == 0 && dy == 0) // no scrolling needed at all
5591 // set values for horizontal/vertical screen scrolling (half tile size)
5592 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5593 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5594 int pos_x = dx * TILEX / 2;
5595 int pos_y = dy * TILEY / 2;
5596 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5597 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5599 ScrollLevel(dx, dy);
5602 // scroll in two steps of half tile size to make things smoother
5603 BlitScreenToBitmapExt_RND(window, fx, fy);
5605 // scroll second step to align at full tile size
5606 BlitScreenToBitmap(window);
5612 SetVideoFrameDelay(frame_delay_value_old);
5615 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5617 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5618 int player_nr = GET_PLAYER_NR(el_player);
5619 struct PlayerInfo *player = &stored_player[player_nr];
5620 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5621 boolean no_delay = (tape.warp_forward);
5622 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5623 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5624 int old_jx = player->jx;
5625 int old_jy = player->jy;
5626 int old_element = Tile[old_jx][old_jy];
5627 int element = Tile[jx][jy];
5628 boolean player_relocated = (old_jx != jx || old_jy != jy);
5630 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5631 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5632 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5633 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5634 int leave_side_horiz = move_dir_horiz;
5635 int leave_side_vert = move_dir_vert;
5636 int enter_side = enter_side_horiz | enter_side_vert;
5637 int leave_side = leave_side_horiz | leave_side_vert;
5639 if (player->buried) // do not reanimate dead player
5642 if (!player_relocated) // no need to relocate the player
5645 if (IS_PLAYER(jx, jy)) // player already placed at new position
5647 RemoveField(jx, jy); // temporarily remove newly placed player
5648 DrawLevelField(jx, jy);
5651 if (player->present)
5653 while (player->MovPos)
5655 ScrollPlayer(player, SCROLL_GO_ON);
5656 ScrollScreen(NULL, SCROLL_GO_ON);
5658 AdvanceFrameAndPlayerCounters(player->index_nr);
5662 BackToFront_WithFrameDelay(wait_delay_value);
5665 DrawPlayer(player); // needed here only to cleanup last field
5666 DrawLevelField(player->jx, player->jy); // remove player graphic
5668 player->is_moving = FALSE;
5671 if (IS_CUSTOM_ELEMENT(old_element))
5672 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5674 player->index_bit, leave_side);
5676 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5678 player->index_bit, leave_side);
5680 Tile[jx][jy] = el_player;
5681 InitPlayerField(jx, jy, el_player, TRUE);
5683 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5684 possible that the relocation target field did not contain a player element,
5685 but a walkable element, to which the new player was relocated -- in this
5686 case, restore that (already initialized!) element on the player field */
5687 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5689 Tile[jx][jy] = element; // restore previously existing element
5692 // only visually relocate centered player
5693 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5694 FALSE, level.instant_relocation);
5696 TestIfPlayerTouchesBadThing(jx, jy);
5697 TestIfPlayerTouchesCustomElement(jx, jy);
5699 if (IS_CUSTOM_ELEMENT(element))
5700 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5701 player->index_bit, enter_side);
5703 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5704 player->index_bit, enter_side);
5706 if (player->is_switching)
5708 /* ensure that relocation while still switching an element does not cause
5709 a new element to be treated as also switched directly after relocation
5710 (this is important for teleporter switches that teleport the player to
5711 a place where another teleporter switch is in the same direction, which
5712 would then incorrectly be treated as immediately switched before the
5713 direction key that caused the switch was released) */
5715 player->switch_x += jx - old_jx;
5716 player->switch_y += jy - old_jy;
5720 static void Explode(int ex, int ey, int phase, int mode)
5726 // !!! eliminate this variable !!!
5727 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5729 if (game.explosions_delayed)
5731 ExplodeField[ex][ey] = mode;
5735 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5737 int center_element = Tile[ex][ey];
5738 int artwork_element, explosion_element; // set these values later
5740 // remove things displayed in background while burning dynamite
5741 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5744 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5746 // put moving element to center field (and let it explode there)
5747 center_element = MovingOrBlocked2Element(ex, ey);
5748 RemoveMovingField(ex, ey);
5749 Tile[ex][ey] = center_element;
5752 // now "center_element" is finally determined -- set related values now
5753 artwork_element = center_element; // for custom player artwork
5754 explosion_element = center_element; // for custom player artwork
5756 if (IS_PLAYER(ex, ey))
5758 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5760 artwork_element = stored_player[player_nr].artwork_element;
5762 if (level.use_explosion_element[player_nr])
5764 explosion_element = level.explosion_element[player_nr];
5765 artwork_element = explosion_element;
5769 if (mode == EX_TYPE_NORMAL ||
5770 mode == EX_TYPE_CENTER ||
5771 mode == EX_TYPE_CROSS)
5772 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5774 last_phase = element_info[explosion_element].explosion_delay + 1;
5776 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5778 int xx = x - ex + 1;
5779 int yy = y - ey + 1;
5782 if (!IN_LEV_FIELD(x, y) ||
5783 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5784 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5787 element = Tile[x][y];
5789 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5791 element = MovingOrBlocked2Element(x, y);
5793 if (!IS_EXPLOSION_PROOF(element))
5794 RemoveMovingField(x, y);
5797 // indestructible elements can only explode in center (but not flames)
5798 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5799 mode == EX_TYPE_BORDER)) ||
5800 element == EL_FLAMES)
5803 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5804 behaviour, for example when touching a yamyam that explodes to rocks
5805 with active deadly shield, a rock is created under the player !!! */
5806 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5808 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5809 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5810 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5812 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5815 if (IS_ACTIVE_BOMB(element))
5817 // re-activate things under the bomb like gate or penguin
5818 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5825 // save walkable background elements while explosion on same tile
5826 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5827 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5828 Back[x][y] = element;
5830 // ignite explodable elements reached by other explosion
5831 if (element == EL_EXPLOSION)
5832 element = Store2[x][y];
5834 if (AmoebaNr[x][y] &&
5835 (element == EL_AMOEBA_FULL ||
5836 element == EL_BD_AMOEBA ||
5837 element == EL_AMOEBA_GROWING))
5839 AmoebaCnt[AmoebaNr[x][y]]--;
5840 AmoebaCnt2[AmoebaNr[x][y]]--;
5845 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5847 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5849 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5851 if (PLAYERINFO(ex, ey)->use_murphy)
5852 Store[x][y] = EL_EMPTY;
5855 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5856 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5857 else if (IS_PLAYER_ELEMENT(center_element))
5858 Store[x][y] = EL_EMPTY;
5859 else if (center_element == EL_YAMYAM)
5860 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5861 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5862 Store[x][y] = element_info[center_element].content.e[xx][yy];
5864 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5865 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5866 // otherwise) -- FIX THIS !!!
5867 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5868 Store[x][y] = element_info[element].content.e[1][1];
5870 else if (!CAN_EXPLODE(element))
5871 Store[x][y] = element_info[element].content.e[1][1];
5874 Store[x][y] = EL_EMPTY;
5876 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5877 center_element == EL_AMOEBA_TO_DIAMOND)
5878 Store2[x][y] = element;
5880 Tile[x][y] = EL_EXPLOSION;
5881 GfxElement[x][y] = artwork_element;
5883 ExplodePhase[x][y] = 1;
5884 ExplodeDelay[x][y] = last_phase;
5889 if (center_element == EL_YAMYAM)
5890 game.yamyam_content_nr =
5891 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5903 GfxFrame[x][y] = 0; // restart explosion animation
5905 last_phase = ExplodeDelay[x][y];
5907 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5909 // this can happen if the player leaves an explosion just in time
5910 if (GfxElement[x][y] == EL_UNDEFINED)
5911 GfxElement[x][y] = EL_EMPTY;
5913 border_element = Store2[x][y];
5914 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5915 border_element = StorePlayer[x][y];
5917 if (phase == element_info[border_element].ignition_delay ||
5918 phase == last_phase)
5920 boolean border_explosion = FALSE;
5922 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5923 !PLAYER_EXPLOSION_PROTECTED(x, y))
5925 KillPlayerUnlessExplosionProtected(x, y);
5926 border_explosion = TRUE;
5928 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5930 Tile[x][y] = Store2[x][y];
5933 border_explosion = TRUE;
5935 else if (border_element == EL_AMOEBA_TO_DIAMOND)
5937 AmoebaToDiamond(x, y);
5939 border_explosion = TRUE;
5942 // if an element just explodes due to another explosion (chain-reaction),
5943 // do not immediately end the new explosion when it was the last frame of
5944 // the explosion (as it would be done in the following "if"-statement!)
5945 if (border_explosion && phase == last_phase)
5949 if (phase == last_phase)
5953 element = Tile[x][y] = Store[x][y];
5954 Store[x][y] = Store2[x][y] = 0;
5955 GfxElement[x][y] = EL_UNDEFINED;
5957 // player can escape from explosions and might therefore be still alive
5958 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
5959 element <= EL_PLAYER_IS_EXPLODING_4)
5961 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
5962 int explosion_element = EL_PLAYER_1 + player_nr;
5963 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
5964 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
5966 if (level.use_explosion_element[player_nr])
5967 explosion_element = level.explosion_element[player_nr];
5969 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
5970 element_info[explosion_element].content.e[xx][yy]);
5973 // restore probably existing indestructible background element
5974 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
5975 element = Tile[x][y] = Back[x][y];
5978 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
5979 GfxDir[x][y] = MV_NONE;
5980 ChangeDelay[x][y] = 0;
5981 ChangePage[x][y] = -1;
5983 CustomValue[x][y] = 0;
5985 InitField_WithBug2(x, y, FALSE);
5987 TEST_DrawLevelField(x, y);
5989 TestIfElementTouchesCustomElement(x, y);
5991 if (GFX_CRUMBLED(element))
5992 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
5994 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
5995 StorePlayer[x][y] = 0;
5997 if (IS_PLAYER_ELEMENT(element))
5998 RelocatePlayer(x, y, element);
6000 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6002 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6003 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
6006 TEST_DrawLevelFieldCrumbled(x, y);
6008 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6010 DrawLevelElement(x, y, Back[x][y]);
6011 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6013 else if (IS_WALKABLE_UNDER(Back[x][y]))
6015 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6016 DrawLevelElementThruMask(x, y, Back[x][y]);
6018 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6019 DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
6023 static void DynaExplode(int ex, int ey)
6026 int dynabomb_element = Tile[ex][ey];
6027 int dynabomb_size = 1;
6028 boolean dynabomb_xl = FALSE;
6029 struct PlayerInfo *player;
6030 static int xy[4][2] =
6038 if (IS_ACTIVE_BOMB(dynabomb_element))
6040 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6041 dynabomb_size = player->dynabomb_size;
6042 dynabomb_xl = player->dynabomb_xl;
6043 player->dynabombs_left++;
6046 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6048 for (i = 0; i < NUM_DIRECTIONS; i++)
6050 for (j = 1; j <= dynabomb_size; j++)
6052 int x = ex + j * xy[i][0];
6053 int y = ey + j * xy[i][1];
6056 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6059 element = Tile[x][y];
6061 // do not restart explosions of fields with active bombs
6062 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6065 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6067 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6068 !IS_DIGGABLE(element) && !dynabomb_xl)
6074 void Bang(int x, int y)
6076 int element = MovingOrBlocked2Element(x, y);
6077 int explosion_type = EX_TYPE_NORMAL;
6079 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6081 struct PlayerInfo *player = PLAYERINFO(x, y);
6083 element = Tile[x][y] = player->initial_element;
6085 if (level.use_explosion_element[player->index_nr])
6087 int explosion_element = level.explosion_element[player->index_nr];
6089 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6090 explosion_type = EX_TYPE_CROSS;
6091 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6092 explosion_type = EX_TYPE_CENTER;
6100 case EL_BD_BUTTERFLY:
6103 case EL_DARK_YAMYAM:
6107 RaiseScoreElement(element);
6110 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6111 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6112 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6113 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6114 case EL_DYNABOMB_INCREASE_NUMBER:
6115 case EL_DYNABOMB_INCREASE_SIZE:
6116 case EL_DYNABOMB_INCREASE_POWER:
6117 explosion_type = EX_TYPE_DYNA;
6120 case EL_DC_LANDMINE:
6121 explosion_type = EX_TYPE_CENTER;
6126 case EL_LAMP_ACTIVE:
6127 case EL_AMOEBA_TO_DIAMOND:
6128 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6129 explosion_type = EX_TYPE_CENTER;
6133 if (element_info[element].explosion_type == EXPLODES_CROSS)
6134 explosion_type = EX_TYPE_CROSS;
6135 else if (element_info[element].explosion_type == EXPLODES_1X1)
6136 explosion_type = EX_TYPE_CENTER;
6140 if (explosion_type == EX_TYPE_DYNA)
6143 Explode(x, y, EX_PHASE_START, explosion_type);
6145 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6148 static void SplashAcid(int x, int y)
6150 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6151 (!IN_LEV_FIELD(x - 1, y - 2) ||
6152 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6153 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6155 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6156 (!IN_LEV_FIELD(x + 1, y - 2) ||
6157 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6158 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6160 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6163 static void InitBeltMovement(void)
6165 static int belt_base_element[4] =
6167 EL_CONVEYOR_BELT_1_LEFT,
6168 EL_CONVEYOR_BELT_2_LEFT,
6169 EL_CONVEYOR_BELT_3_LEFT,
6170 EL_CONVEYOR_BELT_4_LEFT
6172 static int belt_base_active_element[4] =
6174 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6175 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6176 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6177 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6182 // set frame order for belt animation graphic according to belt direction
6183 for (i = 0; i < NUM_BELTS; i++)
6187 for (j = 0; j < NUM_BELT_PARTS; j++)
6189 int element = belt_base_active_element[belt_nr] + j;
6190 int graphic_1 = el2img(element);
6191 int graphic_2 = el2panelimg(element);
6193 if (game.belt_dir[i] == MV_LEFT)
6195 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6196 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6200 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6201 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6206 SCAN_PLAYFIELD(x, y)
6208 int element = Tile[x][y];
6210 for (i = 0; i < NUM_BELTS; i++)
6212 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6214 int e_belt_nr = getBeltNrFromBeltElement(element);
6217 if (e_belt_nr == belt_nr)
6219 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6221 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6228 static void ToggleBeltSwitch(int x, int y)
6230 static int belt_base_element[4] =
6232 EL_CONVEYOR_BELT_1_LEFT,
6233 EL_CONVEYOR_BELT_2_LEFT,
6234 EL_CONVEYOR_BELT_3_LEFT,
6235 EL_CONVEYOR_BELT_4_LEFT
6237 static int belt_base_active_element[4] =
6239 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6240 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6241 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6242 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6244 static int belt_base_switch_element[4] =
6246 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6247 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6248 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6249 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6251 static int belt_move_dir[4] =
6259 int element = Tile[x][y];
6260 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6261 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6262 int belt_dir = belt_move_dir[belt_dir_nr];
6265 if (!IS_BELT_SWITCH(element))
6268 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6269 game.belt_dir[belt_nr] = belt_dir;
6271 if (belt_dir_nr == 3)
6274 // set frame order for belt animation graphic according to belt direction
6275 for (i = 0; i < NUM_BELT_PARTS; i++)
6277 int element = belt_base_active_element[belt_nr] + i;
6278 int graphic_1 = el2img(element);
6279 int graphic_2 = el2panelimg(element);
6281 if (belt_dir == MV_LEFT)
6283 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6284 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6288 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6289 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6293 SCAN_PLAYFIELD(xx, yy)
6295 int element = Tile[xx][yy];
6297 if (IS_BELT_SWITCH(element))
6299 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6301 if (e_belt_nr == belt_nr)
6303 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6304 TEST_DrawLevelField(xx, yy);
6307 else if (IS_BELT(element) && belt_dir != MV_NONE)
6309 int e_belt_nr = getBeltNrFromBeltElement(element);
6311 if (e_belt_nr == belt_nr)
6313 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6315 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6316 TEST_DrawLevelField(xx, yy);
6319 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6321 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6323 if (e_belt_nr == belt_nr)
6325 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6327 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6328 TEST_DrawLevelField(xx, yy);
6334 static void ToggleSwitchgateSwitch(int x, int y)
6338 game.switchgate_pos = !game.switchgate_pos;
6340 SCAN_PLAYFIELD(xx, yy)
6342 int element = Tile[xx][yy];
6344 if (element == EL_SWITCHGATE_SWITCH_UP)
6346 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6347 TEST_DrawLevelField(xx, yy);
6349 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6351 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6352 TEST_DrawLevelField(xx, yy);
6354 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6356 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6357 TEST_DrawLevelField(xx, yy);
6359 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6361 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6362 TEST_DrawLevelField(xx, yy);
6364 else if (element == EL_SWITCHGATE_OPEN ||
6365 element == EL_SWITCHGATE_OPENING)
6367 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6369 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6371 else if (element == EL_SWITCHGATE_CLOSED ||
6372 element == EL_SWITCHGATE_CLOSING)
6374 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6376 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6381 static int getInvisibleActiveFromInvisibleElement(int element)
6383 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6384 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6385 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6389 static int getInvisibleFromInvisibleActiveElement(int element)
6391 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6392 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6393 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6397 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6401 SCAN_PLAYFIELD(x, y)
6403 int element = Tile[x][y];
6405 if (element == EL_LIGHT_SWITCH &&
6406 game.light_time_left > 0)
6408 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6409 TEST_DrawLevelField(x, y);
6411 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6412 game.light_time_left == 0)
6414 Tile[x][y] = EL_LIGHT_SWITCH;
6415 TEST_DrawLevelField(x, y);
6417 else if (element == EL_EMC_DRIPPER &&
6418 game.light_time_left > 0)
6420 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6421 TEST_DrawLevelField(x, y);
6423 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6424 game.light_time_left == 0)
6426 Tile[x][y] = EL_EMC_DRIPPER;
6427 TEST_DrawLevelField(x, y);
6429 else if (element == EL_INVISIBLE_STEELWALL ||
6430 element == EL_INVISIBLE_WALL ||
6431 element == EL_INVISIBLE_SAND)
6433 if (game.light_time_left > 0)
6434 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6436 TEST_DrawLevelField(x, y);
6438 // uncrumble neighbour fields, if needed
6439 if (element == EL_INVISIBLE_SAND)
6440 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6442 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6443 element == EL_INVISIBLE_WALL_ACTIVE ||
6444 element == EL_INVISIBLE_SAND_ACTIVE)
6446 if (game.light_time_left == 0)
6447 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6449 TEST_DrawLevelField(x, y);
6451 // re-crumble neighbour fields, if needed
6452 if (element == EL_INVISIBLE_SAND)
6453 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6458 static void RedrawAllInvisibleElementsForLenses(void)
6462 SCAN_PLAYFIELD(x, y)
6464 int element = Tile[x][y];
6466 if (element == EL_EMC_DRIPPER &&
6467 game.lenses_time_left > 0)
6469 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6470 TEST_DrawLevelField(x, y);
6472 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6473 game.lenses_time_left == 0)
6475 Tile[x][y] = EL_EMC_DRIPPER;
6476 TEST_DrawLevelField(x, y);
6478 else if (element == EL_INVISIBLE_STEELWALL ||
6479 element == EL_INVISIBLE_WALL ||
6480 element == EL_INVISIBLE_SAND)
6482 if (game.lenses_time_left > 0)
6483 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6485 TEST_DrawLevelField(x, y);
6487 // uncrumble neighbour fields, if needed
6488 if (element == EL_INVISIBLE_SAND)
6489 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6491 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6492 element == EL_INVISIBLE_WALL_ACTIVE ||
6493 element == EL_INVISIBLE_SAND_ACTIVE)
6495 if (game.lenses_time_left == 0)
6496 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6498 TEST_DrawLevelField(x, y);
6500 // re-crumble neighbour fields, if needed
6501 if (element == EL_INVISIBLE_SAND)
6502 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6507 static void RedrawAllInvisibleElementsForMagnifier(void)
6511 SCAN_PLAYFIELD(x, y)
6513 int element = Tile[x][y];
6515 if (element == EL_EMC_FAKE_GRASS &&
6516 game.magnify_time_left > 0)
6518 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6519 TEST_DrawLevelField(x, y);
6521 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6522 game.magnify_time_left == 0)
6524 Tile[x][y] = EL_EMC_FAKE_GRASS;
6525 TEST_DrawLevelField(x, y);
6527 else if (IS_GATE_GRAY(element) &&
6528 game.magnify_time_left > 0)
6530 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6531 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6532 IS_EM_GATE_GRAY(element) ?
6533 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6534 IS_EMC_GATE_GRAY(element) ?
6535 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6536 IS_DC_GATE_GRAY(element) ?
6537 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6539 TEST_DrawLevelField(x, y);
6541 else if (IS_GATE_GRAY_ACTIVE(element) &&
6542 game.magnify_time_left == 0)
6544 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6545 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6546 IS_EM_GATE_GRAY_ACTIVE(element) ?
6547 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6548 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6549 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6550 IS_DC_GATE_GRAY_ACTIVE(element) ?
6551 EL_DC_GATE_WHITE_GRAY :
6553 TEST_DrawLevelField(x, y);
6558 static void ToggleLightSwitch(int x, int y)
6560 int element = Tile[x][y];
6562 game.light_time_left =
6563 (element == EL_LIGHT_SWITCH ?
6564 level.time_light * FRAMES_PER_SECOND : 0);
6566 RedrawAllLightSwitchesAndInvisibleElements();
6569 static void ActivateTimegateSwitch(int x, int y)
6573 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6575 SCAN_PLAYFIELD(xx, yy)
6577 int element = Tile[xx][yy];
6579 if (element == EL_TIMEGATE_CLOSED ||
6580 element == EL_TIMEGATE_CLOSING)
6582 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6583 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6587 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6589 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6590 TEST_DrawLevelField(xx, yy);
6596 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6597 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6600 static void Impact(int x, int y)
6602 boolean last_line = (y == lev_fieldy - 1);
6603 boolean object_hit = FALSE;
6604 boolean impact = (last_line || object_hit);
6605 int element = Tile[x][y];
6606 int smashed = EL_STEELWALL;
6608 if (!last_line) // check if element below was hit
6610 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6613 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6614 MovDir[x][y + 1] != MV_DOWN ||
6615 MovPos[x][y + 1] <= TILEY / 2));
6617 // do not smash moving elements that left the smashed field in time
6618 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6619 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6622 #if USE_QUICKSAND_IMPACT_BUGFIX
6623 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6625 RemoveMovingField(x, y + 1);
6626 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6627 Tile[x][y + 2] = EL_ROCK;
6628 TEST_DrawLevelField(x, y + 2);
6633 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6635 RemoveMovingField(x, y + 1);
6636 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6637 Tile[x][y + 2] = EL_ROCK;
6638 TEST_DrawLevelField(x, y + 2);
6645 smashed = MovingOrBlocked2Element(x, y + 1);
6647 impact = (last_line || object_hit);
6650 if (!last_line && smashed == EL_ACID) // element falls into acid
6652 SplashAcid(x, y + 1);
6656 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6657 // only reset graphic animation if graphic really changes after impact
6659 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6661 ResetGfxAnimation(x, y);
6662 TEST_DrawLevelField(x, y);
6665 if (impact && CAN_EXPLODE_IMPACT(element))
6670 else if (impact && element == EL_PEARL &&
6671 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6673 ResetGfxAnimation(x, y);
6675 Tile[x][y] = EL_PEARL_BREAKING;
6676 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6679 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6681 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6686 if (impact && element == EL_AMOEBA_DROP)
6688 if (object_hit && IS_PLAYER(x, y + 1))
6689 KillPlayerUnlessEnemyProtected(x, y + 1);
6690 else if (object_hit && smashed == EL_PENGUIN)
6694 Tile[x][y] = EL_AMOEBA_GROWING;
6695 Store[x][y] = EL_AMOEBA_WET;
6697 ResetRandomAnimationValue(x, y);
6702 if (object_hit) // check which object was hit
6704 if ((CAN_PASS_MAGIC_WALL(element) &&
6705 (smashed == EL_MAGIC_WALL ||
6706 smashed == EL_BD_MAGIC_WALL)) ||
6707 (CAN_PASS_DC_MAGIC_WALL(element) &&
6708 smashed == EL_DC_MAGIC_WALL))
6711 int activated_magic_wall =
6712 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6713 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6714 EL_DC_MAGIC_WALL_ACTIVE);
6716 // activate magic wall / mill
6717 SCAN_PLAYFIELD(xx, yy)
6719 if (Tile[xx][yy] == smashed)
6720 Tile[xx][yy] = activated_magic_wall;
6723 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6724 game.magic_wall_active = TRUE;
6726 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6727 SND_MAGIC_WALL_ACTIVATING :
6728 smashed == EL_BD_MAGIC_WALL ?
6729 SND_BD_MAGIC_WALL_ACTIVATING :
6730 SND_DC_MAGIC_WALL_ACTIVATING));
6733 if (IS_PLAYER(x, y + 1))
6735 if (CAN_SMASH_PLAYER(element))
6737 KillPlayerUnlessEnemyProtected(x, y + 1);
6741 else if (smashed == EL_PENGUIN)
6743 if (CAN_SMASH_PLAYER(element))
6749 else if (element == EL_BD_DIAMOND)
6751 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6757 else if (((element == EL_SP_INFOTRON ||
6758 element == EL_SP_ZONK) &&
6759 (smashed == EL_SP_SNIKSNAK ||
6760 smashed == EL_SP_ELECTRON ||
6761 smashed == EL_SP_DISK_ORANGE)) ||
6762 (element == EL_SP_INFOTRON &&
6763 smashed == EL_SP_DISK_YELLOW))
6768 else if (CAN_SMASH_EVERYTHING(element))
6770 if (IS_CLASSIC_ENEMY(smashed) ||
6771 CAN_EXPLODE_SMASHED(smashed))
6776 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6778 if (smashed == EL_LAMP ||
6779 smashed == EL_LAMP_ACTIVE)
6784 else if (smashed == EL_NUT)
6786 Tile[x][y + 1] = EL_NUT_BREAKING;
6787 PlayLevelSound(x, y, SND_NUT_BREAKING);
6788 RaiseScoreElement(EL_NUT);
6791 else if (smashed == EL_PEARL)
6793 ResetGfxAnimation(x, y);
6795 Tile[x][y + 1] = EL_PEARL_BREAKING;
6796 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6799 else if (smashed == EL_DIAMOND)
6801 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6802 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6805 else if (IS_BELT_SWITCH(smashed))
6807 ToggleBeltSwitch(x, y + 1);
6809 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6810 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6811 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6812 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6814 ToggleSwitchgateSwitch(x, y + 1);
6816 else if (smashed == EL_LIGHT_SWITCH ||
6817 smashed == EL_LIGHT_SWITCH_ACTIVE)
6819 ToggleLightSwitch(x, y + 1);
6823 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6825 CheckElementChangeBySide(x, y + 1, smashed, element,
6826 CE_SWITCHED, CH_SIDE_TOP);
6827 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6833 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6838 // play sound of magic wall / mill
6840 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6841 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6842 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6844 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6845 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6846 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6847 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6848 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6849 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6854 // play sound of object that hits the ground
6855 if (last_line || object_hit)
6856 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6859 static void TurnRoundExt(int x, int y)
6871 { 0, 0 }, { 0, 0 }, { 0, 0 },
6876 int left, right, back;
6880 { MV_DOWN, MV_UP, MV_RIGHT },
6881 { MV_UP, MV_DOWN, MV_LEFT },
6883 { MV_LEFT, MV_RIGHT, MV_DOWN },
6887 { MV_RIGHT, MV_LEFT, MV_UP }
6890 int element = Tile[x][y];
6891 int move_pattern = element_info[element].move_pattern;
6893 int old_move_dir = MovDir[x][y];
6894 int left_dir = turn[old_move_dir].left;
6895 int right_dir = turn[old_move_dir].right;
6896 int back_dir = turn[old_move_dir].back;
6898 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6899 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6900 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6901 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6903 int left_x = x + left_dx, left_y = y + left_dy;
6904 int right_x = x + right_dx, right_y = y + right_dy;
6905 int move_x = x + move_dx, move_y = y + move_dy;
6909 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6911 TestIfBadThingTouchesOtherBadThing(x, y);
6913 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6914 MovDir[x][y] = right_dir;
6915 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6916 MovDir[x][y] = left_dir;
6918 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6920 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6923 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6925 TestIfBadThingTouchesOtherBadThing(x, y);
6927 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6928 MovDir[x][y] = left_dir;
6929 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6930 MovDir[x][y] = right_dir;
6932 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
6934 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
6937 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
6939 TestIfBadThingTouchesOtherBadThing(x, y);
6941 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
6942 MovDir[x][y] = left_dir;
6943 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
6944 MovDir[x][y] = right_dir;
6946 if (MovDir[x][y] != old_move_dir)
6949 else if (element == EL_YAMYAM)
6951 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
6952 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
6954 if (can_turn_left && can_turn_right)
6955 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6956 else if (can_turn_left)
6957 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6958 else if (can_turn_right)
6959 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6961 MovDir[x][y] = back_dir;
6963 MovDelay[x][y] = 16 + 16 * RND(3);
6965 else if (element == EL_DARK_YAMYAM)
6967 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6969 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
6972 if (can_turn_left && can_turn_right)
6973 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6974 else if (can_turn_left)
6975 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6976 else if (can_turn_right)
6977 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6979 MovDir[x][y] = back_dir;
6981 MovDelay[x][y] = 16 + 16 * RND(3);
6983 else if (element == EL_PACMAN)
6985 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
6986 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
6988 if (can_turn_left && can_turn_right)
6989 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
6990 else if (can_turn_left)
6991 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
6992 else if (can_turn_right)
6993 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
6995 MovDir[x][y] = back_dir;
6997 MovDelay[x][y] = 6 + RND(40);
6999 else if (element == EL_PIG)
7001 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7002 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7003 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7004 boolean should_turn_left, should_turn_right, should_move_on;
7006 int rnd = RND(rnd_value);
7008 should_turn_left = (can_turn_left &&
7010 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7011 y + back_dy + left_dy)));
7012 should_turn_right = (can_turn_right &&
7014 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7015 y + back_dy + right_dy)));
7016 should_move_on = (can_move_on &&
7019 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7020 y + move_dy + left_dy) ||
7021 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7022 y + move_dy + right_dy)));
7024 if (should_turn_left || should_turn_right || should_move_on)
7026 if (should_turn_left && should_turn_right && should_move_on)
7027 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7028 rnd < 2 * rnd_value / 3 ? right_dir :
7030 else if (should_turn_left && should_turn_right)
7031 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7032 else if (should_turn_left && should_move_on)
7033 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7034 else if (should_turn_right && should_move_on)
7035 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7036 else if (should_turn_left)
7037 MovDir[x][y] = left_dir;
7038 else if (should_turn_right)
7039 MovDir[x][y] = right_dir;
7040 else if (should_move_on)
7041 MovDir[x][y] = old_move_dir;
7043 else if (can_move_on && rnd > rnd_value / 8)
7044 MovDir[x][y] = old_move_dir;
7045 else if (can_turn_left && can_turn_right)
7046 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7047 else if (can_turn_left && rnd > rnd_value / 8)
7048 MovDir[x][y] = left_dir;
7049 else if (can_turn_right && rnd > rnd_value/8)
7050 MovDir[x][y] = right_dir;
7052 MovDir[x][y] = back_dir;
7054 xx = x + move_xy[MovDir[x][y]].dx;
7055 yy = y + move_xy[MovDir[x][y]].dy;
7057 if (!IN_LEV_FIELD(xx, yy) ||
7058 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7059 MovDir[x][y] = old_move_dir;
7063 else if (element == EL_DRAGON)
7065 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7066 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7067 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7069 int rnd = RND(rnd_value);
7071 if (can_move_on && rnd > rnd_value / 8)
7072 MovDir[x][y] = old_move_dir;
7073 else if (can_turn_left && can_turn_right)
7074 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7075 else if (can_turn_left && rnd > rnd_value / 8)
7076 MovDir[x][y] = left_dir;
7077 else if (can_turn_right && rnd > rnd_value / 8)
7078 MovDir[x][y] = right_dir;
7080 MovDir[x][y] = back_dir;
7082 xx = x + move_xy[MovDir[x][y]].dx;
7083 yy = y + move_xy[MovDir[x][y]].dy;
7085 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7086 MovDir[x][y] = old_move_dir;
7090 else if (element == EL_MOLE)
7092 boolean can_move_on =
7093 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7094 IS_AMOEBOID(Tile[move_x][move_y]) ||
7095 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7098 boolean can_turn_left =
7099 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7100 IS_AMOEBOID(Tile[left_x][left_y])));
7102 boolean can_turn_right =
7103 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7104 IS_AMOEBOID(Tile[right_x][right_y])));
7106 if (can_turn_left && can_turn_right)
7107 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7108 else if (can_turn_left)
7109 MovDir[x][y] = left_dir;
7111 MovDir[x][y] = right_dir;
7114 if (MovDir[x][y] != old_move_dir)
7117 else if (element == EL_BALLOON)
7119 MovDir[x][y] = game.wind_direction;
7122 else if (element == EL_SPRING)
7124 if (MovDir[x][y] & MV_HORIZONTAL)
7126 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7127 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7129 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7130 ResetGfxAnimation(move_x, move_y);
7131 TEST_DrawLevelField(move_x, move_y);
7133 MovDir[x][y] = back_dir;
7135 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7136 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7137 MovDir[x][y] = MV_NONE;
7142 else if (element == EL_ROBOT ||
7143 element == EL_SATELLITE ||
7144 element == EL_PENGUIN ||
7145 element == EL_EMC_ANDROID)
7147 int attr_x = -1, attr_y = -1;
7149 if (game.all_players_gone)
7151 attr_x = game.exit_x;
7152 attr_y = game.exit_y;
7158 for (i = 0; i < MAX_PLAYERS; i++)
7160 struct PlayerInfo *player = &stored_player[i];
7161 int jx = player->jx, jy = player->jy;
7163 if (!player->active)
7167 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7175 if (element == EL_ROBOT &&
7176 game.robot_wheel_x >= 0 &&
7177 game.robot_wheel_y >= 0 &&
7178 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7179 game.engine_version < VERSION_IDENT(3,1,0,0)))
7181 attr_x = game.robot_wheel_x;
7182 attr_y = game.robot_wheel_y;
7185 if (element == EL_PENGUIN)
7188 static int xy[4][2] =
7196 for (i = 0; i < NUM_DIRECTIONS; i++)
7198 int ex = x + xy[i][0];
7199 int ey = y + xy[i][1];
7201 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7202 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7203 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7204 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7213 MovDir[x][y] = MV_NONE;
7215 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7216 else if (attr_x > x)
7217 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7219 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7220 else if (attr_y > y)
7221 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7223 if (element == EL_ROBOT)
7227 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7228 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7229 Moving2Blocked(x, y, &newx, &newy);
7231 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7232 MovDelay[x][y] = 8 + 8 * !RND(3);
7234 MovDelay[x][y] = 16;
7236 else if (element == EL_PENGUIN)
7242 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7244 boolean first_horiz = RND(2);
7245 int new_move_dir = MovDir[x][y];
7248 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7249 Moving2Blocked(x, y, &newx, &newy);
7251 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7255 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7256 Moving2Blocked(x, y, &newx, &newy);
7258 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7261 MovDir[x][y] = old_move_dir;
7265 else if (element == EL_SATELLITE)
7271 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7273 boolean first_horiz = RND(2);
7274 int new_move_dir = MovDir[x][y];
7277 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7278 Moving2Blocked(x, y, &newx, &newy);
7280 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7284 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7285 Moving2Blocked(x, y, &newx, &newy);
7287 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7290 MovDir[x][y] = old_move_dir;
7294 else if (element == EL_EMC_ANDROID)
7296 static int check_pos[16] =
7298 -1, // 0 => (invalid)
7301 -1, // 3 => (invalid)
7303 0, // 5 => MV_LEFT | MV_UP
7304 2, // 6 => MV_RIGHT | MV_UP
7305 -1, // 7 => (invalid)
7307 6, // 9 => MV_LEFT | MV_DOWN
7308 4, // 10 => MV_RIGHT | MV_DOWN
7309 -1, // 11 => (invalid)
7310 -1, // 12 => (invalid)
7311 -1, // 13 => (invalid)
7312 -1, // 14 => (invalid)
7313 -1, // 15 => (invalid)
7321 { -1, -1, MV_LEFT | MV_UP },
7323 { +1, -1, MV_RIGHT | MV_UP },
7324 { +1, 0, MV_RIGHT },
7325 { +1, +1, MV_RIGHT | MV_DOWN },
7327 { -1, +1, MV_LEFT | MV_DOWN },
7330 int start_pos, check_order;
7331 boolean can_clone = FALSE;
7334 // check if there is any free field around current position
7335 for (i = 0; i < 8; i++)
7337 int newx = x + check_xy[i].dx;
7338 int newy = y + check_xy[i].dy;
7340 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7348 if (can_clone) // randomly find an element to clone
7352 start_pos = check_pos[RND(8)];
7353 check_order = (RND(2) ? -1 : +1);
7355 for (i = 0; i < 8; i++)
7357 int pos_raw = start_pos + i * check_order;
7358 int pos = (pos_raw + 8) % 8;
7359 int newx = x + check_xy[pos].dx;
7360 int newy = y + check_xy[pos].dy;
7362 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7364 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7365 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7367 Store[x][y] = Tile[newx][newy];
7376 if (can_clone) // randomly find a direction to move
7380 start_pos = check_pos[RND(8)];
7381 check_order = (RND(2) ? -1 : +1);
7383 for (i = 0; i < 8; i++)
7385 int pos_raw = start_pos + i * check_order;
7386 int pos = (pos_raw + 8) % 8;
7387 int newx = x + check_xy[pos].dx;
7388 int newy = y + check_xy[pos].dy;
7389 int new_move_dir = check_xy[pos].dir;
7391 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7393 MovDir[x][y] = new_move_dir;
7394 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7403 if (can_clone) // cloning and moving successful
7406 // cannot clone -- try to move towards player
7408 start_pos = check_pos[MovDir[x][y] & 0x0f];
7409 check_order = (RND(2) ? -1 : +1);
7411 for (i = 0; i < 3; i++)
7413 // first check start_pos, then previous/next or (next/previous) pos
7414 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7415 int pos = (pos_raw + 8) % 8;
7416 int newx = x + check_xy[pos].dx;
7417 int newy = y + check_xy[pos].dy;
7418 int new_move_dir = check_xy[pos].dir;
7420 if (IS_PLAYER(newx, newy))
7423 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7425 MovDir[x][y] = new_move_dir;
7426 MovDelay[x][y] = level.android_move_time * 8 + 1;
7433 else if (move_pattern == MV_TURNING_LEFT ||
7434 move_pattern == MV_TURNING_RIGHT ||
7435 move_pattern == MV_TURNING_LEFT_RIGHT ||
7436 move_pattern == MV_TURNING_RIGHT_LEFT ||
7437 move_pattern == MV_TURNING_RANDOM ||
7438 move_pattern == MV_ALL_DIRECTIONS)
7440 boolean can_turn_left =
7441 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7442 boolean can_turn_right =
7443 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7445 if (element_info[element].move_stepsize == 0) // "not moving"
7448 if (move_pattern == MV_TURNING_LEFT)
7449 MovDir[x][y] = left_dir;
7450 else if (move_pattern == MV_TURNING_RIGHT)
7451 MovDir[x][y] = right_dir;
7452 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7453 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7454 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7455 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7456 else if (move_pattern == MV_TURNING_RANDOM)
7457 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7458 can_turn_right && !can_turn_left ? right_dir :
7459 RND(2) ? left_dir : right_dir);
7460 else if (can_turn_left && can_turn_right)
7461 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7462 else if (can_turn_left)
7463 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7464 else if (can_turn_right)
7465 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7467 MovDir[x][y] = back_dir;
7469 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7471 else if (move_pattern == MV_HORIZONTAL ||
7472 move_pattern == MV_VERTICAL)
7474 if (move_pattern & old_move_dir)
7475 MovDir[x][y] = back_dir;
7476 else if (move_pattern == MV_HORIZONTAL)
7477 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7478 else if (move_pattern == MV_VERTICAL)
7479 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7481 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7483 else if (move_pattern & MV_ANY_DIRECTION)
7485 MovDir[x][y] = move_pattern;
7486 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7488 else if (move_pattern & MV_WIND_DIRECTION)
7490 MovDir[x][y] = game.wind_direction;
7491 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7493 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7495 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7496 MovDir[x][y] = left_dir;
7497 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7498 MovDir[x][y] = right_dir;
7500 if (MovDir[x][y] != old_move_dir)
7501 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7503 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7505 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7506 MovDir[x][y] = right_dir;
7507 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7508 MovDir[x][y] = left_dir;
7510 if (MovDir[x][y] != old_move_dir)
7511 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7513 else if (move_pattern == MV_TOWARDS_PLAYER ||
7514 move_pattern == MV_AWAY_FROM_PLAYER)
7516 int attr_x = -1, attr_y = -1;
7518 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7520 if (game.all_players_gone)
7522 attr_x = game.exit_x;
7523 attr_y = game.exit_y;
7529 for (i = 0; i < MAX_PLAYERS; i++)
7531 struct PlayerInfo *player = &stored_player[i];
7532 int jx = player->jx, jy = player->jy;
7534 if (!player->active)
7538 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7546 MovDir[x][y] = MV_NONE;
7548 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7549 else if (attr_x > x)
7550 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7552 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7553 else if (attr_y > y)
7554 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7556 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7558 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7560 boolean first_horiz = RND(2);
7561 int new_move_dir = MovDir[x][y];
7563 if (element_info[element].move_stepsize == 0) // "not moving"
7565 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7566 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7572 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7573 Moving2Blocked(x, y, &newx, &newy);
7575 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7579 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7580 Moving2Blocked(x, y, &newx, &newy);
7582 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7585 MovDir[x][y] = old_move_dir;
7588 else if (move_pattern == MV_WHEN_PUSHED ||
7589 move_pattern == MV_WHEN_DROPPED)
7591 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7592 MovDir[x][y] = MV_NONE;
7596 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7598 static int test_xy[7][2] =
7608 static int test_dir[7] =
7618 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7619 int move_preference = -1000000; // start with very low preference
7620 int new_move_dir = MV_NONE;
7621 int start_test = RND(4);
7624 for (i = 0; i < NUM_DIRECTIONS; i++)
7626 int move_dir = test_dir[start_test + i];
7627 int move_dir_preference;
7629 xx = x + test_xy[start_test + i][0];
7630 yy = y + test_xy[start_test + i][1];
7632 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7633 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7635 new_move_dir = move_dir;
7640 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7643 move_dir_preference = -1 * RunnerVisit[xx][yy];
7644 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7645 move_dir_preference = PlayerVisit[xx][yy];
7647 if (move_dir_preference > move_preference)
7649 // prefer field that has not been visited for the longest time
7650 move_preference = move_dir_preference;
7651 new_move_dir = move_dir;
7653 else if (move_dir_preference == move_preference &&
7654 move_dir == old_move_dir)
7656 // prefer last direction when all directions are preferred equally
7657 move_preference = move_dir_preference;
7658 new_move_dir = move_dir;
7662 MovDir[x][y] = new_move_dir;
7663 if (old_move_dir != new_move_dir)
7664 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7668 static void TurnRound(int x, int y)
7670 int direction = MovDir[x][y];
7674 GfxDir[x][y] = MovDir[x][y];
7676 if (direction != MovDir[x][y])
7680 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7682 ResetGfxFrame(x, y);
7685 static boolean JustBeingPushed(int x, int y)
7689 for (i = 0; i < MAX_PLAYERS; i++)
7691 struct PlayerInfo *player = &stored_player[i];
7693 if (player->active && player->is_pushing && player->MovPos)
7695 int next_jx = player->jx + (player->jx - player->last_jx);
7696 int next_jy = player->jy + (player->jy - player->last_jy);
7698 if (x == next_jx && y == next_jy)
7706 static void StartMoving(int x, int y)
7708 boolean started_moving = FALSE; // some elements can fall _and_ move
7709 int element = Tile[x][y];
7714 if (MovDelay[x][y] == 0)
7715 GfxAction[x][y] = ACTION_DEFAULT;
7717 if (CAN_FALL(element) && y < lev_fieldy - 1)
7719 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7720 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7721 if (JustBeingPushed(x, y))
7724 if (element == EL_QUICKSAND_FULL)
7726 if (IS_FREE(x, y + 1))
7728 InitMovingField(x, y, MV_DOWN);
7729 started_moving = TRUE;
7731 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7732 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7733 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7734 Store[x][y] = EL_ROCK;
7736 Store[x][y] = EL_ROCK;
7739 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7741 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7743 if (!MovDelay[x][y])
7745 MovDelay[x][y] = TILEY + 1;
7747 ResetGfxAnimation(x, y);
7748 ResetGfxAnimation(x, y + 1);
7753 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7754 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7761 Tile[x][y] = EL_QUICKSAND_EMPTY;
7762 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7763 Store[x][y + 1] = Store[x][y];
7766 PlayLevelSoundAction(x, y, ACTION_FILLING);
7768 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7770 if (!MovDelay[x][y])
7772 MovDelay[x][y] = TILEY + 1;
7774 ResetGfxAnimation(x, y);
7775 ResetGfxAnimation(x, y + 1);
7780 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7781 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7788 Tile[x][y] = EL_QUICKSAND_EMPTY;
7789 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7790 Store[x][y + 1] = Store[x][y];
7793 PlayLevelSoundAction(x, y, ACTION_FILLING);
7796 else if (element == EL_QUICKSAND_FAST_FULL)
7798 if (IS_FREE(x, y + 1))
7800 InitMovingField(x, y, MV_DOWN);
7801 started_moving = TRUE;
7803 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7804 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7805 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7806 Store[x][y] = EL_ROCK;
7808 Store[x][y] = EL_ROCK;
7811 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7813 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7815 if (!MovDelay[x][y])
7817 MovDelay[x][y] = TILEY + 1;
7819 ResetGfxAnimation(x, y);
7820 ResetGfxAnimation(x, y + 1);
7825 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7826 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7833 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7834 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7835 Store[x][y + 1] = Store[x][y];
7838 PlayLevelSoundAction(x, y, ACTION_FILLING);
7840 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7842 if (!MovDelay[x][y])
7844 MovDelay[x][y] = TILEY + 1;
7846 ResetGfxAnimation(x, y);
7847 ResetGfxAnimation(x, y + 1);
7852 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7853 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7860 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7861 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7862 Store[x][y + 1] = Store[x][y];
7865 PlayLevelSoundAction(x, y, ACTION_FILLING);
7868 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7869 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7871 InitMovingField(x, y, MV_DOWN);
7872 started_moving = TRUE;
7874 Tile[x][y] = EL_QUICKSAND_FILLING;
7875 Store[x][y] = element;
7877 PlayLevelSoundAction(x, y, ACTION_FILLING);
7879 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7880 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7882 InitMovingField(x, y, MV_DOWN);
7883 started_moving = TRUE;
7885 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7886 Store[x][y] = element;
7888 PlayLevelSoundAction(x, y, ACTION_FILLING);
7890 else if (element == EL_MAGIC_WALL_FULL)
7892 if (IS_FREE(x, y + 1))
7894 InitMovingField(x, y, MV_DOWN);
7895 started_moving = TRUE;
7897 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7898 Store[x][y] = EL_CHANGED(Store[x][y]);
7900 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7902 if (!MovDelay[x][y])
7903 MovDelay[x][y] = TILEY / 4 + 1;
7912 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7913 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7914 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7918 else if (element == EL_BD_MAGIC_WALL_FULL)
7920 if (IS_FREE(x, y + 1))
7922 InitMovingField(x, y, MV_DOWN);
7923 started_moving = TRUE;
7925 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7926 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7928 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7930 if (!MovDelay[x][y])
7931 MovDelay[x][y] = TILEY / 4 + 1;
7940 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
7941 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
7942 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
7946 else if (element == EL_DC_MAGIC_WALL_FULL)
7948 if (IS_FREE(x, y + 1))
7950 InitMovingField(x, y, MV_DOWN);
7951 started_moving = TRUE;
7953 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
7954 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
7956 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
7958 if (!MovDelay[x][y])
7959 MovDelay[x][y] = TILEY / 4 + 1;
7968 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
7969 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
7970 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
7974 else if ((CAN_PASS_MAGIC_WALL(element) &&
7975 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
7976 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
7977 (CAN_PASS_DC_MAGIC_WALL(element) &&
7978 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
7981 InitMovingField(x, y, MV_DOWN);
7982 started_moving = TRUE;
7985 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
7986 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
7987 EL_DC_MAGIC_WALL_FILLING);
7988 Store[x][y] = element;
7990 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
7992 SplashAcid(x, y + 1);
7994 InitMovingField(x, y, MV_DOWN);
7995 started_moving = TRUE;
7997 Store[x][y] = EL_ACID;
8000 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8001 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8002 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8003 CAN_FALL(element) && WasJustFalling[x][y] &&
8004 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8006 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8007 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8008 (Tile[x][y + 1] == EL_BLOCKED)))
8010 /* this is needed for a special case not covered by calling "Impact()"
8011 from "ContinueMoving()": if an element moves to a tile directly below
8012 another element which was just falling on that tile (which was empty
8013 in the previous frame), the falling element above would just stop
8014 instead of smashing the element below (in previous version, the above
8015 element was just checked for "moving" instead of "falling", resulting
8016 in incorrect smashes caused by horizontal movement of the above
8017 element; also, the case of the player being the element to smash was
8018 simply not covered here... :-/ ) */
8020 CheckCollision[x][y] = 0;
8021 CheckImpact[x][y] = 0;
8025 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8027 if (MovDir[x][y] == MV_NONE)
8029 InitMovingField(x, y, MV_DOWN);
8030 started_moving = TRUE;
8033 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8035 if (WasJustFalling[x][y]) // prevent animation from being restarted
8036 MovDir[x][y] = MV_DOWN;
8038 InitMovingField(x, y, MV_DOWN);
8039 started_moving = TRUE;
8041 else if (element == EL_AMOEBA_DROP)
8043 Tile[x][y] = EL_AMOEBA_GROWING;
8044 Store[x][y] = EL_AMOEBA_WET;
8046 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8047 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8048 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8049 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8051 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8052 (IS_FREE(x - 1, y + 1) ||
8053 Tile[x - 1][y + 1] == EL_ACID));
8054 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8055 (IS_FREE(x + 1, y + 1) ||
8056 Tile[x + 1][y + 1] == EL_ACID));
8057 boolean can_fall_any = (can_fall_left || can_fall_right);
8058 boolean can_fall_both = (can_fall_left && can_fall_right);
8059 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8061 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8063 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8064 can_fall_right = FALSE;
8065 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8066 can_fall_left = FALSE;
8067 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8068 can_fall_right = FALSE;
8069 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8070 can_fall_left = FALSE;
8072 can_fall_any = (can_fall_left || can_fall_right);
8073 can_fall_both = FALSE;
8078 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8079 can_fall_right = FALSE; // slip down on left side
8081 can_fall_left = !(can_fall_right = RND(2));
8083 can_fall_both = FALSE;
8088 // if not determined otherwise, prefer left side for slipping down
8089 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8090 started_moving = TRUE;
8093 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8095 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8096 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8097 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8098 int belt_dir = game.belt_dir[belt_nr];
8100 if ((belt_dir == MV_LEFT && left_is_free) ||
8101 (belt_dir == MV_RIGHT && right_is_free))
8103 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8105 InitMovingField(x, y, belt_dir);
8106 started_moving = TRUE;
8108 Pushed[x][y] = TRUE;
8109 Pushed[nextx][y] = TRUE;
8111 GfxAction[x][y] = ACTION_DEFAULT;
8115 MovDir[x][y] = 0; // if element was moving, stop it
8120 // not "else if" because of elements that can fall and move (EL_SPRING)
8121 if (CAN_MOVE(element) && !started_moving)
8123 int move_pattern = element_info[element].move_pattern;
8126 Moving2Blocked(x, y, &newx, &newy);
8128 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8131 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8132 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8134 WasJustMoving[x][y] = 0;
8135 CheckCollision[x][y] = 0;
8137 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8139 if (Tile[x][y] != element) // element has changed
8143 if (!MovDelay[x][y]) // start new movement phase
8145 // all objects that can change their move direction after each step
8146 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8148 if (element != EL_YAMYAM &&
8149 element != EL_DARK_YAMYAM &&
8150 element != EL_PACMAN &&
8151 !(move_pattern & MV_ANY_DIRECTION) &&
8152 move_pattern != MV_TURNING_LEFT &&
8153 move_pattern != MV_TURNING_RIGHT &&
8154 move_pattern != MV_TURNING_LEFT_RIGHT &&
8155 move_pattern != MV_TURNING_RIGHT_LEFT &&
8156 move_pattern != MV_TURNING_RANDOM)
8160 if (MovDelay[x][y] && (element == EL_BUG ||
8161 element == EL_SPACESHIP ||
8162 element == EL_SP_SNIKSNAK ||
8163 element == EL_SP_ELECTRON ||
8164 element == EL_MOLE))
8165 TEST_DrawLevelField(x, y);
8169 if (MovDelay[x][y]) // wait some time before next movement
8173 if (element == EL_ROBOT ||
8174 element == EL_YAMYAM ||
8175 element == EL_DARK_YAMYAM)
8177 DrawLevelElementAnimationIfNeeded(x, y, element);
8178 PlayLevelSoundAction(x, y, ACTION_WAITING);
8180 else if (element == EL_SP_ELECTRON)
8181 DrawLevelElementAnimationIfNeeded(x, y, element);
8182 else if (element == EL_DRAGON)
8185 int dir = MovDir[x][y];
8186 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8187 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8188 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8189 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8190 dir == MV_UP ? IMG_FLAMES_1_UP :
8191 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8192 int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]);
8194 GfxAction[x][y] = ACTION_ATTACKING;
8196 if (IS_PLAYER(x, y))
8197 DrawPlayerField(x, y);
8199 TEST_DrawLevelField(x, y);
8201 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8203 for (i = 1; i <= 3; i++)
8205 int xx = x + i * dx;
8206 int yy = y + i * dy;
8207 int sx = SCREENX(xx);
8208 int sy = SCREENY(yy);
8209 int flame_graphic = graphic + (i - 1);
8211 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8216 int flamed = MovingOrBlocked2Element(xx, yy);
8218 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8221 RemoveMovingField(xx, yy);
8223 ChangeDelay[xx][yy] = 0;
8225 Tile[xx][yy] = EL_FLAMES;
8227 if (IN_SCR_FIELD(sx, sy))
8229 TEST_DrawLevelFieldCrumbled(xx, yy);
8230 DrawGraphic(sx, sy, flame_graphic, frame);
8235 if (Tile[xx][yy] == EL_FLAMES)
8236 Tile[xx][yy] = EL_EMPTY;
8237 TEST_DrawLevelField(xx, yy);
8242 if (MovDelay[x][y]) // element still has to wait some time
8244 PlayLevelSoundAction(x, y, ACTION_WAITING);
8250 // now make next step
8252 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8254 if (DONT_COLLIDE_WITH(element) &&
8255 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8256 !PLAYER_ENEMY_PROTECTED(newx, newy))
8258 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8263 else if (CAN_MOVE_INTO_ACID(element) &&
8264 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8265 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8266 (MovDir[x][y] == MV_DOWN ||
8267 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8269 SplashAcid(newx, newy);
8270 Store[x][y] = EL_ACID;
8272 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8274 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8275 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8276 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8277 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8280 TEST_DrawLevelField(x, y);
8282 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8283 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8284 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8286 game.friends_still_needed--;
8287 if (!game.friends_still_needed &&
8289 game.all_players_gone)
8294 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8296 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8297 TEST_DrawLevelField(newx, newy);
8299 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8301 else if (!IS_FREE(newx, newy))
8303 GfxAction[x][y] = ACTION_WAITING;
8305 if (IS_PLAYER(x, y))
8306 DrawPlayerField(x, y);
8308 TEST_DrawLevelField(x, y);
8313 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8315 if (IS_FOOD_PIG(Tile[newx][newy]))
8317 if (IS_MOVING(newx, newy))
8318 RemoveMovingField(newx, newy);
8321 Tile[newx][newy] = EL_EMPTY;
8322 TEST_DrawLevelField(newx, newy);
8325 PlayLevelSound(x, y, SND_PIG_DIGGING);
8327 else if (!IS_FREE(newx, newy))
8329 if (IS_PLAYER(x, y))
8330 DrawPlayerField(x, y);
8332 TEST_DrawLevelField(x, y);
8337 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8339 if (Store[x][y] != EL_EMPTY)
8341 boolean can_clone = FALSE;
8344 // check if element to clone is still there
8345 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8347 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8355 // cannot clone or target field not free anymore -- do not clone
8356 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8357 Store[x][y] = EL_EMPTY;
8360 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8362 if (IS_MV_DIAGONAL(MovDir[x][y]))
8364 int diagonal_move_dir = MovDir[x][y];
8365 int stored = Store[x][y];
8366 int change_delay = 8;
8369 // android is moving diagonally
8371 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8373 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8374 GfxElement[x][y] = EL_EMC_ANDROID;
8375 GfxAction[x][y] = ACTION_SHRINKING;
8376 GfxDir[x][y] = diagonal_move_dir;
8377 ChangeDelay[x][y] = change_delay;
8379 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8382 DrawLevelGraphicAnimation(x, y, graphic);
8383 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8385 if (Tile[newx][newy] == EL_ACID)
8387 SplashAcid(newx, newy);
8392 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8394 Store[newx][newy] = EL_EMC_ANDROID;
8395 GfxElement[newx][newy] = EL_EMC_ANDROID;
8396 GfxAction[newx][newy] = ACTION_GROWING;
8397 GfxDir[newx][newy] = diagonal_move_dir;
8398 ChangeDelay[newx][newy] = change_delay;
8400 graphic = el_act_dir2img(GfxElement[newx][newy],
8401 GfxAction[newx][newy], GfxDir[newx][newy]);
8403 DrawLevelGraphicAnimation(newx, newy, graphic);
8404 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8410 Tile[newx][newy] = EL_EMPTY;
8411 TEST_DrawLevelField(newx, newy);
8413 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8416 else if (!IS_FREE(newx, newy))
8421 else if (IS_CUSTOM_ELEMENT(element) &&
8422 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8424 if (!DigFieldByCE(newx, newy, element))
8427 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8429 RunnerVisit[x][y] = FrameCounter;
8430 PlayerVisit[x][y] /= 8; // expire player visit path
8433 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8435 if (!IS_FREE(newx, newy))
8437 if (IS_PLAYER(x, y))
8438 DrawPlayerField(x, y);
8440 TEST_DrawLevelField(x, y);
8446 boolean wanna_flame = !RND(10);
8447 int dx = newx - x, dy = newy - y;
8448 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8449 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8450 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8451 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8452 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8453 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8456 IS_CLASSIC_ENEMY(element1) ||
8457 IS_CLASSIC_ENEMY(element2)) &&
8458 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8459 element1 != EL_FLAMES && element2 != EL_FLAMES)
8461 ResetGfxAnimation(x, y);
8462 GfxAction[x][y] = ACTION_ATTACKING;
8464 if (IS_PLAYER(x, y))
8465 DrawPlayerField(x, y);
8467 TEST_DrawLevelField(x, y);
8469 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8471 MovDelay[x][y] = 50;
8473 Tile[newx][newy] = EL_FLAMES;
8474 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8475 Tile[newx1][newy1] = EL_FLAMES;
8476 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8477 Tile[newx2][newy2] = EL_FLAMES;
8483 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8484 Tile[newx][newy] == EL_DIAMOND)
8486 if (IS_MOVING(newx, newy))
8487 RemoveMovingField(newx, newy);
8490 Tile[newx][newy] = EL_EMPTY;
8491 TEST_DrawLevelField(newx, newy);
8494 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8496 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8497 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8499 if (AmoebaNr[newx][newy])
8501 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8502 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8503 Tile[newx][newy] == EL_BD_AMOEBA)
8504 AmoebaCnt[AmoebaNr[newx][newy]]--;
8507 if (IS_MOVING(newx, newy))
8509 RemoveMovingField(newx, newy);
8513 Tile[newx][newy] = EL_EMPTY;
8514 TEST_DrawLevelField(newx, newy);
8517 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8519 else if ((element == EL_PACMAN || element == EL_MOLE)
8520 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8522 if (AmoebaNr[newx][newy])
8524 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8525 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8526 Tile[newx][newy] == EL_BD_AMOEBA)
8527 AmoebaCnt[AmoebaNr[newx][newy]]--;
8530 if (element == EL_MOLE)
8532 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8533 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8535 ResetGfxAnimation(x, y);
8536 GfxAction[x][y] = ACTION_DIGGING;
8537 TEST_DrawLevelField(x, y);
8539 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8541 return; // wait for shrinking amoeba
8543 else // element == EL_PACMAN
8545 Tile[newx][newy] = EL_EMPTY;
8546 TEST_DrawLevelField(newx, newy);
8547 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8550 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8551 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8552 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8554 // wait for shrinking amoeba to completely disappear
8557 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8559 // object was running against a wall
8563 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8564 DrawLevelElementAnimation(x, y, element);
8566 if (DONT_TOUCH(element))
8567 TestIfBadThingTouchesPlayer(x, y);
8572 InitMovingField(x, y, MovDir[x][y]);
8574 PlayLevelSoundAction(x, y, ACTION_MOVING);
8578 ContinueMoving(x, y);
8581 void ContinueMoving(int x, int y)
8583 int element = Tile[x][y];
8584 struct ElementInfo *ei = &element_info[element];
8585 int direction = MovDir[x][y];
8586 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8587 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8588 int newx = x + dx, newy = y + dy;
8589 int stored = Store[x][y];
8590 int stored_new = Store[newx][newy];
8591 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8592 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8593 boolean last_line = (newy == lev_fieldy - 1);
8594 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8596 if (pushed_by_player) // special case: moving object pushed by player
8598 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8600 else if (use_step_delay) // special case: moving object has step delay
8602 if (!MovDelay[x][y])
8603 MovPos[x][y] += getElementMoveStepsize(x, y);
8608 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8612 TEST_DrawLevelField(x, y);
8614 return; // element is still waiting
8617 else // normal case: generically moving object
8619 MovPos[x][y] += getElementMoveStepsize(x, y);
8622 if (ABS(MovPos[x][y]) < TILEX)
8624 TEST_DrawLevelField(x, y);
8626 return; // element is still moving
8629 // element reached destination field
8631 Tile[x][y] = EL_EMPTY;
8632 Tile[newx][newy] = element;
8633 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8635 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8637 element = Tile[newx][newy] = EL_ACID;
8639 else if (element == EL_MOLE)
8641 Tile[x][y] = EL_SAND;
8643 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8645 else if (element == EL_QUICKSAND_FILLING)
8647 element = Tile[newx][newy] = get_next_element(element);
8648 Store[newx][newy] = Store[x][y];
8650 else if (element == EL_QUICKSAND_EMPTYING)
8652 Tile[x][y] = get_next_element(element);
8653 element = Tile[newx][newy] = Store[x][y];
8655 else if (element == EL_QUICKSAND_FAST_FILLING)
8657 element = Tile[newx][newy] = get_next_element(element);
8658 Store[newx][newy] = Store[x][y];
8660 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8662 Tile[x][y] = get_next_element(element);
8663 element = Tile[newx][newy] = Store[x][y];
8665 else if (element == EL_MAGIC_WALL_FILLING)
8667 element = Tile[newx][newy] = get_next_element(element);
8668 if (!game.magic_wall_active)
8669 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8670 Store[newx][newy] = Store[x][y];
8672 else if (element == EL_MAGIC_WALL_EMPTYING)
8674 Tile[x][y] = get_next_element(element);
8675 if (!game.magic_wall_active)
8676 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8677 element = Tile[newx][newy] = Store[x][y];
8679 InitField(newx, newy, FALSE);
8681 else if (element == EL_BD_MAGIC_WALL_FILLING)
8683 element = Tile[newx][newy] = get_next_element(element);
8684 if (!game.magic_wall_active)
8685 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8686 Store[newx][newy] = Store[x][y];
8688 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8690 Tile[x][y] = get_next_element(element);
8691 if (!game.magic_wall_active)
8692 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8693 element = Tile[newx][newy] = Store[x][y];
8695 InitField(newx, newy, FALSE);
8697 else if (element == EL_DC_MAGIC_WALL_FILLING)
8699 element = Tile[newx][newy] = get_next_element(element);
8700 if (!game.magic_wall_active)
8701 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8702 Store[newx][newy] = Store[x][y];
8704 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8706 Tile[x][y] = get_next_element(element);
8707 if (!game.magic_wall_active)
8708 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8709 element = Tile[newx][newy] = Store[x][y];
8711 InitField(newx, newy, FALSE);
8713 else if (element == EL_AMOEBA_DROPPING)
8715 Tile[x][y] = get_next_element(element);
8716 element = Tile[newx][newy] = Store[x][y];
8718 else if (element == EL_SOKOBAN_OBJECT)
8721 Tile[x][y] = Back[x][y];
8723 if (Back[newx][newy])
8724 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8726 Back[x][y] = Back[newx][newy] = 0;
8729 Store[x][y] = EL_EMPTY;
8734 MovDelay[newx][newy] = 0;
8736 if (CAN_CHANGE_OR_HAS_ACTION(element))
8738 // copy element change control values to new field
8739 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8740 ChangePage[newx][newy] = ChangePage[x][y];
8741 ChangeCount[newx][newy] = ChangeCount[x][y];
8742 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8745 CustomValue[newx][newy] = CustomValue[x][y];
8747 ChangeDelay[x][y] = 0;
8748 ChangePage[x][y] = -1;
8749 ChangeCount[x][y] = 0;
8750 ChangeEvent[x][y] = -1;
8752 CustomValue[x][y] = 0;
8754 // copy animation control values to new field
8755 GfxFrame[newx][newy] = GfxFrame[x][y];
8756 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8757 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8758 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8760 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8762 // some elements can leave other elements behind after moving
8763 if (ei->move_leave_element != EL_EMPTY &&
8764 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8765 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8767 int move_leave_element = ei->move_leave_element;
8769 // this makes it possible to leave the removed element again
8770 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8771 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8773 Tile[x][y] = move_leave_element;
8775 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8776 MovDir[x][y] = direction;
8778 InitField(x, y, FALSE);
8780 if (GFX_CRUMBLED(Tile[x][y]))
8781 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8783 if (IS_PLAYER_ELEMENT(move_leave_element))
8784 RelocatePlayer(x, y, move_leave_element);
8787 // do this after checking for left-behind element
8788 ResetGfxAnimation(x, y); // reset animation values for old field
8790 if (!CAN_MOVE(element) ||
8791 (CAN_FALL(element) && direction == MV_DOWN &&
8792 (element == EL_SPRING ||
8793 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8794 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8795 GfxDir[x][y] = MovDir[newx][newy] = 0;
8797 TEST_DrawLevelField(x, y);
8798 TEST_DrawLevelField(newx, newy);
8800 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8802 // prevent pushed element from moving on in pushed direction
8803 if (pushed_by_player && CAN_MOVE(element) &&
8804 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8805 !(element_info[element].move_pattern & direction))
8806 TurnRound(newx, newy);
8808 // prevent elements on conveyor belt from moving on in last direction
8809 if (pushed_by_conveyor && CAN_FALL(element) &&
8810 direction & MV_HORIZONTAL)
8811 MovDir[newx][newy] = 0;
8813 if (!pushed_by_player)
8815 int nextx = newx + dx, nexty = newy + dy;
8816 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8818 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8820 if (CAN_FALL(element) && direction == MV_DOWN)
8821 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8823 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8824 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8826 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8827 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8830 if (DONT_TOUCH(element)) // object may be nasty to player or others
8832 TestIfBadThingTouchesPlayer(newx, newy);
8833 TestIfBadThingTouchesFriend(newx, newy);
8835 if (!IS_CUSTOM_ELEMENT(element))
8836 TestIfBadThingTouchesOtherBadThing(newx, newy);
8838 else if (element == EL_PENGUIN)
8839 TestIfFriendTouchesBadThing(newx, newy);
8841 if (DONT_GET_HIT_BY(element))
8843 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8846 // give the player one last chance (one more frame) to move away
8847 if (CAN_FALL(element) && direction == MV_DOWN &&
8848 (last_line || (!IS_FREE(x, newy + 1) &&
8849 (!IS_PLAYER(x, newy + 1) ||
8850 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8853 if (pushed_by_player && !game.use_change_when_pushing_bug)
8855 int push_side = MV_DIR_OPPOSITE(direction);
8856 struct PlayerInfo *player = PLAYERINFO(x, y);
8858 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8859 player->index_bit, push_side);
8860 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8861 player->index_bit, push_side);
8864 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8865 MovDelay[newx][newy] = 1;
8867 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8869 TestIfElementTouchesCustomElement(x, y); // empty or new element
8870 TestIfElementHitsCustomElement(newx, newy, direction);
8871 TestIfPlayerTouchesCustomElement(newx, newy);
8872 TestIfElementTouchesCustomElement(newx, newy);
8874 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8875 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8876 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8877 MV_DIR_OPPOSITE(direction));
8880 int AmoebaNeighbourNr(int ax, int ay)
8883 int element = Tile[ax][ay];
8885 static int xy[4][2] =
8893 for (i = 0; i < NUM_DIRECTIONS; i++)
8895 int x = ax + xy[i][0];
8896 int y = ay + xy[i][1];
8898 if (!IN_LEV_FIELD(x, y))
8901 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8902 group_nr = AmoebaNr[x][y];
8908 static void AmoebaMerge(int ax, int ay)
8910 int i, x, y, xx, yy;
8911 int new_group_nr = AmoebaNr[ax][ay];
8912 static int xy[4][2] =
8920 if (new_group_nr == 0)
8923 for (i = 0; i < NUM_DIRECTIONS; i++)
8928 if (!IN_LEV_FIELD(x, y))
8931 if ((Tile[x][y] == EL_AMOEBA_FULL ||
8932 Tile[x][y] == EL_BD_AMOEBA ||
8933 Tile[x][y] == EL_AMOEBA_DEAD) &&
8934 AmoebaNr[x][y] != new_group_nr)
8936 int old_group_nr = AmoebaNr[x][y];
8938 if (old_group_nr == 0)
8941 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
8942 AmoebaCnt[old_group_nr] = 0;
8943 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
8944 AmoebaCnt2[old_group_nr] = 0;
8946 SCAN_PLAYFIELD(xx, yy)
8948 if (AmoebaNr[xx][yy] == old_group_nr)
8949 AmoebaNr[xx][yy] = new_group_nr;
8955 void AmoebaToDiamond(int ax, int ay)
8959 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
8961 int group_nr = AmoebaNr[ax][ay];
8966 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
8967 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
8973 SCAN_PLAYFIELD(x, y)
8975 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
8978 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
8982 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
8983 SND_AMOEBA_TURNING_TO_GEM :
8984 SND_AMOEBA_TURNING_TO_ROCK));
8989 static int xy[4][2] =
8997 for (i = 0; i < NUM_DIRECTIONS; i++)
9002 if (!IN_LEV_FIELD(x, y))
9005 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9007 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9008 SND_AMOEBA_TURNING_TO_GEM :
9009 SND_AMOEBA_TURNING_TO_ROCK));
9016 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9019 int group_nr = AmoebaNr[ax][ay];
9020 boolean done = FALSE;
9025 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9026 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9032 SCAN_PLAYFIELD(x, y)
9034 if (AmoebaNr[x][y] == group_nr &&
9035 (Tile[x][y] == EL_AMOEBA_DEAD ||
9036 Tile[x][y] == EL_BD_AMOEBA ||
9037 Tile[x][y] == EL_AMOEBA_GROWING))
9040 Tile[x][y] = new_element;
9041 InitField(x, y, FALSE);
9042 TEST_DrawLevelField(x, y);
9048 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9049 SND_BD_AMOEBA_TURNING_TO_ROCK :
9050 SND_BD_AMOEBA_TURNING_TO_GEM));
9053 static void AmoebaGrowing(int x, int y)
9055 static unsigned int sound_delay = 0;
9056 static unsigned int sound_delay_value = 0;
9058 if (!MovDelay[x][y]) // start new growing cycle
9062 if (DelayReached(&sound_delay, sound_delay_value))
9064 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9065 sound_delay_value = 30;
9069 if (MovDelay[x][y]) // wait some time before growing bigger
9072 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9074 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9075 6 - MovDelay[x][y]);
9077 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame);
9080 if (!MovDelay[x][y])
9082 Tile[x][y] = Store[x][y];
9084 TEST_DrawLevelField(x, y);
9089 static void AmoebaShrinking(int x, int y)
9091 static unsigned int sound_delay = 0;
9092 static unsigned int sound_delay_value = 0;
9094 if (!MovDelay[x][y]) // start new shrinking cycle
9098 if (DelayReached(&sound_delay, sound_delay_value))
9099 sound_delay_value = 30;
9102 if (MovDelay[x][y]) // wait some time before shrinking
9105 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9107 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9108 6 - MovDelay[x][y]);
9110 DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame);
9113 if (!MovDelay[x][y])
9115 Tile[x][y] = EL_EMPTY;
9116 TEST_DrawLevelField(x, y);
9118 // don't let mole enter this field in this cycle;
9119 // (give priority to objects falling to this field from above)
9125 static void AmoebaReproduce(int ax, int ay)
9128 int element = Tile[ax][ay];
9129 int graphic = el2img(element);
9130 int newax = ax, neway = ay;
9131 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9132 static int xy[4][2] =
9140 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9142 Tile[ax][ay] = EL_AMOEBA_DEAD;
9143 TEST_DrawLevelField(ax, ay);
9147 if (IS_ANIMATED(graphic))
9148 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9150 if (!MovDelay[ax][ay]) // start making new amoeba field
9151 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9153 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9156 if (MovDelay[ax][ay])
9160 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9163 int x = ax + xy[start][0];
9164 int y = ay + xy[start][1];
9166 if (!IN_LEV_FIELD(x, y))
9169 if (IS_FREE(x, y) ||
9170 CAN_GROW_INTO(Tile[x][y]) ||
9171 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9172 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9178 if (newax == ax && neway == ay)
9181 else // normal or "filled" (BD style) amoeba
9184 boolean waiting_for_player = FALSE;
9186 for (i = 0; i < NUM_DIRECTIONS; i++)
9188 int j = (start + i) % 4;
9189 int x = ax + xy[j][0];
9190 int y = ay + xy[j][1];
9192 if (!IN_LEV_FIELD(x, y))
9195 if (IS_FREE(x, y) ||
9196 CAN_GROW_INTO(Tile[x][y]) ||
9197 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9198 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9204 else if (IS_PLAYER(x, y))
9205 waiting_for_player = TRUE;
9208 if (newax == ax && neway == ay) // amoeba cannot grow
9210 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9212 Tile[ax][ay] = EL_AMOEBA_DEAD;
9213 TEST_DrawLevelField(ax, ay);
9214 AmoebaCnt[AmoebaNr[ax][ay]]--;
9216 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9218 if (element == EL_AMOEBA_FULL)
9219 AmoebaToDiamond(ax, ay);
9220 else if (element == EL_BD_AMOEBA)
9221 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9226 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9228 // amoeba gets larger by growing in some direction
9230 int new_group_nr = AmoebaNr[ax][ay];
9233 if (new_group_nr == 0)
9235 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9237 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9243 AmoebaNr[newax][neway] = new_group_nr;
9244 AmoebaCnt[new_group_nr]++;
9245 AmoebaCnt2[new_group_nr]++;
9247 // if amoeba touches other amoeba(s) after growing, unify them
9248 AmoebaMerge(newax, neway);
9250 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9252 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9258 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9259 (neway == lev_fieldy - 1 && newax != ax))
9261 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9262 Store[newax][neway] = element;
9264 else if (neway == ay || element == EL_EMC_DRIPPER)
9266 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9268 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9272 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9273 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9274 Store[ax][ay] = EL_AMOEBA_DROP;
9275 ContinueMoving(ax, ay);
9279 TEST_DrawLevelField(newax, neway);
9282 static void Life(int ax, int ay)
9286 int element = Tile[ax][ay];
9287 int graphic = el2img(element);
9288 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9290 boolean changed = FALSE;
9292 if (IS_ANIMATED(graphic))
9293 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9298 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9299 MovDelay[ax][ay] = life_time;
9301 if (MovDelay[ax][ay]) // wait some time before next cycle
9304 if (MovDelay[ax][ay])
9308 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9310 int xx = ax+x1, yy = ay+y1;
9311 int old_element = Tile[xx][yy];
9312 int num_neighbours = 0;
9314 if (!IN_LEV_FIELD(xx, yy))
9317 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9319 int x = xx+x2, y = yy+y2;
9321 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9324 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9325 boolean is_neighbour = FALSE;
9327 if (level.use_life_bugs)
9329 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9330 (IS_FREE(x, y) && Stop[x][y]));
9333 (Last[x][y] == element || is_player_cell);
9339 boolean is_free = FALSE;
9341 if (level.use_life_bugs)
9342 is_free = (IS_FREE(xx, yy));
9344 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9346 if (xx == ax && yy == ay) // field in the middle
9348 if (num_neighbours < life_parameter[0] ||
9349 num_neighbours > life_parameter[1])
9351 Tile[xx][yy] = EL_EMPTY;
9352 if (Tile[xx][yy] != old_element)
9353 TEST_DrawLevelField(xx, yy);
9354 Stop[xx][yy] = TRUE;
9358 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9359 { // free border field
9360 if (num_neighbours >= life_parameter[2] &&
9361 num_neighbours <= life_parameter[3])
9363 Tile[xx][yy] = element;
9364 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1);
9365 if (Tile[xx][yy] != old_element)
9366 TEST_DrawLevelField(xx, yy);
9367 Stop[xx][yy] = TRUE;
9374 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9375 SND_GAME_OF_LIFE_GROWING);
9378 static void InitRobotWheel(int x, int y)
9380 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9383 static void RunRobotWheel(int x, int y)
9385 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9388 static void StopRobotWheel(int x, int y)
9390 if (game.robot_wheel_x == x &&
9391 game.robot_wheel_y == y)
9393 game.robot_wheel_x = -1;
9394 game.robot_wheel_y = -1;
9395 game.robot_wheel_active = FALSE;
9399 static void InitTimegateWheel(int x, int y)
9401 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9404 static void RunTimegateWheel(int x, int y)
9406 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9409 static void InitMagicBallDelay(int x, int y)
9411 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9414 static void ActivateMagicBall(int bx, int by)
9418 if (level.ball_random)
9420 int pos_border = RND(8); // select one of the eight border elements
9421 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9422 int xx = pos_content % 3;
9423 int yy = pos_content / 3;
9428 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9429 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9433 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9435 int xx = x - bx + 1;
9436 int yy = y - by + 1;
9438 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9439 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9443 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9446 static void CheckExit(int x, int y)
9448 if (game.gems_still_needed > 0 ||
9449 game.sokoban_fields_still_needed > 0 ||
9450 game.sokoban_objects_still_needed > 0 ||
9451 game.lights_still_needed > 0)
9453 int element = Tile[x][y];
9454 int graphic = el2img(element);
9456 if (IS_ANIMATED(graphic))
9457 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9462 // do not re-open exit door closed after last player
9463 if (game.all_players_gone)
9466 Tile[x][y] = EL_EXIT_OPENING;
9468 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9471 static void CheckExitEM(int x, int y)
9473 if (game.gems_still_needed > 0 ||
9474 game.sokoban_fields_still_needed > 0 ||
9475 game.sokoban_objects_still_needed > 0 ||
9476 game.lights_still_needed > 0)
9478 int element = Tile[x][y];
9479 int graphic = el2img(element);
9481 if (IS_ANIMATED(graphic))
9482 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9487 // do not re-open exit door closed after last player
9488 if (game.all_players_gone)
9491 Tile[x][y] = EL_EM_EXIT_OPENING;
9493 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9496 static void CheckExitSteel(int x, int y)
9498 if (game.gems_still_needed > 0 ||
9499 game.sokoban_fields_still_needed > 0 ||
9500 game.sokoban_objects_still_needed > 0 ||
9501 game.lights_still_needed > 0)
9503 int element = Tile[x][y];
9504 int graphic = el2img(element);
9506 if (IS_ANIMATED(graphic))
9507 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9512 // do not re-open exit door closed after last player
9513 if (game.all_players_gone)
9516 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9518 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9521 static void CheckExitSteelEM(int x, int y)
9523 if (game.gems_still_needed > 0 ||
9524 game.sokoban_fields_still_needed > 0 ||
9525 game.sokoban_objects_still_needed > 0 ||
9526 game.lights_still_needed > 0)
9528 int element = Tile[x][y];
9529 int graphic = el2img(element);
9531 if (IS_ANIMATED(graphic))
9532 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9537 // do not re-open exit door closed after last player
9538 if (game.all_players_gone)
9541 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9543 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9546 static void CheckExitSP(int x, int y)
9548 if (game.gems_still_needed > 0)
9550 int element = Tile[x][y];
9551 int graphic = el2img(element);
9553 if (IS_ANIMATED(graphic))
9554 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9559 // do not re-open exit door closed after last player
9560 if (game.all_players_gone)
9563 Tile[x][y] = EL_SP_EXIT_OPENING;
9565 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9568 static void CloseAllOpenTimegates(void)
9572 SCAN_PLAYFIELD(x, y)
9574 int element = Tile[x][y];
9576 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9578 Tile[x][y] = EL_TIMEGATE_CLOSING;
9580 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9585 static void DrawTwinkleOnField(int x, int y)
9587 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9590 if (Tile[x][y] == EL_BD_DIAMOND)
9593 if (MovDelay[x][y] == 0) // next animation frame
9594 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9596 if (MovDelay[x][y] != 0) // wait some time before next frame
9600 DrawLevelElementAnimation(x, y, Tile[x][y]);
9602 if (MovDelay[x][y] != 0)
9604 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9605 10 - MovDelay[x][y]);
9607 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9612 static void MauerWaechst(int x, int y)
9616 if (!MovDelay[x][y]) // next animation frame
9617 MovDelay[x][y] = 3 * delay;
9619 if (MovDelay[x][y]) // wait some time before next frame
9623 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9625 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9626 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9628 DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame);
9631 if (!MovDelay[x][y])
9633 if (MovDir[x][y] == MV_LEFT)
9635 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9636 TEST_DrawLevelField(x - 1, y);
9638 else if (MovDir[x][y] == MV_RIGHT)
9640 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9641 TEST_DrawLevelField(x + 1, y);
9643 else if (MovDir[x][y] == MV_UP)
9645 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9646 TEST_DrawLevelField(x, y - 1);
9650 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9651 TEST_DrawLevelField(x, y + 1);
9654 Tile[x][y] = Store[x][y];
9656 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9657 TEST_DrawLevelField(x, y);
9662 static void MauerAbleger(int ax, int ay)
9664 int element = Tile[ax][ay];
9665 int graphic = el2img(element);
9666 boolean oben_frei = FALSE, unten_frei = FALSE;
9667 boolean links_frei = FALSE, rechts_frei = FALSE;
9668 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9669 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9670 boolean new_wall = FALSE;
9672 if (IS_ANIMATED(graphic))
9673 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9675 if (!MovDelay[ax][ay]) // start building new wall
9676 MovDelay[ax][ay] = 6;
9678 if (MovDelay[ax][ay]) // wait some time before building new wall
9681 if (MovDelay[ax][ay])
9685 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9687 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9689 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9691 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9694 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9695 element == EL_EXPANDABLE_WALL_ANY)
9699 Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING;
9700 Store[ax][ay-1] = element;
9701 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9702 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9703 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9704 IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9709 Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING;
9710 Store[ax][ay+1] = element;
9711 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9712 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9713 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9714 IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9719 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9720 element == EL_EXPANDABLE_WALL_ANY ||
9721 element == EL_EXPANDABLE_WALL ||
9722 element == EL_BD_EXPANDABLE_WALL)
9726 Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING;
9727 Store[ax-1][ay] = element;
9728 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9729 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9730 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9731 IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9737 Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING;
9738 Store[ax+1][ay] = element;
9739 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9740 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9741 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9742 IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9747 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9748 TEST_DrawLevelField(ax, ay);
9750 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9752 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9753 unten_massiv = TRUE;
9754 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9755 links_massiv = TRUE;
9756 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9757 rechts_massiv = TRUE;
9759 if (((oben_massiv && unten_massiv) ||
9760 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9761 element == EL_EXPANDABLE_WALL) &&
9762 ((links_massiv && rechts_massiv) ||
9763 element == EL_EXPANDABLE_WALL_VERTICAL))
9764 Tile[ax][ay] = EL_WALL;
9767 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9770 static void MauerAblegerStahl(int ax, int ay)
9772 int element = Tile[ax][ay];
9773 int graphic = el2img(element);
9774 boolean oben_frei = FALSE, unten_frei = FALSE;
9775 boolean links_frei = FALSE, rechts_frei = FALSE;
9776 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9777 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9778 boolean new_wall = FALSE;
9780 if (IS_ANIMATED(graphic))
9781 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9783 if (!MovDelay[ax][ay]) // start building new wall
9784 MovDelay[ax][ay] = 6;
9786 if (MovDelay[ax][ay]) // wait some time before building new wall
9789 if (MovDelay[ax][ay])
9793 if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1))
9795 if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1))
9797 if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay))
9799 if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay))
9802 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9803 element == EL_EXPANDABLE_STEELWALL_ANY)
9807 Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING;
9808 Store[ax][ay-1] = element;
9809 GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP;
9810 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1)))
9811 DrawGraphic(SCREENX(ax), SCREENY(ay - 1),
9812 IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9817 Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING;
9818 Store[ax][ay+1] = element;
9819 GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN;
9820 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1)))
9821 DrawGraphic(SCREENX(ax), SCREENY(ay + 1),
9822 IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9827 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9828 element == EL_EXPANDABLE_STEELWALL_ANY)
9832 Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9833 Store[ax-1][ay] = element;
9834 GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT;
9835 if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay)))
9836 DrawGraphic(SCREENX(ax - 1), SCREENY(ay),
9837 IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9843 Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9844 Store[ax+1][ay] = element;
9845 GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT;
9846 if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay)))
9847 DrawGraphic(SCREENX(ax + 1), SCREENY(ay),
9848 IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9853 if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1]))
9855 if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1]))
9856 unten_massiv = TRUE;
9857 if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay]))
9858 links_massiv = TRUE;
9859 if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay]))
9860 rechts_massiv = TRUE;
9862 if (((oben_massiv && unten_massiv) ||
9863 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9864 ((links_massiv && rechts_massiv) ||
9865 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9866 Tile[ax][ay] = EL_STEELWALL;
9869 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9872 static void CheckForDragon(int x, int y)
9875 boolean dragon_found = FALSE;
9876 static int xy[4][2] =
9884 for (i = 0; i < NUM_DIRECTIONS; i++)
9886 for (j = 0; j < 4; j++)
9888 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9890 if (IN_LEV_FIELD(xx, yy) &&
9891 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9893 if (Tile[xx][yy] == EL_DRAGON)
9894 dragon_found = TRUE;
9903 for (i = 0; i < NUM_DIRECTIONS; i++)
9905 for (j = 0; j < 3; j++)
9907 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9909 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9911 Tile[xx][yy] = EL_EMPTY;
9912 TEST_DrawLevelField(xx, yy);
9921 static void InitBuggyBase(int x, int y)
9923 int element = Tile[x][y];
9924 int activating_delay = FRAMES_PER_SECOND / 4;
9927 (element == EL_SP_BUGGY_BASE ?
9928 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9929 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9931 element == EL_SP_BUGGY_BASE_ACTIVE ?
9932 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9935 static void WarnBuggyBase(int x, int y)
9938 static int xy[4][2] =
9946 for (i = 0; i < NUM_DIRECTIONS; i++)
9948 int xx = x + xy[i][0];
9949 int yy = y + xy[i][1];
9951 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
9953 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
9960 static void InitTrap(int x, int y)
9962 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
9965 static void ActivateTrap(int x, int y)
9967 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
9970 static void ChangeActiveTrap(int x, int y)
9972 int graphic = IMG_TRAP_ACTIVE;
9974 // if new animation frame was drawn, correct crumbled sand border
9975 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
9976 TEST_DrawLevelFieldCrumbled(x, y);
9979 static int getSpecialActionElement(int element, int number, int base_element)
9981 return (element != EL_EMPTY ? element :
9982 number != -1 ? base_element + number - 1 :
9986 static int getModifiedActionNumber(int value_old, int operator, int operand,
9987 int value_min, int value_max)
9989 int value_new = (operator == CA_MODE_SET ? operand :
9990 operator == CA_MODE_ADD ? value_old + operand :
9991 operator == CA_MODE_SUBTRACT ? value_old - operand :
9992 operator == CA_MODE_MULTIPLY ? value_old * operand :
9993 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
9994 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
9997 return (value_new < value_min ? value_min :
9998 value_new > value_max ? value_max :
10002 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10004 struct ElementInfo *ei = &element_info[element];
10005 struct ElementChangeInfo *change = &ei->change_page[page];
10006 int target_element = change->target_element;
10007 int action_type = change->action_type;
10008 int action_mode = change->action_mode;
10009 int action_arg = change->action_arg;
10010 int action_element = change->action_element;
10013 if (!change->has_action)
10016 // ---------- determine action paramater values -----------------------------
10018 int level_time_value =
10019 (level.time > 0 ? TimeLeft :
10022 int action_arg_element_raw =
10023 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10024 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10025 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10026 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10027 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10028 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10029 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10031 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10033 int action_arg_direction =
10034 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10035 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10036 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10037 change->actual_trigger_side :
10038 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10039 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10042 int action_arg_number_min =
10043 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10046 int action_arg_number_max =
10047 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10048 action_type == CA_SET_LEVEL_GEMS ? 999 :
10049 action_type == CA_SET_LEVEL_TIME ? 9999 :
10050 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10051 action_type == CA_SET_CE_VALUE ? 9999 :
10052 action_type == CA_SET_CE_SCORE ? 9999 :
10055 int action_arg_number_reset =
10056 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10057 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10058 action_type == CA_SET_LEVEL_TIME ? level.time :
10059 action_type == CA_SET_LEVEL_SCORE ? 0 :
10060 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10061 action_type == CA_SET_CE_SCORE ? 0 :
10064 int action_arg_number =
10065 (action_arg <= CA_ARG_MAX ? action_arg :
10066 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10067 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10068 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10069 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10070 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10071 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10072 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10073 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10074 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10075 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10076 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10077 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10078 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10079 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10080 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10081 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10082 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10083 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10084 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10085 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10086 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10089 int action_arg_number_old =
10090 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10091 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10092 action_type == CA_SET_LEVEL_SCORE ? game.score :
10093 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10094 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10097 int action_arg_number_new =
10098 getModifiedActionNumber(action_arg_number_old,
10099 action_mode, action_arg_number,
10100 action_arg_number_min, action_arg_number_max);
10102 int trigger_player_bits =
10103 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10104 change->actual_trigger_player_bits : change->trigger_player);
10106 int action_arg_player_bits =
10107 (action_arg >= CA_ARG_PLAYER_1 &&
10108 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10109 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10110 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10113 // ---------- execute action -----------------------------------------------
10115 switch (action_type)
10122 // ---------- level actions ----------------------------------------------
10124 case CA_RESTART_LEVEL:
10126 game.restart_level = TRUE;
10131 case CA_SHOW_ENVELOPE:
10133 int element = getSpecialActionElement(action_arg_element,
10134 action_arg_number, EL_ENVELOPE_1);
10136 if (IS_ENVELOPE(element))
10137 local_player->show_envelope = element;
10142 case CA_SET_LEVEL_TIME:
10144 if (level.time > 0) // only modify limited time value
10146 TimeLeft = action_arg_number_new;
10148 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10150 DisplayGameControlValues();
10152 if (!TimeLeft && setup.time_limit)
10153 for (i = 0; i < MAX_PLAYERS; i++)
10154 KillPlayer(&stored_player[i]);
10160 case CA_SET_LEVEL_SCORE:
10162 game.score = action_arg_number_new;
10164 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10166 DisplayGameControlValues();
10171 case CA_SET_LEVEL_GEMS:
10173 game.gems_still_needed = action_arg_number_new;
10175 game.snapshot.collected_item = TRUE;
10177 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10179 DisplayGameControlValues();
10184 case CA_SET_LEVEL_WIND:
10186 game.wind_direction = action_arg_direction;
10191 case CA_SET_LEVEL_RANDOM_SEED:
10193 // ensure that setting a new random seed while playing is predictable
10194 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10199 // ---------- player actions ---------------------------------------------
10201 case CA_MOVE_PLAYER:
10202 case CA_MOVE_PLAYER_NEW:
10204 // automatically move to the next field in specified direction
10205 for (i = 0; i < MAX_PLAYERS; i++)
10206 if (trigger_player_bits & (1 << i))
10207 if (action_type == CA_MOVE_PLAYER ||
10208 stored_player[i].MovPos == 0)
10209 stored_player[i].programmed_action = action_arg_direction;
10214 case CA_EXIT_PLAYER:
10216 for (i = 0; i < MAX_PLAYERS; i++)
10217 if (action_arg_player_bits & (1 << i))
10218 ExitPlayer(&stored_player[i]);
10220 if (game.players_still_needed == 0)
10226 case CA_KILL_PLAYER:
10228 for (i = 0; i < MAX_PLAYERS; i++)
10229 if (action_arg_player_bits & (1 << i))
10230 KillPlayer(&stored_player[i]);
10235 case CA_SET_PLAYER_KEYS:
10237 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10238 int element = getSpecialActionElement(action_arg_element,
10239 action_arg_number, EL_KEY_1);
10241 if (IS_KEY(element))
10243 for (i = 0; i < MAX_PLAYERS; i++)
10245 if (trigger_player_bits & (1 << i))
10247 stored_player[i].key[KEY_NR(element)] = key_state;
10249 DrawGameDoorValues();
10257 case CA_SET_PLAYER_SPEED:
10259 for (i = 0; i < MAX_PLAYERS; i++)
10261 if (trigger_player_bits & (1 << i))
10263 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10265 if (action_arg == CA_ARG_SPEED_FASTER &&
10266 stored_player[i].cannot_move)
10268 action_arg_number = STEPSIZE_VERY_SLOW;
10270 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10271 action_arg == CA_ARG_SPEED_FASTER)
10273 action_arg_number = 2;
10274 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10277 else if (action_arg == CA_ARG_NUMBER_RESET)
10279 action_arg_number = level.initial_player_stepsize[i];
10283 getModifiedActionNumber(move_stepsize,
10286 action_arg_number_min,
10287 action_arg_number_max);
10289 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10296 case CA_SET_PLAYER_SHIELD:
10298 for (i = 0; i < MAX_PLAYERS; i++)
10300 if (trigger_player_bits & (1 << i))
10302 if (action_arg == CA_ARG_SHIELD_OFF)
10304 stored_player[i].shield_normal_time_left = 0;
10305 stored_player[i].shield_deadly_time_left = 0;
10307 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10309 stored_player[i].shield_normal_time_left = 999999;
10311 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10313 stored_player[i].shield_normal_time_left = 999999;
10314 stored_player[i].shield_deadly_time_left = 999999;
10322 case CA_SET_PLAYER_GRAVITY:
10324 for (i = 0; i < MAX_PLAYERS; i++)
10326 if (trigger_player_bits & (1 << i))
10328 stored_player[i].gravity =
10329 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10330 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10331 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10332 stored_player[i].gravity);
10339 case CA_SET_PLAYER_ARTWORK:
10341 for (i = 0; i < MAX_PLAYERS; i++)
10343 if (trigger_player_bits & (1 << i))
10345 int artwork_element = action_arg_element;
10347 if (action_arg == CA_ARG_ELEMENT_RESET)
10349 (level.use_artwork_element[i] ? level.artwork_element[i] :
10350 stored_player[i].element_nr);
10352 if (stored_player[i].artwork_element != artwork_element)
10353 stored_player[i].Frame = 0;
10355 stored_player[i].artwork_element = artwork_element;
10357 SetPlayerWaiting(&stored_player[i], FALSE);
10359 // set number of special actions for bored and sleeping animation
10360 stored_player[i].num_special_action_bored =
10361 get_num_special_action(artwork_element,
10362 ACTION_BORING_1, ACTION_BORING_LAST);
10363 stored_player[i].num_special_action_sleeping =
10364 get_num_special_action(artwork_element,
10365 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10372 case CA_SET_PLAYER_INVENTORY:
10374 for (i = 0; i < MAX_PLAYERS; i++)
10376 struct PlayerInfo *player = &stored_player[i];
10379 if (trigger_player_bits & (1 << i))
10381 int inventory_element = action_arg_element;
10383 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10384 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10385 action_arg == CA_ARG_ELEMENT_ACTION)
10387 int element = inventory_element;
10388 int collect_count = element_info[element].collect_count_initial;
10390 if (!IS_CUSTOM_ELEMENT(element))
10393 if (collect_count == 0)
10394 player->inventory_infinite_element = element;
10396 for (k = 0; k < collect_count; k++)
10397 if (player->inventory_size < MAX_INVENTORY_SIZE)
10398 player->inventory_element[player->inventory_size++] =
10401 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10402 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10403 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10405 if (player->inventory_infinite_element != EL_UNDEFINED &&
10406 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10407 action_arg_element_raw))
10408 player->inventory_infinite_element = EL_UNDEFINED;
10410 for (k = 0, j = 0; j < player->inventory_size; j++)
10412 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10413 action_arg_element_raw))
10414 player->inventory_element[k++] = player->inventory_element[j];
10417 player->inventory_size = k;
10419 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10421 if (player->inventory_size > 0)
10423 for (j = 0; j < player->inventory_size - 1; j++)
10424 player->inventory_element[j] = player->inventory_element[j + 1];
10426 player->inventory_size--;
10429 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10431 if (player->inventory_size > 0)
10432 player->inventory_size--;
10434 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10436 player->inventory_infinite_element = EL_UNDEFINED;
10437 player->inventory_size = 0;
10439 else if (action_arg == CA_ARG_INVENTORY_RESET)
10441 player->inventory_infinite_element = EL_UNDEFINED;
10442 player->inventory_size = 0;
10444 if (level.use_initial_inventory[i])
10446 for (j = 0; j < level.initial_inventory_size[i]; j++)
10448 int element = level.initial_inventory_content[i][j];
10449 int collect_count = element_info[element].collect_count_initial;
10451 if (!IS_CUSTOM_ELEMENT(element))
10454 if (collect_count == 0)
10455 player->inventory_infinite_element = element;
10457 for (k = 0; k < collect_count; k++)
10458 if (player->inventory_size < MAX_INVENTORY_SIZE)
10459 player->inventory_element[player->inventory_size++] =
10470 // ---------- CE actions -------------------------------------------------
10472 case CA_SET_CE_VALUE:
10474 int last_ce_value = CustomValue[x][y];
10476 CustomValue[x][y] = action_arg_number_new;
10478 if (CustomValue[x][y] != last_ce_value)
10480 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10481 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10483 if (CustomValue[x][y] == 0)
10485 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10486 ChangeCount[x][y] = 0; // allow at least one more change
10488 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10489 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10496 case CA_SET_CE_SCORE:
10498 int last_ce_score = ei->collect_score;
10500 ei->collect_score = action_arg_number_new;
10502 if (ei->collect_score != last_ce_score)
10504 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10505 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10507 if (ei->collect_score == 0)
10511 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10512 ChangeCount[x][y] = 0; // allow at least one more change
10514 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10515 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10518 This is a very special case that seems to be a mixture between
10519 CheckElementChange() and CheckTriggeredElementChange(): while
10520 the first one only affects single elements that are triggered
10521 directly, the second one affects multiple elements in the playfield
10522 that are triggered indirectly by another element. This is a third
10523 case: Changing the CE score always affects multiple identical CEs,
10524 so every affected CE must be checked, not only the single CE for
10525 which the CE score was changed in the first place (as every instance
10526 of that CE shares the same CE score, and therefore also can change)!
10528 SCAN_PLAYFIELD(xx, yy)
10530 if (Tile[xx][yy] == element)
10531 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10532 CE_SCORE_GETS_ZERO);
10540 case CA_SET_CE_ARTWORK:
10542 int artwork_element = action_arg_element;
10543 boolean reset_frame = FALSE;
10546 if (action_arg == CA_ARG_ELEMENT_RESET)
10547 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10550 if (ei->gfx_element != artwork_element)
10551 reset_frame = TRUE;
10553 ei->gfx_element = artwork_element;
10555 SCAN_PLAYFIELD(xx, yy)
10557 if (Tile[xx][yy] == element)
10561 ResetGfxAnimation(xx, yy);
10562 ResetRandomAnimationValue(xx, yy);
10565 TEST_DrawLevelField(xx, yy);
10572 // ---------- engine actions ---------------------------------------------
10574 case CA_SET_ENGINE_SCAN_MODE:
10576 InitPlayfieldScanMode(action_arg);
10586 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10588 int old_element = Tile[x][y];
10589 int new_element = GetElementFromGroupElement(element);
10590 int previous_move_direction = MovDir[x][y];
10591 int last_ce_value = CustomValue[x][y];
10592 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10593 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10594 boolean add_player_onto_element = (new_element_is_player &&
10595 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10596 IS_WALKABLE(old_element));
10598 if (!add_player_onto_element)
10600 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10601 RemoveMovingField(x, y);
10605 Tile[x][y] = new_element;
10607 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10608 MovDir[x][y] = previous_move_direction;
10610 if (element_info[new_element].use_last_ce_value)
10611 CustomValue[x][y] = last_ce_value;
10613 InitField_WithBug1(x, y, FALSE);
10615 new_element = Tile[x][y]; // element may have changed
10617 ResetGfxAnimation(x, y);
10618 ResetRandomAnimationValue(x, y);
10620 TEST_DrawLevelField(x, y);
10622 if (GFX_CRUMBLED(new_element))
10623 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10626 // check if element under the player changes from accessible to unaccessible
10627 // (needed for special case of dropping element which then changes)
10628 // (must be checked after creating new element for walkable group elements)
10629 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10630 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10637 // "ChangeCount" not set yet to allow "entered by player" change one time
10638 if (new_element_is_player)
10639 RelocatePlayer(x, y, new_element);
10642 ChangeCount[x][y]++; // count number of changes in the same frame
10644 TestIfBadThingTouchesPlayer(x, y);
10645 TestIfPlayerTouchesCustomElement(x, y);
10646 TestIfElementTouchesCustomElement(x, y);
10649 static void CreateField(int x, int y, int element)
10651 CreateFieldExt(x, y, element, FALSE);
10654 static void CreateElementFromChange(int x, int y, int element)
10656 element = GET_VALID_RUNTIME_ELEMENT(element);
10658 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10660 int old_element = Tile[x][y];
10662 // prevent changed element from moving in same engine frame
10663 // unless both old and new element can either fall or move
10664 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10665 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10669 CreateFieldExt(x, y, element, TRUE);
10672 static boolean ChangeElement(int x, int y, int element, int page)
10674 struct ElementInfo *ei = &element_info[element];
10675 struct ElementChangeInfo *change = &ei->change_page[page];
10676 int ce_value = CustomValue[x][y];
10677 int ce_score = ei->collect_score;
10678 int target_element;
10679 int old_element = Tile[x][y];
10681 // always use default change event to prevent running into a loop
10682 if (ChangeEvent[x][y] == -1)
10683 ChangeEvent[x][y] = CE_DELAY;
10685 if (ChangeEvent[x][y] == CE_DELAY)
10687 // reset actual trigger element, trigger player and action element
10688 change->actual_trigger_element = EL_EMPTY;
10689 change->actual_trigger_player = EL_EMPTY;
10690 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10691 change->actual_trigger_side = CH_SIDE_NONE;
10692 change->actual_trigger_ce_value = 0;
10693 change->actual_trigger_ce_score = 0;
10696 // do not change elements more than a specified maximum number of changes
10697 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10700 ChangeCount[x][y]++; // count number of changes in the same frame
10702 if (change->explode)
10709 if (change->use_target_content)
10711 boolean complete_replace = TRUE;
10712 boolean can_replace[3][3];
10715 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10718 boolean is_walkable;
10719 boolean is_diggable;
10720 boolean is_collectible;
10721 boolean is_removable;
10722 boolean is_destructible;
10723 int ex = x + xx - 1;
10724 int ey = y + yy - 1;
10725 int content_element = change->target_content.e[xx][yy];
10728 can_replace[xx][yy] = TRUE;
10730 if (ex == x && ey == y) // do not check changing element itself
10733 if (content_element == EL_EMPTY_SPACE)
10735 can_replace[xx][yy] = FALSE; // do not replace border with space
10740 if (!IN_LEV_FIELD(ex, ey))
10742 can_replace[xx][yy] = FALSE;
10743 complete_replace = FALSE;
10750 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10751 e = MovingOrBlocked2Element(ex, ey);
10753 is_empty = (IS_FREE(ex, ey) ||
10754 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10756 is_walkable = (is_empty || IS_WALKABLE(e));
10757 is_diggable = (is_empty || IS_DIGGABLE(e));
10758 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10759 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10760 is_removable = (is_diggable || is_collectible);
10762 can_replace[xx][yy] =
10763 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10764 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10765 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10766 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10767 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10768 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10769 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10771 if (!can_replace[xx][yy])
10772 complete_replace = FALSE;
10775 if (!change->only_if_complete || complete_replace)
10777 boolean something_has_changed = FALSE;
10779 if (change->only_if_complete && change->use_random_replace &&
10780 RND(100) < change->random_percentage)
10783 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10785 int ex = x + xx - 1;
10786 int ey = y + yy - 1;
10787 int content_element;
10789 if (can_replace[xx][yy] && (!change->use_random_replace ||
10790 RND(100) < change->random_percentage))
10792 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10793 RemoveMovingField(ex, ey);
10795 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10797 content_element = change->target_content.e[xx][yy];
10798 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10799 ce_value, ce_score);
10801 CreateElementFromChange(ex, ey, target_element);
10803 something_has_changed = TRUE;
10805 // for symmetry reasons, freeze newly created border elements
10806 if (ex != x || ey != y)
10807 Stop[ex][ey] = TRUE; // no more moving in this frame
10811 if (something_has_changed)
10813 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10814 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10820 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10821 ce_value, ce_score);
10823 if (element == EL_DIAGONAL_GROWING ||
10824 element == EL_DIAGONAL_SHRINKING)
10826 target_element = Store[x][y];
10828 Store[x][y] = EL_EMPTY;
10831 // special case: element changes to player (and may be kept if walkable)
10832 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10833 CreateElementFromChange(x, y, EL_EMPTY);
10835 CreateElementFromChange(x, y, target_element);
10837 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10838 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10841 // this uses direct change before indirect change
10842 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10847 static void HandleElementChange(int x, int y, int page)
10849 int element = MovingOrBlocked2Element(x, y);
10850 struct ElementInfo *ei = &element_info[element];
10851 struct ElementChangeInfo *change = &ei->change_page[page];
10852 boolean handle_action_before_change = FALSE;
10855 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10856 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10858 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10859 x, y, element, element_info[element].token_name);
10860 Debug("game:playing:HandleElementChange", "This should never happen!");
10864 // this can happen with classic bombs on walkable, changing elements
10865 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10870 if (ChangeDelay[x][y] == 0) // initialize element change
10872 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10874 if (change->can_change)
10876 // !!! not clear why graphic animation should be reset at all here !!!
10877 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10878 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10881 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10883 When using an animation frame delay of 1 (this only happens with
10884 "sp_zonk.moving.left/right" in the classic graphics), the default
10885 (non-moving) animation shows wrong animation frames (while the
10886 moving animation, like "sp_zonk.moving.left/right", is correct,
10887 so this graphical bug never shows up with the classic graphics).
10888 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10889 be drawn instead of the correct frames 0,1,2,3. This is caused by
10890 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10891 an element change: First when the change delay ("ChangeDelay[][]")
10892 counter has reached zero after decrementing, then a second time in
10893 the next frame (after "GfxFrame[][]" was already incremented) when
10894 "ChangeDelay[][]" is reset to the initial delay value again.
10896 This causes frame 0 to be drawn twice, while the last frame won't
10897 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10899 As some animations may already be cleverly designed around this bug
10900 (at least the "Snake Bite" snake tail animation does this), it cannot
10901 simply be fixed here without breaking such existing animations.
10902 Unfortunately, it cannot easily be detected if a graphics set was
10903 designed "before" or "after" the bug was fixed. As a workaround,
10904 a new graphics set option "game.graphics_engine_version" was added
10905 to be able to specify the game's major release version for which the
10906 graphics set was designed, which can then be used to decide if the
10907 bugfix should be used (version 4 and above) or not (version 3 or
10908 below, or if no version was specified at all, as with old sets).
10910 (The wrong/fixed animation frames can be tested with the test level set
10911 "test_gfxframe" and level "000", which contains a specially prepared
10912 custom element at level position (x/y) == (11/9) which uses the zonk
10913 animation mentioned above. Using "game.graphics_engine_version: 4"
10914 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10915 This can also be seen from the debug output for this test element.)
10918 // when a custom element is about to change (for example by change delay),
10919 // do not reset graphic animation when the custom element is moving
10920 if (game.graphics_engine_version < 4 &&
10923 ResetGfxAnimation(x, y);
10924 ResetRandomAnimationValue(x, y);
10927 if (change->pre_change_function)
10928 change->pre_change_function(x, y);
10932 ChangeDelay[x][y]--;
10934 if (ChangeDelay[x][y] != 0) // continue element change
10936 if (change->can_change)
10938 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
10940 if (IS_ANIMATED(graphic))
10941 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
10943 if (change->change_function)
10944 change->change_function(x, y);
10947 else // finish element change
10949 if (ChangePage[x][y] != -1) // remember page from delayed change
10951 page = ChangePage[x][y];
10952 ChangePage[x][y] = -1;
10954 change = &ei->change_page[page];
10957 if (IS_MOVING(x, y)) // never change a running system ;-)
10959 ChangeDelay[x][y] = 1; // try change after next move step
10960 ChangePage[x][y] = page; // remember page to use for change
10965 // special case: set new level random seed before changing element
10966 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
10967 handle_action_before_change = TRUE;
10969 if (change->has_action && handle_action_before_change)
10970 ExecuteCustomElementAction(x, y, element, page);
10972 if (change->can_change)
10974 if (ChangeElement(x, y, element, page))
10976 if (change->post_change_function)
10977 change->post_change_function(x, y);
10981 if (change->has_action && !handle_action_before_change)
10982 ExecuteCustomElementAction(x, y, element, page);
10986 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
10987 int trigger_element,
10989 int trigger_player,
10993 boolean change_done_any = FALSE;
10994 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
10997 if (!(trigger_events[trigger_element][trigger_event]))
11000 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11002 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11004 int element = EL_CUSTOM_START + i;
11005 boolean change_done = FALSE;
11008 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11009 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11012 for (p = 0; p < element_info[element].num_change_pages; p++)
11014 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11016 if (change->can_change_or_has_action &&
11017 change->has_event[trigger_event] &&
11018 change->trigger_side & trigger_side &&
11019 change->trigger_player & trigger_player &&
11020 change->trigger_page & trigger_page_bits &&
11021 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11023 change->actual_trigger_element = trigger_element;
11024 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11025 change->actual_trigger_player_bits = trigger_player;
11026 change->actual_trigger_side = trigger_side;
11027 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11028 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11030 if ((change->can_change && !change_done) || change->has_action)
11034 SCAN_PLAYFIELD(x, y)
11036 if (Tile[x][y] == element)
11038 if (change->can_change && !change_done)
11040 // if element already changed in this frame, not only prevent
11041 // another element change (checked in ChangeElement()), but
11042 // also prevent additional element actions for this element
11044 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11045 !level.use_action_after_change_bug)
11048 ChangeDelay[x][y] = 1;
11049 ChangeEvent[x][y] = trigger_event;
11051 HandleElementChange(x, y, p);
11053 else if (change->has_action)
11055 // if element already changed in this frame, not only prevent
11056 // another element change (checked in ChangeElement()), but
11057 // also prevent additional element actions for this element
11059 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11060 !level.use_action_after_change_bug)
11063 ExecuteCustomElementAction(x, y, element, p);
11064 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11069 if (change->can_change)
11071 change_done = TRUE;
11072 change_done_any = TRUE;
11079 RECURSION_LOOP_DETECTION_END();
11081 return change_done_any;
11084 static boolean CheckElementChangeExt(int x, int y,
11086 int trigger_element,
11088 int trigger_player,
11091 boolean change_done = FALSE;
11094 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11095 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11098 if (Tile[x][y] == EL_BLOCKED)
11100 Blocked2Moving(x, y, &x, &y);
11101 element = Tile[x][y];
11104 // check if element has already changed or is about to change after moving
11105 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11106 Tile[x][y] != element) ||
11108 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11109 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11110 ChangePage[x][y] != -1)))
11113 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11115 for (p = 0; p < element_info[element].num_change_pages; p++)
11117 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11119 /* check trigger element for all events where the element that is checked
11120 for changing interacts with a directly adjacent element -- this is
11121 different to element changes that affect other elements to change on the
11122 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11123 boolean check_trigger_element =
11124 (trigger_event == CE_TOUCHING_X ||
11125 trigger_event == CE_HITTING_X ||
11126 trigger_event == CE_HIT_BY_X ||
11127 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11129 if (change->can_change_or_has_action &&
11130 change->has_event[trigger_event] &&
11131 change->trigger_side & trigger_side &&
11132 change->trigger_player & trigger_player &&
11133 (!check_trigger_element ||
11134 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11136 change->actual_trigger_element = trigger_element;
11137 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11138 change->actual_trigger_player_bits = trigger_player;
11139 change->actual_trigger_side = trigger_side;
11140 change->actual_trigger_ce_value = CustomValue[x][y];
11141 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11143 // special case: trigger element not at (x,y) position for some events
11144 if (check_trigger_element)
11156 { 0, 0 }, { 0, 0 }, { 0, 0 },
11160 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11161 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11163 change->actual_trigger_ce_value = CustomValue[xx][yy];
11164 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11167 if (change->can_change && !change_done)
11169 ChangeDelay[x][y] = 1;
11170 ChangeEvent[x][y] = trigger_event;
11172 HandleElementChange(x, y, p);
11174 change_done = TRUE;
11176 else if (change->has_action)
11178 ExecuteCustomElementAction(x, y, element, p);
11179 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11184 RECURSION_LOOP_DETECTION_END();
11186 return change_done;
11189 static void PlayPlayerSound(struct PlayerInfo *player)
11191 int jx = player->jx, jy = player->jy;
11192 int sound_element = player->artwork_element;
11193 int last_action = player->last_action_waiting;
11194 int action = player->action_waiting;
11196 if (player->is_waiting)
11198 if (action != last_action)
11199 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11201 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11205 if (action != last_action)
11206 StopSound(element_info[sound_element].sound[last_action]);
11208 if (last_action == ACTION_SLEEPING)
11209 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11213 static void PlayAllPlayersSound(void)
11217 for (i = 0; i < MAX_PLAYERS; i++)
11218 if (stored_player[i].active)
11219 PlayPlayerSound(&stored_player[i]);
11222 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11224 boolean last_waiting = player->is_waiting;
11225 int move_dir = player->MovDir;
11227 player->dir_waiting = move_dir;
11228 player->last_action_waiting = player->action_waiting;
11232 if (!last_waiting) // not waiting -> waiting
11234 player->is_waiting = TRUE;
11236 player->frame_counter_bored =
11238 game.player_boring_delay_fixed +
11239 GetSimpleRandom(game.player_boring_delay_random);
11240 player->frame_counter_sleeping =
11242 game.player_sleeping_delay_fixed +
11243 GetSimpleRandom(game.player_sleeping_delay_random);
11245 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11248 if (game.player_sleeping_delay_fixed +
11249 game.player_sleeping_delay_random > 0 &&
11250 player->anim_delay_counter == 0 &&
11251 player->post_delay_counter == 0 &&
11252 FrameCounter >= player->frame_counter_sleeping)
11253 player->is_sleeping = TRUE;
11254 else if (game.player_boring_delay_fixed +
11255 game.player_boring_delay_random > 0 &&
11256 FrameCounter >= player->frame_counter_bored)
11257 player->is_bored = TRUE;
11259 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11260 player->is_bored ? ACTION_BORING :
11263 if (player->is_sleeping && player->use_murphy)
11265 // special case for sleeping Murphy when leaning against non-free tile
11267 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11268 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11269 !IS_MOVING(player->jx - 1, player->jy)))
11270 move_dir = MV_LEFT;
11271 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11272 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11273 !IS_MOVING(player->jx + 1, player->jy)))
11274 move_dir = MV_RIGHT;
11276 player->is_sleeping = FALSE;
11278 player->dir_waiting = move_dir;
11281 if (player->is_sleeping)
11283 if (player->num_special_action_sleeping > 0)
11285 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11287 int last_special_action = player->special_action_sleeping;
11288 int num_special_action = player->num_special_action_sleeping;
11289 int special_action =
11290 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11291 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11292 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11293 last_special_action + 1 : ACTION_SLEEPING);
11294 int special_graphic =
11295 el_act_dir2img(player->artwork_element, special_action, move_dir);
11297 player->anim_delay_counter =
11298 graphic_info[special_graphic].anim_delay_fixed +
11299 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11300 player->post_delay_counter =
11301 graphic_info[special_graphic].post_delay_fixed +
11302 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11304 player->special_action_sleeping = special_action;
11307 if (player->anim_delay_counter > 0)
11309 player->action_waiting = player->special_action_sleeping;
11310 player->anim_delay_counter--;
11312 else if (player->post_delay_counter > 0)
11314 player->post_delay_counter--;
11318 else if (player->is_bored)
11320 if (player->num_special_action_bored > 0)
11322 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11324 int special_action =
11325 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11326 int special_graphic =
11327 el_act_dir2img(player->artwork_element, special_action, move_dir);
11329 player->anim_delay_counter =
11330 graphic_info[special_graphic].anim_delay_fixed +
11331 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11332 player->post_delay_counter =
11333 graphic_info[special_graphic].post_delay_fixed +
11334 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11336 player->special_action_bored = special_action;
11339 if (player->anim_delay_counter > 0)
11341 player->action_waiting = player->special_action_bored;
11342 player->anim_delay_counter--;
11344 else if (player->post_delay_counter > 0)
11346 player->post_delay_counter--;
11351 else if (last_waiting) // waiting -> not waiting
11353 player->is_waiting = FALSE;
11354 player->is_bored = FALSE;
11355 player->is_sleeping = FALSE;
11357 player->frame_counter_bored = -1;
11358 player->frame_counter_sleeping = -1;
11360 player->anim_delay_counter = 0;
11361 player->post_delay_counter = 0;
11363 player->dir_waiting = player->MovDir;
11364 player->action_waiting = ACTION_DEFAULT;
11366 player->special_action_bored = ACTION_DEFAULT;
11367 player->special_action_sleeping = ACTION_DEFAULT;
11371 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11373 if ((!player->is_moving && player->was_moving) ||
11374 (player->MovPos == 0 && player->was_moving) ||
11375 (player->is_snapping && !player->was_snapping) ||
11376 (player->is_dropping && !player->was_dropping))
11378 if (!CheckSaveEngineSnapshotToList())
11381 player->was_moving = FALSE;
11382 player->was_snapping = TRUE;
11383 player->was_dropping = TRUE;
11387 if (player->is_moving)
11388 player->was_moving = TRUE;
11390 if (!player->is_snapping)
11391 player->was_snapping = FALSE;
11393 if (!player->is_dropping)
11394 player->was_dropping = FALSE;
11397 static struct MouseActionInfo mouse_action_last = { 0 };
11398 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11399 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11402 CheckSaveEngineSnapshotToList();
11404 mouse_action_last = mouse_action;
11407 static void CheckSingleStepMode(struct PlayerInfo *player)
11409 if (tape.single_step && tape.recording && !tape.pausing)
11411 // as it is called "single step mode", just return to pause mode when the
11412 // player stopped moving after one tile (or never starts moving at all)
11413 // (reverse logic needed here in case single step mode used in team mode)
11414 if (player->is_moving ||
11415 player->is_pushing ||
11416 player->is_dropping_pressed ||
11417 player->effective_mouse_action.button)
11418 game.enter_single_step_mode = FALSE;
11421 CheckSaveEngineSnapshot(player);
11424 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11426 int left = player_action & JOY_LEFT;
11427 int right = player_action & JOY_RIGHT;
11428 int up = player_action & JOY_UP;
11429 int down = player_action & JOY_DOWN;
11430 int button1 = player_action & JOY_BUTTON_1;
11431 int button2 = player_action & JOY_BUTTON_2;
11432 int dx = (left ? -1 : right ? 1 : 0);
11433 int dy = (up ? -1 : down ? 1 : 0);
11435 if (!player->active || tape.pausing)
11441 SnapField(player, dx, dy);
11445 DropElement(player);
11447 MovePlayer(player, dx, dy);
11450 CheckSingleStepMode(player);
11452 SetPlayerWaiting(player, FALSE);
11454 return player_action;
11458 // no actions for this player (no input at player's configured device)
11460 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11461 SnapField(player, 0, 0);
11462 CheckGravityMovementWhenNotMoving(player);
11464 if (player->MovPos == 0)
11465 SetPlayerWaiting(player, TRUE);
11467 if (player->MovPos == 0) // needed for tape.playing
11468 player->is_moving = FALSE;
11470 player->is_dropping = FALSE;
11471 player->is_dropping_pressed = FALSE;
11472 player->drop_pressed_delay = 0;
11474 CheckSingleStepMode(player);
11480 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11483 if (!tape.use_mouse_actions)
11486 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11487 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11488 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11491 static void SetTapeActionFromMouseAction(byte *tape_action,
11492 struct MouseActionInfo *mouse_action)
11494 if (!tape.use_mouse_actions)
11497 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11498 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11499 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11502 static void CheckLevelSolved(void)
11504 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11506 if (game_em.level_solved &&
11507 !game_em.game_over) // game won
11511 game_em.game_over = TRUE;
11513 game.all_players_gone = TRUE;
11516 if (game_em.game_over) // game lost
11517 game.all_players_gone = TRUE;
11519 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11521 if (game_sp.level_solved &&
11522 !game_sp.game_over) // game won
11526 game_sp.game_over = TRUE;
11528 game.all_players_gone = TRUE;
11531 if (game_sp.game_over) // game lost
11532 game.all_players_gone = TRUE;
11534 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11536 if (game_mm.level_solved &&
11537 !game_mm.game_over) // game won
11541 game_mm.game_over = TRUE;
11543 game.all_players_gone = TRUE;
11546 if (game_mm.game_over) // game lost
11547 game.all_players_gone = TRUE;
11551 static void CheckLevelTime(void)
11555 if (TimeFrames >= FRAMES_PER_SECOND)
11560 for (i = 0; i < MAX_PLAYERS; i++)
11562 struct PlayerInfo *player = &stored_player[i];
11564 if (SHIELD_ON(player))
11566 player->shield_normal_time_left--;
11568 if (player->shield_deadly_time_left > 0)
11569 player->shield_deadly_time_left--;
11573 if (!game.LevelSolved && !level.use_step_counter)
11581 if (TimeLeft <= 10 && setup.time_limit)
11582 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11584 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11585 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11587 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11589 if (!TimeLeft && setup.time_limit)
11591 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11592 game_em.lev->killed_out_of_time = TRUE;
11594 for (i = 0; i < MAX_PLAYERS; i++)
11595 KillPlayer(&stored_player[i]);
11598 else if (game.no_time_limit && !game.all_players_gone)
11600 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11603 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11606 if (tape.recording || tape.playing)
11607 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11610 if (tape.recording || tape.playing)
11611 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11613 UpdateAndDisplayGameControlValues();
11616 void AdvanceFrameAndPlayerCounters(int player_nr)
11620 // advance frame counters (global frame counter and time frame counter)
11624 // advance player counters (counters for move delay, move animation etc.)
11625 for (i = 0; i < MAX_PLAYERS; i++)
11627 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11628 int move_delay_value = stored_player[i].move_delay_value;
11629 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11631 if (!advance_player_counters) // not all players may be affected
11634 if (move_frames == 0) // less than one move per game frame
11636 int stepsize = TILEX / move_delay_value;
11637 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11638 int count = (stored_player[i].is_moving ?
11639 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11641 if (count % delay == 0)
11645 stored_player[i].Frame += move_frames;
11647 if (stored_player[i].MovPos != 0)
11648 stored_player[i].StepFrame += move_frames;
11650 if (stored_player[i].move_delay > 0)
11651 stored_player[i].move_delay--;
11653 // due to bugs in previous versions, counter must count up, not down
11654 if (stored_player[i].push_delay != -1)
11655 stored_player[i].push_delay++;
11657 if (stored_player[i].drop_delay > 0)
11658 stored_player[i].drop_delay--;
11660 if (stored_player[i].is_dropping_pressed)
11661 stored_player[i].drop_pressed_delay++;
11665 void StartGameActions(boolean init_network_game, boolean record_tape,
11668 unsigned int new_random_seed = InitRND(random_seed);
11671 TapeStartRecording(new_random_seed);
11673 if (init_network_game)
11675 SendToServer_LevelFile();
11676 SendToServer_StartPlaying();
11684 static void GameActionsExt(void)
11687 static unsigned int game_frame_delay = 0;
11689 unsigned int game_frame_delay_value;
11690 byte *recorded_player_action;
11691 byte summarized_player_action = 0;
11692 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11695 // detect endless loops, caused by custom element programming
11696 if (recursion_loop_detected && recursion_loop_depth == 0)
11698 char *message = getStringCat3("Internal Error! Element ",
11699 EL_NAME(recursion_loop_element),
11700 " caused endless loop! Quit the game?");
11702 Warn("element '%s' caused endless loop in game engine",
11703 EL_NAME(recursion_loop_element));
11705 RequestQuitGameExt(FALSE, level_editor_test_game, message);
11707 recursion_loop_detected = FALSE; // if game should be continued
11714 if (game.restart_level)
11715 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11717 CheckLevelSolved();
11719 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11722 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11725 if (game_status != GAME_MODE_PLAYING) // status might have changed
11728 game_frame_delay_value =
11729 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11731 if (tape.playing && tape.warp_forward && !tape.pausing)
11732 game_frame_delay_value = 0;
11734 SetVideoFrameDelay(game_frame_delay_value);
11736 // (de)activate virtual buttons depending on current game status
11737 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11739 if (game.all_players_gone) // if no players there to be controlled anymore
11740 SetOverlayActive(FALSE);
11741 else if (!tape.playing) // if game continues after tape stopped playing
11742 SetOverlayActive(TRUE);
11747 // ---------- main game synchronization point ----------
11749 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11751 Debug("game:playing:skip", "skip == %d", skip);
11754 // ---------- main game synchronization point ----------
11756 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11760 if (network_playing && !network_player_action_received)
11762 // try to get network player actions in time
11764 // last chance to get network player actions without main loop delay
11765 HandleNetworking();
11767 // game was quit by network peer
11768 if (game_status != GAME_MODE_PLAYING)
11771 // check if network player actions still missing and game still running
11772 if (!network_player_action_received && !checkGameEnded())
11773 return; // failed to get network player actions in time
11775 // do not yet reset "network_player_action_received" (for tape.pausing)
11781 // at this point we know that we really continue executing the game
11783 network_player_action_received = FALSE;
11785 // when playing tape, read previously recorded player input from tape data
11786 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11788 local_player->effective_mouse_action = local_player->mouse_action;
11790 if (recorded_player_action != NULL)
11791 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11792 recorded_player_action);
11794 // TapePlayAction() may return NULL when toggling to "pause before death"
11798 if (tape.set_centered_player)
11800 game.centered_player_nr_next = tape.centered_player_nr_next;
11801 game.set_centered_player = TRUE;
11804 for (i = 0; i < MAX_PLAYERS; i++)
11806 summarized_player_action |= stored_player[i].action;
11808 if (!network_playing && (game.team_mode || tape.playing))
11809 stored_player[i].effective_action = stored_player[i].action;
11812 if (network_playing && !checkGameEnded())
11813 SendToServer_MovePlayer(summarized_player_action);
11815 // summarize all actions at local players mapped input device position
11816 // (this allows using different input devices in single player mode)
11817 if (!network.enabled && !game.team_mode)
11818 stored_player[map_player_action[local_player->index_nr]].effective_action =
11819 summarized_player_action;
11821 // summarize all actions at centered player in local team mode
11822 if (tape.recording &&
11823 setup.team_mode && !network.enabled &&
11824 setup.input_on_focus &&
11825 game.centered_player_nr != -1)
11827 for (i = 0; i < MAX_PLAYERS; i++)
11828 stored_player[map_player_action[i]].effective_action =
11829 (i == game.centered_player_nr ? summarized_player_action : 0);
11832 if (recorded_player_action != NULL)
11833 for (i = 0; i < MAX_PLAYERS; i++)
11834 stored_player[i].effective_action = recorded_player_action[i];
11836 for (i = 0; i < MAX_PLAYERS; i++)
11838 tape_action[i] = stored_player[i].effective_action;
11840 /* (this may happen in the RND game engine if a player was not present on
11841 the playfield on level start, but appeared later from a custom element */
11842 if (setup.team_mode &&
11845 !tape.player_participates[i])
11846 tape.player_participates[i] = TRUE;
11849 SetTapeActionFromMouseAction(tape_action,
11850 &local_player->effective_mouse_action);
11852 // only record actions from input devices, but not programmed actions
11853 if (tape.recording)
11854 TapeRecordAction(tape_action);
11856 // remember if game was played (especially after tape stopped playing)
11857 if (!tape.playing && summarized_player_action)
11858 game.GamePlayed = TRUE;
11860 #if USE_NEW_PLAYER_ASSIGNMENTS
11861 // !!! also map player actions in single player mode !!!
11862 // if (game.team_mode)
11865 byte mapped_action[MAX_PLAYERS];
11867 #if DEBUG_PLAYER_ACTIONS
11868 for (i = 0; i < MAX_PLAYERS; i++)
11869 DebugContinued("", "%d, ", stored_player[i].effective_action);
11872 for (i = 0; i < MAX_PLAYERS; i++)
11873 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11875 for (i = 0; i < MAX_PLAYERS; i++)
11876 stored_player[i].effective_action = mapped_action[i];
11878 #if DEBUG_PLAYER_ACTIONS
11879 DebugContinued("", "=> ");
11880 for (i = 0; i < MAX_PLAYERS; i++)
11881 DebugContinued("", "%d, ", stored_player[i].effective_action);
11882 DebugContinued("game:playing:player", "\n");
11885 #if DEBUG_PLAYER_ACTIONS
11888 for (i = 0; i < MAX_PLAYERS; i++)
11889 DebugContinued("", "%d, ", stored_player[i].effective_action);
11890 DebugContinued("game:playing:player", "\n");
11895 for (i = 0; i < MAX_PLAYERS; i++)
11897 // allow engine snapshot in case of changed movement attempt
11898 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11899 (stored_player[i].effective_action & KEY_MOTION))
11900 game.snapshot.changed_action = TRUE;
11902 // allow engine snapshot in case of snapping/dropping attempt
11903 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
11904 (stored_player[i].effective_action & KEY_BUTTON) != 0)
11905 game.snapshot.changed_action = TRUE;
11907 game.snapshot.last_action[i] = stored_player[i].effective_action;
11910 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11912 GameActions_EM_Main();
11914 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11916 GameActions_SP_Main();
11918 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11920 GameActions_MM_Main();
11924 GameActions_RND_Main();
11927 BlitScreenToBitmap(backbuffer);
11929 CheckLevelSolved();
11932 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
11934 if (global.show_frames_per_second)
11936 static unsigned int fps_counter = 0;
11937 static int fps_frames = 0;
11938 unsigned int fps_delay_ms = Counter() - fps_counter;
11942 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
11944 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
11947 fps_counter = Counter();
11949 // always draw FPS to screen after FPS value was updated
11950 redraw_mask |= REDRAW_FPS;
11953 // only draw FPS if no screen areas are deactivated (invisible warp mode)
11954 if (GetDrawDeactivationMask() == REDRAW_NONE)
11955 redraw_mask |= REDRAW_FPS;
11959 static void GameActions_CheckSaveEngineSnapshot(void)
11961 if (!game.snapshot.save_snapshot)
11964 // clear flag for saving snapshot _before_ saving snapshot
11965 game.snapshot.save_snapshot = FALSE;
11967 SaveEngineSnapshotToList();
11970 void GameActions(void)
11974 GameActions_CheckSaveEngineSnapshot();
11977 void GameActions_EM_Main(void)
11979 byte effective_action[MAX_PLAYERS];
11980 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11983 for (i = 0; i < MAX_PLAYERS; i++)
11984 effective_action[i] = stored_player[i].effective_action;
11986 GameActions_EM(effective_action, warp_mode);
11989 void GameActions_SP_Main(void)
11991 byte effective_action[MAX_PLAYERS];
11992 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
11995 for (i = 0; i < MAX_PLAYERS; i++)
11996 effective_action[i] = stored_player[i].effective_action;
11998 GameActions_SP(effective_action, warp_mode);
12000 for (i = 0; i < MAX_PLAYERS; i++)
12002 if (stored_player[i].force_dropping)
12003 stored_player[i].action |= KEY_BUTTON_DROP;
12005 stored_player[i].force_dropping = FALSE;
12009 void GameActions_MM_Main(void)
12011 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12013 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12016 void GameActions_RND_Main(void)
12021 void GameActions_RND(void)
12023 static struct MouseActionInfo mouse_action_last = { 0 };
12024 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12025 int magic_wall_x = 0, magic_wall_y = 0;
12026 int i, x, y, element, graphic, last_gfx_frame;
12028 InitPlayfieldScanModeVars();
12030 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12032 SCAN_PLAYFIELD(x, y)
12034 ChangeCount[x][y] = 0;
12035 ChangeEvent[x][y] = -1;
12039 if (game.set_centered_player)
12041 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12043 // switching to "all players" only possible if all players fit to screen
12044 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12046 game.centered_player_nr_next = game.centered_player_nr;
12047 game.set_centered_player = FALSE;
12050 // do not switch focus to non-existing (or non-active) player
12051 if (game.centered_player_nr_next >= 0 &&
12052 !stored_player[game.centered_player_nr_next].active)
12054 game.centered_player_nr_next = game.centered_player_nr;
12055 game.set_centered_player = FALSE;
12059 if (game.set_centered_player &&
12060 ScreenMovPos == 0) // screen currently aligned at tile position
12064 if (game.centered_player_nr_next == -1)
12066 setScreenCenteredToAllPlayers(&sx, &sy);
12070 sx = stored_player[game.centered_player_nr_next].jx;
12071 sy = stored_player[game.centered_player_nr_next].jy;
12074 game.centered_player_nr = game.centered_player_nr_next;
12075 game.set_centered_player = FALSE;
12077 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12078 DrawGameDoorValues();
12081 // check single step mode (set flag and clear again if any player is active)
12082 game.enter_single_step_mode =
12083 (tape.single_step && tape.recording && !tape.pausing);
12085 for (i = 0; i < MAX_PLAYERS; i++)
12087 int actual_player_action = stored_player[i].effective_action;
12090 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12091 - rnd_equinox_tetrachloride 048
12092 - rnd_equinox_tetrachloride_ii 096
12093 - rnd_emanuel_schmieg 002
12094 - doctor_sloan_ww 001, 020
12096 if (stored_player[i].MovPos == 0)
12097 CheckGravityMovement(&stored_player[i]);
12100 // overwrite programmed action with tape action
12101 if (stored_player[i].programmed_action)
12102 actual_player_action = stored_player[i].programmed_action;
12104 PlayerActions(&stored_player[i], actual_player_action);
12106 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12109 // single step pause mode may already have been toggled by "ScrollPlayer()"
12110 if (game.enter_single_step_mode && !tape.pausing)
12111 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12113 ScrollScreen(NULL, SCROLL_GO_ON);
12115 /* for backwards compatibility, the following code emulates a fixed bug that
12116 occured when pushing elements (causing elements that just made their last
12117 pushing step to already (if possible) make their first falling step in the
12118 same game frame, which is bad); this code is also needed to use the famous
12119 "spring push bug" which is used in older levels and might be wanted to be
12120 used also in newer levels, but in this case the buggy pushing code is only
12121 affecting the "spring" element and no other elements */
12123 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12125 for (i = 0; i < MAX_PLAYERS; i++)
12127 struct PlayerInfo *player = &stored_player[i];
12128 int x = player->jx;
12129 int y = player->jy;
12131 if (player->active && player->is_pushing && player->is_moving &&
12133 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12134 Tile[x][y] == EL_SPRING))
12136 ContinueMoving(x, y);
12138 // continue moving after pushing (this is actually a bug)
12139 if (!IS_MOVING(x, y))
12140 Stop[x][y] = FALSE;
12145 SCAN_PLAYFIELD(x, y)
12147 Last[x][y] = Tile[x][y];
12149 ChangeCount[x][y] = 0;
12150 ChangeEvent[x][y] = -1;
12152 // this must be handled before main playfield loop
12153 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12156 if (MovDelay[x][y] <= 0)
12160 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12163 if (MovDelay[x][y] <= 0)
12165 int element = Store[x][y];
12166 int move_direction = MovDir[x][y];
12167 int player_index_bit = Store2[x][y];
12173 TEST_DrawLevelField(x, y);
12175 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12177 if (IS_ENVELOPE(element))
12178 local_player->show_envelope = element;
12183 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12185 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12187 Debug("game:playing:GameActions_RND", "This should never happen!");
12189 ChangePage[x][y] = -1;
12193 Stop[x][y] = FALSE;
12194 if (WasJustMoving[x][y] > 0)
12195 WasJustMoving[x][y]--;
12196 if (WasJustFalling[x][y] > 0)
12197 WasJustFalling[x][y]--;
12198 if (CheckCollision[x][y] > 0)
12199 CheckCollision[x][y]--;
12200 if (CheckImpact[x][y] > 0)
12201 CheckImpact[x][y]--;
12205 /* reset finished pushing action (not done in ContinueMoving() to allow
12206 continuous pushing animation for elements with zero push delay) */
12207 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12209 ResetGfxAnimation(x, y);
12210 TEST_DrawLevelField(x, y);
12214 if (IS_BLOCKED(x, y))
12218 Blocked2Moving(x, y, &oldx, &oldy);
12219 if (!IS_MOVING(oldx, oldy))
12221 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12222 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12223 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12224 Debug("game:playing:GameActions_RND", "This should never happen!");
12230 if (mouse_action.button)
12232 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12233 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12235 x = mouse_action.lx;
12236 y = mouse_action.ly;
12237 element = Tile[x][y];
12241 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12242 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12246 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12247 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12251 SCAN_PLAYFIELD(x, y)
12253 element = Tile[x][y];
12254 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12255 last_gfx_frame = GfxFrame[x][y];
12257 ResetGfxFrame(x, y);
12259 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12260 DrawLevelGraphicAnimation(x, y, graphic);
12262 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12263 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12264 ResetRandomAnimationValue(x, y);
12266 SetRandomAnimationValue(x, y);
12268 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12270 if (IS_INACTIVE(element))
12272 if (IS_ANIMATED(graphic))
12273 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12278 // this may take place after moving, so 'element' may have changed
12279 if (IS_CHANGING(x, y) &&
12280 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12282 int page = element_info[element].event_page_nr[CE_DELAY];
12284 HandleElementChange(x, y, page);
12286 element = Tile[x][y];
12287 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12290 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12294 element = Tile[x][y];
12295 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12297 if (IS_ANIMATED(graphic) &&
12298 !IS_MOVING(x, y) &&
12300 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12302 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12303 TEST_DrawTwinkleOnField(x, y);
12305 else if (element == EL_ACID)
12308 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12310 else if ((element == EL_EXIT_OPEN ||
12311 element == EL_EM_EXIT_OPEN ||
12312 element == EL_SP_EXIT_OPEN ||
12313 element == EL_STEEL_EXIT_OPEN ||
12314 element == EL_EM_STEEL_EXIT_OPEN ||
12315 element == EL_SP_TERMINAL ||
12316 element == EL_SP_TERMINAL_ACTIVE ||
12317 element == EL_EXTRA_TIME ||
12318 element == EL_SHIELD_NORMAL ||
12319 element == EL_SHIELD_DEADLY) &&
12320 IS_ANIMATED(graphic))
12321 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12322 else if (IS_MOVING(x, y))
12323 ContinueMoving(x, y);
12324 else if (IS_ACTIVE_BOMB(element))
12325 CheckDynamite(x, y);
12326 else if (element == EL_AMOEBA_GROWING)
12327 AmoebaGrowing(x, y);
12328 else if (element == EL_AMOEBA_SHRINKING)
12329 AmoebaShrinking(x, y);
12331 #if !USE_NEW_AMOEBA_CODE
12332 else if (IS_AMOEBALIVE(element))
12333 AmoebaReproduce(x, y);
12336 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12338 else if (element == EL_EXIT_CLOSED)
12340 else if (element == EL_EM_EXIT_CLOSED)
12342 else if (element == EL_STEEL_EXIT_CLOSED)
12343 CheckExitSteel(x, y);
12344 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12345 CheckExitSteelEM(x, y);
12346 else if (element == EL_SP_EXIT_CLOSED)
12348 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12349 element == EL_EXPANDABLE_STEELWALL_GROWING)
12350 MauerWaechst(x, y);
12351 else if (element == EL_EXPANDABLE_WALL ||
12352 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12353 element == EL_EXPANDABLE_WALL_VERTICAL ||
12354 element == EL_EXPANDABLE_WALL_ANY ||
12355 element == EL_BD_EXPANDABLE_WALL)
12356 MauerAbleger(x, y);
12357 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12358 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12359 element == EL_EXPANDABLE_STEELWALL_ANY)
12360 MauerAblegerStahl(x, y);
12361 else if (element == EL_FLAMES)
12362 CheckForDragon(x, y);
12363 else if (element == EL_EXPLOSION)
12364 ; // drawing of correct explosion animation is handled separately
12365 else if (element == EL_ELEMENT_SNAPPING ||
12366 element == EL_DIAGONAL_SHRINKING ||
12367 element == EL_DIAGONAL_GROWING)
12369 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12371 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12373 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12374 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12376 if (IS_BELT_ACTIVE(element))
12377 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12379 if (game.magic_wall_active)
12381 int jx = local_player->jx, jy = local_player->jy;
12383 // play the element sound at the position nearest to the player
12384 if ((element == EL_MAGIC_WALL_FULL ||
12385 element == EL_MAGIC_WALL_ACTIVE ||
12386 element == EL_MAGIC_WALL_EMPTYING ||
12387 element == EL_BD_MAGIC_WALL_FULL ||
12388 element == EL_BD_MAGIC_WALL_ACTIVE ||
12389 element == EL_BD_MAGIC_WALL_EMPTYING ||
12390 element == EL_DC_MAGIC_WALL_FULL ||
12391 element == EL_DC_MAGIC_WALL_ACTIVE ||
12392 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12393 ABS(x - jx) + ABS(y - jy) <
12394 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12402 #if USE_NEW_AMOEBA_CODE
12403 // new experimental amoeba growth stuff
12404 if (!(FrameCounter % 8))
12406 static unsigned int random = 1684108901;
12408 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12410 x = RND(lev_fieldx);
12411 y = RND(lev_fieldy);
12412 element = Tile[x][y];
12414 if (!IS_PLAYER(x,y) &&
12415 (element == EL_EMPTY ||
12416 CAN_GROW_INTO(element) ||
12417 element == EL_QUICKSAND_EMPTY ||
12418 element == EL_QUICKSAND_FAST_EMPTY ||
12419 element == EL_ACID_SPLASH_LEFT ||
12420 element == EL_ACID_SPLASH_RIGHT))
12422 if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) ||
12423 (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) ||
12424 (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) ||
12425 (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET))
12426 Tile[x][y] = EL_AMOEBA_DROP;
12429 random = random * 129 + 1;
12434 game.explosions_delayed = FALSE;
12436 SCAN_PLAYFIELD(x, y)
12438 element = Tile[x][y];
12440 if (ExplodeField[x][y])
12441 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12442 else if (element == EL_EXPLOSION)
12443 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12445 ExplodeField[x][y] = EX_TYPE_NONE;
12448 game.explosions_delayed = TRUE;
12450 if (game.magic_wall_active)
12452 if (!(game.magic_wall_time_left % 4))
12454 int element = Tile[magic_wall_x][magic_wall_y];
12456 if (element == EL_BD_MAGIC_WALL_FULL ||
12457 element == EL_BD_MAGIC_WALL_ACTIVE ||
12458 element == EL_BD_MAGIC_WALL_EMPTYING)
12459 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12460 else if (element == EL_DC_MAGIC_WALL_FULL ||
12461 element == EL_DC_MAGIC_WALL_ACTIVE ||
12462 element == EL_DC_MAGIC_WALL_EMPTYING)
12463 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12465 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12468 if (game.magic_wall_time_left > 0)
12470 game.magic_wall_time_left--;
12472 if (!game.magic_wall_time_left)
12474 SCAN_PLAYFIELD(x, y)
12476 element = Tile[x][y];
12478 if (element == EL_MAGIC_WALL_ACTIVE ||
12479 element == EL_MAGIC_WALL_FULL)
12481 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12482 TEST_DrawLevelField(x, y);
12484 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12485 element == EL_BD_MAGIC_WALL_FULL)
12487 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12488 TEST_DrawLevelField(x, y);
12490 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12491 element == EL_DC_MAGIC_WALL_FULL)
12493 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12494 TEST_DrawLevelField(x, y);
12498 game.magic_wall_active = FALSE;
12503 if (game.light_time_left > 0)
12505 game.light_time_left--;
12507 if (game.light_time_left == 0)
12508 RedrawAllLightSwitchesAndInvisibleElements();
12511 if (game.timegate_time_left > 0)
12513 game.timegate_time_left--;
12515 if (game.timegate_time_left == 0)
12516 CloseAllOpenTimegates();
12519 if (game.lenses_time_left > 0)
12521 game.lenses_time_left--;
12523 if (game.lenses_time_left == 0)
12524 RedrawAllInvisibleElementsForLenses();
12527 if (game.magnify_time_left > 0)
12529 game.magnify_time_left--;
12531 if (game.magnify_time_left == 0)
12532 RedrawAllInvisibleElementsForMagnifier();
12535 for (i = 0; i < MAX_PLAYERS; i++)
12537 struct PlayerInfo *player = &stored_player[i];
12539 if (SHIELD_ON(player))
12541 if (player->shield_deadly_time_left)
12542 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12543 else if (player->shield_normal_time_left)
12544 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12548 #if USE_DELAYED_GFX_REDRAW
12549 SCAN_PLAYFIELD(x, y)
12551 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12553 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12554 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12556 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12557 DrawLevelField(x, y);
12559 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12560 DrawLevelFieldCrumbled(x, y);
12562 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12563 DrawLevelFieldCrumbledNeighbours(x, y);
12565 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12566 DrawTwinkleOnField(x, y);
12569 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12574 PlayAllPlayersSound();
12576 for (i = 0; i < MAX_PLAYERS; i++)
12578 struct PlayerInfo *player = &stored_player[i];
12580 if (player->show_envelope != 0 && (!player->active ||
12581 player->MovPos == 0))
12583 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12585 player->show_envelope = 0;
12589 // use random number generator in every frame to make it less predictable
12590 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12593 mouse_action_last = mouse_action;
12596 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12598 int min_x = x, min_y = y, max_x = x, max_y = y;
12599 int scr_fieldx = getScreenFieldSizeX();
12600 int scr_fieldy = getScreenFieldSizeY();
12603 for (i = 0; i < MAX_PLAYERS; i++)
12605 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12607 if (!stored_player[i].active || &stored_player[i] == player)
12610 min_x = MIN(min_x, jx);
12611 min_y = MIN(min_y, jy);
12612 max_x = MAX(max_x, jx);
12613 max_y = MAX(max_y, jy);
12616 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12619 static boolean AllPlayersInVisibleScreen(void)
12623 for (i = 0; i < MAX_PLAYERS; i++)
12625 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12627 if (!stored_player[i].active)
12630 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12637 void ScrollLevel(int dx, int dy)
12639 int scroll_offset = 2 * TILEX_VAR;
12642 BlitBitmap(drawto_field, drawto_field,
12643 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12644 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12645 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12646 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12647 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12648 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12652 x = (dx == 1 ? BX1 : BX2);
12653 for (y = BY1; y <= BY2; y++)
12654 DrawScreenField(x, y);
12659 y = (dy == 1 ? BY1 : BY2);
12660 for (x = BX1; x <= BX2; x++)
12661 DrawScreenField(x, y);
12664 redraw_mask |= REDRAW_FIELD;
12667 static boolean canFallDown(struct PlayerInfo *player)
12669 int jx = player->jx, jy = player->jy;
12671 return (IN_LEV_FIELD(jx, jy + 1) &&
12672 (IS_FREE(jx, jy + 1) ||
12673 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12674 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12675 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12678 static boolean canPassField(int x, int y, int move_dir)
12680 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12681 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12682 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12683 int nextx = x + dx;
12684 int nexty = y + dy;
12685 int element = Tile[x][y];
12687 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12688 !CAN_MOVE(element) &&
12689 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12690 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12691 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12694 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12696 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12697 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12698 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12702 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12703 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12704 (IS_DIGGABLE(Tile[newx][newy]) ||
12705 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12706 canPassField(newx, newy, move_dir)));
12709 static void CheckGravityMovement(struct PlayerInfo *player)
12711 if (player->gravity && !player->programmed_action)
12713 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12714 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12715 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12716 int jx = player->jx, jy = player->jy;
12717 boolean player_is_moving_to_valid_field =
12718 (!player_is_snapping &&
12719 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12720 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12721 boolean player_can_fall_down = canFallDown(player);
12723 if (player_can_fall_down &&
12724 !player_is_moving_to_valid_field)
12725 player->programmed_action = MV_DOWN;
12729 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12731 return CheckGravityMovement(player);
12733 if (player->gravity && !player->programmed_action)
12735 int jx = player->jx, jy = player->jy;
12736 boolean field_under_player_is_free =
12737 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12738 boolean player_is_standing_on_valid_field =
12739 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12740 (IS_WALKABLE(Tile[jx][jy]) &&
12741 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12743 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12744 player->programmed_action = MV_DOWN;
12749 MovePlayerOneStep()
12750 -----------------------------------------------------------------------------
12751 dx, dy: direction (non-diagonal) to try to move the player to
12752 real_dx, real_dy: direction as read from input device (can be diagonal)
12755 boolean MovePlayerOneStep(struct PlayerInfo *player,
12756 int dx, int dy, int real_dx, int real_dy)
12758 int jx = player->jx, jy = player->jy;
12759 int new_jx = jx + dx, new_jy = jy + dy;
12761 boolean player_can_move = !player->cannot_move;
12763 if (!player->active || (!dx && !dy))
12764 return MP_NO_ACTION;
12766 player->MovDir = (dx < 0 ? MV_LEFT :
12767 dx > 0 ? MV_RIGHT :
12769 dy > 0 ? MV_DOWN : MV_NONE);
12771 if (!IN_LEV_FIELD(new_jx, new_jy))
12772 return MP_NO_ACTION;
12774 if (!player_can_move)
12776 if (player->MovPos == 0)
12778 player->is_moving = FALSE;
12779 player->is_digging = FALSE;
12780 player->is_collecting = FALSE;
12781 player->is_snapping = FALSE;
12782 player->is_pushing = FALSE;
12786 if (!network.enabled && game.centered_player_nr == -1 &&
12787 !AllPlayersInSight(player, new_jx, new_jy))
12788 return MP_NO_ACTION;
12790 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12791 if (can_move != MP_MOVING)
12794 // check if DigField() has caused relocation of the player
12795 if (player->jx != jx || player->jy != jy)
12796 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12798 StorePlayer[jx][jy] = 0;
12799 player->last_jx = jx;
12800 player->last_jy = jy;
12801 player->jx = new_jx;
12802 player->jy = new_jy;
12803 StorePlayer[new_jx][new_jy] = player->element_nr;
12805 if (player->move_delay_value_next != -1)
12807 player->move_delay_value = player->move_delay_value_next;
12808 player->move_delay_value_next = -1;
12812 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12814 player->step_counter++;
12816 PlayerVisit[jx][jy] = FrameCounter;
12818 player->is_moving = TRUE;
12821 // should better be called in MovePlayer(), but this breaks some tapes
12822 ScrollPlayer(player, SCROLL_INIT);
12828 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12830 int jx = player->jx, jy = player->jy;
12831 int old_jx = jx, old_jy = jy;
12832 int moved = MP_NO_ACTION;
12834 if (!player->active)
12839 if (player->MovPos == 0)
12841 player->is_moving = FALSE;
12842 player->is_digging = FALSE;
12843 player->is_collecting = FALSE;
12844 player->is_snapping = FALSE;
12845 player->is_pushing = FALSE;
12851 if (player->move_delay > 0)
12854 player->move_delay = -1; // set to "uninitialized" value
12856 // store if player is automatically moved to next field
12857 player->is_auto_moving = (player->programmed_action != MV_NONE);
12859 // remove the last programmed player action
12860 player->programmed_action = 0;
12862 if (player->MovPos)
12864 // should only happen if pre-1.2 tape recordings are played
12865 // this is only for backward compatibility
12867 int original_move_delay_value = player->move_delay_value;
12870 Debug("game:playing:MovePlayer",
12871 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12875 // scroll remaining steps with finest movement resolution
12876 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12878 while (player->MovPos)
12880 ScrollPlayer(player, SCROLL_GO_ON);
12881 ScrollScreen(NULL, SCROLL_GO_ON);
12883 AdvanceFrameAndPlayerCounters(player->index_nr);
12886 BackToFront_WithFrameDelay(0);
12889 player->move_delay_value = original_move_delay_value;
12892 player->is_active = FALSE;
12894 if (player->last_move_dir & MV_HORIZONTAL)
12896 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
12897 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
12901 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
12902 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
12905 if (!moved && !player->is_active)
12907 player->is_moving = FALSE;
12908 player->is_digging = FALSE;
12909 player->is_collecting = FALSE;
12910 player->is_snapping = FALSE;
12911 player->is_pushing = FALSE;
12917 if (moved & MP_MOVING && !ScreenMovPos &&
12918 (player->index_nr == game.centered_player_nr ||
12919 game.centered_player_nr == -1))
12921 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
12923 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12925 // actual player has left the screen -- scroll in that direction
12926 if (jx != old_jx) // player has moved horizontally
12927 scroll_x += (jx - old_jx);
12928 else // player has moved vertically
12929 scroll_y += (jy - old_jy);
12933 int offset_raw = game.scroll_delay_value;
12935 if (jx != old_jx) // player has moved horizontally
12937 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
12938 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
12939 int new_scroll_x = jx - MIDPOSX + offset_x;
12941 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
12942 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
12943 scroll_x = new_scroll_x;
12945 // don't scroll over playfield boundaries
12946 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
12948 // don't scroll more than one field at a time
12949 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
12951 // don't scroll against the player's moving direction
12952 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
12953 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
12954 scroll_x = old_scroll_x;
12956 else // player has moved vertically
12958 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
12959 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
12960 int new_scroll_y = jy - MIDPOSY + offset_y;
12962 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
12963 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
12964 scroll_y = new_scroll_y;
12966 // don't scroll over playfield boundaries
12967 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
12969 // don't scroll more than one field at a time
12970 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
12972 // don't scroll against the player's moving direction
12973 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
12974 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
12975 scroll_y = old_scroll_y;
12979 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
12981 if (!network.enabled && game.centered_player_nr == -1 &&
12982 !AllPlayersInVisibleScreen())
12984 scroll_x = old_scroll_x;
12985 scroll_y = old_scroll_y;
12989 ScrollScreen(player, SCROLL_INIT);
12990 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
12995 player->StepFrame = 0;
12997 if (moved & MP_MOVING)
12999 if (old_jx != jx && old_jy == jy)
13000 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13001 else if (old_jx == jx && old_jy != jy)
13002 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13004 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13006 player->last_move_dir = player->MovDir;
13007 player->is_moving = TRUE;
13008 player->is_snapping = FALSE;
13009 player->is_switching = FALSE;
13010 player->is_dropping = FALSE;
13011 player->is_dropping_pressed = FALSE;
13012 player->drop_pressed_delay = 0;
13015 // should better be called here than above, but this breaks some tapes
13016 ScrollPlayer(player, SCROLL_INIT);
13021 CheckGravityMovementWhenNotMoving(player);
13023 player->is_moving = FALSE;
13025 /* at this point, the player is allowed to move, but cannot move right now
13026 (e.g. because of something blocking the way) -- ensure that the player
13027 is also allowed to move in the next frame (in old versions before 3.1.1,
13028 the player was forced to wait again for eight frames before next try) */
13030 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13031 player->move_delay = 0; // allow direct movement in the next frame
13034 if (player->move_delay == -1) // not yet initialized by DigField()
13035 player->move_delay = player->move_delay_value;
13037 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13039 TestIfPlayerTouchesBadThing(jx, jy);
13040 TestIfPlayerTouchesCustomElement(jx, jy);
13043 if (!player->active)
13044 RemovePlayer(player);
13049 void ScrollPlayer(struct PlayerInfo *player, int mode)
13051 int jx = player->jx, jy = player->jy;
13052 int last_jx = player->last_jx, last_jy = player->last_jy;
13053 int move_stepsize = TILEX / player->move_delay_value;
13055 if (!player->active)
13058 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13061 if (mode == SCROLL_INIT)
13063 player->actual_frame_counter = FrameCounter;
13064 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13066 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13067 Tile[last_jx][last_jy] == EL_EMPTY)
13069 int last_field_block_delay = 0; // start with no blocking at all
13070 int block_delay_adjustment = player->block_delay_adjustment;
13072 // if player blocks last field, add delay for exactly one move
13073 if (player->block_last_field)
13075 last_field_block_delay += player->move_delay_value;
13077 // when blocking enabled, prevent moving up despite gravity
13078 if (player->gravity && player->MovDir == MV_UP)
13079 block_delay_adjustment = -1;
13082 // add block delay adjustment (also possible when not blocking)
13083 last_field_block_delay += block_delay_adjustment;
13085 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13086 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13089 if (player->MovPos != 0) // player has not yet reached destination
13092 else if (!FrameReached(&player->actual_frame_counter, 1))
13095 if (player->MovPos != 0)
13097 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13098 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13100 // before DrawPlayer() to draw correct player graphic for this case
13101 if (player->MovPos == 0)
13102 CheckGravityMovement(player);
13105 if (player->MovPos == 0) // player reached destination field
13107 if (player->move_delay_reset_counter > 0)
13109 player->move_delay_reset_counter--;
13111 if (player->move_delay_reset_counter == 0)
13113 // continue with normal speed after quickly moving through gate
13114 HALVE_PLAYER_SPEED(player);
13116 // be able to make the next move without delay
13117 player->move_delay = 0;
13121 player->last_jx = jx;
13122 player->last_jy = jy;
13124 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13125 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13126 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13127 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13128 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13129 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13130 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13131 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13133 ExitPlayer(player);
13135 if (game.players_still_needed == 0 &&
13136 (game.friends_still_needed == 0 ||
13137 IS_SP_ELEMENT(Tile[jx][jy])))
13141 // this breaks one level: "machine", level 000
13143 int move_direction = player->MovDir;
13144 int enter_side = MV_DIR_OPPOSITE(move_direction);
13145 int leave_side = move_direction;
13146 int old_jx = last_jx;
13147 int old_jy = last_jy;
13148 int old_element = Tile[old_jx][old_jy];
13149 int new_element = Tile[jx][jy];
13151 if (IS_CUSTOM_ELEMENT(old_element))
13152 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13154 player->index_bit, leave_side);
13156 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13157 CE_PLAYER_LEAVES_X,
13158 player->index_bit, leave_side);
13160 if (IS_CUSTOM_ELEMENT(new_element))
13161 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13162 player->index_bit, enter_side);
13164 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13165 CE_PLAYER_ENTERS_X,
13166 player->index_bit, enter_side);
13168 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13169 CE_MOVE_OF_X, move_direction);
13172 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13174 TestIfPlayerTouchesBadThing(jx, jy);
13175 TestIfPlayerTouchesCustomElement(jx, jy);
13177 /* needed because pushed element has not yet reached its destination,
13178 so it would trigger a change event at its previous field location */
13179 if (!player->is_pushing)
13180 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13182 if (level.finish_dig_collect &&
13183 (player->is_digging || player->is_collecting))
13185 int last_element = player->last_removed_element;
13186 int move_direction = player->MovDir;
13187 int enter_side = MV_DIR_OPPOSITE(move_direction);
13188 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13189 CE_PLAYER_COLLECTS_X);
13191 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13192 player->index_bit, enter_side);
13194 player->last_removed_element = EL_UNDEFINED;
13197 if (!player->active)
13198 RemovePlayer(player);
13201 if (level.use_step_counter)
13211 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
13212 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
13214 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
13216 DisplayGameControlValues();
13218 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
13219 for (i = 0; i < MAX_PLAYERS; i++)
13220 KillPlayer(&stored_player[i]);
13222 else if (game.no_time_limit && !game.all_players_gone)
13224 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
13226 DisplayGameControlValues();
13230 if (tape.single_step && tape.recording && !tape.pausing &&
13231 !player->programmed_action)
13232 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13234 if (!player->programmed_action)
13235 CheckSaveEngineSnapshot(player);
13239 void ScrollScreen(struct PlayerInfo *player, int mode)
13241 static unsigned int screen_frame_counter = 0;
13243 if (mode == SCROLL_INIT)
13245 // set scrolling step size according to actual player's moving speed
13246 ScrollStepSize = TILEX / player->move_delay_value;
13248 screen_frame_counter = FrameCounter;
13249 ScreenMovDir = player->MovDir;
13250 ScreenMovPos = player->MovPos;
13251 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13254 else if (!FrameReached(&screen_frame_counter, 1))
13259 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13260 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13261 redraw_mask |= REDRAW_FIELD;
13264 ScreenMovDir = MV_NONE;
13267 void TestIfPlayerTouchesCustomElement(int x, int y)
13269 static int xy[4][2] =
13276 static int trigger_sides[4][2] =
13278 // center side border side
13279 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13280 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13281 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13282 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13284 static int touch_dir[4] =
13286 MV_LEFT | MV_RIGHT,
13291 int center_element = Tile[x][y]; // should always be non-moving!
13294 for (i = 0; i < NUM_DIRECTIONS; i++)
13296 int xx = x + xy[i][0];
13297 int yy = y + xy[i][1];
13298 int center_side = trigger_sides[i][0];
13299 int border_side = trigger_sides[i][1];
13300 int border_element;
13302 if (!IN_LEV_FIELD(xx, yy))
13305 if (IS_PLAYER(x, y)) // player found at center element
13307 struct PlayerInfo *player = PLAYERINFO(x, y);
13309 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13310 border_element = Tile[xx][yy]; // may be moving!
13311 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13312 border_element = Tile[xx][yy];
13313 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13314 border_element = MovingOrBlocked2Element(xx, yy);
13316 continue; // center and border element do not touch
13318 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13319 player->index_bit, border_side);
13320 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13321 CE_PLAYER_TOUCHES_X,
13322 player->index_bit, border_side);
13325 /* use player element that is initially defined in the level playfield,
13326 not the player element that corresponds to the runtime player number
13327 (example: a level that contains EL_PLAYER_3 as the only player would
13328 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13329 int player_element = PLAYERINFO(x, y)->initial_element;
13331 CheckElementChangeBySide(xx, yy, border_element, player_element,
13332 CE_TOUCHING_X, border_side);
13335 else if (IS_PLAYER(xx, yy)) // player found at border element
13337 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13339 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13341 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13342 continue; // center and border element do not touch
13345 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13346 player->index_bit, center_side);
13347 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13348 CE_PLAYER_TOUCHES_X,
13349 player->index_bit, center_side);
13352 /* use player element that is initially defined in the level playfield,
13353 not the player element that corresponds to the runtime player number
13354 (example: a level that contains EL_PLAYER_3 as the only player would
13355 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13356 int player_element = PLAYERINFO(xx, yy)->initial_element;
13358 CheckElementChangeBySide(x, y, center_element, player_element,
13359 CE_TOUCHING_X, center_side);
13367 void TestIfElementTouchesCustomElement(int x, int y)
13369 static int xy[4][2] =
13376 static int trigger_sides[4][2] =
13378 // center side border side
13379 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13380 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13381 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13382 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13384 static int touch_dir[4] =
13386 MV_LEFT | MV_RIGHT,
13391 boolean change_center_element = FALSE;
13392 int center_element = Tile[x][y]; // should always be non-moving!
13393 int border_element_old[NUM_DIRECTIONS];
13396 for (i = 0; i < NUM_DIRECTIONS; i++)
13398 int xx = x + xy[i][0];
13399 int yy = y + xy[i][1];
13400 int border_element;
13402 border_element_old[i] = -1;
13404 if (!IN_LEV_FIELD(xx, yy))
13407 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13408 border_element = Tile[xx][yy]; // may be moving!
13409 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13410 border_element = Tile[xx][yy];
13411 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13412 border_element = MovingOrBlocked2Element(xx, yy);
13414 continue; // center and border element do not touch
13416 border_element_old[i] = border_element;
13419 for (i = 0; i < NUM_DIRECTIONS; i++)
13421 int xx = x + xy[i][0];
13422 int yy = y + xy[i][1];
13423 int center_side = trigger_sides[i][0];
13424 int border_element = border_element_old[i];
13426 if (border_element == -1)
13429 // check for change of border element
13430 CheckElementChangeBySide(xx, yy, border_element, center_element,
13431 CE_TOUCHING_X, center_side);
13433 // (center element cannot be player, so we dont have to check this here)
13436 for (i = 0; i < NUM_DIRECTIONS; i++)
13438 int xx = x + xy[i][0];
13439 int yy = y + xy[i][1];
13440 int border_side = trigger_sides[i][1];
13441 int border_element = border_element_old[i];
13443 if (border_element == -1)
13446 // check for change of center element (but change it only once)
13447 if (!change_center_element)
13448 change_center_element =
13449 CheckElementChangeBySide(x, y, center_element, border_element,
13450 CE_TOUCHING_X, border_side);
13452 if (IS_PLAYER(xx, yy))
13454 /* use player element that is initially defined in the level playfield,
13455 not the player element that corresponds to the runtime player number
13456 (example: a level that contains EL_PLAYER_3 as the only player would
13457 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13458 int player_element = PLAYERINFO(xx, yy)->initial_element;
13460 CheckElementChangeBySide(x, y, center_element, player_element,
13461 CE_TOUCHING_X, border_side);
13466 void TestIfElementHitsCustomElement(int x, int y, int direction)
13468 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13469 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13470 int hitx = x + dx, hity = y + dy;
13471 int hitting_element = Tile[x][y];
13472 int touched_element;
13474 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13477 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13478 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13480 if (IN_LEV_FIELD(hitx, hity))
13482 int opposite_direction = MV_DIR_OPPOSITE(direction);
13483 int hitting_side = direction;
13484 int touched_side = opposite_direction;
13485 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13486 MovDir[hitx][hity] != direction ||
13487 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13493 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13494 CE_HITTING_X, touched_side);
13496 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13497 CE_HIT_BY_X, hitting_side);
13499 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13500 CE_HIT_BY_SOMETHING, opposite_direction);
13502 if (IS_PLAYER(hitx, hity))
13504 /* use player element that is initially defined in the level playfield,
13505 not the player element that corresponds to the runtime player number
13506 (example: a level that contains EL_PLAYER_3 as the only player would
13507 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13508 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13510 CheckElementChangeBySide(x, y, hitting_element, player_element,
13511 CE_HITTING_X, touched_side);
13516 // "hitting something" is also true when hitting the playfield border
13517 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13518 CE_HITTING_SOMETHING, direction);
13521 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13523 int i, kill_x = -1, kill_y = -1;
13525 int bad_element = -1;
13526 static int test_xy[4][2] =
13533 static int test_dir[4] =
13541 for (i = 0; i < NUM_DIRECTIONS; i++)
13543 int test_x, test_y, test_move_dir, test_element;
13545 test_x = good_x + test_xy[i][0];
13546 test_y = good_y + test_xy[i][1];
13548 if (!IN_LEV_FIELD(test_x, test_y))
13552 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13554 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13556 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13557 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13559 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13560 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13564 bad_element = test_element;
13570 if (kill_x != -1 || kill_y != -1)
13572 if (IS_PLAYER(good_x, good_y))
13574 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13576 if (player->shield_deadly_time_left > 0 &&
13577 !IS_INDESTRUCTIBLE(bad_element))
13578 Bang(kill_x, kill_y);
13579 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13580 KillPlayer(player);
13583 Bang(good_x, good_y);
13587 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13589 int i, kill_x = -1, kill_y = -1;
13590 int bad_element = Tile[bad_x][bad_y];
13591 static int test_xy[4][2] =
13598 static int touch_dir[4] =
13600 MV_LEFT | MV_RIGHT,
13605 static int test_dir[4] =
13613 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13616 for (i = 0; i < NUM_DIRECTIONS; i++)
13618 int test_x, test_y, test_move_dir, test_element;
13620 test_x = bad_x + test_xy[i][0];
13621 test_y = bad_y + test_xy[i][1];
13623 if (!IN_LEV_FIELD(test_x, test_y))
13627 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13629 test_element = Tile[test_x][test_y];
13631 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13632 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13634 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13635 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13637 // good thing is player or penguin that does not move away
13638 if (IS_PLAYER(test_x, test_y))
13640 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13642 if (bad_element == EL_ROBOT && player->is_moving)
13643 continue; // robot does not kill player if he is moving
13645 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13647 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13648 continue; // center and border element do not touch
13656 else if (test_element == EL_PENGUIN)
13666 if (kill_x != -1 || kill_y != -1)
13668 if (IS_PLAYER(kill_x, kill_y))
13670 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13672 if (player->shield_deadly_time_left > 0 &&
13673 !IS_INDESTRUCTIBLE(bad_element))
13674 Bang(bad_x, bad_y);
13675 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13676 KillPlayer(player);
13679 Bang(kill_x, kill_y);
13683 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13685 int bad_element = Tile[bad_x][bad_y];
13686 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13687 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13688 int test_x = bad_x + dx, test_y = bad_y + dy;
13689 int test_move_dir, test_element;
13690 int kill_x = -1, kill_y = -1;
13692 if (!IN_LEV_FIELD(test_x, test_y))
13696 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13698 test_element = Tile[test_x][test_y];
13700 if (test_move_dir != bad_move_dir)
13702 // good thing can be player or penguin that does not move away
13703 if (IS_PLAYER(test_x, test_y))
13705 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13707 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13708 player as being hit when he is moving towards the bad thing, because
13709 the "get hit by" condition would be lost after the player stops) */
13710 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13711 return; // player moves away from bad thing
13716 else if (test_element == EL_PENGUIN)
13723 if (kill_x != -1 || kill_y != -1)
13725 if (IS_PLAYER(kill_x, kill_y))
13727 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13729 if (player->shield_deadly_time_left > 0 &&
13730 !IS_INDESTRUCTIBLE(bad_element))
13731 Bang(bad_x, bad_y);
13732 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13733 KillPlayer(player);
13736 Bang(kill_x, kill_y);
13740 void TestIfPlayerTouchesBadThing(int x, int y)
13742 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13745 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13747 TestIfGoodThingHitsBadThing(x, y, move_dir);
13750 void TestIfBadThingTouchesPlayer(int x, int y)
13752 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13755 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13757 TestIfBadThingHitsGoodThing(x, y, move_dir);
13760 void TestIfFriendTouchesBadThing(int x, int y)
13762 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13765 void TestIfBadThingTouchesFriend(int x, int y)
13767 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13770 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13772 int i, kill_x = bad_x, kill_y = bad_y;
13773 static int xy[4][2] =
13781 for (i = 0; i < NUM_DIRECTIONS; i++)
13785 x = bad_x + xy[i][0];
13786 y = bad_y + xy[i][1];
13787 if (!IN_LEV_FIELD(x, y))
13790 element = Tile[x][y];
13791 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
13792 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
13800 if (kill_x != bad_x || kill_y != bad_y)
13801 Bang(bad_x, bad_y);
13804 void KillPlayer(struct PlayerInfo *player)
13806 int jx = player->jx, jy = player->jy;
13808 if (!player->active)
13812 Debug("game:playing:KillPlayer",
13813 "0: killed == %d, active == %d, reanimated == %d",
13814 player->killed, player->active, player->reanimated);
13817 /* the following code was introduced to prevent an infinite loop when calling
13819 -> CheckTriggeredElementChangeExt()
13820 -> ExecuteCustomElementAction()
13822 -> (infinitely repeating the above sequence of function calls)
13823 which occurs when killing the player while having a CE with the setting
13824 "kill player X when explosion of <player X>"; the solution using a new
13825 field "player->killed" was chosen for backwards compatibility, although
13826 clever use of the fields "player->active" etc. would probably also work */
13828 if (player->killed)
13832 player->killed = TRUE;
13834 // remove accessible field at the player's position
13835 Tile[jx][jy] = EL_EMPTY;
13837 // deactivate shield (else Bang()/Explode() would not work right)
13838 player->shield_normal_time_left = 0;
13839 player->shield_deadly_time_left = 0;
13842 Debug("game:playing:KillPlayer",
13843 "1: killed == %d, active == %d, reanimated == %d",
13844 player->killed, player->active, player->reanimated);
13850 Debug("game:playing:KillPlayer",
13851 "2: killed == %d, active == %d, reanimated == %d",
13852 player->killed, player->active, player->reanimated);
13855 if (player->reanimated) // killed player may have been reanimated
13856 player->killed = player->reanimated = FALSE;
13858 BuryPlayer(player);
13861 static void KillPlayerUnlessEnemyProtected(int x, int y)
13863 if (!PLAYER_ENEMY_PROTECTED(x, y))
13864 KillPlayer(PLAYERINFO(x, y));
13867 static void KillPlayerUnlessExplosionProtected(int x, int y)
13869 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
13870 KillPlayer(PLAYERINFO(x, y));
13873 void BuryPlayer(struct PlayerInfo *player)
13875 int jx = player->jx, jy = player->jy;
13877 if (!player->active)
13880 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
13881 PlayLevelSound(jx, jy, SND_GAME_LOSING);
13883 RemovePlayer(player);
13885 player->buried = TRUE;
13887 if (game.all_players_gone)
13888 game.GameOver = TRUE;
13891 void RemovePlayer(struct PlayerInfo *player)
13893 int jx = player->jx, jy = player->jy;
13894 int i, found = FALSE;
13896 player->present = FALSE;
13897 player->active = FALSE;
13899 // required for some CE actions (even if the player is not active anymore)
13900 player->MovPos = 0;
13902 if (!ExplodeField[jx][jy])
13903 StorePlayer[jx][jy] = 0;
13905 if (player->is_moving)
13906 TEST_DrawLevelField(player->last_jx, player->last_jy);
13908 for (i = 0; i < MAX_PLAYERS; i++)
13909 if (stored_player[i].active)
13914 game.all_players_gone = TRUE;
13915 game.GameOver = TRUE;
13918 game.exit_x = game.robot_wheel_x = jx;
13919 game.exit_y = game.robot_wheel_y = jy;
13922 void ExitPlayer(struct PlayerInfo *player)
13924 DrawPlayer(player); // needed here only to cleanup last field
13925 RemovePlayer(player);
13927 if (game.players_still_needed > 0)
13928 game.players_still_needed--;
13931 static void SetFieldForSnapping(int x, int y, int element, int direction,
13932 int player_index_bit)
13934 struct ElementInfo *ei = &element_info[element];
13935 int direction_bit = MV_DIR_TO_BIT(direction);
13936 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
13937 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
13938 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
13940 Tile[x][y] = EL_ELEMENT_SNAPPING;
13941 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
13942 MovDir[x][y] = direction;
13943 Store[x][y] = element;
13944 Store2[x][y] = player_index_bit;
13946 ResetGfxAnimation(x, y);
13948 GfxElement[x][y] = element;
13949 GfxAction[x][y] = action;
13950 GfxDir[x][y] = direction;
13951 GfxFrame[x][y] = -1;
13954 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
13955 int player_index_bit)
13957 TestIfElementTouchesCustomElement(x, y); // for empty space
13959 if (level.finish_dig_collect)
13961 int dig_side = MV_DIR_OPPOSITE(direction);
13963 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
13964 player_index_bit, dig_side);
13969 =============================================================================
13970 checkDiagonalPushing()
13971 -----------------------------------------------------------------------------
13972 check if diagonal input device direction results in pushing of object
13973 (by checking if the alternative direction is walkable, diggable, ...)
13974 =============================================================================
13977 static boolean checkDiagonalPushing(struct PlayerInfo *player,
13978 int x, int y, int real_dx, int real_dy)
13980 int jx, jy, dx, dy, xx, yy;
13982 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
13985 // diagonal direction: check alternative direction
13990 xx = jx + (dx == 0 ? real_dx : 0);
13991 yy = jy + (dy == 0 ? real_dy : 0);
13993 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
13997 =============================================================================
13999 -----------------------------------------------------------------------------
14000 x, y: field next to player (non-diagonal) to try to dig to
14001 real_dx, real_dy: direction as read from input device (can be diagonal)
14002 =============================================================================
14005 static int DigField(struct PlayerInfo *player,
14006 int oldx, int oldy, int x, int y,
14007 int real_dx, int real_dy, int mode)
14009 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14010 boolean player_was_pushing = player->is_pushing;
14011 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14012 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14013 int jx = oldx, jy = oldy;
14014 int dx = x - jx, dy = y - jy;
14015 int nextx = x + dx, nexty = y + dy;
14016 int move_direction = (dx == -1 ? MV_LEFT :
14017 dx == +1 ? MV_RIGHT :
14019 dy == +1 ? MV_DOWN : MV_NONE);
14020 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14021 int dig_side = MV_DIR_OPPOSITE(move_direction);
14022 int old_element = Tile[jx][jy];
14023 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14026 if (is_player) // function can also be called by EL_PENGUIN
14028 if (player->MovPos == 0)
14030 player->is_digging = FALSE;
14031 player->is_collecting = FALSE;
14034 if (player->MovPos == 0) // last pushing move finished
14035 player->is_pushing = FALSE;
14037 if (mode == DF_NO_PUSH) // player just stopped pushing
14039 player->is_switching = FALSE;
14040 player->push_delay = -1;
14042 return MP_NO_ACTION;
14046 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14047 old_element = Back[jx][jy];
14049 // in case of element dropped at player position, check background
14050 else if (Back[jx][jy] != EL_EMPTY &&
14051 game.engine_version >= VERSION_IDENT(2,2,0,0))
14052 old_element = Back[jx][jy];
14054 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14055 return MP_NO_ACTION; // field has no opening in this direction
14057 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14058 return MP_NO_ACTION; // field has no opening in this direction
14060 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14064 Tile[jx][jy] = player->artwork_element;
14065 InitMovingField(jx, jy, MV_DOWN);
14066 Store[jx][jy] = EL_ACID;
14067 ContinueMoving(jx, jy);
14068 BuryPlayer(player);
14070 return MP_DONT_RUN_INTO;
14073 if (player_can_move && DONT_RUN_INTO(element))
14075 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14077 return MP_DONT_RUN_INTO;
14080 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14081 return MP_NO_ACTION;
14083 collect_count = element_info[element].collect_count_initial;
14085 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14086 return MP_NO_ACTION;
14088 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14089 player_can_move = player_can_move_or_snap;
14091 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14092 game.engine_version >= VERSION_IDENT(2,2,0,0))
14094 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14095 player->index_bit, dig_side);
14096 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14097 player->index_bit, dig_side);
14099 if (element == EL_DC_LANDMINE)
14102 if (Tile[x][y] != element) // field changed by snapping
14105 return MP_NO_ACTION;
14108 if (player->gravity && is_player && !player->is_auto_moving &&
14109 canFallDown(player) && move_direction != MV_DOWN &&
14110 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14111 return MP_NO_ACTION; // player cannot walk here due to gravity
14113 if (player_can_move &&
14114 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14116 int sound_element = SND_ELEMENT(element);
14117 int sound_action = ACTION_WALKING;
14119 if (IS_RND_GATE(element))
14121 if (!player->key[RND_GATE_NR(element)])
14122 return MP_NO_ACTION;
14124 else if (IS_RND_GATE_GRAY(element))
14126 if (!player->key[RND_GATE_GRAY_NR(element)])
14127 return MP_NO_ACTION;
14129 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14131 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14132 return MP_NO_ACTION;
14134 else if (element == EL_EXIT_OPEN ||
14135 element == EL_EM_EXIT_OPEN ||
14136 element == EL_EM_EXIT_OPENING ||
14137 element == EL_STEEL_EXIT_OPEN ||
14138 element == EL_EM_STEEL_EXIT_OPEN ||
14139 element == EL_EM_STEEL_EXIT_OPENING ||
14140 element == EL_SP_EXIT_OPEN ||
14141 element == EL_SP_EXIT_OPENING)
14143 sound_action = ACTION_PASSING; // player is passing exit
14145 else if (element == EL_EMPTY)
14147 sound_action = ACTION_MOVING; // nothing to walk on
14150 // play sound from background or player, whatever is available
14151 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14152 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14154 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14156 else if (player_can_move &&
14157 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14159 if (!ACCESS_FROM(element, opposite_direction))
14160 return MP_NO_ACTION; // field not accessible from this direction
14162 if (CAN_MOVE(element)) // only fixed elements can be passed!
14163 return MP_NO_ACTION;
14165 if (IS_EM_GATE(element))
14167 if (!player->key[EM_GATE_NR(element)])
14168 return MP_NO_ACTION;
14170 else if (IS_EM_GATE_GRAY(element))
14172 if (!player->key[EM_GATE_GRAY_NR(element)])
14173 return MP_NO_ACTION;
14175 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14177 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14178 return MP_NO_ACTION;
14180 else if (IS_EMC_GATE(element))
14182 if (!player->key[EMC_GATE_NR(element)])
14183 return MP_NO_ACTION;
14185 else if (IS_EMC_GATE_GRAY(element))
14187 if (!player->key[EMC_GATE_GRAY_NR(element)])
14188 return MP_NO_ACTION;
14190 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14192 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14193 return MP_NO_ACTION;
14195 else if (element == EL_DC_GATE_WHITE ||
14196 element == EL_DC_GATE_WHITE_GRAY ||
14197 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14199 if (player->num_white_keys == 0)
14200 return MP_NO_ACTION;
14202 player->num_white_keys--;
14204 else if (IS_SP_PORT(element))
14206 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14207 element == EL_SP_GRAVITY_PORT_RIGHT ||
14208 element == EL_SP_GRAVITY_PORT_UP ||
14209 element == EL_SP_GRAVITY_PORT_DOWN)
14210 player->gravity = !player->gravity;
14211 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14212 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14213 element == EL_SP_GRAVITY_ON_PORT_UP ||
14214 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14215 player->gravity = TRUE;
14216 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14217 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14218 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14219 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14220 player->gravity = FALSE;
14223 // automatically move to the next field with double speed
14224 player->programmed_action = move_direction;
14226 if (player->move_delay_reset_counter == 0)
14228 player->move_delay_reset_counter = 2; // two double speed steps
14230 DOUBLE_PLAYER_SPEED(player);
14233 PlayLevelSoundAction(x, y, ACTION_PASSING);
14235 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14239 if (mode != DF_SNAP)
14241 GfxElement[x][y] = GFX_ELEMENT(element);
14242 player->is_digging = TRUE;
14245 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14247 // use old behaviour for old levels (digging)
14248 if (!level.finish_dig_collect)
14250 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14251 player->index_bit, dig_side);
14253 // if digging triggered player relocation, finish digging tile
14254 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14255 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14258 if (mode == DF_SNAP)
14260 if (level.block_snap_field)
14261 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14263 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14265 // use old behaviour for old levels (snapping)
14266 if (!level.finish_dig_collect)
14267 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14268 player->index_bit, dig_side);
14271 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14275 if (is_player && mode != DF_SNAP)
14277 GfxElement[x][y] = element;
14278 player->is_collecting = TRUE;
14281 if (element == EL_SPEED_PILL)
14283 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14285 else if (element == EL_EXTRA_TIME && level.time > 0)
14287 TimeLeft += level.extra_time;
14289 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14291 DisplayGameControlValues();
14293 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14295 player->shield_normal_time_left += level.shield_normal_time;
14296 if (element == EL_SHIELD_DEADLY)
14297 player->shield_deadly_time_left += level.shield_deadly_time;
14299 else if (element == EL_DYNAMITE ||
14300 element == EL_EM_DYNAMITE ||
14301 element == EL_SP_DISK_RED)
14303 if (player->inventory_size < MAX_INVENTORY_SIZE)
14304 player->inventory_element[player->inventory_size++] = element;
14306 DrawGameDoorValues();
14308 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14310 player->dynabomb_count++;
14311 player->dynabombs_left++;
14313 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14315 player->dynabomb_size++;
14317 else if (element == EL_DYNABOMB_INCREASE_POWER)
14319 player->dynabomb_xl = TRUE;
14321 else if (IS_KEY(element))
14323 player->key[KEY_NR(element)] = TRUE;
14325 DrawGameDoorValues();
14327 else if (element == EL_DC_KEY_WHITE)
14329 player->num_white_keys++;
14331 // display white keys?
14332 // DrawGameDoorValues();
14334 else if (IS_ENVELOPE(element))
14336 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14338 if (!wait_for_snapping)
14339 player->show_envelope = element;
14341 else if (element == EL_EMC_LENSES)
14343 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14345 RedrawAllInvisibleElementsForLenses();
14347 else if (element == EL_EMC_MAGNIFIER)
14349 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14351 RedrawAllInvisibleElementsForMagnifier();
14353 else if (IS_DROPPABLE(element) ||
14354 IS_THROWABLE(element)) // can be collected and dropped
14358 if (collect_count == 0)
14359 player->inventory_infinite_element = element;
14361 for (i = 0; i < collect_count; i++)
14362 if (player->inventory_size < MAX_INVENTORY_SIZE)
14363 player->inventory_element[player->inventory_size++] = element;
14365 DrawGameDoorValues();
14367 else if (collect_count > 0)
14369 game.gems_still_needed -= collect_count;
14370 if (game.gems_still_needed < 0)
14371 game.gems_still_needed = 0;
14373 game.snapshot.collected_item = TRUE;
14375 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14377 DisplayGameControlValues();
14380 RaiseScoreElement(element);
14381 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14383 // use old behaviour for old levels (collecting)
14384 if (!level.finish_dig_collect && is_player)
14386 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14387 player->index_bit, dig_side);
14389 // if collecting triggered player relocation, finish collecting tile
14390 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14391 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14394 if (mode == DF_SNAP)
14396 if (level.block_snap_field)
14397 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14399 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14401 // use old behaviour for old levels (snapping)
14402 if (!level.finish_dig_collect)
14403 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14404 player->index_bit, dig_side);
14407 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14409 if (mode == DF_SNAP && element != EL_BD_ROCK)
14410 return MP_NO_ACTION;
14412 if (CAN_FALL(element) && dy)
14413 return MP_NO_ACTION;
14415 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14416 !(element == EL_SPRING && level.use_spring_bug))
14417 return MP_NO_ACTION;
14419 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14420 ((move_direction & MV_VERTICAL &&
14421 ((element_info[element].move_pattern & MV_LEFT &&
14422 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14423 (element_info[element].move_pattern & MV_RIGHT &&
14424 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14425 (move_direction & MV_HORIZONTAL &&
14426 ((element_info[element].move_pattern & MV_UP &&
14427 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14428 (element_info[element].move_pattern & MV_DOWN &&
14429 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14430 return MP_NO_ACTION;
14432 // do not push elements already moving away faster than player
14433 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14434 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14435 return MP_NO_ACTION;
14437 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14439 if (player->push_delay_value == -1 || !player_was_pushing)
14440 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14442 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14444 if (player->push_delay_value == -1)
14445 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14447 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14449 if (!player->is_pushing)
14450 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14453 player->is_pushing = TRUE;
14454 player->is_active = TRUE;
14456 if (!(IN_LEV_FIELD(nextx, nexty) &&
14457 (IS_FREE(nextx, nexty) ||
14458 (IS_SB_ELEMENT(element) &&
14459 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14460 (IS_CUSTOM_ELEMENT(element) &&
14461 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14462 return MP_NO_ACTION;
14464 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14465 return MP_NO_ACTION;
14467 if (player->push_delay == -1) // new pushing; restart delay
14468 player->push_delay = 0;
14470 if (player->push_delay < player->push_delay_value &&
14471 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14472 element != EL_SPRING && element != EL_BALLOON)
14474 // make sure that there is no move delay before next try to push
14475 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14476 player->move_delay = 0;
14478 return MP_NO_ACTION;
14481 if (IS_CUSTOM_ELEMENT(element) &&
14482 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14484 if (!DigFieldByCE(nextx, nexty, element))
14485 return MP_NO_ACTION;
14488 if (IS_SB_ELEMENT(element))
14490 boolean sokoban_task_solved = FALSE;
14492 if (element == EL_SOKOBAN_FIELD_FULL)
14494 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14496 IncrementSokobanFieldsNeeded();
14497 IncrementSokobanObjectsNeeded();
14500 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14502 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14504 DecrementSokobanFieldsNeeded();
14505 DecrementSokobanObjectsNeeded();
14507 // sokoban object was pushed from empty field to sokoban field
14508 if (Back[x][y] == EL_EMPTY)
14509 sokoban_task_solved = TRUE;
14512 Tile[x][y] = EL_SOKOBAN_OBJECT;
14514 if (Back[x][y] == Back[nextx][nexty])
14515 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14516 else if (Back[x][y] != 0)
14517 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14520 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14523 if (sokoban_task_solved &&
14524 game.sokoban_fields_still_needed == 0 &&
14525 game.sokoban_objects_still_needed == 0 &&
14526 level.auto_exit_sokoban)
14528 game.players_still_needed = 0;
14532 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14536 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14538 InitMovingField(x, y, move_direction);
14539 GfxAction[x][y] = ACTION_PUSHING;
14541 if (mode == DF_SNAP)
14542 ContinueMoving(x, y);
14544 MovPos[x][y] = (dx != 0 ? dx : dy);
14546 Pushed[x][y] = TRUE;
14547 Pushed[nextx][nexty] = TRUE;
14549 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14550 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14552 player->push_delay_value = -1; // get new value later
14554 // check for element change _after_ element has been pushed
14555 if (game.use_change_when_pushing_bug)
14557 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14558 player->index_bit, dig_side);
14559 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14560 player->index_bit, dig_side);
14563 else if (IS_SWITCHABLE(element))
14565 if (PLAYER_SWITCHING(player, x, y))
14567 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14568 player->index_bit, dig_side);
14573 player->is_switching = TRUE;
14574 player->switch_x = x;
14575 player->switch_y = y;
14577 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14579 if (element == EL_ROBOT_WHEEL)
14581 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14583 game.robot_wheel_x = x;
14584 game.robot_wheel_y = y;
14585 game.robot_wheel_active = TRUE;
14587 TEST_DrawLevelField(x, y);
14589 else if (element == EL_SP_TERMINAL)
14593 SCAN_PLAYFIELD(xx, yy)
14595 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14599 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14601 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14603 ResetGfxAnimation(xx, yy);
14604 TEST_DrawLevelField(xx, yy);
14608 else if (IS_BELT_SWITCH(element))
14610 ToggleBeltSwitch(x, y);
14612 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14613 element == EL_SWITCHGATE_SWITCH_DOWN ||
14614 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14615 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14617 ToggleSwitchgateSwitch(x, y);
14619 else if (element == EL_LIGHT_SWITCH ||
14620 element == EL_LIGHT_SWITCH_ACTIVE)
14622 ToggleLightSwitch(x, y);
14624 else if (element == EL_TIMEGATE_SWITCH ||
14625 element == EL_DC_TIMEGATE_SWITCH)
14627 ActivateTimegateSwitch(x, y);
14629 else if (element == EL_BALLOON_SWITCH_LEFT ||
14630 element == EL_BALLOON_SWITCH_RIGHT ||
14631 element == EL_BALLOON_SWITCH_UP ||
14632 element == EL_BALLOON_SWITCH_DOWN ||
14633 element == EL_BALLOON_SWITCH_NONE ||
14634 element == EL_BALLOON_SWITCH_ANY)
14636 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14637 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14638 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14639 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14640 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14643 else if (element == EL_LAMP)
14645 Tile[x][y] = EL_LAMP_ACTIVE;
14646 game.lights_still_needed--;
14648 ResetGfxAnimation(x, y);
14649 TEST_DrawLevelField(x, y);
14651 else if (element == EL_TIME_ORB_FULL)
14653 Tile[x][y] = EL_TIME_ORB_EMPTY;
14655 if (level.time > 0 || level.use_time_orb_bug)
14657 TimeLeft += level.time_orb_time;
14658 game.no_time_limit = FALSE;
14660 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14662 DisplayGameControlValues();
14665 ResetGfxAnimation(x, y);
14666 TEST_DrawLevelField(x, y);
14668 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14669 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14673 game.ball_active = !game.ball_active;
14675 SCAN_PLAYFIELD(xx, yy)
14677 int e = Tile[xx][yy];
14679 if (game.ball_active)
14681 if (e == EL_EMC_MAGIC_BALL)
14682 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14683 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14684 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14688 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14689 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14690 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14691 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14696 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14697 player->index_bit, dig_side);
14699 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14700 player->index_bit, dig_side);
14702 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14703 player->index_bit, dig_side);
14709 if (!PLAYER_SWITCHING(player, x, y))
14711 player->is_switching = TRUE;
14712 player->switch_x = x;
14713 player->switch_y = y;
14715 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14716 player->index_bit, dig_side);
14717 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14718 player->index_bit, dig_side);
14720 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14721 player->index_bit, dig_side);
14722 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14723 player->index_bit, dig_side);
14726 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14727 player->index_bit, dig_side);
14728 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14729 player->index_bit, dig_side);
14731 return MP_NO_ACTION;
14734 player->push_delay = -1;
14736 if (is_player) // function can also be called by EL_PENGUIN
14738 if (Tile[x][y] != element) // really digged/collected something
14740 player->is_collecting = !player->is_digging;
14741 player->is_active = TRUE;
14743 player->last_removed_element = element;
14750 static boolean DigFieldByCE(int x, int y, int digging_element)
14752 int element = Tile[x][y];
14754 if (!IS_FREE(x, y))
14756 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14757 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14760 // no element can dig solid indestructible elements
14761 if (IS_INDESTRUCTIBLE(element) &&
14762 !IS_DIGGABLE(element) &&
14763 !IS_COLLECTIBLE(element))
14766 if (AmoebaNr[x][y] &&
14767 (element == EL_AMOEBA_FULL ||
14768 element == EL_BD_AMOEBA ||
14769 element == EL_AMOEBA_GROWING))
14771 AmoebaCnt[AmoebaNr[x][y]]--;
14772 AmoebaCnt2[AmoebaNr[x][y]]--;
14775 if (IS_MOVING(x, y))
14776 RemoveMovingField(x, y);
14780 TEST_DrawLevelField(x, y);
14783 // if digged element was about to explode, prevent the explosion
14784 ExplodeField[x][y] = EX_TYPE_NONE;
14786 PlayLevelSoundAction(x, y, action);
14789 Store[x][y] = EL_EMPTY;
14791 // this makes it possible to leave the removed element again
14792 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
14793 Store[x][y] = element;
14798 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
14800 int jx = player->jx, jy = player->jy;
14801 int x = jx + dx, y = jy + dy;
14802 int snap_direction = (dx == -1 ? MV_LEFT :
14803 dx == +1 ? MV_RIGHT :
14805 dy == +1 ? MV_DOWN : MV_NONE);
14806 boolean can_continue_snapping = (level.continuous_snapping &&
14807 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
14809 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
14812 if (!player->active || !IN_LEV_FIELD(x, y))
14820 if (player->MovPos == 0)
14821 player->is_pushing = FALSE;
14823 player->is_snapping = FALSE;
14825 if (player->MovPos == 0)
14827 player->is_moving = FALSE;
14828 player->is_digging = FALSE;
14829 player->is_collecting = FALSE;
14835 // prevent snapping with already pressed snap key when not allowed
14836 if (player->is_snapping && !can_continue_snapping)
14839 player->MovDir = snap_direction;
14841 if (player->MovPos == 0)
14843 player->is_moving = FALSE;
14844 player->is_digging = FALSE;
14845 player->is_collecting = FALSE;
14848 player->is_dropping = FALSE;
14849 player->is_dropping_pressed = FALSE;
14850 player->drop_pressed_delay = 0;
14852 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
14855 player->is_snapping = TRUE;
14856 player->is_active = TRUE;
14858 if (player->MovPos == 0)
14860 player->is_moving = FALSE;
14861 player->is_digging = FALSE;
14862 player->is_collecting = FALSE;
14865 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
14866 TEST_DrawLevelField(player->last_jx, player->last_jy);
14868 TEST_DrawLevelField(x, y);
14873 static boolean DropElement(struct PlayerInfo *player)
14875 int old_element, new_element;
14876 int dropx = player->jx, dropy = player->jy;
14877 int drop_direction = player->MovDir;
14878 int drop_side = drop_direction;
14879 int drop_element = get_next_dropped_element(player);
14881 /* do not drop an element on top of another element; when holding drop key
14882 pressed without moving, dropped element must move away before the next
14883 element can be dropped (this is especially important if the next element
14884 is dynamite, which can be placed on background for historical reasons) */
14885 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
14888 if (IS_THROWABLE(drop_element))
14890 dropx += GET_DX_FROM_DIR(drop_direction);
14891 dropy += GET_DY_FROM_DIR(drop_direction);
14893 if (!IN_LEV_FIELD(dropx, dropy))
14897 old_element = Tile[dropx][dropy]; // old element at dropping position
14898 new_element = drop_element; // default: no change when dropping
14900 // check if player is active, not moving and ready to drop
14901 if (!player->active || player->MovPos || player->drop_delay > 0)
14904 // check if player has anything that can be dropped
14905 if (new_element == EL_UNDEFINED)
14908 // only set if player has anything that can be dropped
14909 player->is_dropping_pressed = TRUE;
14911 // check if drop key was pressed long enough for EM style dynamite
14912 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
14915 // check if anything can be dropped at the current position
14916 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
14919 // collected custom elements can only be dropped on empty fields
14920 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
14923 if (old_element != EL_EMPTY)
14924 Back[dropx][dropy] = old_element; // store old element on this field
14926 ResetGfxAnimation(dropx, dropy);
14927 ResetRandomAnimationValue(dropx, dropy);
14929 if (player->inventory_size > 0 ||
14930 player->inventory_infinite_element != EL_UNDEFINED)
14932 if (player->inventory_size > 0)
14934 player->inventory_size--;
14936 DrawGameDoorValues();
14938 if (new_element == EL_DYNAMITE)
14939 new_element = EL_DYNAMITE_ACTIVE;
14940 else if (new_element == EL_EM_DYNAMITE)
14941 new_element = EL_EM_DYNAMITE_ACTIVE;
14942 else if (new_element == EL_SP_DISK_RED)
14943 new_element = EL_SP_DISK_RED_ACTIVE;
14946 Tile[dropx][dropy] = new_element;
14948 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14949 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14950 el2img(Tile[dropx][dropy]), 0);
14952 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14954 // needed if previous element just changed to "empty" in the last frame
14955 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14957 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
14958 player->index_bit, drop_side);
14959 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
14961 player->index_bit, drop_side);
14963 TestIfElementTouchesCustomElement(dropx, dropy);
14965 else // player is dropping a dyna bomb
14967 player->dynabombs_left--;
14969 Tile[dropx][dropy] = new_element;
14971 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
14972 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
14973 el2img(Tile[dropx][dropy]), 0);
14975 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
14978 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
14979 InitField_WithBug1(dropx, dropy, FALSE);
14981 new_element = Tile[dropx][dropy]; // element might have changed
14983 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
14984 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
14986 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
14987 MovDir[dropx][dropy] = drop_direction;
14989 ChangeCount[dropx][dropy] = 0; // allow at least one more change
14991 // do not cause impact style collision by dropping elements that can fall
14992 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
14995 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
14996 player->is_dropping = TRUE;
14998 player->drop_pressed_delay = 0;
14999 player->is_dropping_pressed = FALSE;
15001 player->drop_x = dropx;
15002 player->drop_y = dropy;
15007 // ----------------------------------------------------------------------------
15008 // game sound playing functions
15009 // ----------------------------------------------------------------------------
15011 static int *loop_sound_frame = NULL;
15012 static int *loop_sound_volume = NULL;
15014 void InitPlayLevelSound(void)
15016 int num_sounds = getSoundListSize();
15018 checked_free(loop_sound_frame);
15019 checked_free(loop_sound_volume);
15021 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15022 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15025 static void PlayLevelSound(int x, int y, int nr)
15027 int sx = SCREENX(x), sy = SCREENY(y);
15028 int volume, stereo_position;
15029 int max_distance = 8;
15030 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15032 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15033 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15036 if (!IN_LEV_FIELD(x, y) ||
15037 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15038 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15041 volume = SOUND_MAX_VOLUME;
15043 if (!IN_SCR_FIELD(sx, sy))
15045 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15046 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15048 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15051 stereo_position = (SOUND_MAX_LEFT +
15052 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15053 (SCR_FIELDX + 2 * max_distance));
15055 if (IS_LOOP_SOUND(nr))
15057 /* This assures that quieter loop sounds do not overwrite louder ones,
15058 while restarting sound volume comparison with each new game frame. */
15060 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15063 loop_sound_volume[nr] = volume;
15064 loop_sound_frame[nr] = FrameCounter;
15067 PlaySoundExt(nr, volume, stereo_position, type);
15070 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15072 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15073 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15074 y < LEVELY(BY1) ? LEVELY(BY1) :
15075 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15079 static void PlayLevelSoundAction(int x, int y, int action)
15081 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15084 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15086 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15088 if (sound_effect != SND_UNDEFINED)
15089 PlayLevelSound(x, y, sound_effect);
15092 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15095 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15097 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15098 PlayLevelSound(x, y, sound_effect);
15101 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15103 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15105 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15106 PlayLevelSound(x, y, sound_effect);
15109 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15111 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15113 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15114 StopSound(sound_effect);
15117 static int getLevelMusicNr(void)
15119 if (levelset.music[level_nr] != MUS_UNDEFINED)
15120 return levelset.music[level_nr]; // from config file
15122 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15125 static void FadeLevelSounds(void)
15130 static void FadeLevelMusic(void)
15132 int music_nr = getLevelMusicNr();
15133 char *curr_music = getCurrentlyPlayingMusicFilename();
15134 char *next_music = getMusicInfoEntryFilename(music_nr);
15136 if (!strEqual(curr_music, next_music))
15140 void FadeLevelSoundsAndMusic(void)
15146 static void PlayLevelMusic(void)
15148 int music_nr = getLevelMusicNr();
15149 char *curr_music = getCurrentlyPlayingMusicFilename();
15150 char *next_music = getMusicInfoEntryFilename(music_nr);
15152 if (!strEqual(curr_music, next_music))
15153 PlayMusicLoop(music_nr);
15156 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15158 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15160 int x = xx - offset;
15161 int y = yy - offset;
15166 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15170 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15174 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15178 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15182 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15186 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15190 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15193 case SOUND_android_clone:
15194 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15197 case SOUND_android_move:
15198 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15202 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15206 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15210 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15213 case SOUND_eater_eat:
15214 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15218 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15221 case SOUND_collect:
15222 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15225 case SOUND_diamond:
15226 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15230 // !!! CHECK THIS !!!
15232 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15234 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15238 case SOUND_wonderfall:
15239 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15243 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15247 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15251 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15255 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15259 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15263 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15267 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15271 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15274 case SOUND_exit_open:
15275 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15278 case SOUND_exit_leave:
15279 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15282 case SOUND_dynamite:
15283 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15287 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15291 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15295 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15299 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15303 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15307 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15311 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15316 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15318 int element = map_element_SP_to_RND(element_sp);
15319 int action = map_action_SP_to_RND(action_sp);
15320 int offset = (setup.sp_show_border_elements ? 0 : 1);
15321 int x = xx - offset;
15322 int y = yy - offset;
15324 PlayLevelSoundElementAction(x, y, element, action);
15327 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15329 int element = map_element_MM_to_RND(element_mm);
15330 int action = map_action_MM_to_RND(action_mm);
15332 int x = xx - offset;
15333 int y = yy - offset;
15335 if (!IS_MM_ELEMENT(element))
15336 element = EL_MM_DEFAULT;
15338 PlayLevelSoundElementAction(x, y, element, action);
15341 void PlaySound_MM(int sound_mm)
15343 int sound = map_sound_MM_to_RND(sound_mm);
15345 if (sound == SND_UNDEFINED)
15351 void PlaySoundLoop_MM(int sound_mm)
15353 int sound = map_sound_MM_to_RND(sound_mm);
15355 if (sound == SND_UNDEFINED)
15358 PlaySoundLoop(sound);
15361 void StopSound_MM(int sound_mm)
15363 int sound = map_sound_MM_to_RND(sound_mm);
15365 if (sound == SND_UNDEFINED)
15371 void RaiseScore(int value)
15373 game.score += value;
15375 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15377 DisplayGameControlValues();
15380 void RaiseScoreElement(int element)
15385 case EL_BD_DIAMOND:
15386 case EL_EMERALD_YELLOW:
15387 case EL_EMERALD_RED:
15388 case EL_EMERALD_PURPLE:
15389 case EL_SP_INFOTRON:
15390 RaiseScore(level.score[SC_EMERALD]);
15393 RaiseScore(level.score[SC_DIAMOND]);
15396 RaiseScore(level.score[SC_CRYSTAL]);
15399 RaiseScore(level.score[SC_PEARL]);
15402 case EL_BD_BUTTERFLY:
15403 case EL_SP_ELECTRON:
15404 RaiseScore(level.score[SC_BUG]);
15407 case EL_BD_FIREFLY:
15408 case EL_SP_SNIKSNAK:
15409 RaiseScore(level.score[SC_SPACESHIP]);
15412 case EL_DARK_YAMYAM:
15413 RaiseScore(level.score[SC_YAMYAM]);
15416 RaiseScore(level.score[SC_ROBOT]);
15419 RaiseScore(level.score[SC_PACMAN]);
15422 RaiseScore(level.score[SC_NUT]);
15425 case EL_EM_DYNAMITE:
15426 case EL_SP_DISK_RED:
15427 case EL_DYNABOMB_INCREASE_NUMBER:
15428 case EL_DYNABOMB_INCREASE_SIZE:
15429 case EL_DYNABOMB_INCREASE_POWER:
15430 RaiseScore(level.score[SC_DYNAMITE]);
15432 case EL_SHIELD_NORMAL:
15433 case EL_SHIELD_DEADLY:
15434 RaiseScore(level.score[SC_SHIELD]);
15436 case EL_EXTRA_TIME:
15437 RaiseScore(level.extra_time_score);
15451 case EL_DC_KEY_WHITE:
15452 RaiseScore(level.score[SC_KEY]);
15455 RaiseScore(element_info[element].collect_score);
15460 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15462 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15466 // prevent short reactivation of overlay buttons while closing door
15467 SetOverlayActive(FALSE);
15469 // door may still be open due to skipped or envelope style request
15470 CloseDoor(DOOR_CLOSE_1);
15473 if (network.enabled)
15474 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15478 FadeSkipNextFadeIn();
15480 SetGameStatus(GAME_MODE_MAIN);
15485 else // continue playing the game
15487 if (tape.playing && tape.deactivate_display)
15488 TapeDeactivateDisplayOff(TRUE);
15490 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15492 if (tape.playing && tape.deactivate_display)
15493 TapeDeactivateDisplayOn();
15497 void RequestQuitGame(boolean escape_key_pressed)
15499 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15500 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15501 level_editor_test_game);
15502 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15505 RequestQuitGameExt(skip_request, quick_quit,
15506 "Do you really want to quit the game?");
15509 void RequestRestartGame(char *message)
15511 game.restart_game_message = NULL;
15513 boolean has_started_game = hasStartedNetworkGame();
15514 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15516 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15518 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15522 // needed in case of envelope request to close game panel
15523 CloseDoor(DOOR_CLOSE_1);
15525 SetGameStatus(GAME_MODE_MAIN);
15531 void CheckGameOver(void)
15533 static boolean last_game_over = FALSE;
15534 static int game_over_delay = 0;
15535 int game_over_delay_value = 50;
15536 boolean game_over = checkGameFailed();
15538 // do not handle game over if request dialog is already active
15539 if (game.request_active)
15542 // do not ask to play again if game was never actually played
15543 if (!game.GamePlayed)
15548 last_game_over = FALSE;
15549 game_over_delay = game_over_delay_value;
15554 if (game_over_delay > 0)
15561 if (last_game_over != game_over)
15562 game.restart_game_message = (hasStartedNetworkGame() ?
15563 "Game over! Play it again?" :
15566 last_game_over = game_over;
15569 boolean checkGameSolved(void)
15571 // set for all game engines if level was solved
15572 return game.LevelSolved_GameEnd;
15575 boolean checkGameFailed(void)
15577 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15578 return (game_em.game_over && !game_em.level_solved);
15579 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15580 return (game_sp.game_over && !game_sp.level_solved);
15581 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15582 return (game_mm.game_over && !game_mm.level_solved);
15583 else // GAME_ENGINE_TYPE_RND
15584 return (game.GameOver && !game.LevelSolved);
15587 boolean checkGameEnded(void)
15589 return (checkGameSolved() || checkGameFailed());
15593 // ----------------------------------------------------------------------------
15594 // random generator functions
15595 // ----------------------------------------------------------------------------
15597 unsigned int InitEngineRandom_RND(int seed)
15599 game.num_random_calls = 0;
15601 return InitEngineRandom(seed);
15604 unsigned int RND(int max)
15608 game.num_random_calls++;
15610 return GetEngineRandom(max);
15617 // ----------------------------------------------------------------------------
15618 // game engine snapshot handling functions
15619 // ----------------------------------------------------------------------------
15621 struct EngineSnapshotInfo
15623 // runtime values for custom element collect score
15624 int collect_score[NUM_CUSTOM_ELEMENTS];
15626 // runtime values for group element choice position
15627 int choice_pos[NUM_GROUP_ELEMENTS];
15629 // runtime values for belt position animations
15630 int belt_graphic[4][NUM_BELT_PARTS];
15631 int belt_anim_mode[4][NUM_BELT_PARTS];
15634 static struct EngineSnapshotInfo engine_snapshot_rnd;
15635 static char *snapshot_level_identifier = NULL;
15636 static int snapshot_level_nr = -1;
15638 static void SaveEngineSnapshotValues_RND(void)
15640 static int belt_base_active_element[4] =
15642 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15643 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15644 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15645 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15649 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15651 int element = EL_CUSTOM_START + i;
15653 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15656 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15658 int element = EL_GROUP_START + i;
15660 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15663 for (i = 0; i < 4; i++)
15665 for (j = 0; j < NUM_BELT_PARTS; j++)
15667 int element = belt_base_active_element[i] + j;
15668 int graphic = el2img(element);
15669 int anim_mode = graphic_info[graphic].anim_mode;
15671 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15672 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15677 static void LoadEngineSnapshotValues_RND(void)
15679 unsigned int num_random_calls = game.num_random_calls;
15682 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15684 int element = EL_CUSTOM_START + i;
15686 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15689 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15691 int element = EL_GROUP_START + i;
15693 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15696 for (i = 0; i < 4; i++)
15698 for (j = 0; j < NUM_BELT_PARTS; j++)
15700 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15701 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15703 graphic_info[graphic].anim_mode = anim_mode;
15707 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15709 InitRND(tape.random_seed);
15710 for (i = 0; i < num_random_calls; i++)
15714 if (game.num_random_calls != num_random_calls)
15716 Error("number of random calls out of sync");
15717 Error("number of random calls should be %d", num_random_calls);
15718 Error("number of random calls is %d", game.num_random_calls);
15720 Fail("this should not happen -- please debug");
15724 void FreeEngineSnapshotSingle(void)
15726 FreeSnapshotSingle();
15728 setString(&snapshot_level_identifier, NULL);
15729 snapshot_level_nr = -1;
15732 void FreeEngineSnapshotList(void)
15734 FreeSnapshotList();
15737 static ListNode *SaveEngineSnapshotBuffers(void)
15739 ListNode *buffers = NULL;
15741 // copy some special values to a structure better suited for the snapshot
15743 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15744 SaveEngineSnapshotValues_RND();
15745 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15746 SaveEngineSnapshotValues_EM();
15747 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15748 SaveEngineSnapshotValues_SP(&buffers);
15749 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15750 SaveEngineSnapshotValues_MM(&buffers);
15752 // save values stored in special snapshot structure
15754 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15755 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15756 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15757 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15758 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15759 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15760 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15761 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15763 // save further RND engine values
15765 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15766 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15767 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15769 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15770 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15771 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15772 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15773 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15775 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15776 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15777 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15779 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15781 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15782 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15784 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15785 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15786 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15787 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
15788 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
15789 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
15790 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
15791 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
15792 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
15793 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
15794 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
15795 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
15796 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
15797 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
15798 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
15799 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
15800 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
15801 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
15803 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
15804 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
15806 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
15807 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
15808 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
15810 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
15811 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
15813 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
15814 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
15815 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
15816 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
15817 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
15819 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
15820 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
15823 ListNode *node = engine_snapshot_list_rnd;
15826 while (node != NULL)
15828 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
15833 Debug("game:playing:SaveEngineSnapshotBuffers",
15834 "size of engine snapshot: %d bytes", num_bytes);
15840 void SaveEngineSnapshotSingle(void)
15842 ListNode *buffers = SaveEngineSnapshotBuffers();
15844 // finally save all snapshot buffers to single snapshot
15845 SaveSnapshotSingle(buffers);
15847 // save level identification information
15848 setString(&snapshot_level_identifier, leveldir_current->identifier);
15849 snapshot_level_nr = level_nr;
15852 boolean CheckSaveEngineSnapshotToList(void)
15854 boolean save_snapshot =
15855 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
15856 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
15857 game.snapshot.changed_action) ||
15858 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
15859 game.snapshot.collected_item));
15861 game.snapshot.changed_action = FALSE;
15862 game.snapshot.collected_item = FALSE;
15863 game.snapshot.save_snapshot = save_snapshot;
15865 return save_snapshot;
15868 void SaveEngineSnapshotToList(void)
15870 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
15874 ListNode *buffers = SaveEngineSnapshotBuffers();
15876 // finally save all snapshot buffers to snapshot list
15877 SaveSnapshotToList(buffers);
15880 void SaveEngineSnapshotToListInitial(void)
15882 FreeEngineSnapshotList();
15884 SaveEngineSnapshotToList();
15887 static void LoadEngineSnapshotValues(void)
15889 // restore special values from snapshot structure
15891 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15892 LoadEngineSnapshotValues_RND();
15893 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15894 LoadEngineSnapshotValues_EM();
15895 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15896 LoadEngineSnapshotValues_SP();
15897 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15898 LoadEngineSnapshotValues_MM();
15901 void LoadEngineSnapshotSingle(void)
15903 LoadSnapshotSingle();
15905 LoadEngineSnapshotValues();
15908 static void LoadEngineSnapshot_Undo(int steps)
15910 LoadSnapshotFromList_Older(steps);
15912 LoadEngineSnapshotValues();
15915 static void LoadEngineSnapshot_Redo(int steps)
15917 LoadSnapshotFromList_Newer(steps);
15919 LoadEngineSnapshotValues();
15922 boolean CheckEngineSnapshotSingle(void)
15924 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
15925 snapshot_level_nr == level_nr);
15928 boolean CheckEngineSnapshotList(void)
15930 return CheckSnapshotList();
15934 // ---------- new game button stuff -------------------------------------------
15941 boolean *setup_value;
15942 boolean allowed_on_tape;
15943 boolean is_touch_button;
15945 } gamebutton_info[NUM_GAME_BUTTONS] =
15948 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
15949 GAME_CTRL_ID_STOP, NULL,
15950 TRUE, FALSE, "stop game"
15953 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
15954 GAME_CTRL_ID_PAUSE, NULL,
15955 TRUE, FALSE, "pause game"
15958 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
15959 GAME_CTRL_ID_PLAY, NULL,
15960 TRUE, FALSE, "play game"
15963 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
15964 GAME_CTRL_ID_UNDO, NULL,
15965 TRUE, FALSE, "undo step"
15968 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
15969 GAME_CTRL_ID_REDO, NULL,
15970 TRUE, FALSE, "redo step"
15973 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
15974 GAME_CTRL_ID_SAVE, NULL,
15975 TRUE, FALSE, "save game"
15978 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
15979 GAME_CTRL_ID_PAUSE2, NULL,
15980 TRUE, FALSE, "pause game"
15983 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
15984 GAME_CTRL_ID_LOAD, NULL,
15985 TRUE, FALSE, "load game"
15988 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
15989 GAME_CTRL_ID_PANEL_STOP, NULL,
15990 FALSE, FALSE, "stop game"
15993 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
15994 GAME_CTRL_ID_PANEL_PAUSE, NULL,
15995 FALSE, FALSE, "pause game"
15998 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
15999 GAME_CTRL_ID_PANEL_PLAY, NULL,
16000 FALSE, FALSE, "play game"
16003 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16004 GAME_CTRL_ID_TOUCH_STOP, NULL,
16005 FALSE, TRUE, "stop game"
16008 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16009 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16010 FALSE, TRUE, "pause game"
16013 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16014 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16015 TRUE, FALSE, "background music on/off"
16018 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16019 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16020 TRUE, FALSE, "sound loops on/off"
16023 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16024 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16025 TRUE, FALSE, "normal sounds on/off"
16028 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16029 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16030 FALSE, FALSE, "background music on/off"
16033 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16034 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16035 FALSE, FALSE, "sound loops on/off"
16038 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16039 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16040 FALSE, FALSE, "normal sounds on/off"
16044 void CreateGameButtons(void)
16048 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16050 int graphic = gamebutton_info[i].graphic;
16051 struct GraphicInfo *gfx = &graphic_info[graphic];
16052 struct XY *pos = gamebutton_info[i].pos;
16053 struct GadgetInfo *gi;
16056 unsigned int event_mask;
16057 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16058 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16059 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16060 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16061 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16062 int gd_x = gfx->src_x;
16063 int gd_y = gfx->src_y;
16064 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16065 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16066 int gd_xa = gfx->src_x + gfx->active_xoffset;
16067 int gd_ya = gfx->src_y + gfx->active_yoffset;
16068 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16069 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16070 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16071 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16074 if (gfx->bitmap == NULL)
16076 game_gadget[id] = NULL;
16081 if (id == GAME_CTRL_ID_STOP ||
16082 id == GAME_CTRL_ID_PANEL_STOP ||
16083 id == GAME_CTRL_ID_TOUCH_STOP ||
16084 id == GAME_CTRL_ID_PLAY ||
16085 id == GAME_CTRL_ID_PANEL_PLAY ||
16086 id == GAME_CTRL_ID_SAVE ||
16087 id == GAME_CTRL_ID_LOAD)
16089 button_type = GD_TYPE_NORMAL_BUTTON;
16091 event_mask = GD_EVENT_RELEASED;
16093 else if (id == GAME_CTRL_ID_UNDO ||
16094 id == GAME_CTRL_ID_REDO)
16096 button_type = GD_TYPE_NORMAL_BUTTON;
16098 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16102 button_type = GD_TYPE_CHECK_BUTTON;
16103 checked = (gamebutton_info[i].setup_value != NULL ?
16104 *gamebutton_info[i].setup_value : FALSE);
16105 event_mask = GD_EVENT_PRESSED;
16108 gi = CreateGadget(GDI_CUSTOM_ID, id,
16109 GDI_IMAGE_ID, graphic,
16110 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16113 GDI_WIDTH, gfx->width,
16114 GDI_HEIGHT, gfx->height,
16115 GDI_TYPE, button_type,
16116 GDI_STATE, GD_BUTTON_UNPRESSED,
16117 GDI_CHECKED, checked,
16118 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16119 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16120 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16121 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16122 GDI_DIRECT_DRAW, FALSE,
16123 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16124 GDI_EVENT_MASK, event_mask,
16125 GDI_CALLBACK_ACTION, HandleGameButtons,
16129 Fail("cannot create gadget");
16131 game_gadget[id] = gi;
16135 void FreeGameButtons(void)
16139 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16140 FreeGadget(game_gadget[i]);
16143 static void UnmapGameButtonsAtSamePosition(int id)
16147 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16149 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16150 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16151 UnmapGadget(game_gadget[i]);
16154 static void UnmapGameButtonsAtSamePosition_All(void)
16156 if (setup.show_snapshot_buttons)
16158 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16159 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16160 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16164 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16165 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16166 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16168 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16169 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16170 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16174 static void MapGameButtonsAtSamePosition(int id)
16178 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16180 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16181 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16182 MapGadget(game_gadget[i]);
16184 UnmapGameButtonsAtSamePosition_All();
16187 void MapUndoRedoButtons(void)
16189 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16190 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16192 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16193 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16196 void UnmapUndoRedoButtons(void)
16198 UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16199 UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16201 MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16202 MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16205 void ModifyPauseButtons(void)
16209 GAME_CTRL_ID_PAUSE,
16210 GAME_CTRL_ID_PAUSE2,
16211 GAME_CTRL_ID_PANEL_PAUSE,
16212 GAME_CTRL_ID_TOUCH_PAUSE,
16217 for (i = 0; ids[i] > -1; i++)
16218 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16221 static void MapGameButtonsExt(boolean on_tape)
16225 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16226 if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
16227 i != GAME_CTRL_ID_UNDO &&
16228 i != GAME_CTRL_ID_REDO)
16229 MapGadget(game_gadget[i]);
16231 UnmapGameButtonsAtSamePosition_All();
16233 RedrawGameButtons();
16236 static void UnmapGameButtonsExt(boolean on_tape)
16240 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16241 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16242 UnmapGadget(game_gadget[i]);
16245 static void RedrawGameButtonsExt(boolean on_tape)
16249 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16250 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16251 RedrawGadget(game_gadget[i]);
16254 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16259 gi->checked = state;
16262 static void RedrawSoundButtonGadget(int id)
16264 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16265 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16266 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16267 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16268 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16269 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16272 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16273 RedrawGadget(game_gadget[id2]);
16276 void MapGameButtons(void)
16278 MapGameButtonsExt(FALSE);
16281 void UnmapGameButtons(void)
16283 UnmapGameButtonsExt(FALSE);
16286 void RedrawGameButtons(void)
16288 RedrawGameButtonsExt(FALSE);
16291 void MapGameButtonsOnTape(void)
16293 MapGameButtonsExt(TRUE);
16296 void UnmapGameButtonsOnTape(void)
16298 UnmapGameButtonsExt(TRUE);
16301 void RedrawGameButtonsOnTape(void)
16303 RedrawGameButtonsExt(TRUE);
16306 static void GameUndoRedoExt(void)
16308 ClearPlayerAction();
16310 tape.pausing = TRUE;
16313 UpdateAndDisplayGameControlValues();
16315 DrawCompleteVideoDisplay();
16316 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16317 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16318 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16323 static void GameUndo(int steps)
16325 if (!CheckEngineSnapshotList())
16328 LoadEngineSnapshot_Undo(steps);
16333 static void GameRedo(int steps)
16335 if (!CheckEngineSnapshotList())
16338 LoadEngineSnapshot_Redo(steps);
16343 static void HandleGameButtonsExt(int id, int button)
16345 static boolean game_undo_executed = FALSE;
16346 int steps = BUTTON_STEPSIZE(button);
16347 boolean handle_game_buttons =
16348 (game_status == GAME_MODE_PLAYING ||
16349 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16351 if (!handle_game_buttons)
16356 case GAME_CTRL_ID_STOP:
16357 case GAME_CTRL_ID_PANEL_STOP:
16358 case GAME_CTRL_ID_TOUCH_STOP:
16359 if (game_status == GAME_MODE_MAIN)
16365 RequestQuitGame(FALSE);
16369 case GAME_CTRL_ID_PAUSE:
16370 case GAME_CTRL_ID_PAUSE2:
16371 case GAME_CTRL_ID_PANEL_PAUSE:
16372 case GAME_CTRL_ID_TOUCH_PAUSE:
16373 if (network.enabled && game_status == GAME_MODE_PLAYING)
16376 SendToServer_ContinuePlaying();
16378 SendToServer_PausePlaying();
16381 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16383 game_undo_executed = FALSE;
16387 case GAME_CTRL_ID_PLAY:
16388 case GAME_CTRL_ID_PANEL_PLAY:
16389 if (game_status == GAME_MODE_MAIN)
16391 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16393 else if (tape.pausing)
16395 if (network.enabled)
16396 SendToServer_ContinuePlaying();
16398 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16402 case GAME_CTRL_ID_UNDO:
16403 // Important: When using "save snapshot when collecting an item" mode,
16404 // load last (current) snapshot for first "undo" after pressing "pause"
16405 // (else the last-but-one snapshot would be loaded, because the snapshot
16406 // pointer already points to the last snapshot when pressing "pause",
16407 // which is fine for "every step/move" mode, but not for "every collect")
16408 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16409 !game_undo_executed)
16412 game_undo_executed = TRUE;
16417 case GAME_CTRL_ID_REDO:
16421 case GAME_CTRL_ID_SAVE:
16425 case GAME_CTRL_ID_LOAD:
16429 case SOUND_CTRL_ID_MUSIC:
16430 case SOUND_CTRL_ID_PANEL_MUSIC:
16431 if (setup.sound_music)
16433 setup.sound_music = FALSE;
16437 else if (audio.music_available)
16439 setup.sound = setup.sound_music = TRUE;
16441 SetAudioMode(setup.sound);
16443 if (game_status == GAME_MODE_PLAYING)
16447 RedrawSoundButtonGadget(id);
16451 case SOUND_CTRL_ID_LOOPS:
16452 case SOUND_CTRL_ID_PANEL_LOOPS:
16453 if (setup.sound_loops)
16454 setup.sound_loops = FALSE;
16455 else if (audio.loops_available)
16457 setup.sound = setup.sound_loops = TRUE;
16459 SetAudioMode(setup.sound);
16462 RedrawSoundButtonGadget(id);
16466 case SOUND_CTRL_ID_SIMPLE:
16467 case SOUND_CTRL_ID_PANEL_SIMPLE:
16468 if (setup.sound_simple)
16469 setup.sound_simple = FALSE;
16470 else if (audio.sound_available)
16472 setup.sound = setup.sound_simple = TRUE;
16474 SetAudioMode(setup.sound);
16477 RedrawSoundButtonGadget(id);
16486 static void HandleGameButtons(struct GadgetInfo *gi)
16488 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16491 void HandleSoundButtonKeys(Key key)
16493 if (key == setup.shortcut.sound_simple)
16494 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16495 else if (key == setup.shortcut.sound_loops)
16496 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16497 else if (key == setup.shortcut.sound_music)
16498 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);