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 CheckNextToConditions(int, int);
1062 static void TestIfPlayerNextToCustomElement(int, int);
1063 static void TestIfPlayerTouchesCustomElement(int, int);
1064 static void TestIfElementNextToCustomElement(int, int);
1065 static void TestIfElementTouchesCustomElement(int, int);
1066 static void TestIfElementHitsCustomElement(int, int, int);
1068 static void HandleElementChange(int, int, int);
1069 static void ExecuteCustomElementAction(int, int, int, int);
1070 static boolean ChangeElement(int, int, int, int);
1072 static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int);
1073 #define CheckTriggeredElementChange(x, y, e, ev) \
1074 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1)
1075 #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \
1076 CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1)
1077 #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \
1078 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1079 #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \
1080 CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p)
1081 #define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \
1082 CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1)
1084 static boolean CheckElementChangeExt(int, int, int, int, int, int, int);
1085 #define CheckElementChange(x, y, e, te, ev) \
1086 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, CH_SIDE_ANY)
1087 #define CheckElementChangeByPlayer(x, y, e, ev, p, s) \
1088 CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s)
1089 #define CheckElementChangeBySide(x, y, e, te, ev, s) \
1090 CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s)
1091 #define CheckElementChangeByMouse(x, y, e, ev, s) \
1092 CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s)
1094 static void PlayLevelSound(int, int, int);
1095 static void PlayLevelSoundNearest(int, int, int);
1096 static void PlayLevelSoundAction(int, int, int);
1097 static void PlayLevelSoundElementAction(int, int, int, int);
1098 static void PlayLevelSoundElementActionIfLoop(int, int, int, int);
1099 static void PlayLevelSoundActionIfLoop(int, int, int);
1100 static void StopLevelSoundActionIfLoop(int, int, int);
1101 static void PlayLevelMusic(void);
1102 static void FadeLevelSoundsAndMusic(void);
1104 static void HandleGameButtons(struct GadgetInfo *);
1106 int AmoebaNeighbourNr(int, int);
1107 void AmoebaToDiamond(int, int);
1108 void ContinueMoving(int, int);
1109 void Bang(int, int);
1110 void InitMovDir(int, int);
1111 void InitAmoebaNr(int, int);
1112 void NewHighScore(int, boolean);
1114 void TestIfGoodThingHitsBadThing(int, int, int);
1115 void TestIfBadThingHitsGoodThing(int, int, int);
1116 void TestIfPlayerTouchesBadThing(int, int);
1117 void TestIfPlayerRunsIntoBadThing(int, int, int);
1118 void TestIfBadThingTouchesPlayer(int, int);
1119 void TestIfBadThingRunsIntoPlayer(int, int, int);
1120 void TestIfFriendTouchesBadThing(int, int);
1121 void TestIfBadThingTouchesFriend(int, int);
1122 void TestIfBadThingTouchesOtherBadThing(int, int);
1123 void TestIfGoodThingGetsHitByBadThing(int, int, int);
1125 void KillPlayer(struct PlayerInfo *);
1126 void BuryPlayer(struct PlayerInfo *);
1127 void RemovePlayer(struct PlayerInfo *);
1128 void ExitPlayer(struct PlayerInfo *);
1130 static int getInvisibleActiveFromInvisibleElement(int);
1131 static int getInvisibleFromInvisibleActiveElement(int);
1133 static void TestFieldAfterSnapping(int, int, int, int, int);
1135 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
1137 // for detection of endless loops, caused by custom element programming
1138 // (using maximal playfield width x 10 is just a rough approximation)
1139 #define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10)
1141 #define RECURSION_LOOP_DETECTION_START(e, rc) \
1143 if (recursion_loop_detected) \
1146 if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
1148 recursion_loop_detected = TRUE; \
1149 recursion_loop_element = (e); \
1152 recursion_loop_depth++; \
1155 #define RECURSION_LOOP_DETECTION_END() \
1157 recursion_loop_depth--; \
1160 static int recursion_loop_depth;
1161 static boolean recursion_loop_detected;
1162 static boolean recursion_loop_element;
1164 static int map_player_action[MAX_PLAYERS];
1167 // ----------------------------------------------------------------------------
1168 // definition of elements that automatically change to other elements after
1169 // a specified time, eventually calling a function when changing
1170 // ----------------------------------------------------------------------------
1172 // forward declaration for changer functions
1173 static void InitBuggyBase(int, int);
1174 static void WarnBuggyBase(int, int);
1176 static void InitTrap(int, int);
1177 static void ActivateTrap(int, int);
1178 static void ChangeActiveTrap(int, int);
1180 static void InitRobotWheel(int, int);
1181 static void RunRobotWheel(int, int);
1182 static void StopRobotWheel(int, int);
1184 static void InitTimegateWheel(int, int);
1185 static void RunTimegateWheel(int, int);
1187 static void InitMagicBallDelay(int, int);
1188 static void ActivateMagicBall(int, int);
1190 struct ChangingElementInfo
1195 void (*pre_change_function)(int x, int y);
1196 void (*change_function)(int x, int y);
1197 void (*post_change_function)(int x, int y);
1200 static struct ChangingElementInfo change_delay_list[] =
1235 EL_STEEL_EXIT_OPENING,
1243 EL_STEEL_EXIT_CLOSING,
1244 EL_STEEL_EXIT_CLOSED,
1267 EL_EM_STEEL_EXIT_OPENING,
1268 EL_EM_STEEL_EXIT_OPEN,
1275 EL_EM_STEEL_EXIT_CLOSING,
1299 EL_SWITCHGATE_OPENING,
1307 EL_SWITCHGATE_CLOSING,
1308 EL_SWITCHGATE_CLOSED,
1315 EL_TIMEGATE_OPENING,
1323 EL_TIMEGATE_CLOSING,
1332 EL_ACID_SPLASH_LEFT,
1340 EL_ACID_SPLASH_RIGHT,
1349 EL_SP_BUGGY_BASE_ACTIVATING,
1356 EL_SP_BUGGY_BASE_ACTIVATING,
1357 EL_SP_BUGGY_BASE_ACTIVE,
1364 EL_SP_BUGGY_BASE_ACTIVE,
1388 EL_ROBOT_WHEEL_ACTIVE,
1396 EL_TIMEGATE_SWITCH_ACTIVE,
1404 EL_DC_TIMEGATE_SWITCH_ACTIVE,
1405 EL_DC_TIMEGATE_SWITCH,
1412 EL_EMC_MAGIC_BALL_ACTIVE,
1413 EL_EMC_MAGIC_BALL_ACTIVE,
1420 EL_EMC_SPRING_BUMPER_ACTIVE,
1421 EL_EMC_SPRING_BUMPER,
1428 EL_DIAGONAL_SHRINKING,
1436 EL_DIAGONAL_GROWING,
1457 int push_delay_fixed, push_delay_random;
1461 { EL_SPRING, 0, 0 },
1462 { EL_BALLOON, 0, 0 },
1464 { EL_SOKOBAN_OBJECT, 2, 0 },
1465 { EL_SOKOBAN_FIELD_FULL, 2, 0 },
1466 { EL_SATELLITE, 2, 0 },
1467 { EL_SP_DISK_YELLOW, 2, 0 },
1469 { EL_UNDEFINED, 0, 0 },
1477 move_stepsize_list[] =
1479 { EL_AMOEBA_DROP, 2 },
1480 { EL_AMOEBA_DROPPING, 2 },
1481 { EL_QUICKSAND_FILLING, 1 },
1482 { EL_QUICKSAND_EMPTYING, 1 },
1483 { EL_QUICKSAND_FAST_FILLING, 2 },
1484 { EL_QUICKSAND_FAST_EMPTYING, 2 },
1485 { EL_MAGIC_WALL_FILLING, 2 },
1486 { EL_MAGIC_WALL_EMPTYING, 2 },
1487 { EL_BD_MAGIC_WALL_FILLING, 2 },
1488 { EL_BD_MAGIC_WALL_EMPTYING, 2 },
1489 { EL_DC_MAGIC_WALL_FILLING, 2 },
1490 { EL_DC_MAGIC_WALL_EMPTYING, 2 },
1492 { EL_UNDEFINED, 0 },
1500 collect_count_list[] =
1503 { EL_BD_DIAMOND, 1 },
1504 { EL_EMERALD_YELLOW, 1 },
1505 { EL_EMERALD_RED, 1 },
1506 { EL_EMERALD_PURPLE, 1 },
1508 { EL_SP_INFOTRON, 1 },
1512 { EL_UNDEFINED, 0 },
1520 access_direction_list[] =
1522 { EL_TUBE_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1523 { EL_TUBE_VERTICAL, MV_UP | MV_DOWN },
1524 { EL_TUBE_HORIZONTAL, MV_LEFT | MV_RIGHT },
1525 { EL_TUBE_VERTICAL_LEFT, MV_LEFT | MV_UP | MV_DOWN },
1526 { EL_TUBE_VERTICAL_RIGHT, MV_RIGHT | MV_UP | MV_DOWN },
1527 { EL_TUBE_HORIZONTAL_UP, MV_LEFT | MV_RIGHT | MV_UP },
1528 { EL_TUBE_HORIZONTAL_DOWN, MV_LEFT | MV_RIGHT | MV_DOWN },
1529 { EL_TUBE_LEFT_UP, MV_LEFT | MV_UP },
1530 { EL_TUBE_LEFT_DOWN, MV_LEFT | MV_DOWN },
1531 { EL_TUBE_RIGHT_UP, MV_RIGHT | MV_UP },
1532 { EL_TUBE_RIGHT_DOWN, MV_RIGHT | MV_DOWN },
1534 { EL_SP_PORT_LEFT, MV_RIGHT },
1535 { EL_SP_PORT_RIGHT, MV_LEFT },
1536 { EL_SP_PORT_UP, MV_DOWN },
1537 { EL_SP_PORT_DOWN, MV_UP },
1538 { EL_SP_PORT_HORIZONTAL, MV_LEFT | MV_RIGHT },
1539 { EL_SP_PORT_VERTICAL, MV_UP | MV_DOWN },
1540 { EL_SP_PORT_ANY, MV_LEFT | MV_RIGHT | MV_UP | MV_DOWN },
1541 { EL_SP_GRAVITY_PORT_LEFT, MV_RIGHT },
1542 { EL_SP_GRAVITY_PORT_RIGHT, MV_LEFT },
1543 { EL_SP_GRAVITY_PORT_UP, MV_DOWN },
1544 { EL_SP_GRAVITY_PORT_DOWN, MV_UP },
1545 { EL_SP_GRAVITY_ON_PORT_LEFT, MV_RIGHT },
1546 { EL_SP_GRAVITY_ON_PORT_RIGHT, MV_LEFT },
1547 { EL_SP_GRAVITY_ON_PORT_UP, MV_DOWN },
1548 { EL_SP_GRAVITY_ON_PORT_DOWN, MV_UP },
1549 { EL_SP_GRAVITY_OFF_PORT_LEFT, MV_RIGHT },
1550 { EL_SP_GRAVITY_OFF_PORT_RIGHT, MV_LEFT },
1551 { EL_SP_GRAVITY_OFF_PORT_UP, MV_DOWN },
1552 { EL_SP_GRAVITY_OFF_PORT_DOWN, MV_UP },
1554 { EL_UNDEFINED, MV_NONE }
1557 static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS];
1559 #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY])
1560 #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0)
1561 #define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \
1562 IS_JUST_CHANGING(x, y))
1564 #define CE_PAGE(e, ce) (element_info[e].event_page[ce])
1566 // static variables for playfield scan mode (scanning forward or backward)
1567 static int playfield_scan_start_x = 0;
1568 static int playfield_scan_start_y = 0;
1569 static int playfield_scan_delta_x = 1;
1570 static int playfield_scan_delta_y = 1;
1572 #define SCAN_PLAYFIELD(x, y) for ((y) = playfield_scan_start_y; \
1573 (y) >= 0 && (y) <= lev_fieldy - 1; \
1574 (y) += playfield_scan_delta_y) \
1575 for ((x) = playfield_scan_start_x; \
1576 (x) >= 0 && (x) <= lev_fieldx - 1; \
1577 (x) += playfield_scan_delta_x)
1580 void DEBUG_SetMaximumDynamite(void)
1584 for (i = 0; i < MAX_INVENTORY_SIZE; i++)
1585 if (local_player->inventory_size < MAX_INVENTORY_SIZE)
1586 local_player->inventory_element[local_player->inventory_size++] =
1591 static void InitPlayfieldScanModeVars(void)
1593 if (game.use_reverse_scan_direction)
1595 playfield_scan_start_x = lev_fieldx - 1;
1596 playfield_scan_start_y = lev_fieldy - 1;
1598 playfield_scan_delta_x = -1;
1599 playfield_scan_delta_y = -1;
1603 playfield_scan_start_x = 0;
1604 playfield_scan_start_y = 0;
1606 playfield_scan_delta_x = 1;
1607 playfield_scan_delta_y = 1;
1611 static void InitPlayfieldScanMode(int mode)
1613 game.use_reverse_scan_direction =
1614 (mode == CA_ARG_SCAN_MODE_REVERSE ? TRUE : FALSE);
1616 InitPlayfieldScanModeVars();
1619 static int get_move_delay_from_stepsize(int move_stepsize)
1622 MIN(MAX(MOVE_STEPSIZE_MIN, move_stepsize), MOVE_STEPSIZE_MAX);
1624 // make sure that stepsize value is always a power of 2
1625 move_stepsize = (1 << log_2(move_stepsize));
1627 return TILEX / move_stepsize;
1630 static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize,
1633 int player_nr = player->index_nr;
1634 int move_delay = get_move_delay_from_stepsize(move_stepsize);
1635 boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE);
1637 // do no immediately change move delay -- the player might just be moving
1638 player->move_delay_value_next = move_delay;
1640 // information if player can move must be set separately
1641 player->cannot_move = cannot_move;
1645 player->move_delay = game.initial_move_delay[player_nr];
1646 player->move_delay_value = game.initial_move_delay_value[player_nr];
1648 player->move_delay_value_next = -1;
1650 player->move_delay_reset_counter = 0;
1654 void GetPlayerConfig(void)
1656 GameFrameDelay = setup.game_frame_delay;
1658 if (!audio.sound_available)
1659 setup.sound_simple = FALSE;
1661 if (!audio.loops_available)
1662 setup.sound_loops = FALSE;
1664 if (!audio.music_available)
1665 setup.sound_music = FALSE;
1667 if (!video.fullscreen_available)
1668 setup.fullscreen = FALSE;
1670 setup.sound = (setup.sound_simple || setup.sound_loops || setup.sound_music);
1672 SetAudioMode(setup.sound);
1675 int GetElementFromGroupElement(int element)
1677 if (IS_GROUP_ELEMENT(element))
1679 struct ElementGroupInfo *group = element_info[element].group;
1680 int last_anim_random_frame = gfx.anim_random_frame;
1683 if (group->choice_mode == ANIM_RANDOM)
1684 gfx.anim_random_frame = RND(group->num_elements_resolved);
1686 element_pos = getAnimationFrame(group->num_elements_resolved, 1,
1687 group->choice_mode, 0,
1690 if (group->choice_mode == ANIM_RANDOM)
1691 gfx.anim_random_frame = last_anim_random_frame;
1693 group->choice_pos++;
1695 element = group->element_resolved[element_pos];
1701 static void IncrementSokobanFieldsNeeded(void)
1703 if (level.sb_fields_needed)
1704 game.sokoban_fields_still_needed++;
1707 static void IncrementSokobanObjectsNeeded(void)
1709 if (level.sb_objects_needed)
1710 game.sokoban_objects_still_needed++;
1713 static void DecrementSokobanFieldsNeeded(void)
1715 if (game.sokoban_fields_still_needed > 0)
1716 game.sokoban_fields_still_needed--;
1719 static void DecrementSokobanObjectsNeeded(void)
1721 if (game.sokoban_objects_still_needed > 0)
1722 game.sokoban_objects_still_needed--;
1725 static void InitPlayerField(int x, int y, int element, boolean init_game)
1727 if (element == EL_SP_MURPHY)
1731 if (stored_player[0].present)
1733 Tile[x][y] = EL_SP_MURPHY_CLONE;
1739 stored_player[0].initial_element = element;
1740 stored_player[0].use_murphy = TRUE;
1742 if (!level.use_artwork_element[0])
1743 stored_player[0].artwork_element = EL_SP_MURPHY;
1746 Tile[x][y] = EL_PLAYER_1;
1752 struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1];
1753 int jx = player->jx, jy = player->jy;
1755 player->present = TRUE;
1757 player->block_last_field = (element == EL_SP_MURPHY ?
1758 level.sp_block_last_field :
1759 level.block_last_field);
1761 // ---------- initialize player's last field block delay ------------------
1763 // always start with reliable default value (no adjustment needed)
1764 player->block_delay_adjustment = 0;
1766 // special case 1: in Supaplex, Murphy blocks last field one more frame
1767 if (player->block_last_field && element == EL_SP_MURPHY)
1768 player->block_delay_adjustment = 1;
1770 // special case 2: in game engines before 3.1.1, blocking was different
1771 if (game.use_block_last_field_bug)
1772 player->block_delay_adjustment = (player->block_last_field ? -1 : 1);
1774 if (!network.enabled || player->connected_network)
1776 player->active = TRUE;
1778 // remove potentially duplicate players
1779 if (StorePlayer[jx][jy] == Tile[x][y])
1780 StorePlayer[jx][jy] = 0;
1782 StorePlayer[x][y] = Tile[x][y];
1784 #if DEBUG_INIT_PLAYER
1785 Debug("game:init:player", "- player element %d activated",
1786 player->element_nr);
1787 Debug("game:init:player", " (local player is %d and currently %s)",
1788 local_player->element_nr,
1789 local_player->active ? "active" : "not active");
1793 Tile[x][y] = EL_EMPTY;
1795 player->jx = player->last_jx = x;
1796 player->jy = player->last_jy = y;
1799 // always check if player was just killed and should be reanimated
1801 int player_nr = GET_PLAYER_NR(element);
1802 struct PlayerInfo *player = &stored_player[player_nr];
1804 if (player->active && player->killed)
1805 player->reanimated = TRUE; // if player was just killed, reanimate him
1809 static void InitField(int x, int y, boolean init_game)
1811 int element = Tile[x][y];
1820 InitPlayerField(x, y, element, init_game);
1823 case EL_SOKOBAN_FIELD_PLAYER:
1824 element = Tile[x][y] = EL_PLAYER_1;
1825 InitField(x, y, init_game);
1827 element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY;
1828 InitField(x, y, init_game);
1831 case EL_SOKOBAN_FIELD_EMPTY:
1832 IncrementSokobanFieldsNeeded();
1835 case EL_SOKOBAN_OBJECT:
1836 IncrementSokobanObjectsNeeded();
1840 if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID)
1841 Tile[x][y] = EL_ACID_POOL_TOPLEFT;
1842 else if (x > 0 && Tile[x - 1][y] == EL_ACID)
1843 Tile[x][y] = EL_ACID_POOL_TOPRIGHT;
1844 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT)
1845 Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT;
1846 else if (y > 0 && Tile[x][y - 1] == EL_ACID)
1847 Tile[x][y] = EL_ACID_POOL_BOTTOM;
1848 else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT)
1849 Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT;
1858 case EL_SPACESHIP_RIGHT:
1859 case EL_SPACESHIP_UP:
1860 case EL_SPACESHIP_LEFT:
1861 case EL_SPACESHIP_DOWN:
1862 case EL_BD_BUTTERFLY:
1863 case EL_BD_BUTTERFLY_RIGHT:
1864 case EL_BD_BUTTERFLY_UP:
1865 case EL_BD_BUTTERFLY_LEFT:
1866 case EL_BD_BUTTERFLY_DOWN:
1868 case EL_BD_FIREFLY_RIGHT:
1869 case EL_BD_FIREFLY_UP:
1870 case EL_BD_FIREFLY_LEFT:
1871 case EL_BD_FIREFLY_DOWN:
1872 case EL_PACMAN_RIGHT:
1874 case EL_PACMAN_LEFT:
1875 case EL_PACMAN_DOWN:
1877 case EL_YAMYAM_LEFT:
1878 case EL_YAMYAM_RIGHT:
1880 case EL_YAMYAM_DOWN:
1881 case EL_DARK_YAMYAM:
1884 case EL_SP_SNIKSNAK:
1885 case EL_SP_ELECTRON:
1891 case EL_SPRING_LEFT:
1892 case EL_SPRING_RIGHT:
1896 case EL_AMOEBA_FULL:
1901 case EL_AMOEBA_DROP:
1902 if (y == lev_fieldy - 1)
1904 Tile[x][y] = EL_AMOEBA_GROWING;
1905 Store[x][y] = EL_AMOEBA_WET;
1909 case EL_DYNAMITE_ACTIVE:
1910 case EL_SP_DISK_RED_ACTIVE:
1911 case EL_DYNABOMB_PLAYER_1_ACTIVE:
1912 case EL_DYNABOMB_PLAYER_2_ACTIVE:
1913 case EL_DYNABOMB_PLAYER_3_ACTIVE:
1914 case EL_DYNABOMB_PLAYER_4_ACTIVE:
1915 MovDelay[x][y] = 96;
1918 case EL_EM_DYNAMITE_ACTIVE:
1919 MovDelay[x][y] = 32;
1923 game.lights_still_needed++;
1927 game.friends_still_needed++;
1932 GfxDir[x][y] = MovDir[x][y] = 1 << RND(4);
1935 case EL_CONVEYOR_BELT_1_SWITCH_LEFT:
1936 case EL_CONVEYOR_BELT_1_SWITCH_MIDDLE:
1937 case EL_CONVEYOR_BELT_1_SWITCH_RIGHT:
1938 case EL_CONVEYOR_BELT_2_SWITCH_LEFT:
1939 case EL_CONVEYOR_BELT_2_SWITCH_MIDDLE:
1940 case EL_CONVEYOR_BELT_2_SWITCH_RIGHT:
1941 case EL_CONVEYOR_BELT_3_SWITCH_LEFT:
1942 case EL_CONVEYOR_BELT_3_SWITCH_MIDDLE:
1943 case EL_CONVEYOR_BELT_3_SWITCH_RIGHT:
1944 case EL_CONVEYOR_BELT_4_SWITCH_LEFT:
1945 case EL_CONVEYOR_BELT_4_SWITCH_MIDDLE:
1946 case EL_CONVEYOR_BELT_4_SWITCH_RIGHT:
1949 int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]);
1950 int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]);
1951 int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]);
1953 if (game.belt_dir_nr[belt_nr] == 3) // initial value
1955 game.belt_dir[belt_nr] = belt_dir;
1956 game.belt_dir_nr[belt_nr] = belt_dir_nr;
1958 else // more than one switch -- set it like the first switch
1960 Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr];
1965 case EL_LIGHT_SWITCH_ACTIVE:
1967 game.light_time_left = level.time_light * FRAMES_PER_SECOND;
1970 case EL_INVISIBLE_STEELWALL:
1971 case EL_INVISIBLE_WALL:
1972 case EL_INVISIBLE_SAND:
1973 if (game.light_time_left > 0 ||
1974 game.lenses_time_left > 0)
1975 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
1978 case EL_EMC_MAGIC_BALL:
1979 if (game.ball_active)
1980 Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
1983 case EL_EMC_MAGIC_BALL_SWITCH:
1984 if (game.ball_active)
1985 Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
1988 case EL_TRIGGER_PLAYER:
1989 case EL_TRIGGER_ELEMENT:
1990 case EL_TRIGGER_CE_VALUE:
1991 case EL_TRIGGER_CE_SCORE:
1993 case EL_ANY_ELEMENT:
1994 case EL_CURRENT_CE_VALUE:
1995 case EL_CURRENT_CE_SCORE:
2012 // reference elements should not be used on the playfield
2013 Tile[x][y] = EL_EMPTY;
2017 if (IS_CUSTOM_ELEMENT(element))
2019 if (CAN_MOVE(element))
2022 if (!element_info[element].use_last_ce_value || init_game)
2023 CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]);
2025 else if (IS_GROUP_ELEMENT(element))
2027 Tile[x][y] = GetElementFromGroupElement(element);
2029 InitField(x, y, init_game);
2031 else if (IS_EMPTY_ELEMENT(element))
2033 GfxElementEmpty[x][y] = element;
2034 Tile[x][y] = EL_EMPTY;
2036 if (element_info[element].use_gfx_element)
2037 game.use_masked_elements = TRUE;
2044 CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
2047 static void InitField_WithBug1(int x, int y, boolean init_game)
2049 InitField(x, y, init_game);
2051 // not needed to call InitMovDir() -- already done by InitField()!
2052 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2053 CAN_MOVE(Tile[x][y]))
2057 static void InitField_WithBug2(int x, int y, boolean init_game)
2059 int old_element = Tile[x][y];
2061 InitField(x, y, init_game);
2063 // not needed to call InitMovDir() -- already done by InitField()!
2064 if (game.engine_version < VERSION_IDENT(3,1,0,0) &&
2065 CAN_MOVE(old_element) &&
2066 (old_element < EL_MOLE_LEFT || old_element > EL_MOLE_DOWN))
2069 /* this case is in fact a combination of not less than three bugs:
2070 first, it calls InitMovDir() for elements that can move, although this is
2071 already done by InitField(); then, it checks the element that was at this
2072 field _before_ the call to InitField() (which can change it); lastly, it
2073 was not called for "mole with direction" elements, which were treated as
2074 "cannot move" due to (fixed) wrong element initialization in "src/init.c"
2078 static int get_key_element_from_nr(int key_nr)
2080 int key_base_element = (key_nr >= STD_NUM_KEYS ? EL_EMC_KEY_5 - STD_NUM_KEYS :
2081 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2082 EL_EM_KEY_1 : EL_KEY_1);
2084 return key_base_element + key_nr;
2087 static int get_next_dropped_element(struct PlayerInfo *player)
2089 return (player->inventory_size > 0 ?
2090 player->inventory_element[player->inventory_size - 1] :
2091 player->inventory_infinite_element != EL_UNDEFINED ?
2092 player->inventory_infinite_element :
2093 player->dynabombs_left > 0 ?
2094 EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr :
2098 static int get_inventory_element_from_pos(struct PlayerInfo *player, int pos)
2100 // pos >= 0: get element from bottom of the stack;
2101 // pos < 0: get element from top of the stack
2105 int min_inventory_size = -pos;
2106 int inventory_pos = player->inventory_size - min_inventory_size;
2107 int min_dynabombs_left = min_inventory_size - player->inventory_size;
2109 return (player->inventory_size >= min_inventory_size ?
2110 player->inventory_element[inventory_pos] :
2111 player->inventory_infinite_element != EL_UNDEFINED ?
2112 player->inventory_infinite_element :
2113 player->dynabombs_left >= min_dynabombs_left ?
2114 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2119 int min_dynabombs_left = pos + 1;
2120 int min_inventory_size = pos + 1 - player->dynabombs_left;
2121 int inventory_pos = pos - player->dynabombs_left;
2123 return (player->inventory_infinite_element != EL_UNDEFINED ?
2124 player->inventory_infinite_element :
2125 player->dynabombs_left >= min_dynabombs_left ?
2126 EL_DYNABOMB_PLAYER_1 + player->index_nr :
2127 player->inventory_size >= min_inventory_size ?
2128 player->inventory_element[inventory_pos] :
2133 static int compareGamePanelOrderInfo(const void *object1, const void *object2)
2135 const struct GamePanelOrderInfo *gpo1 = (struct GamePanelOrderInfo *)object1;
2136 const struct GamePanelOrderInfo *gpo2 = (struct GamePanelOrderInfo *)object2;
2139 if (gpo1->sort_priority != gpo2->sort_priority)
2140 compare_result = gpo1->sort_priority - gpo2->sort_priority;
2142 compare_result = gpo1->nr - gpo2->nr;
2144 return compare_result;
2147 int getPlayerInventorySize(int player_nr)
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2150 return game_em.ply[player_nr]->dynamite;
2151 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
2152 return game_sp.red_disk_count;
2154 return stored_player[player_nr].inventory_size;
2157 static void InitGameControlValues(void)
2161 for (i = 0; game_panel_controls[i].nr != -1; i++)
2163 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2164 struct GamePanelOrderInfo *gpo = &game_panel_order[i];
2165 struct TextPosInfo *pos = gpc->pos;
2167 int type = gpc->type;
2171 Error("'game_panel_controls' structure corrupted at %d", i);
2173 Fail("this should not happen -- please debug");
2176 // force update of game controls after initialization
2177 gpc->value = gpc->last_value = -1;
2178 gpc->frame = gpc->last_frame = -1;
2179 gpc->gfx_frame = -1;
2181 // determine panel value width for later calculation of alignment
2182 if (type == TYPE_INTEGER || type == TYPE_STRING)
2184 pos->width = pos->size * getFontWidth(pos->font);
2185 pos->height = getFontHeight(pos->font);
2187 else if (type == TYPE_ELEMENT)
2189 pos->width = pos->size;
2190 pos->height = pos->size;
2193 // fill structure for game panel draw order
2195 gpo->sort_priority = pos->sort_priority;
2198 // sort game panel controls according to sort_priority and control number
2199 qsort(game_panel_order, NUM_GAME_PANEL_CONTROLS,
2200 sizeof(struct GamePanelOrderInfo), compareGamePanelOrderInfo);
2203 static void UpdatePlayfieldElementCount(void)
2205 boolean use_element_count = FALSE;
2208 // first check if it is needed at all to calculate playfield element count
2209 for (i = GAME_PANEL_ELEMENT_COUNT_1; i <= GAME_PANEL_ELEMENT_COUNT_8; i++)
2210 if (!PANEL_DEACTIVATED(game_panel_controls[i].pos))
2211 use_element_count = TRUE;
2213 if (!use_element_count)
2216 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
2217 element_info[i].element_count = 0;
2219 SCAN_PLAYFIELD(x, y)
2221 element_info[Tile[x][y]].element_count++;
2224 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
2225 for (j = 0; j < MAX_NUM_ELEMENTS; j++)
2226 if (IS_IN_GROUP(j, i))
2227 element_info[EL_GROUP_START + i].element_count +=
2228 element_info[j].element_count;
2231 static void UpdateGameControlValues(void)
2234 int time = (game.LevelSolved ?
2235 game.LevelSolved_CountingTime :
2236 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2238 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2239 game_sp.time_played :
2240 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2241 game_mm.energy_left :
2242 game.no_time_limit ? TimePlayed : TimeLeft);
2243 int score = (game.LevelSolved ?
2244 game.LevelSolved_CountingScore :
2245 level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2246 game_em.lev->score :
2247 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2249 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2252 int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2253 game_em.lev->gems_needed :
2254 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2255 game_sp.infotrons_still_needed :
2256 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2257 game_mm.kettles_still_needed :
2258 game.gems_still_needed);
2259 int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
2260 game_em.lev->gems_needed > 0 :
2261 level.game_engine_type == GAME_ENGINE_TYPE_SP ?
2262 game_sp.infotrons_still_needed > 0 :
2263 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2264 game_mm.kettles_still_needed > 0 ||
2265 game_mm.lights_still_needed > 0 :
2266 game.gems_still_needed > 0 ||
2267 game.sokoban_fields_still_needed > 0 ||
2268 game.sokoban_objects_still_needed > 0 ||
2269 game.lights_still_needed > 0);
2270 int health = (game.LevelSolved ?
2271 game.LevelSolved_CountingHealth :
2272 level.game_engine_type == GAME_ENGINE_TYPE_MM ?
2273 MM_HEALTH(game_mm.laser_overload_value) :
2275 int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized
2277 UpdatePlayfieldElementCount();
2279 // update game panel control values
2281 // used instead of "level_nr" (for network games)
2282 game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr;
2283 game_panel_controls[GAME_PANEL_GEMS].value = gems;
2285 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0;
2286 for (i = 0; i < MAX_NUM_KEYS; i++)
2287 game_panel_controls[GAME_PANEL_KEY_1 + i].value = EL_EMPTY;
2288 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_EMPTY;
2289 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value = 0;
2291 if (game.centered_player_nr == -1)
2293 for (i = 0; i < MAX_PLAYERS; i++)
2295 // only one player in Supaplex game engine
2296 if (level.game_engine_type == GAME_ENGINE_TYPE_SP && i > 0)
2299 for (k = 0; k < MAX_NUM_KEYS; k++)
2301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2303 if (game_em.ply[i]->keys & (1 << k))
2304 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2305 get_key_element_from_nr(k);
2307 else if (stored_player[i].key[k])
2308 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2309 get_key_element_from_nr(k);
2312 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2313 getPlayerInventorySize(i);
2315 if (stored_player[i].num_white_keys > 0)
2316 game_panel_controls[GAME_PANEL_KEY_WHITE].value =
2319 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2320 stored_player[i].num_white_keys;
2325 int player_nr = game.centered_player_nr;
2327 for (k = 0; k < MAX_NUM_KEYS; k++)
2329 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
2331 if (game_em.ply[player_nr]->keys & (1 << k))
2332 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2333 get_key_element_from_nr(k);
2335 else if (stored_player[player_nr].key[k])
2336 game_panel_controls[GAME_PANEL_KEY_1 + k].value =
2337 get_key_element_from_nr(k);
2340 game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value +=
2341 getPlayerInventorySize(player_nr);
2343 if (stored_player[player_nr].num_white_keys > 0)
2344 game_panel_controls[GAME_PANEL_KEY_WHITE].value = EL_DC_KEY_WHITE;
2346 game_panel_controls[GAME_PANEL_KEY_WHITE_COUNT].value +=
2347 stored_player[player_nr].num_white_keys;
2350 // re-arrange keys on game panel, if needed or if defined by style settings
2351 for (i = 0; i < MAX_NUM_KEYS + 1; i++) // all normal keys + white key
2353 int nr = GAME_PANEL_KEY_1 + i;
2354 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2355 struct TextPosInfo *pos = gpc->pos;
2357 // skip check if key is not in the player's inventory
2358 if (gpc->value == EL_EMPTY)
2361 // check if keys should be arranged on panel from left to right
2362 if (pos->style == STYLE_LEFTMOST_POSITION)
2364 // check previous key positions (left from current key)
2365 for (k = 0; k < i; k++)
2367 int nr_new = GAME_PANEL_KEY_1 + k;
2369 if (game_panel_controls[nr_new].value == EL_EMPTY)
2371 game_panel_controls[nr_new].value = gpc->value;
2372 gpc->value = EL_EMPTY;
2379 // check if "undefined" keys can be placed at some other position
2380 if (pos->x == -1 && pos->y == -1)
2382 int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
2384 // 1st try: display key at the same position as normal or EM keys
2385 if (game_panel_controls[nr_new].value == EL_EMPTY)
2387 game_panel_controls[nr_new].value = gpc->value;
2391 // 2nd try: display key at the next free position in the key panel
2392 for (k = 0; k < STD_NUM_KEYS; k++)
2394 nr_new = GAME_PANEL_KEY_1 + k;
2396 if (game_panel_controls[nr_new].value == EL_EMPTY)
2398 game_panel_controls[nr_new].value = gpc->value;
2407 for (i = 0; i < NUM_PANEL_INVENTORY; i++)
2409 game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
2410 get_inventory_element_from_pos(local_player, i);
2411 game_panel_controls[GAME_PANEL_INVENTORY_LAST_1 + i].value =
2412 get_inventory_element_from_pos(local_player, -i - 1);
2415 game_panel_controls[GAME_PANEL_SCORE].value = score;
2416 game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score;
2418 game_panel_controls[GAME_PANEL_TIME].value = time;
2420 game_panel_controls[GAME_PANEL_TIME_HH].value = time / 3600;
2421 game_panel_controls[GAME_PANEL_TIME_MM].value = (time / 60) % 60;
2422 game_panel_controls[GAME_PANEL_TIME_SS].value = time % 60;
2424 if (level.time == 0)
2425 game_panel_controls[GAME_PANEL_TIME_ANIM].value = 100;
2427 game_panel_controls[GAME_PANEL_TIME_ANIM].value = time * 100 / level.time;
2429 game_panel_controls[GAME_PANEL_HEALTH].value = health;
2430 game_panel_controls[GAME_PANEL_HEALTH_ANIM].value = health;
2432 game_panel_controls[GAME_PANEL_FRAME].value = FrameCounter;
2434 game_panel_controls[GAME_PANEL_SHIELD_NORMAL].value =
2435 (local_player->shield_normal_time_left > 0 ? EL_SHIELD_NORMAL_ACTIVE :
2437 game_panel_controls[GAME_PANEL_SHIELD_NORMAL_TIME].value =
2438 local_player->shield_normal_time_left;
2439 game_panel_controls[GAME_PANEL_SHIELD_DEADLY].value =
2440 (local_player->shield_deadly_time_left > 0 ? EL_SHIELD_DEADLY_ACTIVE :
2442 game_panel_controls[GAME_PANEL_SHIELD_DEADLY_TIME].value =
2443 local_player->shield_deadly_time_left;
2445 game_panel_controls[GAME_PANEL_EXIT].value =
2446 (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
2448 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
2449 (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
2450 game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
2451 (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
2452 EL_EMC_MAGIC_BALL_SWITCH);
2454 game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
2455 (game.light_time_left > 0 ? EL_LIGHT_SWITCH_ACTIVE : EL_LIGHT_SWITCH);
2456 game_panel_controls[GAME_PANEL_LIGHT_SWITCH_TIME].value =
2457 game.light_time_left;
2459 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH].value =
2460 (game.timegate_time_left > 0 ? EL_TIMEGATE_OPEN : EL_TIMEGATE_CLOSED);
2461 game_panel_controls[GAME_PANEL_TIMEGATE_SWITCH_TIME].value =
2462 game.timegate_time_left;
2464 game_panel_controls[GAME_PANEL_SWITCHGATE_SWITCH].value =
2465 EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos;
2467 game_panel_controls[GAME_PANEL_EMC_LENSES].value =
2468 (game.lenses_time_left > 0 ? EL_EMC_LENSES : EL_EMPTY);
2469 game_panel_controls[GAME_PANEL_EMC_LENSES_TIME].value =
2470 game.lenses_time_left;
2472 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER].value =
2473 (game.magnify_time_left > 0 ? EL_EMC_MAGNIFIER : EL_EMPTY);
2474 game_panel_controls[GAME_PANEL_EMC_MAGNIFIER_TIME].value =
2475 game.magnify_time_left;
2477 game_panel_controls[GAME_PANEL_BALLOON_SWITCH].value =
2478 (game.wind_direction == MV_LEFT ? EL_BALLOON_SWITCH_LEFT :
2479 game.wind_direction == MV_RIGHT ? EL_BALLOON_SWITCH_RIGHT :
2480 game.wind_direction == MV_UP ? EL_BALLOON_SWITCH_UP :
2481 game.wind_direction == MV_DOWN ? EL_BALLOON_SWITCH_DOWN :
2482 EL_BALLOON_SWITCH_NONE);
2484 game_panel_controls[GAME_PANEL_DYNABOMB_NUMBER].value =
2485 local_player->dynabomb_count;
2486 game_panel_controls[GAME_PANEL_DYNABOMB_SIZE].value =
2487 local_player->dynabomb_size;
2488 game_panel_controls[GAME_PANEL_DYNABOMB_POWER].value =
2489 (local_player->dynabomb_xl ? EL_DYNABOMB_INCREASE_POWER : EL_EMPTY);
2491 game_panel_controls[GAME_PANEL_PENGUINS].value =
2492 game.friends_still_needed;
2494 game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
2495 game.sokoban_objects_still_needed;
2496 game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
2497 game.sokoban_fields_still_needed;
2499 game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
2500 (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
2502 for (i = 0; i < NUM_BELTS; i++)
2504 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1 + i].value =
2505 (game.belt_dir[i] != MV_NONE ? EL_CONVEYOR_BELT_1_MIDDLE_ACTIVE :
2506 EL_CONVEYOR_BELT_1_MIDDLE) + i;
2507 game_panel_controls[GAME_PANEL_CONVEYOR_BELT_1_SWITCH + i].value =
2508 getBeltSwitchElementFromBeltNrAndBeltDir(i, game.belt_dir[i]);
2511 game_panel_controls[GAME_PANEL_MAGIC_WALL].value =
2512 (game.magic_wall_active ? EL_MAGIC_WALL_ACTIVE : EL_MAGIC_WALL);
2513 game_panel_controls[GAME_PANEL_MAGIC_WALL_TIME].value =
2514 game.magic_wall_time_left;
2516 game_panel_controls[GAME_PANEL_GRAVITY_STATE].value =
2517 local_player->gravity;
2519 for (i = 0; i < NUM_PANEL_GRAPHICS; i++)
2520 game_panel_controls[GAME_PANEL_GRAPHIC_1 + i].value = EL_GRAPHIC_1 + i;
2522 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2523 game_panel_controls[GAME_PANEL_ELEMENT_1 + i].value =
2524 (IS_DRAWABLE_ELEMENT(game.panel.element[i].id) ?
2525 game.panel.element[i].id : EL_UNDEFINED);
2527 for (i = 0; i < NUM_PANEL_ELEMENTS; i++)
2528 game_panel_controls[GAME_PANEL_ELEMENT_COUNT_1 + i].value =
2529 (IS_VALID_ELEMENT(game.panel.element_count[i].id) ?
2530 element_info[game.panel.element_count[i].id].element_count : 0);
2532 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2533 game_panel_controls[GAME_PANEL_CE_SCORE_1 + i].value =
2534 (IS_CUSTOM_ELEMENT(game.panel.ce_score[i].id) ?
2535 element_info[game.panel.ce_score[i].id].collect_score : 0);
2537 for (i = 0; i < NUM_PANEL_CE_SCORE; i++)
2538 game_panel_controls[GAME_PANEL_CE_SCORE_1_ELEMENT + i].value =
2539 (IS_CUSTOM_ELEMENT(game.panel.ce_score_element[i].id) ?
2540 element_info[game.panel.ce_score_element[i].id].collect_score :
2543 game_panel_controls[GAME_PANEL_PLAYER_NAME].value = 0;
2544 game_panel_controls[GAME_PANEL_LEVEL_NAME].value = 0;
2545 game_panel_controls[GAME_PANEL_LEVEL_AUTHOR].value = 0;
2547 // update game panel control frames
2549 for (i = 0; game_panel_controls[i].nr != -1; i++)
2551 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2553 if (gpc->type == TYPE_ELEMENT)
2555 if (gpc->value != EL_UNDEFINED && gpc->value != EL_EMPTY)
2557 int last_anim_random_frame = gfx.anim_random_frame;
2558 int element = gpc->value;
2559 int graphic = el2panelimg(element);
2560 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2561 sync_random_frame : INIT_GFX_RANDOM());
2563 if (gpc->value != gpc->last_value)
2566 gpc->gfx_random = init_gfx_random;
2572 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2573 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2574 gpc->gfx_random = init_gfx_random;
2577 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2578 gfx.anim_random_frame = gpc->gfx_random;
2580 if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
2581 gpc->gfx_frame = element_info[element].collect_score;
2583 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2585 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2586 gfx.anim_random_frame = last_anim_random_frame;
2589 else if (gpc->type == TYPE_GRAPHIC)
2591 if (gpc->graphic != IMG_UNDEFINED)
2593 int last_anim_random_frame = gfx.anim_random_frame;
2594 int graphic = gpc->graphic;
2595 int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
2596 sync_random_frame : INIT_GFX_RANDOM());
2598 if (gpc->value != gpc->last_value)
2601 gpc->gfx_random = init_gfx_random;
2607 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
2608 IS_NEXT_FRAME(gpc->gfx_frame, graphic))
2609 gpc->gfx_random = init_gfx_random;
2612 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2613 gfx.anim_random_frame = gpc->gfx_random;
2615 gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
2617 if (ANIM_MODE(graphic) == ANIM_RANDOM)
2618 gfx.anim_random_frame = last_anim_random_frame;
2624 static void DisplayGameControlValues(void)
2626 boolean redraw_panel = FALSE;
2629 for (i = 0; game_panel_controls[i].nr != -1; i++)
2631 struct GamePanelControlInfo *gpc = &game_panel_controls[i];
2633 if (PANEL_DEACTIVATED(gpc->pos))
2636 if (gpc->value == gpc->last_value &&
2637 gpc->frame == gpc->last_frame)
2640 redraw_panel = TRUE;
2646 // copy default game door content to main double buffer
2648 // !!! CHECK AGAIN !!!
2649 SetPanelBackground();
2650 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
2651 DrawBackground(DX, DY, DXSIZE, DYSIZE);
2653 // redraw game control buttons
2654 RedrawGameButtons();
2656 SetGameStatus(GAME_MODE_PSEUDO_PANEL);
2658 for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++)
2660 int nr = game_panel_order[i].nr;
2661 struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
2662 struct TextPosInfo *pos = gpc->pos;
2663 int type = gpc->type;
2664 int value = gpc->value;
2665 int frame = gpc->frame;
2666 int size = pos->size;
2667 int font = pos->font;
2668 boolean draw_masked = pos->draw_masked;
2669 int mask_mode = (draw_masked ? BLIT_MASKED : BLIT_OPAQUE);
2671 if (PANEL_DEACTIVATED(pos))
2674 if (pos->class == get_hash_from_key("extra_panel_items") &&
2675 !setup.prefer_extra_panel_items)
2678 gpc->last_value = value;
2679 gpc->last_frame = frame;
2681 if (type == TYPE_INTEGER)
2683 if (nr == GAME_PANEL_LEVEL_NUMBER ||
2684 nr == GAME_PANEL_INVENTORY_COUNT ||
2685 nr == GAME_PANEL_SCORE ||
2686 nr == GAME_PANEL_HIGHSCORE ||
2687 nr == GAME_PANEL_TIME)
2689 boolean use_dynamic_size = (size == -1 ? TRUE : FALSE);
2691 if (use_dynamic_size) // use dynamic number of digits
2693 int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 :
2694 nr == GAME_PANEL_INVENTORY_COUNT ||
2695 nr == GAME_PANEL_TIME ? 1000 : 100000);
2696 int size_add = (nr == GAME_PANEL_LEVEL_NUMBER ||
2697 nr == GAME_PANEL_INVENTORY_COUNT ||
2698 nr == GAME_PANEL_TIME ? 1 : 2);
2699 int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 :
2700 nr == GAME_PANEL_INVENTORY_COUNT ||
2701 nr == GAME_PANEL_TIME ? 3 : 5);
2702 int size2 = size1 + size_add;
2703 int font1 = pos->font;
2704 int font2 = pos->font_alt;
2706 size = (value < value_change ? size1 : size2);
2707 font = (value < value_change ? font1 : font2);
2711 // correct text size if "digits" is zero or less
2713 size = strlen(int2str(value, size));
2715 // dynamically correct text alignment
2716 pos->width = size * getFontWidth(font);
2718 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2719 int2str(value, size), font, mask_mode);
2721 else if (type == TYPE_ELEMENT)
2723 int element, graphic;
2727 int dst_x = PANEL_XPOS(pos);
2728 int dst_y = PANEL_YPOS(pos);
2730 if (value != EL_UNDEFINED && value != EL_EMPTY)
2733 graphic = el2panelimg(value);
2736 Debug("game:DisplayGameControlValues", "%d, '%s' [%d]",
2737 element, EL_NAME(element), size);
2740 if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0)
2743 getSizedGraphicSource(graphic, frame, size, &src_bitmap,
2746 width = graphic_info[graphic].width * size / TILESIZE;
2747 height = graphic_info[graphic].height * size / TILESIZE;
2750 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2753 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2757 else if (type == TYPE_GRAPHIC)
2759 int graphic = gpc->graphic;
2760 int graphic_active = gpc->graphic_active;
2764 int dst_x = PANEL_XPOS(pos);
2765 int dst_y = PANEL_YPOS(pos);
2766 boolean skip = (pos->class == get_hash_from_key("mm_engine_only") &&
2767 level.game_engine_type != GAME_ENGINE_TYPE_MM);
2769 if (graphic != IMG_UNDEFINED && !skip)
2771 if (pos->style == STYLE_REVERSE)
2772 value = 100 - value;
2774 getGraphicSource(graphic_active, frame, &src_bitmap, &src_x, &src_y);
2776 if (pos->direction & MV_HORIZONTAL)
2778 width = graphic_info[graphic_active].width * value / 100;
2779 height = graphic_info[graphic_active].height;
2781 if (pos->direction == MV_LEFT)
2783 src_x += graphic_info[graphic_active].width - width;
2784 dst_x += graphic_info[graphic_active].width - width;
2789 width = graphic_info[graphic_active].width;
2790 height = graphic_info[graphic_active].height * value / 100;
2792 if (pos->direction == MV_UP)
2794 src_y += graphic_info[graphic_active].height - height;
2795 dst_y += graphic_info[graphic_active].height - height;
2800 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2803 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2806 getGraphicSource(graphic, frame, &src_bitmap, &src_x, &src_y);
2808 if (pos->direction & MV_HORIZONTAL)
2810 if (pos->direction == MV_RIGHT)
2817 dst_x = PANEL_XPOS(pos);
2820 width = graphic_info[graphic].width - width;
2824 if (pos->direction == MV_DOWN)
2831 dst_y = PANEL_YPOS(pos);
2834 height = graphic_info[graphic].height - height;
2838 BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, width, height,
2841 BlitBitmap(src_bitmap, drawto, src_x, src_y, width, height,
2845 else if (type == TYPE_STRING)
2847 boolean active = (value != 0);
2848 char *state_normal = "off";
2849 char *state_active = "on";
2850 char *state = (active ? state_active : state_normal);
2851 char *s = (nr == GAME_PANEL_GRAVITY_STATE ? state :
2852 nr == GAME_PANEL_PLAYER_NAME ? setup.player_name :
2853 nr == GAME_PANEL_LEVEL_NAME ? level.name :
2854 nr == GAME_PANEL_LEVEL_AUTHOR ? level.author : NULL);
2856 if (nr == GAME_PANEL_GRAVITY_STATE)
2858 int font1 = pos->font; // (used for normal state)
2859 int font2 = pos->font_alt; // (used for active state)
2861 font = (active ? font2 : font1);
2870 // don't truncate output if "chars" is zero or less
2873 // dynamically correct text alignment
2874 pos->width = size * getFontWidth(font);
2877 s_cut = getStringCopyN(s, size);
2879 DrawTextExt(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos),
2880 s_cut, font, mask_mode);
2886 redraw_mask |= REDRAW_DOOR_1;
2889 SetGameStatus(GAME_MODE_PLAYING);
2892 void UpdateAndDisplayGameControlValues(void)
2894 if (tape.deactivate_display)
2897 UpdateGameControlValues();
2898 DisplayGameControlValues();
2901 void UpdateGameDoorValues(void)
2903 UpdateGameControlValues();
2906 void DrawGameDoorValues(void)
2908 DisplayGameControlValues();
2912 // ============================================================================
2914 // ----------------------------------------------------------------------------
2915 // initialize game engine due to level / tape version number
2916 // ============================================================================
2918 static void InitGameEngine(void)
2920 int i, j, k, l, x, y;
2922 // set game engine from tape file when re-playing, else from level file
2923 game.engine_version = (tape.playing ? tape.engine_version :
2924 level.game_version);
2926 // set single or multi-player game mode (needed for re-playing tapes)
2927 game.team_mode = setup.team_mode;
2931 int num_players = 0;
2933 for (i = 0; i < MAX_PLAYERS; i++)
2934 if (tape.player_participates[i])
2937 // multi-player tapes contain input data for more than one player
2938 game.team_mode = (num_players > 1);
2942 Debug("game:init:level", "level %d: level.game_version == %06d", level_nr,
2943 level.game_version);
2944 Debug("game:init:level", " tape.file_version == %06d",
2946 Debug("game:init:level", " tape.game_version == %06d",
2948 Debug("game:init:level", " tape.engine_version == %06d",
2949 tape.engine_version);
2950 Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]",
2951 game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
2954 // --------------------------------------------------------------------------
2955 // set flags for bugs and changes according to active game engine version
2956 // --------------------------------------------------------------------------
2960 Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
2962 Bug was introduced in version:
2965 Bug was fixed in version:
2969 In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
2970 but the property "can fall" was missing, which caused some levels to be
2971 unsolvable. This was fixed in version 4.2.0.0.
2973 Affected levels/tapes:
2974 An example for a tape that was fixed by this bugfix is tape 029 from the
2975 level set "rnd_sam_bateman".
2976 The wrong behaviour will still be used for all levels or tapes that were
2977 created/recorded with it. An example for this is tape 023 from the level
2978 set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
2981 boolean use_amoeba_dropping_cannot_fall_bug =
2982 ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
2983 game.engine_version < VERSION_IDENT(4,2,0,0)) ||
2985 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
2986 tape.game_version < VERSION_IDENT(4,2,0,0)));
2989 Summary of bugfix/change:
2990 Fixed move speed of elements entering or leaving magic wall.
2992 Fixed/changed in version:
2996 Before 2.0.1, move speed of elements entering or leaving magic wall was
2997 twice as fast as it is now.
2998 Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
3000 Affected levels/tapes:
3001 The first condition is generally needed for all levels/tapes before version
3002 2.0.1, which might use the old behaviour before it was changed; known tapes
3003 that are affected: Tape 014 from the level set "rnd_conor_mancone".
3004 The second condition is an exception from the above case and is needed for
3005 the special case of tapes recorded with game (not engine!) version 2.0.1 or
3006 above, but before it was known that this change would break tapes like the
3007 above and was fixed in 4.2.0.0, so that the changed behaviour was active
3008 although the engine version while recording maybe was before 2.0.1. There
3009 are a lot of tapes that are affected by this exception, like tape 006 from
3010 the level set "rnd_conor_mancone".
3013 boolean use_old_move_stepsize_for_magic_wall =
3014 (game.engine_version < VERSION_IDENT(2,0,1,0) &&
3016 tape.game_version >= VERSION_IDENT(2,0,1,0) &&
3017 tape.game_version < VERSION_IDENT(4,2,0,0)));
3020 Summary of bugfix/change:
3021 Fixed handling for custom elements that change when pushed by the player.
3023 Fixed/changed in version:
3027 Before 3.1.0, custom elements that "change when pushing" changed directly
3028 after the player started pushing them (until then handled in "DigField()").
3029 Since 3.1.0, these custom elements are not changed until the "pushing"
3030 move of the element is finished (now handled in "ContinueMoving()").
3032 Affected levels/tapes:
3033 The first condition is generally needed for all levels/tapes before version
3034 3.1.0, which might use the old behaviour before it was changed; known tapes
3035 that are affected are some tapes from the level set "Walpurgis Gardens" by
3037 The second condition is an exception from the above case and is needed for
3038 the special case of tapes recorded with game (not engine!) version 3.1.0 or
3039 above (including some development versions of 3.1.0), but before it was
3040 known that this change would break tapes like the above and was fixed in
3041 3.1.1, so that the changed behaviour was active although the engine version
3042 while recording maybe was before 3.1.0. There is at least one tape that is
3043 affected by this exception, which is the tape for the one-level set "Bug
3044 Machine" by Juergen Bonhagen.
3047 game.use_change_when_pushing_bug =
3048 (game.engine_version < VERSION_IDENT(3,1,0,0) &&
3050 tape.game_version >= VERSION_IDENT(3,1,0,0) &&
3051 tape.game_version < VERSION_IDENT(3,1,1,0)));
3054 Summary of bugfix/change:
3055 Fixed handling for blocking the field the player leaves when moving.
3057 Fixed/changed in version:
3061 Before 3.1.1, when "block last field when moving" was enabled, the field
3062 the player is leaving when moving was blocked for the time of the move,
3063 and was directly unblocked afterwards. This resulted in the last field
3064 being blocked for exactly one less than the number of frames of one player
3065 move. Additionally, even when blocking was disabled, the last field was
3066 blocked for exactly one frame.
3067 Since 3.1.1, due to changes in player movement handling, the last field
3068 is not blocked at all when blocking is disabled. When blocking is enabled,
3069 the last field is blocked for exactly the number of frames of one player
3070 move. Additionally, if the player is Murphy, the hero of Supaplex, the
3071 last field is blocked for exactly one more than the number of frames of
3074 Affected levels/tapes:
3075 (!!! yet to be determined -- probably many !!!)
3078 game.use_block_last_field_bug =
3079 (game.engine_version < VERSION_IDENT(3,1,1,0));
3081 /* various special flags and settings for native Emerald Mine game engine */
3083 game_em.use_single_button =
3084 (game.engine_version > VERSION_IDENT(4,0,0,2));
3086 game_em.use_snap_key_bug =
3087 (game.engine_version < VERSION_IDENT(4,0,1,0));
3089 game_em.use_random_bug =
3090 (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG);
3092 boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0));
3094 game_em.use_old_explosions = use_old_em_engine;
3095 game_em.use_old_android = use_old_em_engine;
3096 game_em.use_old_push_elements = use_old_em_engine;
3097 game_em.use_old_push_into_acid = use_old_em_engine;
3099 game_em.use_wrap_around = !use_old_em_engine;
3101 // --------------------------------------------------------------------------
3103 // set maximal allowed number of custom element changes per game frame
3104 game.max_num_changes_per_frame = 1;
3106 // default scan direction: scan playfield from top/left to bottom/right
3107 InitPlayfieldScanMode(CA_ARG_SCAN_MODE_NORMAL);
3109 // dynamically adjust element properties according to game engine version
3110 InitElementPropertiesEngine(game.engine_version);
3112 // ---------- initialize special element properties -------------------------
3114 // "EL_AMOEBA_DROPPING" missed property "can fall" in older game versions
3115 if (use_amoeba_dropping_cannot_fall_bug)
3116 SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
3118 // ---------- initialize player's initial move delay ------------------------
3120 // dynamically adjust player properties according to level information
3121 for (i = 0; i < MAX_PLAYERS; i++)
3122 game.initial_move_delay_value[i] =
3123 get_move_delay_from_stepsize(level.initial_player_stepsize[i]);
3125 // dynamically adjust player properties according to game engine version
3126 for (i = 0; i < MAX_PLAYERS; i++)
3127 game.initial_move_delay[i] =
3128 (game.engine_version <= VERSION_IDENT(2,0,1,0) ?
3129 game.initial_move_delay_value[i] : 0);
3131 // ---------- initialize player's initial push delay ------------------------
3133 // dynamically adjust player properties according to game engine version
3134 game.initial_push_delay_value =
3135 (game.engine_version < VERSION_IDENT(3,0,7,1) ? 5 : -1);
3137 // ---------- initialize changing elements ----------------------------------
3139 // initialize changing elements information
3140 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3142 struct ElementInfo *ei = &element_info[i];
3144 // this pointer might have been changed in the level editor
3145 ei->change = &ei->change_page[0];
3147 if (!IS_CUSTOM_ELEMENT(i))
3149 ei->change->target_element = EL_EMPTY_SPACE;
3150 ei->change->delay_fixed = 0;
3151 ei->change->delay_random = 0;
3152 ei->change->delay_frames = 1;
3155 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3157 ei->has_change_event[j] = FALSE;
3159 ei->event_page_nr[j] = 0;
3160 ei->event_page[j] = &ei->change_page[0];
3164 // add changing elements from pre-defined list
3165 for (i = 0; change_delay_list[i].element != EL_UNDEFINED; i++)
3167 struct ChangingElementInfo *ch_delay = &change_delay_list[i];
3168 struct ElementInfo *ei = &element_info[ch_delay->element];
3170 ei->change->target_element = ch_delay->target_element;
3171 ei->change->delay_fixed = ch_delay->change_delay;
3173 ei->change->pre_change_function = ch_delay->pre_change_function;
3174 ei->change->change_function = ch_delay->change_function;
3175 ei->change->post_change_function = ch_delay->post_change_function;
3177 ei->change->can_change = TRUE;
3178 ei->change->can_change_or_has_action = TRUE;
3180 ei->has_change_event[CE_DELAY] = TRUE;
3182 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE, TRUE);
3183 SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE);
3186 // ---------- initialize internal run-time variables ------------------------
3188 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3190 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3192 for (j = 0; j < ei->num_change_pages; j++)
3194 ei->change_page[j].can_change_or_has_action =
3195 (ei->change_page[j].can_change |
3196 ei->change_page[j].has_action);
3200 // add change events from custom element configuration
3201 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3203 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3205 for (j = 0; j < ei->num_change_pages; j++)
3207 if (!ei->change_page[j].can_change_or_has_action)
3210 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3212 // only add event page for the first page found with this event
3213 if (ei->change_page[j].has_event[k] && !(ei->has_change_event[k]))
3215 ei->has_change_event[k] = TRUE;
3217 ei->event_page_nr[k] = j;
3218 ei->event_page[k] = &ei->change_page[j];
3224 // ---------- initialize reference elements in change conditions ------------
3226 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3228 int element = EL_CUSTOM_START + i;
3229 struct ElementInfo *ei = &element_info[element];
3231 for (j = 0; j < ei->num_change_pages; j++)
3233 int trigger_element = ei->change_page[j].initial_trigger_element;
3235 if (trigger_element >= EL_PREV_CE_8 &&
3236 trigger_element <= EL_NEXT_CE_8)
3237 trigger_element = RESOLVED_REFERENCE_ELEMENT(element, trigger_element);
3239 ei->change_page[j].trigger_element = trigger_element;
3243 // ---------- initialize run-time trigger player and element ----------------
3245 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3247 struct ElementInfo *ei = &element_info[EL_CUSTOM_START + i];
3249 for (j = 0; j < ei->num_change_pages; j++)
3251 ei->change_page[j].actual_trigger_element = EL_EMPTY;
3252 ei->change_page[j].actual_trigger_player = EL_EMPTY;
3253 ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE;
3254 ei->change_page[j].actual_trigger_side = CH_SIDE_NONE;
3255 ei->change_page[j].actual_trigger_ce_value = 0;
3256 ei->change_page[j].actual_trigger_ce_score = 0;
3260 // ---------- initialize trigger events -------------------------------------
3262 // initialize trigger events information
3263 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3264 for (j = 0; j < NUM_CHANGE_EVENTS; j++)
3265 trigger_events[i][j] = FALSE;
3267 // add trigger events from element change event properties
3268 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3270 struct ElementInfo *ei = &element_info[i];
3272 for (j = 0; j < ei->num_change_pages; j++)
3274 if (!ei->change_page[j].can_change_or_has_action)
3277 if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION])
3279 int trigger_element = ei->change_page[j].trigger_element;
3281 for (k = 0; k < NUM_CHANGE_EVENTS; k++)
3283 if (ei->change_page[j].has_event[k])
3285 if (IS_GROUP_ELEMENT(trigger_element))
3287 struct ElementGroupInfo *group =
3288 element_info[trigger_element].group;
3290 for (l = 0; l < group->num_elements_resolved; l++)
3291 trigger_events[group->element_resolved[l]][k] = TRUE;
3293 else if (trigger_element == EL_ANY_ELEMENT)
3294 for (l = 0; l < MAX_NUM_ELEMENTS; l++)
3295 trigger_events[l][k] = TRUE;
3297 trigger_events[trigger_element][k] = TRUE;
3304 // ---------- initialize push delay -----------------------------------------
3306 // initialize push delay values to default
3307 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3309 if (!IS_CUSTOM_ELEMENT(i))
3311 // set default push delay values (corrected since version 3.0.7-1)
3312 if (game.engine_version < VERSION_IDENT(3,0,7,1))
3314 element_info[i].push_delay_fixed = 2;
3315 element_info[i].push_delay_random = 8;
3319 element_info[i].push_delay_fixed = 8;
3320 element_info[i].push_delay_random = 8;
3325 // set push delay value for certain elements from pre-defined list
3326 for (i = 0; push_delay_list[i].element != EL_UNDEFINED; i++)
3328 int e = push_delay_list[i].element;
3330 element_info[e].push_delay_fixed = push_delay_list[i].push_delay_fixed;
3331 element_info[e].push_delay_random = push_delay_list[i].push_delay_random;
3334 // set push delay value for Supaplex elements for newer engine versions
3335 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
3337 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3339 if (IS_SP_ELEMENT(i))
3341 // set SP push delay to just enough to push under a falling zonk
3342 int delay = (game.engine_version >= VERSION_IDENT(3,1,1,0) ? 8 : 6);
3344 element_info[i].push_delay_fixed = delay;
3345 element_info[i].push_delay_random = 0;
3350 // ---------- initialize move stepsize --------------------------------------
3352 // initialize move stepsize values to default
3353 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3354 if (!IS_CUSTOM_ELEMENT(i))
3355 element_info[i].move_stepsize = MOVE_STEPSIZE_NORMAL;
3357 // set move stepsize value for certain elements from pre-defined list
3358 for (i = 0; move_stepsize_list[i].element != EL_UNDEFINED; i++)
3360 int e = move_stepsize_list[i].element;
3362 element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
3364 // set move stepsize value for certain elements for older engine versions
3365 if (use_old_move_stepsize_for_magic_wall)
3367 if (e == EL_MAGIC_WALL_FILLING ||
3368 e == EL_MAGIC_WALL_EMPTYING ||
3369 e == EL_BD_MAGIC_WALL_FILLING ||
3370 e == EL_BD_MAGIC_WALL_EMPTYING)
3371 element_info[e].move_stepsize *= 2;
3375 // ---------- initialize collect score --------------------------------------
3377 // initialize collect score values for custom elements from initial value
3378 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3379 if (IS_CUSTOM_ELEMENT(i))
3380 element_info[i].collect_score = element_info[i].collect_score_initial;
3382 // ---------- initialize collect count --------------------------------------
3384 // initialize collect count values for non-custom elements
3385 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3386 if (!IS_CUSTOM_ELEMENT(i))
3387 element_info[i].collect_count_initial = 0;
3389 // add collect count values for all elements from pre-defined list
3390 for (i = 0; collect_count_list[i].element != EL_UNDEFINED; i++)
3391 element_info[collect_count_list[i].element].collect_count_initial =
3392 collect_count_list[i].count;
3394 // ---------- initialize access direction -----------------------------------
3396 // initialize access direction values to default (access from every side)
3397 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3398 if (!IS_CUSTOM_ELEMENT(i))
3399 element_info[i].access_direction = MV_ALL_DIRECTIONS;
3401 // set access direction value for certain elements from pre-defined list
3402 for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++)
3403 element_info[access_direction_list[i].element].access_direction =
3404 access_direction_list[i].direction;
3406 // ---------- initialize explosion content ----------------------------------
3407 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3409 if (IS_CUSTOM_ELEMENT(i))
3412 for (y = 0; y < 3; y++) for (x = 0; x < 3; x++)
3414 // (content for EL_YAMYAM set at run-time with game.yamyam_content_nr)
3416 element_info[i].content.e[x][y] =
3417 (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW :
3418 i == EL_PLAYER_2 ? EL_EMERALD_RED :
3419 i == EL_PLAYER_3 ? EL_EMERALD :
3420 i == EL_PLAYER_4 ? EL_EMERALD_PURPLE :
3421 i == EL_MOLE ? EL_EMERALD_RED :
3422 i == EL_PENGUIN ? EL_EMERALD_PURPLE :
3423 i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) :
3424 i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND :
3425 i == EL_SP_ELECTRON ? EL_SP_INFOTRON :
3426 i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content :
3427 i == EL_WALL_EMERALD ? EL_EMERALD :
3428 i == EL_WALL_DIAMOND ? EL_DIAMOND :
3429 i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND :
3430 i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW :
3431 i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED :
3432 i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE :
3433 i == EL_WALL_PEARL ? EL_PEARL :
3434 i == EL_WALL_CRYSTAL ? EL_CRYSTAL :
3439 // ---------- initialize recursion detection --------------------------------
3440 recursion_loop_depth = 0;
3441 recursion_loop_detected = FALSE;
3442 recursion_loop_element = EL_UNDEFINED;
3444 // ---------- initialize graphics engine ------------------------------------
3445 game.scroll_delay_value =
3446 (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
3447 level.game_engine_type == GAME_ENGINE_TYPE_EM &&
3448 !setup.forced_scroll_delay ? 0 :
3449 setup.scroll_delay ? setup.scroll_delay_value : 0);
3450 game.scroll_delay_value =
3451 MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
3453 // ---------- initialize game engine snapshots ------------------------------
3454 for (i = 0; i < MAX_PLAYERS; i++)
3455 game.snapshot.last_action[i] = 0;
3456 game.snapshot.changed_action = FALSE;
3457 game.snapshot.collected_item = FALSE;
3458 game.snapshot.mode =
3459 (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ?
3460 SNAPSHOT_MODE_EVERY_STEP :
3461 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ?
3462 SNAPSHOT_MODE_EVERY_MOVE :
3463 strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ?
3464 SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF);
3465 game.snapshot.save_snapshot = FALSE;
3467 // ---------- initialize level time for Supaplex engine ---------------------
3468 // Supaplex levels with time limit currently unsupported -- should be added
3469 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
3472 // ---------- initialize flags for handling game actions --------------------
3474 // set flags for game actions to default values
3475 game.use_key_actions = TRUE;
3476 game.use_mouse_actions = FALSE;
3478 // when using Mirror Magic game engine, handle mouse events only
3479 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
3481 game.use_key_actions = FALSE;
3482 game.use_mouse_actions = TRUE;
3485 // check for custom elements with mouse click events
3486 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
3488 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
3490 int element = EL_CUSTOM_START + i;
3492 if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
3493 HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
3494 HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
3495 HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
3496 game.use_mouse_actions = TRUE;
3501 static int get_num_special_action(int element, int action_first,
3504 int num_special_action = 0;
3507 for (i = action_first; i <= action_last; i++)
3509 boolean found = FALSE;
3511 for (j = 0; j < NUM_DIRECTIONS; j++)
3512 if (el_act_dir2img(element, i, j) !=
3513 el_act_dir2img(element, ACTION_DEFAULT, j))
3517 num_special_action++;
3522 return num_special_action;
3526 // ============================================================================
3528 // ----------------------------------------------------------------------------
3529 // initialize and start new game
3530 // ============================================================================
3532 #if DEBUG_INIT_PLAYER
3533 static void DebugPrintPlayerStatus(char *message)
3540 Debug("game:init:player", "%s:", message);
3542 for (i = 0; i < MAX_PLAYERS; i++)
3544 struct PlayerInfo *player = &stored_player[i];
3546 Debug("game:init:player",
3547 "- player %d: present == %d, connected == %d [%d/%d], active == %d%s",
3551 player->connected_locally,
3552 player->connected_network,
3554 (local_player == player ? " (local player)" : ""));
3561 int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
3562 int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
3563 int fade_mask = REDRAW_FIELD;
3565 boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found
3566 boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found
3567 int initial_move_dir = MV_DOWN;
3570 // required here to update video display before fading (FIX THIS)
3571 DrawMaskedBorder(REDRAW_DOOR_2);
3573 if (!game.restart_level)
3574 CloseDoor(DOOR_CLOSE_1);
3576 SetGameStatus(GAME_MODE_PLAYING);
3578 if (level_editor_test_game)
3579 FadeSkipNextFadeOut();
3581 FadeSetEnterScreen();
3584 fade_mask = REDRAW_ALL;
3586 FadeLevelSoundsAndMusic();
3588 ExpireSoundLoops(TRUE);
3592 if (level_editor_test_game)
3593 FadeSkipNextFadeIn();
3595 // needed if different viewport properties defined for playing
3596 ChangeViewportPropertiesIfNeeded();
3600 DrawCompleteVideoDisplay();
3602 OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
3605 InitGameControlValues();
3609 // initialize tape actions from game when recording tape
3610 tape.use_key_actions = game.use_key_actions;
3611 tape.use_mouse_actions = game.use_mouse_actions;
3613 // initialize visible playfield size when recording tape (for team mode)
3614 tape.scr_fieldx = SCR_FIELDX;
3615 tape.scr_fieldy = SCR_FIELDY;
3618 // don't play tapes over network
3619 network_playing = (network.enabled && !tape.playing);
3621 for (i = 0; i < MAX_PLAYERS; i++)
3623 struct PlayerInfo *player = &stored_player[i];
3625 player->index_nr = i;
3626 player->index_bit = (1 << i);
3627 player->element_nr = EL_PLAYER_1 + i;
3629 player->present = FALSE;
3630 player->active = FALSE;
3631 player->mapped = FALSE;
3633 player->killed = FALSE;
3634 player->reanimated = FALSE;
3635 player->buried = FALSE;
3638 player->effective_action = 0;
3639 player->programmed_action = 0;
3640 player->snap_action = 0;
3642 player->mouse_action.lx = 0;
3643 player->mouse_action.ly = 0;
3644 player->mouse_action.button = 0;
3645 player->mouse_action.button_hint = 0;
3647 player->effective_mouse_action.lx = 0;
3648 player->effective_mouse_action.ly = 0;
3649 player->effective_mouse_action.button = 0;
3650 player->effective_mouse_action.button_hint = 0;
3652 for (j = 0; j < MAX_NUM_KEYS; j++)
3653 player->key[j] = FALSE;
3655 player->num_white_keys = 0;
3657 player->dynabomb_count = 0;
3658 player->dynabomb_size = 1;
3659 player->dynabombs_left = 0;
3660 player->dynabomb_xl = FALSE;
3662 player->MovDir = initial_move_dir;
3665 player->GfxDir = initial_move_dir;
3666 player->GfxAction = ACTION_DEFAULT;
3668 player->StepFrame = 0;
3670 player->initial_element = player->element_nr;
3671 player->artwork_element =
3672 (level.use_artwork_element[i] ? level.artwork_element[i] :
3673 player->element_nr);
3674 player->use_murphy = FALSE;
3676 player->block_last_field = FALSE; // initialized in InitPlayerField()
3677 player->block_delay_adjustment = 0; // initialized in InitPlayerField()
3679 player->gravity = level.initial_player_gravity[i];
3681 player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr);
3683 player->actual_frame_counter = 0;
3685 player->step_counter = 0;
3687 player->last_move_dir = initial_move_dir;
3689 player->is_active = FALSE;
3691 player->is_waiting = FALSE;
3692 player->is_moving = FALSE;
3693 player->is_auto_moving = FALSE;
3694 player->is_digging = FALSE;
3695 player->is_snapping = FALSE;
3696 player->is_collecting = FALSE;
3697 player->is_pushing = FALSE;
3698 player->is_switching = FALSE;
3699 player->is_dropping = FALSE;
3700 player->is_dropping_pressed = FALSE;
3702 player->is_bored = FALSE;
3703 player->is_sleeping = FALSE;
3705 player->was_waiting = TRUE;
3706 player->was_moving = FALSE;
3707 player->was_snapping = FALSE;
3708 player->was_dropping = FALSE;
3710 player->force_dropping = FALSE;
3712 player->frame_counter_bored = -1;
3713 player->frame_counter_sleeping = -1;
3715 player->anim_delay_counter = 0;
3716 player->post_delay_counter = 0;
3718 player->dir_waiting = initial_move_dir;
3719 player->action_waiting = ACTION_DEFAULT;
3720 player->last_action_waiting = ACTION_DEFAULT;
3721 player->special_action_bored = ACTION_DEFAULT;
3722 player->special_action_sleeping = ACTION_DEFAULT;
3724 player->switch_x = -1;
3725 player->switch_y = -1;
3727 player->drop_x = -1;
3728 player->drop_y = -1;
3730 player->show_envelope = 0;
3732 SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE);
3734 player->push_delay = -1; // initialized when pushing starts
3735 player->push_delay_value = game.initial_push_delay_value;
3737 player->drop_delay = 0;
3738 player->drop_pressed_delay = 0;
3740 player->last_jx = -1;
3741 player->last_jy = -1;
3745 player->shield_normal_time_left = 0;
3746 player->shield_deadly_time_left = 0;
3748 player->last_removed_element = EL_UNDEFINED;
3750 player->inventory_infinite_element = EL_UNDEFINED;
3751 player->inventory_size = 0;
3753 if (level.use_initial_inventory[i])
3755 for (j = 0; j < level.initial_inventory_size[i]; j++)
3757 int element = level.initial_inventory_content[i][j];
3758 int collect_count = element_info[element].collect_count_initial;
3761 if (!IS_CUSTOM_ELEMENT(element))
3764 if (collect_count == 0)
3765 player->inventory_infinite_element = element;
3767 for (k = 0; k < collect_count; k++)
3768 if (player->inventory_size < MAX_INVENTORY_SIZE)
3769 player->inventory_element[player->inventory_size++] = element;
3773 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
3774 SnapField(player, 0, 0);
3776 map_player_action[i] = i;
3779 network_player_action_received = FALSE;
3781 // initial null action
3782 if (network_playing)
3783 SendToServer_MovePlayer(MV_NONE);
3788 TimeLeft = level.time;
3791 ScreenMovDir = MV_NONE;
3795 ScrollStepSize = 0; // will be correctly initialized by ScrollScreen()
3797 game.robot_wheel_x = -1;
3798 game.robot_wheel_y = -1;
3803 game.all_players_gone = FALSE;
3805 game.LevelSolved = FALSE;
3806 game.GameOver = FALSE;
3808 game.GamePlayed = !tape.playing;
3810 game.LevelSolved_GameWon = FALSE;
3811 game.LevelSolved_GameEnd = FALSE;
3812 game.LevelSolved_SaveTape = FALSE;
3813 game.LevelSolved_SaveScore = FALSE;
3815 game.LevelSolved_CountingTime = 0;
3816 game.LevelSolved_CountingScore = 0;
3817 game.LevelSolved_CountingHealth = 0;
3819 game.panel.active = TRUE;
3821 game.no_time_limit = (level.time == 0);
3823 game.yamyam_content_nr = 0;
3824 game.robot_wheel_active = FALSE;
3825 game.magic_wall_active = FALSE;
3826 game.magic_wall_time_left = 0;
3827 game.light_time_left = 0;
3828 game.timegate_time_left = 0;
3829 game.switchgate_pos = 0;
3830 game.wind_direction = level.wind_direction_initial;
3832 game.time_final = 0;
3833 game.score_time_final = 0;
3836 game.score_final = 0;
3838 game.health = MAX_HEALTH;
3839 game.health_final = MAX_HEALTH;
3841 game.gems_still_needed = level.gems_needed;
3842 game.sokoban_fields_still_needed = 0;
3843 game.sokoban_objects_still_needed = 0;
3844 game.lights_still_needed = 0;
3845 game.players_still_needed = 0;
3846 game.friends_still_needed = 0;
3848 game.lenses_time_left = 0;
3849 game.magnify_time_left = 0;
3851 game.ball_active = level.ball_active_initial;
3852 game.ball_content_nr = 0;
3854 game.explosions_delayed = TRUE;
3856 game.envelope_active = FALSE;
3858 // special case: set custom artwork setting to initial value
3859 game.use_masked_elements = game.use_masked_elements_initial;
3861 for (i = 0; i < NUM_BELTS; i++)
3863 game.belt_dir[i] = MV_NONE;
3864 game.belt_dir_nr[i] = 3; // not moving, next moving left
3867 for (i = 0; i < MAX_NUM_AMOEBA; i++)
3868 AmoebaCnt[i] = AmoebaCnt2[i] = 0;
3870 #if DEBUG_INIT_PLAYER
3871 DebugPrintPlayerStatus("Player status at level initialization");
3874 SCAN_PLAYFIELD(x, y)
3876 Tile[x][y] = Last[x][y] = level.field[x][y];
3877 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
3878 ChangeDelay[x][y] = 0;
3879 ChangePage[x][y] = -1;
3880 CustomValue[x][y] = 0; // initialized in InitField()
3881 Store[x][y] = Store2[x][y] = StorePlayer[x][y] = Back[x][y] = 0;
3883 WasJustMoving[x][y] = 0;
3884 WasJustFalling[x][y] = 0;
3885 CheckCollision[x][y] = 0;
3886 CheckImpact[x][y] = 0;
3888 Pushed[x][y] = FALSE;
3890 ChangeCount[x][y] = 0;
3891 ChangeEvent[x][y] = -1;
3893 ExplodePhase[x][y] = 0;
3894 ExplodeDelay[x][y] = 0;
3895 ExplodeField[x][y] = EX_TYPE_NONE;
3897 RunnerVisit[x][y] = 0;
3898 PlayerVisit[x][y] = 0;
3901 GfxRandom[x][y] = INIT_GFX_RANDOM();
3902 GfxRandomStatic[x][y] = INIT_GFX_RANDOM();
3903 GfxElement[x][y] = EL_UNDEFINED;
3904 GfxElementEmpty[x][y] = EL_EMPTY;
3905 GfxAction[x][y] = ACTION_DEFAULT;
3906 GfxDir[x][y] = MV_NONE;
3907 GfxRedraw[x][y] = GFX_REDRAW_NONE;
3910 SCAN_PLAYFIELD(x, y)
3912 if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
3914 if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
3917 InitField(x, y, TRUE);
3919 ResetGfxAnimation(x, y);
3924 for (i = 0; i < MAX_PLAYERS; i++)
3926 struct PlayerInfo *player = &stored_player[i];
3928 // set number of special actions for bored and sleeping animation
3929 player->num_special_action_bored =
3930 get_num_special_action(player->artwork_element,
3931 ACTION_BORING_1, ACTION_BORING_LAST);
3932 player->num_special_action_sleeping =
3933 get_num_special_action(player->artwork_element,
3934 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
3937 game.emulation = (emulate_bd ? EMU_BOULDERDASH :
3938 emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
3940 // initialize type of slippery elements
3941 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3943 if (!IS_CUSTOM_ELEMENT(i))
3945 // default: elements slip down either to the left or right randomly
3946 element_info[i].slippery_type = SLIPPERY_ANY_RANDOM;
3948 // SP style elements prefer to slip down on the left side
3949 if (game.engine_version >= VERSION_IDENT(3,1,1,0) && IS_SP_ELEMENT(i))
3950 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3952 // BD style elements prefer to slip down on the left side
3953 if (game.emulation == EMU_BOULDERDASH)
3954 element_info[i].slippery_type = SLIPPERY_ANY_LEFT_RIGHT;
3958 // initialize explosion and ignition delay
3959 for (i = 0; i < MAX_NUM_ELEMENTS; i++)
3961 if (!IS_CUSTOM_ELEMENT(i))
3964 int delay = (((IS_SP_ELEMENT(i) && i != EL_EMPTY_SPACE) &&
3965 game.engine_version >= VERSION_IDENT(3,1,0,0)) ||
3966 game.emulation == EMU_SUPAPLEX ? 3 : 2);
3967 int last_phase = (num_phase + 1) * delay;
3968 int half_phase = (num_phase / 2) * delay;
3970 element_info[i].explosion_delay = last_phase - 1;
3971 element_info[i].ignition_delay = half_phase;
3973 if (i == EL_BLACK_ORB)
3974 element_info[i].ignition_delay = 1;
3978 // correct non-moving belts to start moving left
3979 for (i = 0; i < NUM_BELTS; i++)
3980 if (game.belt_dir[i] == MV_NONE)
3981 game.belt_dir_nr[i] = 3; // not moving, next moving left
3983 #if USE_NEW_PLAYER_ASSIGNMENTS
3984 // use preferred player also in local single-player mode
3985 if (!network.enabled && !game.team_mode)
3987 int new_index_nr = setup.network_player_nr;
3989 if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
3991 for (i = 0; i < MAX_PLAYERS; i++)
3992 stored_player[i].connected_locally = FALSE;
3994 stored_player[new_index_nr].connected_locally = TRUE;
3998 for (i = 0; i < MAX_PLAYERS; i++)
4000 stored_player[i].connected = FALSE;
4002 // in network game mode, the local player might not be the first player
4003 if (stored_player[i].connected_locally)
4004 local_player = &stored_player[i];
4007 if (!network.enabled)
4008 local_player->connected = TRUE;
4012 for (i = 0; i < MAX_PLAYERS; i++)
4013 stored_player[i].connected = tape.player_participates[i];
4015 else if (network.enabled)
4017 // add team mode players connected over the network (needed for correct
4018 // assignment of player figures from level to locally playing players)
4020 for (i = 0; i < MAX_PLAYERS; i++)
4021 if (stored_player[i].connected_network)
4022 stored_player[i].connected = TRUE;
4024 else if (game.team_mode)
4026 // try to guess locally connected team mode players (needed for correct
4027 // assignment of player figures from level to locally playing players)
4029 for (i = 0; i < MAX_PLAYERS; i++)
4030 if (setup.input[i].use_joystick ||
4031 setup.input[i].key.left != KSYM_UNDEFINED)
4032 stored_player[i].connected = TRUE;
4035 #if DEBUG_INIT_PLAYER
4036 DebugPrintPlayerStatus("Player status after level initialization");
4039 #if DEBUG_INIT_PLAYER
4040 Debug("game:init:player", "Reassigning players ...");
4043 // check if any connected player was not found in playfield
4044 for (i = 0; i < MAX_PLAYERS; i++)
4046 struct PlayerInfo *player = &stored_player[i];
4048 if (player->connected && !player->present)
4050 struct PlayerInfo *field_player = NULL;
4052 #if DEBUG_INIT_PLAYER
4053 Debug("game:init:player",
4054 "- looking for field player for player %d ...", i + 1);
4057 // assign first free player found that is present in the playfield
4059 // first try: look for unmapped playfield player that is not connected
4060 for (j = 0; j < MAX_PLAYERS; j++)
4061 if (field_player == NULL &&
4062 stored_player[j].present &&
4063 !stored_player[j].mapped &&
4064 !stored_player[j].connected)
4065 field_player = &stored_player[j];
4067 // second try: look for *any* unmapped playfield player
4068 for (j = 0; j < MAX_PLAYERS; j++)
4069 if (field_player == NULL &&
4070 stored_player[j].present &&
4071 !stored_player[j].mapped)
4072 field_player = &stored_player[j];
4074 if (field_player != NULL)
4076 int jx = field_player->jx, jy = field_player->jy;
4078 #if DEBUG_INIT_PLAYER
4079 Debug("game:init:player", "- found player %d",
4080 field_player->index_nr + 1);
4083 player->present = FALSE;
4084 player->active = FALSE;
4086 field_player->present = TRUE;
4087 field_player->active = TRUE;
4090 player->initial_element = field_player->initial_element;
4091 player->artwork_element = field_player->artwork_element;
4093 player->block_last_field = field_player->block_last_field;
4094 player->block_delay_adjustment = field_player->block_delay_adjustment;
4097 StorePlayer[jx][jy] = field_player->element_nr;
4099 field_player->jx = field_player->last_jx = jx;
4100 field_player->jy = field_player->last_jy = jy;
4102 if (local_player == player)
4103 local_player = field_player;
4105 map_player_action[field_player->index_nr] = i;
4107 field_player->mapped = TRUE;
4109 #if DEBUG_INIT_PLAYER
4110 Debug("game:init:player", "- map_player_action[%d] == %d",
4111 field_player->index_nr + 1, i + 1);
4116 if (player->connected && player->present)
4117 player->mapped = TRUE;
4120 #if DEBUG_INIT_PLAYER
4121 DebugPrintPlayerStatus("Player status after player assignment (first stage)");
4126 // check if any connected player was not found in playfield
4127 for (i = 0; i < MAX_PLAYERS; i++)
4129 struct PlayerInfo *player = &stored_player[i];
4131 if (player->connected && !player->present)
4133 for (j = 0; j < MAX_PLAYERS; j++)
4135 struct PlayerInfo *field_player = &stored_player[j];
4136 int jx = field_player->jx, jy = field_player->jy;
4138 // assign first free player found that is present in the playfield
4139 if (field_player->present && !field_player->connected)
4141 player->present = TRUE;
4142 player->active = TRUE;
4144 field_player->present = FALSE;
4145 field_player->active = FALSE;
4147 player->initial_element = field_player->initial_element;
4148 player->artwork_element = field_player->artwork_element;
4150 player->block_last_field = field_player->block_last_field;
4151 player->block_delay_adjustment = field_player->block_delay_adjustment;
4153 StorePlayer[jx][jy] = player->element_nr;
4155 player->jx = player->last_jx = jx;
4156 player->jy = player->last_jy = jy;
4166 Debug("game:init:player", "local_player->present == %d",
4167 local_player->present);
4170 // set focus to local player for network games, else to all players
4171 game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
4172 game.centered_player_nr_next = game.centered_player_nr;
4173 game.set_centered_player = FALSE;
4174 game.set_centered_player_wrap = FALSE;
4176 if (network_playing && tape.recording)
4178 // store client dependent player focus when recording network games
4179 tape.centered_player_nr_next = game.centered_player_nr_next;
4180 tape.set_centered_player = TRUE;
4185 // when playing a tape, eliminate all players who do not participate
4187 #if USE_NEW_PLAYER_ASSIGNMENTS
4189 if (!game.team_mode)
4191 for (i = 0; i < MAX_PLAYERS; i++)
4193 if (stored_player[i].active &&
4194 !tape.player_participates[map_player_action[i]])
4196 struct PlayerInfo *player = &stored_player[i];
4197 int jx = player->jx, jy = player->jy;
4199 #if DEBUG_INIT_PLAYER
4200 Debug("game:init:player", "Removing player %d at (%d, %d)",
4204 player->active = FALSE;
4205 StorePlayer[jx][jy] = 0;
4206 Tile[jx][jy] = EL_EMPTY;
4213 for (i = 0; i < MAX_PLAYERS; i++)
4215 if (stored_player[i].active &&
4216 !tape.player_participates[i])
4218 struct PlayerInfo *player = &stored_player[i];
4219 int jx = player->jx, jy = player->jy;
4221 player->active = FALSE;
4222 StorePlayer[jx][jy] = 0;
4223 Tile[jx][jy] = EL_EMPTY;
4228 else if (!network.enabled && !game.team_mode) // && !tape.playing
4230 // when in single player mode, eliminate all but the local player
4232 for (i = 0; i < MAX_PLAYERS; i++)
4234 struct PlayerInfo *player = &stored_player[i];
4236 if (player->active && player != local_player)
4238 int jx = player->jx, jy = player->jy;
4240 player->active = FALSE;
4241 player->present = FALSE;
4243 StorePlayer[jx][jy] = 0;
4244 Tile[jx][jy] = EL_EMPTY;
4249 for (i = 0; i < MAX_PLAYERS; i++)
4250 if (stored_player[i].active)
4251 game.players_still_needed++;
4253 if (level.solved_by_one_player)
4254 game.players_still_needed = 1;
4256 // when recording the game, store which players take part in the game
4259 #if USE_NEW_PLAYER_ASSIGNMENTS
4260 for (i = 0; i < MAX_PLAYERS; i++)
4261 if (stored_player[i].connected)
4262 tape.player_participates[i] = TRUE;
4264 for (i = 0; i < MAX_PLAYERS; i++)
4265 if (stored_player[i].active)
4266 tape.player_participates[i] = TRUE;
4270 #if DEBUG_INIT_PLAYER
4271 DebugPrintPlayerStatus("Player status after player assignment (final stage)");
4274 if (BorderElement == EL_EMPTY)
4277 SBX_Right = lev_fieldx - SCR_FIELDX;
4279 SBY_Lower = lev_fieldy - SCR_FIELDY;
4284 SBX_Right = lev_fieldx - SCR_FIELDX + 1;
4286 SBY_Lower = lev_fieldy - SCR_FIELDY + 1;
4289 if (full_lev_fieldx <= SCR_FIELDX)
4290 SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
4291 if (full_lev_fieldy <= SCR_FIELDY)
4292 SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
4294 if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
4296 if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
4299 // if local player not found, look for custom element that might create
4300 // the player (make some assumptions about the right custom element)
4301 if (!local_player->present)
4303 int start_x = 0, start_y = 0;
4304 int found_rating = 0;
4305 int found_element = EL_UNDEFINED;
4306 int player_nr = local_player->index_nr;
4308 SCAN_PLAYFIELD(x, y)
4310 int element = Tile[x][y];
4315 if (level.use_start_element[player_nr] &&
4316 level.start_element[player_nr] == element &&
4323 found_element = element;
4326 if (!IS_CUSTOM_ELEMENT(element))
4329 if (CAN_CHANGE(element))
4331 for (i = 0; i < element_info[element].num_change_pages; i++)
4333 // check for player created from custom element as single target
4334 content = element_info[element].change_page[i].target_element;
4335 is_player = IS_PLAYER_ELEMENT(content);
4337 if (is_player && (found_rating < 3 ||
4338 (found_rating == 3 && element < found_element)))
4344 found_element = element;
4349 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++)
4351 // check for player created from custom element as explosion content
4352 content = element_info[element].content.e[xx][yy];
4353 is_player = IS_PLAYER_ELEMENT(content);
4355 if (is_player && (found_rating < 2 ||
4356 (found_rating == 2 && element < found_element)))
4358 start_x = x + xx - 1;
4359 start_y = y + yy - 1;
4362 found_element = element;
4365 if (!CAN_CHANGE(element))
4368 for (i = 0; i < element_info[element].num_change_pages; i++)
4370 // check for player created from custom element as extended target
4372 element_info[element].change_page[i].target_content.e[xx][yy];
4374 is_player = IS_PLAYER_ELEMENT(content);
4376 if (is_player && (found_rating < 1 ||
4377 (found_rating == 1 && element < found_element)))
4379 start_x = x + xx - 1;
4380 start_y = y + yy - 1;
4383 found_element = element;
4389 scroll_x = SCROLL_POSITION_X(start_x);
4390 scroll_y = SCROLL_POSITION_Y(start_y);
4394 scroll_x = SCROLL_POSITION_X(local_player->jx);
4395 scroll_y = SCROLL_POSITION_Y(local_player->jy);
4398 // !!! FIX THIS (START) !!!
4399 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
4401 InitGameEngine_EM();
4403 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
4405 InitGameEngine_SP();
4407 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4409 InitGameEngine_MM();
4413 DrawLevel(REDRAW_FIELD);
4416 // after drawing the level, correct some elements
4417 if (game.timegate_time_left == 0)
4418 CloseAllOpenTimegates();
4421 // blit playfield from scroll buffer to normal back buffer for fading in
4422 BlitScreenToBitmap(backbuffer);
4423 // !!! FIX THIS (END) !!!
4425 DrawMaskedBorder(fade_mask);
4430 // full screen redraw is required at this point in the following cases:
4431 // - special editor door undrawn when game was started from level editor
4432 // - drawing area (playfield) was changed and has to be removed completely
4433 redraw_mask = REDRAW_ALL;
4437 if (!game.restart_level)
4439 // copy default game door content to main double buffer
4441 // !!! CHECK AGAIN !!!
4442 SetPanelBackground();
4443 // SetDoorBackgroundImage(IMG_BACKGROUND_PANEL);
4444 DrawBackground(DX, DY, DXSIZE, DYSIZE);
4447 SetPanelBackground();
4448 SetDrawBackgroundMask(REDRAW_DOOR_1);
4450 UpdateAndDisplayGameControlValues();
4452 if (!game.restart_level)
4458 CreateGameButtons();
4463 // copy actual game door content to door double buffer for OpenDoor()
4464 BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
4466 OpenDoor(DOOR_OPEN_ALL);
4468 KeyboardAutoRepeatOffUnlessAutoplay();
4470 #if DEBUG_INIT_PLAYER
4471 DebugPrintPlayerStatus("Player status (final)");
4480 if (!game.restart_level && !tape.playing)
4482 LevelStats_incPlayed(level_nr);
4484 SaveLevelSetup_SeriesInfo();
4487 game.restart_level = FALSE;
4488 game.restart_game_message = NULL;
4490 game.request_active = FALSE;
4491 game.request_active_or_moving = FALSE;
4493 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4494 InitGameActions_MM();
4496 SaveEngineSnapshotToListInitial();
4498 if (!game.restart_level)
4500 PlaySound(SND_GAME_STARTING);
4502 if (setup.sound_music)
4506 SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions);
4509 void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
4510 int actual_player_x, int actual_player_y)
4512 // this is used for non-R'n'D game engines to update certain engine values
4514 // needed to determine if sounds are played within the visible screen area
4515 scroll_x = actual_scroll_x;
4516 scroll_y = actual_scroll_y;
4518 // needed to get player position for "follow finger" playing input method
4519 local_player->jx = actual_player_x;
4520 local_player->jy = actual_player_y;
4523 void InitMovDir(int x, int y)
4525 int i, element = Tile[x][y];
4526 static int xy[4][2] =
4533 static int direction[3][4] =
4535 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
4536 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
4537 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
4546 Tile[x][y] = EL_BUG;
4547 MovDir[x][y] = direction[0][element - EL_BUG_RIGHT];
4550 case EL_SPACESHIP_RIGHT:
4551 case EL_SPACESHIP_UP:
4552 case EL_SPACESHIP_LEFT:
4553 case EL_SPACESHIP_DOWN:
4554 Tile[x][y] = EL_SPACESHIP;
4555 MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT];
4558 case EL_BD_BUTTERFLY_RIGHT:
4559 case EL_BD_BUTTERFLY_UP:
4560 case EL_BD_BUTTERFLY_LEFT:
4561 case EL_BD_BUTTERFLY_DOWN:
4562 Tile[x][y] = EL_BD_BUTTERFLY;
4563 MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT];
4566 case EL_BD_FIREFLY_RIGHT:
4567 case EL_BD_FIREFLY_UP:
4568 case EL_BD_FIREFLY_LEFT:
4569 case EL_BD_FIREFLY_DOWN:
4570 Tile[x][y] = EL_BD_FIREFLY;
4571 MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT];
4574 case EL_PACMAN_RIGHT:
4576 case EL_PACMAN_LEFT:
4577 case EL_PACMAN_DOWN:
4578 Tile[x][y] = EL_PACMAN;
4579 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
4582 case EL_YAMYAM_LEFT:
4583 case EL_YAMYAM_RIGHT:
4585 case EL_YAMYAM_DOWN:
4586 Tile[x][y] = EL_YAMYAM;
4587 MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT];
4590 case EL_SP_SNIKSNAK:
4591 MovDir[x][y] = MV_UP;
4594 case EL_SP_ELECTRON:
4595 MovDir[x][y] = MV_LEFT;
4602 Tile[x][y] = EL_MOLE;
4603 MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
4606 case EL_SPRING_LEFT:
4607 case EL_SPRING_RIGHT:
4608 Tile[x][y] = EL_SPRING;
4609 MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
4613 if (IS_CUSTOM_ELEMENT(element))
4615 struct ElementInfo *ei = &element_info[element];
4616 int move_direction_initial = ei->move_direction_initial;
4617 int move_pattern = ei->move_pattern;
4619 if (move_direction_initial == MV_START_PREVIOUS)
4621 if (MovDir[x][y] != MV_NONE)
4624 move_direction_initial = MV_START_AUTOMATIC;
4627 if (move_direction_initial == MV_START_RANDOM)
4628 MovDir[x][y] = 1 << RND(4);
4629 else if (move_direction_initial & MV_ANY_DIRECTION)
4630 MovDir[x][y] = move_direction_initial;
4631 else if (move_pattern == MV_ALL_DIRECTIONS ||
4632 move_pattern == MV_TURNING_LEFT ||
4633 move_pattern == MV_TURNING_RIGHT ||
4634 move_pattern == MV_TURNING_LEFT_RIGHT ||
4635 move_pattern == MV_TURNING_RIGHT_LEFT ||
4636 move_pattern == MV_TURNING_RANDOM)
4637 MovDir[x][y] = 1 << RND(4);
4638 else if (move_pattern == MV_HORIZONTAL)
4639 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
4640 else if (move_pattern == MV_VERTICAL)
4641 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
4642 else if (move_pattern & MV_ANY_DIRECTION)
4643 MovDir[x][y] = element_info[element].move_pattern;
4644 else if (move_pattern == MV_ALONG_LEFT_SIDE ||
4645 move_pattern == MV_ALONG_RIGHT_SIDE)
4647 // use random direction as default start direction
4648 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
4649 MovDir[x][y] = 1 << RND(4);
4651 for (i = 0; i < NUM_DIRECTIONS; i++)
4653 int x1 = x + xy[i][0];
4654 int y1 = y + xy[i][1];
4656 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4658 if (move_pattern == MV_ALONG_RIGHT_SIDE)
4659 MovDir[x][y] = direction[0][i];
4661 MovDir[x][y] = direction[1][i];
4670 MovDir[x][y] = 1 << RND(4);
4672 if (element != EL_BUG &&
4673 element != EL_SPACESHIP &&
4674 element != EL_BD_BUTTERFLY &&
4675 element != EL_BD_FIREFLY)
4678 for (i = 0; i < NUM_DIRECTIONS; i++)
4680 int x1 = x + xy[i][0];
4681 int y1 = y + xy[i][1];
4683 if (!IN_LEV_FIELD(x1, y1) || !IS_FREE(x1, y1))
4685 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
4687 MovDir[x][y] = direction[0][i];
4690 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY ||
4691 element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
4693 MovDir[x][y] = direction[1][i];
4702 GfxDir[x][y] = MovDir[x][y];
4705 void InitAmoebaNr(int x, int y)
4708 int group_nr = AmoebaNeighbourNr(x, y);
4712 for (i = 1; i < MAX_NUM_AMOEBA; i++)
4714 if (AmoebaCnt[i] == 0)
4722 AmoebaNr[x][y] = group_nr;
4723 AmoebaCnt[group_nr]++;
4724 AmoebaCnt2[group_nr]++;
4727 static void LevelSolved_SetFinalGameValues(void)
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;
4748 static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
4750 game.LevelSolved_CountingTime = time;
4751 game.LevelSolved_CountingScore = score;
4752 game.LevelSolved_CountingHealth = health;
4754 game_panel_controls[GAME_PANEL_TIME].value = time;
4755 game_panel_controls[GAME_PANEL_SCORE].value = score;
4756 game_panel_controls[GAME_PANEL_HEALTH].value = health;
4758 DisplayGameControlValues();
4761 static void LevelSolved(void)
4763 if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
4764 game.players_still_needed > 0)
4767 game.LevelSolved = TRUE;
4768 game.GameOver = TRUE;
4770 // needed here to display correct panel values while player walks into exit
4771 LevelSolved_SetFinalGameValues();
4776 static int time_count_steps;
4777 static int time, time_final;
4778 static float score, score_final; // needed for time score < 10 for 10 seconds
4779 static int health, health_final;
4780 static int game_over_delay_1 = 0;
4781 static int game_over_delay_2 = 0;
4782 static int game_over_delay_3 = 0;
4783 int time_score_base = MIN(MAX(1, level.time_score_base), 10);
4784 float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
4786 if (!game.LevelSolved_GameWon)
4790 // do not start end game actions before the player stops moving (to exit)
4791 if (local_player->active && local_player->MovPos)
4794 // calculate final game values after player finished walking into exit
4795 LevelSolved_SetFinalGameValues();
4797 game.LevelSolved_GameWon = TRUE;
4798 game.LevelSolved_SaveTape = tape.recording;
4799 game.LevelSolved_SaveScore = !tape.playing;
4803 LevelStats_incSolved(level_nr);
4805 SaveLevelSetup_SeriesInfo();
4808 if (tape.auto_play) // tape might already be stopped here
4809 tape.auto_play_level_solved = TRUE;
4813 game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time
4814 game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health
4815 game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game
4817 time = time_final = game.time_final;
4818 score = score_final = game.score_final;
4819 health = health_final = game.health_final;
4821 // update game panel values before (delayed) counting of score (if any)
4822 LevelSolved_DisplayFinalGameValues(time, score, health);
4824 // if level has time score defined, calculate new final game values
4827 int time_final_max = 999;
4828 int time_frames_final_max = time_final_max * FRAMES_PER_SECOND;
4829 int time_frames = 0;
4830 int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
4831 int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames;
4836 time_frames = time_frames_left;
4838 else if (game.no_time_limit && TimePlayed < time_final_max)
4840 time_final = time_final_max;
4841 time_frames = time_frames_final_max - time_frames_played;
4844 score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
4846 time_count_steps = MAX(1, ABS(time_final - time) / 100);
4848 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
4851 score_final += health * time_score;
4854 game.score_final = score_final;
4855 game.health_final = health_final;
4858 // if not counting score after game, immediately update game panel values
4859 if (level_editor_test_game || !setup.count_score_after_game)
4862 score = score_final;
4864 LevelSolved_DisplayFinalGameValues(time, score, health);
4867 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
4869 // check if last player has left the level
4870 if (game.exit_x >= 0 &&
4873 int x = game.exit_x;
4874 int y = game.exit_y;
4875 int element = Tile[x][y];
4877 // close exit door after last player
4878 if ((game.all_players_gone &&
4879 (element == EL_EXIT_OPEN ||
4880 element == EL_SP_EXIT_OPEN ||
4881 element == EL_STEEL_EXIT_OPEN)) ||
4882 element == EL_EM_EXIT_OPEN ||
4883 element == EL_EM_STEEL_EXIT_OPEN)
4887 (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING :
4888 element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING :
4889 element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING:
4890 element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING:
4891 EL_EM_STEEL_EXIT_CLOSING);
4893 PlayLevelSoundElementAction(x, y, element, ACTION_CLOSING);
4896 // player disappears
4897 DrawLevelField(x, y);
4900 for (i = 0; i < MAX_PLAYERS; i++)
4902 struct PlayerInfo *player = &stored_player[i];
4904 if (player->present)
4906 RemovePlayer(player);
4908 // player disappears
4909 DrawLevelField(player->jx, player->jy);
4914 PlaySound(SND_GAME_WINNING);
4917 if (setup.count_score_after_game)
4919 if (time != time_final)
4921 if (game_over_delay_1 > 0)
4923 game_over_delay_1--;
4928 int time_to_go = ABS(time_final - time);
4929 int time_count_dir = (time < time_final ? +1 : -1);
4931 if (time_to_go < time_count_steps)
4932 time_count_steps = 1;
4934 time += time_count_steps * time_count_dir;
4935 score += time_count_steps * time_score;
4937 // set final score to correct rounding differences after counting score
4938 if (time == time_final)
4939 score = score_final;
4941 LevelSolved_DisplayFinalGameValues(time, score, health);
4943 if (time == time_final)
4944 StopSound(SND_GAME_LEVELTIME_BONUS);
4945 else if (setup.sound_loops)
4946 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4948 PlaySound(SND_GAME_LEVELTIME_BONUS);
4953 if (health != health_final)
4955 if (game_over_delay_2 > 0)
4957 game_over_delay_2--;
4962 int health_count_dir = (health < health_final ? +1 : -1);
4964 health += health_count_dir;
4965 score += time_score;
4967 LevelSolved_DisplayFinalGameValues(time, score, health);
4969 if (health == health_final)
4970 StopSound(SND_GAME_LEVELTIME_BONUS);
4971 else if (setup.sound_loops)
4972 PlaySoundLoop(SND_GAME_LEVELTIME_BONUS);
4974 PlaySound(SND_GAME_LEVELTIME_BONUS);
4980 game.panel.active = FALSE;
4982 if (game_over_delay_3 > 0)
4984 game_over_delay_3--;
4994 // used instead of "level_nr" (needed for network games)
4995 int last_level_nr = levelset.level_nr;
4996 boolean tape_saved = FALSE;
4998 game.LevelSolved_GameEnd = TRUE;
5000 if (game.LevelSolved_SaveTape)
5002 // make sure that request dialog to save tape does not open door again
5003 if (!global.use_envelope_request)
5004 CloseDoor(DOOR_CLOSE_1);
5007 tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
5009 // set unique basename for score tape (also saved in high score table)
5010 strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
5013 // if no tape is to be saved, close both doors simultaneously
5014 CloseDoor(DOOR_CLOSE_ALL);
5016 if (level_editor_test_game)
5018 SetGameStatus(GAME_MODE_MAIN);
5025 if (!game.LevelSolved_SaveScore)
5027 SetGameStatus(GAME_MODE_MAIN);
5034 if (level_nr == leveldir_current->handicap_level)
5036 leveldir_current->handicap_level++;
5038 SaveLevelSetup_SeriesInfo();
5041 // save score and score tape before potentially erasing tape below
5042 NewHighScore(last_level_nr, tape_saved);
5044 if (setup.increment_levels &&
5045 level_nr < leveldir_current->last_level &&
5048 level_nr++; // advance to next level
5049 TapeErase(); // start with empty tape
5051 if (setup.auto_play_next_level)
5053 LoadLevel(level_nr);
5055 SaveLevelSetup_SeriesInfo();
5059 if (scores.last_added >= 0 && setup.show_scores_after_game)
5061 SetGameStatus(GAME_MODE_SCORES);
5063 DrawHallOfFame(last_level_nr);
5065 else if (setup.auto_play_next_level && setup.increment_levels &&
5066 last_level_nr < leveldir_current->last_level &&
5069 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5073 SetGameStatus(GAME_MODE_MAIN);
5079 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5080 boolean one_score_entry_per_name)
5084 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5087 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5089 struct ScoreEntry *entry = &list->entry[i];
5090 boolean score_is_better = (new_entry->score > entry->score);
5091 boolean score_is_equal = (new_entry->score == entry->score);
5092 boolean time_is_better = (new_entry->time < entry->time);
5093 boolean time_is_equal = (new_entry->time == entry->time);
5094 boolean better_by_score = (score_is_better ||
5095 (score_is_equal && time_is_better));
5096 boolean better_by_time = (time_is_better ||
5097 (time_is_equal && score_is_better));
5098 boolean is_better = (level.rate_time_over_score ? better_by_time :
5100 boolean entry_is_empty = (entry->score == 0 &&
5103 // prevent adding server score entries if also existing in local score file
5104 // (special case: historic score entries have an empty tape basename entry)
5105 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5106 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5108 // special case: use server score instead of local score value if higher
5109 // (historic scores might have been truncated to 16-bit values locally)
5110 if (score_is_better)
5111 entry->score = new_entry->score;
5116 if (is_better || entry_is_empty)
5118 // player has made it to the hall of fame
5120 if (i < MAX_SCORE_ENTRIES - 1)
5122 int m = MAX_SCORE_ENTRIES - 1;
5125 if (one_score_entry_per_name)
5127 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5128 if (strEqual(list->entry[l].name, new_entry->name))
5131 if (m == i) // player's new highscore overwrites his old one
5135 for (l = m; l > i; l--)
5136 list->entry[l] = list->entry[l - 1];
5141 *entry = *new_entry;
5145 else if (one_score_entry_per_name &&
5146 strEqual(entry->name, new_entry->name))
5148 // player already in high score list with better score or time
5157 void NewHighScore(int level_nr, boolean tape_saved)
5159 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5160 boolean one_per_name = FALSE;
5162 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5163 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5165 new_entry.score = game.score_final;
5166 new_entry.time = game.score_time_final;
5168 LoadScore(level_nr);
5170 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5172 if (scores.last_added < 0)
5175 SaveScore(level_nr);
5177 // store last added local score entry (before merging server scores)
5178 scores.last_added_local = scores.last_added;
5180 if (!game.LevelSolved_SaveTape)
5183 SaveScoreTape(level_nr);
5185 if (setup.ask_for_using_api_server)
5187 setup.use_api_server =
5188 Request("Upload your score and tape to the high score server?", REQ_ASK);
5190 if (!setup.use_api_server)
5191 Request("Not using high score server! Use setup menu to enable again!",
5194 runtime.use_api_server = setup.use_api_server;
5196 // after asking for using API server once, do not ask again
5197 setup.ask_for_using_api_server = FALSE;
5199 SaveSetup_ServerSetup();
5202 SaveServerScore(level_nr, tape_saved);
5205 void MergeServerScore(void)
5207 struct ScoreEntry last_added_entry;
5208 boolean one_per_name = FALSE;
5211 if (scores.last_added >= 0)
5212 last_added_entry = scores.entry[scores.last_added];
5214 for (i = 0; i < server_scores.num_entries; i++)
5216 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5218 if (pos >= 0 && pos <= scores.last_added)
5219 scores.last_added++;
5222 if (scores.last_added >= MAX_SCORE_ENTRIES)
5224 scores.last_added = MAX_SCORE_ENTRIES - 1;
5225 scores.force_last_added = TRUE;
5227 scores.entry[scores.last_added] = last_added_entry;
5231 static int getElementMoveStepsizeExt(int x, int y, int direction)
5233 int element = Tile[x][y];
5234 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5235 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5236 int horiz_move = (dx != 0);
5237 int sign = (horiz_move ? dx : dy);
5238 int step = sign * element_info[element].move_stepsize;
5240 // special values for move stepsize for spring and things on conveyor belt
5243 if (CAN_FALL(element) &&
5244 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5245 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5246 else if (element == EL_SPRING)
5247 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5253 static int getElementMoveStepsize(int x, int y)
5255 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5258 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5260 if (player->GfxAction != action || player->GfxDir != dir)
5262 player->GfxAction = action;
5263 player->GfxDir = dir;
5265 player->StepFrame = 0;
5269 static void ResetGfxFrame(int x, int y)
5271 // profiling showed that "autotest" spends 10~20% of its time in this function
5272 if (DrawingDeactivatedField())
5275 int element = Tile[x][y];
5276 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5278 if (graphic_info[graphic].anim_global_sync)
5279 GfxFrame[x][y] = FrameCounter;
5280 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5281 GfxFrame[x][y] = CustomValue[x][y];
5282 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5283 GfxFrame[x][y] = element_info[element].collect_score;
5284 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5285 GfxFrame[x][y] = ChangeDelay[x][y];
5288 static void ResetGfxAnimation(int x, int y)
5290 GfxAction[x][y] = ACTION_DEFAULT;
5291 GfxDir[x][y] = MovDir[x][y];
5294 ResetGfxFrame(x, y);
5297 static void ResetRandomAnimationValue(int x, int y)
5299 GfxRandom[x][y] = INIT_GFX_RANDOM();
5302 static void InitMovingField(int x, int y, int direction)
5304 int element = Tile[x][y];
5305 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5306 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5309 boolean is_moving_before, is_moving_after;
5311 // check if element was/is moving or being moved before/after mode change
5312 is_moving_before = (WasJustMoving[x][y] != 0);
5313 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5315 // reset animation only for moving elements which change direction of moving
5316 // or which just started or stopped moving
5317 // (else CEs with property "can move" / "not moving" are reset each frame)
5318 if (is_moving_before != is_moving_after ||
5319 direction != MovDir[x][y])
5320 ResetGfxAnimation(x, y);
5322 MovDir[x][y] = direction;
5323 GfxDir[x][y] = direction;
5325 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5326 direction == MV_DOWN && CAN_FALL(element) ?
5327 ACTION_FALLING : ACTION_MOVING);
5329 // this is needed for CEs with property "can move" / "not moving"
5331 if (is_moving_after)
5333 if (Tile[newx][newy] == EL_EMPTY)
5334 Tile[newx][newy] = EL_BLOCKED;
5336 MovDir[newx][newy] = MovDir[x][y];
5338 CustomValue[newx][newy] = CustomValue[x][y];
5340 GfxFrame[newx][newy] = GfxFrame[x][y];
5341 GfxRandom[newx][newy] = GfxRandom[x][y];
5342 GfxAction[newx][newy] = GfxAction[x][y];
5343 GfxDir[newx][newy] = GfxDir[x][y];
5347 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5349 int direction = MovDir[x][y];
5350 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5351 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5357 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5359 int oldx = x, oldy = y;
5360 int direction = MovDir[x][y];
5362 if (direction == MV_LEFT)
5364 else if (direction == MV_RIGHT)
5366 else if (direction == MV_UP)
5368 else if (direction == MV_DOWN)
5371 *comes_from_x = oldx;
5372 *comes_from_y = oldy;
5375 static int MovingOrBlocked2Element(int x, int y)
5377 int element = Tile[x][y];
5379 if (element == EL_BLOCKED)
5383 Blocked2Moving(x, y, &oldx, &oldy);
5384 return Tile[oldx][oldy];
5390 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5392 // like MovingOrBlocked2Element(), but if element is moving
5393 // and (x,y) is the field the moving element is just leaving,
5394 // return EL_BLOCKED instead of the element value
5395 int element = Tile[x][y];
5397 if (IS_MOVING(x, y))
5399 if (element == EL_BLOCKED)
5403 Blocked2Moving(x, y, &oldx, &oldy);
5404 return Tile[oldx][oldy];
5413 static void RemoveField(int x, int y)
5415 Tile[x][y] = EL_EMPTY;
5421 CustomValue[x][y] = 0;
5424 ChangeDelay[x][y] = 0;
5425 ChangePage[x][y] = -1;
5426 Pushed[x][y] = FALSE;
5428 GfxElement[x][y] = EL_UNDEFINED;
5429 GfxAction[x][y] = ACTION_DEFAULT;
5430 GfxDir[x][y] = MV_NONE;
5433 static void RemoveMovingField(int x, int y)
5435 int oldx = x, oldy = y, newx = x, newy = y;
5436 int element = Tile[x][y];
5437 int next_element = EL_UNDEFINED;
5439 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5442 if (IS_MOVING(x, y))
5444 Moving2Blocked(x, y, &newx, &newy);
5446 if (Tile[newx][newy] != EL_BLOCKED)
5448 // element is moving, but target field is not free (blocked), but
5449 // already occupied by something different (example: acid pool);
5450 // in this case, only remove the moving field, but not the target
5452 RemoveField(oldx, oldy);
5454 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5456 TEST_DrawLevelField(oldx, oldy);
5461 else if (element == EL_BLOCKED)
5463 Blocked2Moving(x, y, &oldx, &oldy);
5464 if (!IS_MOVING(oldx, oldy))
5468 if (element == EL_BLOCKED &&
5469 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5470 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5471 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5472 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5473 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5474 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5475 next_element = get_next_element(Tile[oldx][oldy]);
5477 RemoveField(oldx, oldy);
5478 RemoveField(newx, newy);
5480 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5482 if (next_element != EL_UNDEFINED)
5483 Tile[oldx][oldy] = next_element;
5485 TEST_DrawLevelField(oldx, oldy);
5486 TEST_DrawLevelField(newx, newy);
5489 void DrawDynamite(int x, int y)
5491 int sx = SCREENX(x), sy = SCREENY(y);
5492 int graphic = el2img(Tile[x][y]);
5495 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5498 if (IS_WALKABLE_INSIDE(Back[x][y]))
5502 DrawLevelElement(x, y, Back[x][y]);
5503 else if (Store[x][y])
5504 DrawLevelElement(x, y, Store[x][y]);
5505 else if (game.use_masked_elements)
5506 DrawLevelElement(x, y, EL_EMPTY);
5508 frame = getGraphicAnimationFrameXY(graphic, x, y);
5510 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5511 DrawGraphicThruMask(sx, sy, graphic, frame);
5513 DrawGraphic(sx, sy, graphic, frame);
5516 static void CheckDynamite(int x, int y)
5518 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5522 if (MovDelay[x][y] != 0)
5525 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5531 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5536 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5538 boolean num_checked_players = 0;
5541 for (i = 0; i < MAX_PLAYERS; i++)
5543 if (stored_player[i].active)
5545 int sx = stored_player[i].jx;
5546 int sy = stored_player[i].jy;
5548 if (num_checked_players == 0)
5555 *sx1 = MIN(*sx1, sx);
5556 *sy1 = MIN(*sy1, sy);
5557 *sx2 = MAX(*sx2, sx);
5558 *sy2 = MAX(*sy2, sy);
5561 num_checked_players++;
5566 static boolean checkIfAllPlayersFitToScreen_RND(void)
5568 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5570 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5572 return (sx2 - sx1 < SCR_FIELDX &&
5573 sy2 - sy1 < SCR_FIELDY);
5576 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5578 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5580 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5582 *sx = (sx1 + sx2) / 2;
5583 *sy = (sy1 + sy2) / 2;
5586 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5587 boolean center_screen, boolean quick_relocation)
5589 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5590 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5591 boolean no_delay = (tape.warp_forward);
5592 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5593 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5594 int new_scroll_x, new_scroll_y;
5596 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5598 // case 1: quick relocation inside visible screen (without scrolling)
5605 if (!level.shifted_relocation || center_screen)
5607 // relocation _with_ centering of screen
5609 new_scroll_x = SCROLL_POSITION_X(x);
5610 new_scroll_y = SCROLL_POSITION_Y(y);
5614 // relocation _without_ centering of screen
5616 int center_scroll_x = SCROLL_POSITION_X(old_x);
5617 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5618 int offset_x = x + (scroll_x - center_scroll_x);
5619 int offset_y = y + (scroll_y - center_scroll_y);
5621 // for new screen position, apply previous offset to center position
5622 new_scroll_x = SCROLL_POSITION_X(offset_x);
5623 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5626 if (quick_relocation)
5628 // case 2: quick relocation (redraw without visible scrolling)
5630 scroll_x = new_scroll_x;
5631 scroll_y = new_scroll_y;
5638 // case 3: visible relocation (with scrolling to new position)
5640 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5642 SetVideoFrameDelay(wait_delay_value);
5644 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5646 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5647 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5649 if (dx == 0 && dy == 0) // no scrolling needed at all
5655 // set values for horizontal/vertical screen scrolling (half tile size)
5656 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5657 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5658 int pos_x = dx * TILEX / 2;
5659 int pos_y = dy * TILEY / 2;
5660 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5661 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5663 ScrollLevel(dx, dy);
5666 // scroll in two steps of half tile size to make things smoother
5667 BlitScreenToBitmapExt_RND(window, fx, fy);
5669 // scroll second step to align at full tile size
5670 BlitScreenToBitmap(window);
5676 SetVideoFrameDelay(frame_delay_value_old);
5679 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5681 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5682 int player_nr = GET_PLAYER_NR(el_player);
5683 struct PlayerInfo *player = &stored_player[player_nr];
5684 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5685 boolean no_delay = (tape.warp_forward);
5686 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5687 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5688 int old_jx = player->jx;
5689 int old_jy = player->jy;
5690 int old_element = Tile[old_jx][old_jy];
5691 int element = Tile[jx][jy];
5692 boolean player_relocated = (old_jx != jx || old_jy != jy);
5694 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5695 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5696 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5697 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5698 int leave_side_horiz = move_dir_horiz;
5699 int leave_side_vert = move_dir_vert;
5700 int enter_side = enter_side_horiz | enter_side_vert;
5701 int leave_side = leave_side_horiz | leave_side_vert;
5703 if (player->buried) // do not reanimate dead player
5706 if (!player_relocated) // no need to relocate the player
5709 if (IS_PLAYER(jx, jy)) // player already placed at new position
5711 RemoveField(jx, jy); // temporarily remove newly placed player
5712 DrawLevelField(jx, jy);
5715 if (player->present)
5717 while (player->MovPos)
5719 ScrollPlayer(player, SCROLL_GO_ON);
5720 ScrollScreen(NULL, SCROLL_GO_ON);
5722 AdvanceFrameAndPlayerCounters(player->index_nr);
5726 BackToFront_WithFrameDelay(wait_delay_value);
5729 DrawPlayer(player); // needed here only to cleanup last field
5730 DrawLevelField(player->jx, player->jy); // remove player graphic
5732 player->is_moving = FALSE;
5735 if (IS_CUSTOM_ELEMENT(old_element))
5736 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5738 player->index_bit, leave_side);
5740 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5742 player->index_bit, leave_side);
5744 Tile[jx][jy] = el_player;
5745 InitPlayerField(jx, jy, el_player, TRUE);
5747 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5748 possible that the relocation target field did not contain a player element,
5749 but a walkable element, to which the new player was relocated -- in this
5750 case, restore that (already initialized!) element on the player field */
5751 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5753 Tile[jx][jy] = element; // restore previously existing element
5756 // only visually relocate centered player
5757 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5758 FALSE, level.instant_relocation);
5760 TestIfPlayerTouchesBadThing(jx, jy);
5761 TestIfPlayerTouchesCustomElement(jx, jy);
5763 if (IS_CUSTOM_ELEMENT(element))
5764 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5765 player->index_bit, enter_side);
5767 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5768 player->index_bit, enter_side);
5770 if (player->is_switching)
5772 /* ensure that relocation while still switching an element does not cause
5773 a new element to be treated as also switched directly after relocation
5774 (this is important for teleporter switches that teleport the player to
5775 a place where another teleporter switch is in the same direction, which
5776 would then incorrectly be treated as immediately switched before the
5777 direction key that caused the switch was released) */
5779 player->switch_x += jx - old_jx;
5780 player->switch_y += jy - old_jy;
5784 static void Explode(int ex, int ey, int phase, int mode)
5790 // !!! eliminate this variable !!!
5791 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5793 if (game.explosions_delayed)
5795 ExplodeField[ex][ey] = mode;
5799 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5801 int center_element = Tile[ex][ey];
5802 int artwork_element, explosion_element; // set these values later
5804 // remove things displayed in background while burning dynamite
5805 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5808 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5810 // put moving element to center field (and let it explode there)
5811 center_element = MovingOrBlocked2Element(ex, ey);
5812 RemoveMovingField(ex, ey);
5813 Tile[ex][ey] = center_element;
5816 // now "center_element" is finally determined -- set related values now
5817 artwork_element = center_element; // for custom player artwork
5818 explosion_element = center_element; // for custom player artwork
5820 if (IS_PLAYER(ex, ey))
5822 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5824 artwork_element = stored_player[player_nr].artwork_element;
5826 if (level.use_explosion_element[player_nr])
5828 explosion_element = level.explosion_element[player_nr];
5829 artwork_element = explosion_element;
5833 if (mode == EX_TYPE_NORMAL ||
5834 mode == EX_TYPE_CENTER ||
5835 mode == EX_TYPE_CROSS)
5836 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5838 last_phase = element_info[explosion_element].explosion_delay + 1;
5840 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5842 int xx = x - ex + 1;
5843 int yy = y - ey + 1;
5846 if (!IN_LEV_FIELD(x, y) ||
5847 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5848 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5851 element = Tile[x][y];
5853 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5855 element = MovingOrBlocked2Element(x, y);
5857 if (!IS_EXPLOSION_PROOF(element))
5858 RemoveMovingField(x, y);
5861 // indestructible elements can only explode in center (but not flames)
5862 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5863 mode == EX_TYPE_BORDER)) ||
5864 element == EL_FLAMES)
5867 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5868 behaviour, for example when touching a yamyam that explodes to rocks
5869 with active deadly shield, a rock is created under the player !!! */
5870 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5872 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5873 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5874 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5876 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5879 if (IS_ACTIVE_BOMB(element))
5881 // re-activate things under the bomb like gate or penguin
5882 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5889 // save walkable background elements while explosion on same tile
5890 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5891 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5892 Back[x][y] = element;
5894 // ignite explodable elements reached by other explosion
5895 if (element == EL_EXPLOSION)
5896 element = Store2[x][y];
5898 if (AmoebaNr[x][y] &&
5899 (element == EL_AMOEBA_FULL ||
5900 element == EL_BD_AMOEBA ||
5901 element == EL_AMOEBA_GROWING))
5903 AmoebaCnt[AmoebaNr[x][y]]--;
5904 AmoebaCnt2[AmoebaNr[x][y]]--;
5909 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5911 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5913 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5915 if (PLAYERINFO(ex, ey)->use_murphy)
5916 Store[x][y] = EL_EMPTY;
5919 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5920 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5921 else if (IS_PLAYER_ELEMENT(center_element))
5922 Store[x][y] = EL_EMPTY;
5923 else if (center_element == EL_YAMYAM)
5924 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5925 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5926 Store[x][y] = element_info[center_element].content.e[xx][yy];
5928 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5929 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5930 // otherwise) -- FIX THIS !!!
5931 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5932 Store[x][y] = element_info[element].content.e[1][1];
5934 else if (!CAN_EXPLODE(element))
5935 Store[x][y] = element_info[element].content.e[1][1];
5938 Store[x][y] = EL_EMPTY;
5940 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5941 center_element == EL_AMOEBA_TO_DIAMOND)
5942 Store2[x][y] = element;
5944 Tile[x][y] = EL_EXPLOSION;
5945 GfxElement[x][y] = artwork_element;
5947 ExplodePhase[x][y] = 1;
5948 ExplodeDelay[x][y] = last_phase;
5953 if (center_element == EL_YAMYAM)
5954 game.yamyam_content_nr =
5955 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5967 GfxFrame[x][y] = 0; // restart explosion animation
5969 last_phase = ExplodeDelay[x][y];
5971 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5973 // this can happen if the player leaves an explosion just in time
5974 if (GfxElement[x][y] == EL_UNDEFINED)
5975 GfxElement[x][y] = EL_EMPTY;
5977 border_element = Store2[x][y];
5978 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5979 border_element = StorePlayer[x][y];
5981 if (phase == element_info[border_element].ignition_delay ||
5982 phase == last_phase)
5984 boolean border_explosion = FALSE;
5986 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
5987 !PLAYER_EXPLOSION_PROTECTED(x, y))
5989 KillPlayerUnlessExplosionProtected(x, y);
5990 border_explosion = TRUE;
5992 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
5994 Tile[x][y] = Store2[x][y];
5997 border_explosion = TRUE;
5999 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6001 AmoebaToDiamond(x, y);
6003 border_explosion = TRUE;
6006 // if an element just explodes due to another explosion (chain-reaction),
6007 // do not immediately end the new explosion when it was the last frame of
6008 // the explosion (as it would be done in the following "if"-statement!)
6009 if (border_explosion && phase == last_phase)
6013 // this can happen if the player was just killed by an explosion
6014 if (GfxElement[x][y] == EL_UNDEFINED)
6015 GfxElement[x][y] = EL_EMPTY;
6017 if (phase == last_phase)
6021 element = Tile[x][y] = Store[x][y];
6022 Store[x][y] = Store2[x][y] = 0;
6023 GfxElement[x][y] = EL_UNDEFINED;
6025 // player can escape from explosions and might therefore be still alive
6026 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6027 element <= EL_PLAYER_IS_EXPLODING_4)
6029 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6030 int explosion_element = EL_PLAYER_1 + player_nr;
6031 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6032 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6034 if (level.use_explosion_element[player_nr])
6035 explosion_element = level.explosion_element[player_nr];
6037 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6038 element_info[explosion_element].content.e[xx][yy]);
6041 // restore probably existing indestructible background element
6042 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6043 element = Tile[x][y] = Back[x][y];
6046 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6047 GfxDir[x][y] = MV_NONE;
6048 ChangeDelay[x][y] = 0;
6049 ChangePage[x][y] = -1;
6051 CustomValue[x][y] = 0;
6053 InitField_WithBug2(x, y, FALSE);
6055 TEST_DrawLevelField(x, y);
6057 TestIfElementTouchesCustomElement(x, y);
6059 if (GFX_CRUMBLED(element))
6060 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6062 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6063 StorePlayer[x][y] = 0;
6065 if (IS_PLAYER_ELEMENT(element))
6066 RelocatePlayer(x, y, element);
6068 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6070 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6071 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6074 TEST_DrawLevelFieldCrumbled(x, y);
6076 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6078 DrawLevelElement(x, y, Back[x][y]);
6079 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6081 else if (IS_WALKABLE_UNDER(Back[x][y]))
6083 DrawLevelGraphic(x, y, graphic, frame);
6084 DrawLevelElementThruMask(x, y, Back[x][y]);
6086 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6087 DrawLevelGraphic(x, y, graphic, frame);
6091 static void DynaExplode(int ex, int ey)
6094 int dynabomb_element = Tile[ex][ey];
6095 int dynabomb_size = 1;
6096 boolean dynabomb_xl = FALSE;
6097 struct PlayerInfo *player;
6098 static int xy[4][2] =
6106 if (IS_ACTIVE_BOMB(dynabomb_element))
6108 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6109 dynabomb_size = player->dynabomb_size;
6110 dynabomb_xl = player->dynabomb_xl;
6111 player->dynabombs_left++;
6114 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6116 for (i = 0; i < NUM_DIRECTIONS; i++)
6118 for (j = 1; j <= dynabomb_size; j++)
6120 int x = ex + j * xy[i][0];
6121 int y = ey + j * xy[i][1];
6124 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6127 element = Tile[x][y];
6129 // do not restart explosions of fields with active bombs
6130 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6133 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6135 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6136 !IS_DIGGABLE(element) && !dynabomb_xl)
6142 void Bang(int x, int y)
6144 int element = MovingOrBlocked2Element(x, y);
6145 int explosion_type = EX_TYPE_NORMAL;
6147 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6149 struct PlayerInfo *player = PLAYERINFO(x, y);
6151 element = Tile[x][y] = player->initial_element;
6153 if (level.use_explosion_element[player->index_nr])
6155 int explosion_element = level.explosion_element[player->index_nr];
6157 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6158 explosion_type = EX_TYPE_CROSS;
6159 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6160 explosion_type = EX_TYPE_CENTER;
6168 case EL_BD_BUTTERFLY:
6171 case EL_DARK_YAMYAM:
6175 RaiseScoreElement(element);
6178 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6179 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6180 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6181 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6182 case EL_DYNABOMB_INCREASE_NUMBER:
6183 case EL_DYNABOMB_INCREASE_SIZE:
6184 case EL_DYNABOMB_INCREASE_POWER:
6185 explosion_type = EX_TYPE_DYNA;
6188 case EL_DC_LANDMINE:
6189 explosion_type = EX_TYPE_CENTER;
6194 case EL_LAMP_ACTIVE:
6195 case EL_AMOEBA_TO_DIAMOND:
6196 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6197 explosion_type = EX_TYPE_CENTER;
6201 if (element_info[element].explosion_type == EXPLODES_CROSS)
6202 explosion_type = EX_TYPE_CROSS;
6203 else if (element_info[element].explosion_type == EXPLODES_1X1)
6204 explosion_type = EX_TYPE_CENTER;
6208 if (explosion_type == EX_TYPE_DYNA)
6211 Explode(x, y, EX_PHASE_START, explosion_type);
6213 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6216 static void SplashAcid(int x, int y)
6218 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6219 (!IN_LEV_FIELD(x - 1, y - 2) ||
6220 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6221 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6223 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6224 (!IN_LEV_FIELD(x + 1, y - 2) ||
6225 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6226 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6228 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6231 static void InitBeltMovement(void)
6233 static int belt_base_element[4] =
6235 EL_CONVEYOR_BELT_1_LEFT,
6236 EL_CONVEYOR_BELT_2_LEFT,
6237 EL_CONVEYOR_BELT_3_LEFT,
6238 EL_CONVEYOR_BELT_4_LEFT
6240 static int belt_base_active_element[4] =
6242 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6243 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6244 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6245 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6250 // set frame order for belt animation graphic according to belt direction
6251 for (i = 0; i < NUM_BELTS; i++)
6255 for (j = 0; j < NUM_BELT_PARTS; j++)
6257 int element = belt_base_active_element[belt_nr] + j;
6258 int graphic_1 = el2img(element);
6259 int graphic_2 = el2panelimg(element);
6261 if (game.belt_dir[i] == MV_LEFT)
6263 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6264 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6268 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6269 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6274 SCAN_PLAYFIELD(x, y)
6276 int element = Tile[x][y];
6278 for (i = 0; i < NUM_BELTS; i++)
6280 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6282 int e_belt_nr = getBeltNrFromBeltElement(element);
6285 if (e_belt_nr == belt_nr)
6287 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6289 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6296 static void ToggleBeltSwitch(int x, int y)
6298 static int belt_base_element[4] =
6300 EL_CONVEYOR_BELT_1_LEFT,
6301 EL_CONVEYOR_BELT_2_LEFT,
6302 EL_CONVEYOR_BELT_3_LEFT,
6303 EL_CONVEYOR_BELT_4_LEFT
6305 static int belt_base_active_element[4] =
6307 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6308 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6309 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6310 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6312 static int belt_base_switch_element[4] =
6314 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6315 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6316 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6317 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6319 static int belt_move_dir[4] =
6327 int element = Tile[x][y];
6328 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6329 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6330 int belt_dir = belt_move_dir[belt_dir_nr];
6333 if (!IS_BELT_SWITCH(element))
6336 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6337 game.belt_dir[belt_nr] = belt_dir;
6339 if (belt_dir_nr == 3)
6342 // set frame order for belt animation graphic according to belt direction
6343 for (i = 0; i < NUM_BELT_PARTS; i++)
6345 int element = belt_base_active_element[belt_nr] + i;
6346 int graphic_1 = el2img(element);
6347 int graphic_2 = el2panelimg(element);
6349 if (belt_dir == MV_LEFT)
6351 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6352 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6356 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6357 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6361 SCAN_PLAYFIELD(xx, yy)
6363 int element = Tile[xx][yy];
6365 if (IS_BELT_SWITCH(element))
6367 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6369 if (e_belt_nr == belt_nr)
6371 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6372 TEST_DrawLevelField(xx, yy);
6375 else if (IS_BELT(element) && belt_dir != MV_NONE)
6377 int e_belt_nr = getBeltNrFromBeltElement(element);
6379 if (e_belt_nr == belt_nr)
6381 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6383 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6384 TEST_DrawLevelField(xx, yy);
6387 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6389 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6391 if (e_belt_nr == belt_nr)
6393 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6395 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6396 TEST_DrawLevelField(xx, yy);
6402 static void ToggleSwitchgateSwitch(int x, int y)
6406 game.switchgate_pos = !game.switchgate_pos;
6408 SCAN_PLAYFIELD(xx, yy)
6410 int element = Tile[xx][yy];
6412 if (element == EL_SWITCHGATE_SWITCH_UP)
6414 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6415 TEST_DrawLevelField(xx, yy);
6417 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6419 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6420 TEST_DrawLevelField(xx, yy);
6422 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6424 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6425 TEST_DrawLevelField(xx, yy);
6427 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6429 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6430 TEST_DrawLevelField(xx, yy);
6432 else if (element == EL_SWITCHGATE_OPEN ||
6433 element == EL_SWITCHGATE_OPENING)
6435 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6437 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6439 else if (element == EL_SWITCHGATE_CLOSED ||
6440 element == EL_SWITCHGATE_CLOSING)
6442 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6444 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6449 static int getInvisibleActiveFromInvisibleElement(int element)
6451 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6452 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6453 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6457 static int getInvisibleFromInvisibleActiveElement(int element)
6459 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6460 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6461 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6465 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6469 SCAN_PLAYFIELD(x, y)
6471 int element = Tile[x][y];
6473 if (element == EL_LIGHT_SWITCH &&
6474 game.light_time_left > 0)
6476 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6477 TEST_DrawLevelField(x, y);
6479 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6480 game.light_time_left == 0)
6482 Tile[x][y] = EL_LIGHT_SWITCH;
6483 TEST_DrawLevelField(x, y);
6485 else if (element == EL_EMC_DRIPPER &&
6486 game.light_time_left > 0)
6488 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6489 TEST_DrawLevelField(x, y);
6491 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6492 game.light_time_left == 0)
6494 Tile[x][y] = EL_EMC_DRIPPER;
6495 TEST_DrawLevelField(x, y);
6497 else if (element == EL_INVISIBLE_STEELWALL ||
6498 element == EL_INVISIBLE_WALL ||
6499 element == EL_INVISIBLE_SAND)
6501 if (game.light_time_left > 0)
6502 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6504 TEST_DrawLevelField(x, y);
6506 // uncrumble neighbour fields, if needed
6507 if (element == EL_INVISIBLE_SAND)
6508 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6510 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6511 element == EL_INVISIBLE_WALL_ACTIVE ||
6512 element == EL_INVISIBLE_SAND_ACTIVE)
6514 if (game.light_time_left == 0)
6515 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6517 TEST_DrawLevelField(x, y);
6519 // re-crumble neighbour fields, if needed
6520 if (element == EL_INVISIBLE_SAND)
6521 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6526 static void RedrawAllInvisibleElementsForLenses(void)
6530 SCAN_PLAYFIELD(x, y)
6532 int element = Tile[x][y];
6534 if (element == EL_EMC_DRIPPER &&
6535 game.lenses_time_left > 0)
6537 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6538 TEST_DrawLevelField(x, y);
6540 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6541 game.lenses_time_left == 0)
6543 Tile[x][y] = EL_EMC_DRIPPER;
6544 TEST_DrawLevelField(x, y);
6546 else if (element == EL_INVISIBLE_STEELWALL ||
6547 element == EL_INVISIBLE_WALL ||
6548 element == EL_INVISIBLE_SAND)
6550 if (game.lenses_time_left > 0)
6551 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6553 TEST_DrawLevelField(x, y);
6555 // uncrumble neighbour fields, if needed
6556 if (element == EL_INVISIBLE_SAND)
6557 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6559 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6560 element == EL_INVISIBLE_WALL_ACTIVE ||
6561 element == EL_INVISIBLE_SAND_ACTIVE)
6563 if (game.lenses_time_left == 0)
6564 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6566 TEST_DrawLevelField(x, y);
6568 // re-crumble neighbour fields, if needed
6569 if (element == EL_INVISIBLE_SAND)
6570 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6575 static void RedrawAllInvisibleElementsForMagnifier(void)
6579 SCAN_PLAYFIELD(x, y)
6581 int element = Tile[x][y];
6583 if (element == EL_EMC_FAKE_GRASS &&
6584 game.magnify_time_left > 0)
6586 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6587 TEST_DrawLevelField(x, y);
6589 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6590 game.magnify_time_left == 0)
6592 Tile[x][y] = EL_EMC_FAKE_GRASS;
6593 TEST_DrawLevelField(x, y);
6595 else if (IS_GATE_GRAY(element) &&
6596 game.magnify_time_left > 0)
6598 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6599 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6600 IS_EM_GATE_GRAY(element) ?
6601 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6602 IS_EMC_GATE_GRAY(element) ?
6603 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6604 IS_DC_GATE_GRAY(element) ?
6605 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6607 TEST_DrawLevelField(x, y);
6609 else if (IS_GATE_GRAY_ACTIVE(element) &&
6610 game.magnify_time_left == 0)
6612 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6613 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6614 IS_EM_GATE_GRAY_ACTIVE(element) ?
6615 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6616 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6617 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6618 IS_DC_GATE_GRAY_ACTIVE(element) ?
6619 EL_DC_GATE_WHITE_GRAY :
6621 TEST_DrawLevelField(x, y);
6626 static void ToggleLightSwitch(int x, int y)
6628 int element = Tile[x][y];
6630 game.light_time_left =
6631 (element == EL_LIGHT_SWITCH ?
6632 level.time_light * FRAMES_PER_SECOND : 0);
6634 RedrawAllLightSwitchesAndInvisibleElements();
6637 static void ActivateTimegateSwitch(int x, int y)
6641 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6643 SCAN_PLAYFIELD(xx, yy)
6645 int element = Tile[xx][yy];
6647 if (element == EL_TIMEGATE_CLOSED ||
6648 element == EL_TIMEGATE_CLOSING)
6650 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6651 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6655 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6657 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6658 TEST_DrawLevelField(xx, yy);
6664 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6665 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6668 static void Impact(int x, int y)
6670 boolean last_line = (y == lev_fieldy - 1);
6671 boolean object_hit = FALSE;
6672 boolean impact = (last_line || object_hit);
6673 int element = Tile[x][y];
6674 int smashed = EL_STEELWALL;
6676 if (!last_line) // check if element below was hit
6678 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6681 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6682 MovDir[x][y + 1] != MV_DOWN ||
6683 MovPos[x][y + 1] <= TILEY / 2));
6685 // do not smash moving elements that left the smashed field in time
6686 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6687 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6690 #if USE_QUICKSAND_IMPACT_BUGFIX
6691 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6693 RemoveMovingField(x, y + 1);
6694 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6695 Tile[x][y + 2] = EL_ROCK;
6696 TEST_DrawLevelField(x, y + 2);
6701 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6703 RemoveMovingField(x, y + 1);
6704 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6705 Tile[x][y + 2] = EL_ROCK;
6706 TEST_DrawLevelField(x, y + 2);
6713 smashed = MovingOrBlocked2Element(x, y + 1);
6715 impact = (last_line || object_hit);
6718 if (!last_line && smashed == EL_ACID) // element falls into acid
6720 SplashAcid(x, y + 1);
6724 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6725 // only reset graphic animation if graphic really changes after impact
6727 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6729 ResetGfxAnimation(x, y);
6730 TEST_DrawLevelField(x, y);
6733 if (impact && CAN_EXPLODE_IMPACT(element))
6738 else if (impact && element == EL_PEARL &&
6739 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6741 ResetGfxAnimation(x, y);
6743 Tile[x][y] = EL_PEARL_BREAKING;
6744 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6747 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6749 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6754 if (impact && element == EL_AMOEBA_DROP)
6756 if (object_hit && IS_PLAYER(x, y + 1))
6757 KillPlayerUnlessEnemyProtected(x, y + 1);
6758 else if (object_hit && smashed == EL_PENGUIN)
6762 Tile[x][y] = EL_AMOEBA_GROWING;
6763 Store[x][y] = EL_AMOEBA_WET;
6765 ResetRandomAnimationValue(x, y);
6770 if (object_hit) // check which object was hit
6772 if ((CAN_PASS_MAGIC_WALL(element) &&
6773 (smashed == EL_MAGIC_WALL ||
6774 smashed == EL_BD_MAGIC_WALL)) ||
6775 (CAN_PASS_DC_MAGIC_WALL(element) &&
6776 smashed == EL_DC_MAGIC_WALL))
6779 int activated_magic_wall =
6780 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6781 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6782 EL_DC_MAGIC_WALL_ACTIVE);
6784 // activate magic wall / mill
6785 SCAN_PLAYFIELD(xx, yy)
6787 if (Tile[xx][yy] == smashed)
6788 Tile[xx][yy] = activated_magic_wall;
6791 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6792 game.magic_wall_active = TRUE;
6794 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6795 SND_MAGIC_WALL_ACTIVATING :
6796 smashed == EL_BD_MAGIC_WALL ?
6797 SND_BD_MAGIC_WALL_ACTIVATING :
6798 SND_DC_MAGIC_WALL_ACTIVATING));
6801 if (IS_PLAYER(x, y + 1))
6803 if (CAN_SMASH_PLAYER(element))
6805 KillPlayerUnlessEnemyProtected(x, y + 1);
6809 else if (smashed == EL_PENGUIN)
6811 if (CAN_SMASH_PLAYER(element))
6817 else if (element == EL_BD_DIAMOND)
6819 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6825 else if (((element == EL_SP_INFOTRON ||
6826 element == EL_SP_ZONK) &&
6827 (smashed == EL_SP_SNIKSNAK ||
6828 smashed == EL_SP_ELECTRON ||
6829 smashed == EL_SP_DISK_ORANGE)) ||
6830 (element == EL_SP_INFOTRON &&
6831 smashed == EL_SP_DISK_YELLOW))
6836 else if (CAN_SMASH_EVERYTHING(element))
6838 if (IS_CLASSIC_ENEMY(smashed) ||
6839 CAN_EXPLODE_SMASHED(smashed))
6844 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6846 if (smashed == EL_LAMP ||
6847 smashed == EL_LAMP_ACTIVE)
6852 else if (smashed == EL_NUT)
6854 Tile[x][y + 1] = EL_NUT_BREAKING;
6855 PlayLevelSound(x, y, SND_NUT_BREAKING);
6856 RaiseScoreElement(EL_NUT);
6859 else if (smashed == EL_PEARL)
6861 ResetGfxAnimation(x, y);
6863 Tile[x][y + 1] = EL_PEARL_BREAKING;
6864 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6867 else if (smashed == EL_DIAMOND)
6869 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6870 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6873 else if (IS_BELT_SWITCH(smashed))
6875 ToggleBeltSwitch(x, y + 1);
6877 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6878 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6879 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6880 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6882 ToggleSwitchgateSwitch(x, y + 1);
6884 else if (smashed == EL_LIGHT_SWITCH ||
6885 smashed == EL_LIGHT_SWITCH_ACTIVE)
6887 ToggleLightSwitch(x, y + 1);
6891 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6893 CheckElementChangeBySide(x, y + 1, smashed, element,
6894 CE_SWITCHED, CH_SIDE_TOP);
6895 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6901 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6906 // play sound of magic wall / mill
6908 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6909 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6910 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6912 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6913 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6914 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6915 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6916 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6917 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6922 // play sound of object that hits the ground
6923 if (last_line || object_hit)
6924 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6927 static void TurnRoundExt(int x, int y)
6939 { 0, 0 }, { 0, 0 }, { 0, 0 },
6944 int left, right, back;
6948 { MV_DOWN, MV_UP, MV_RIGHT },
6949 { MV_UP, MV_DOWN, MV_LEFT },
6951 { MV_LEFT, MV_RIGHT, MV_DOWN },
6955 { MV_RIGHT, MV_LEFT, MV_UP }
6958 int element = Tile[x][y];
6959 int move_pattern = element_info[element].move_pattern;
6961 int old_move_dir = MovDir[x][y];
6962 int left_dir = turn[old_move_dir].left;
6963 int right_dir = turn[old_move_dir].right;
6964 int back_dir = turn[old_move_dir].back;
6966 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6967 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6968 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6969 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6971 int left_x = x + left_dx, left_y = y + left_dy;
6972 int right_x = x + right_dx, right_y = y + right_dy;
6973 int move_x = x + move_dx, move_y = y + move_dy;
6977 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6979 TestIfBadThingTouchesOtherBadThing(x, y);
6981 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6982 MovDir[x][y] = right_dir;
6983 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6984 MovDir[x][y] = left_dir;
6986 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
6988 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
6991 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
6993 TestIfBadThingTouchesOtherBadThing(x, y);
6995 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
6996 MovDir[x][y] = left_dir;
6997 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6998 MovDir[x][y] = right_dir;
7000 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7002 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7005 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7007 TestIfBadThingTouchesOtherBadThing(x, y);
7009 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7010 MovDir[x][y] = left_dir;
7011 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7012 MovDir[x][y] = right_dir;
7014 if (MovDir[x][y] != old_move_dir)
7017 else if (element == EL_YAMYAM)
7019 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7020 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7022 if (can_turn_left && can_turn_right)
7023 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7024 else if (can_turn_left)
7025 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7026 else if (can_turn_right)
7027 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7029 MovDir[x][y] = back_dir;
7031 MovDelay[x][y] = 16 + 16 * RND(3);
7033 else if (element == EL_DARK_YAMYAM)
7035 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7037 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7040 if (can_turn_left && can_turn_right)
7041 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7042 else if (can_turn_left)
7043 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7044 else if (can_turn_right)
7045 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7047 MovDir[x][y] = back_dir;
7049 MovDelay[x][y] = 16 + 16 * RND(3);
7051 else if (element == EL_PACMAN)
7053 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7054 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7056 if (can_turn_left && can_turn_right)
7057 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7058 else if (can_turn_left)
7059 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7060 else if (can_turn_right)
7061 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7063 MovDir[x][y] = back_dir;
7065 MovDelay[x][y] = 6 + RND(40);
7067 else if (element == EL_PIG)
7069 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7070 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7071 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7072 boolean should_turn_left, should_turn_right, should_move_on;
7074 int rnd = RND(rnd_value);
7076 should_turn_left = (can_turn_left &&
7078 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7079 y + back_dy + left_dy)));
7080 should_turn_right = (can_turn_right &&
7082 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7083 y + back_dy + right_dy)));
7084 should_move_on = (can_move_on &&
7087 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7088 y + move_dy + left_dy) ||
7089 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7090 y + move_dy + right_dy)));
7092 if (should_turn_left || should_turn_right || should_move_on)
7094 if (should_turn_left && should_turn_right && should_move_on)
7095 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7096 rnd < 2 * rnd_value / 3 ? right_dir :
7098 else if (should_turn_left && should_turn_right)
7099 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7100 else if (should_turn_left && should_move_on)
7101 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7102 else if (should_turn_right && should_move_on)
7103 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7104 else if (should_turn_left)
7105 MovDir[x][y] = left_dir;
7106 else if (should_turn_right)
7107 MovDir[x][y] = right_dir;
7108 else if (should_move_on)
7109 MovDir[x][y] = old_move_dir;
7111 else if (can_move_on && rnd > rnd_value / 8)
7112 MovDir[x][y] = old_move_dir;
7113 else if (can_turn_left && can_turn_right)
7114 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7115 else if (can_turn_left && rnd > rnd_value / 8)
7116 MovDir[x][y] = left_dir;
7117 else if (can_turn_right && rnd > rnd_value/8)
7118 MovDir[x][y] = right_dir;
7120 MovDir[x][y] = back_dir;
7122 xx = x + move_xy[MovDir[x][y]].dx;
7123 yy = y + move_xy[MovDir[x][y]].dy;
7125 if (!IN_LEV_FIELD(xx, yy) ||
7126 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7127 MovDir[x][y] = old_move_dir;
7131 else if (element == EL_DRAGON)
7133 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7134 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7135 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7137 int rnd = RND(rnd_value);
7139 if (can_move_on && rnd > rnd_value / 8)
7140 MovDir[x][y] = old_move_dir;
7141 else if (can_turn_left && can_turn_right)
7142 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7143 else if (can_turn_left && rnd > rnd_value / 8)
7144 MovDir[x][y] = left_dir;
7145 else if (can_turn_right && rnd > rnd_value / 8)
7146 MovDir[x][y] = right_dir;
7148 MovDir[x][y] = back_dir;
7150 xx = x + move_xy[MovDir[x][y]].dx;
7151 yy = y + move_xy[MovDir[x][y]].dy;
7153 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7154 MovDir[x][y] = old_move_dir;
7158 else if (element == EL_MOLE)
7160 boolean can_move_on =
7161 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7162 IS_AMOEBOID(Tile[move_x][move_y]) ||
7163 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7166 boolean can_turn_left =
7167 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7168 IS_AMOEBOID(Tile[left_x][left_y])));
7170 boolean can_turn_right =
7171 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7172 IS_AMOEBOID(Tile[right_x][right_y])));
7174 if (can_turn_left && can_turn_right)
7175 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7176 else if (can_turn_left)
7177 MovDir[x][y] = left_dir;
7179 MovDir[x][y] = right_dir;
7182 if (MovDir[x][y] != old_move_dir)
7185 else if (element == EL_BALLOON)
7187 MovDir[x][y] = game.wind_direction;
7190 else if (element == EL_SPRING)
7192 if (MovDir[x][y] & MV_HORIZONTAL)
7194 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7195 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7197 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7198 ResetGfxAnimation(move_x, move_y);
7199 TEST_DrawLevelField(move_x, move_y);
7201 MovDir[x][y] = back_dir;
7203 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7204 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7205 MovDir[x][y] = MV_NONE;
7210 else if (element == EL_ROBOT ||
7211 element == EL_SATELLITE ||
7212 element == EL_PENGUIN ||
7213 element == EL_EMC_ANDROID)
7215 int attr_x = -1, attr_y = -1;
7217 if (game.all_players_gone)
7219 attr_x = game.exit_x;
7220 attr_y = game.exit_y;
7226 for (i = 0; i < MAX_PLAYERS; i++)
7228 struct PlayerInfo *player = &stored_player[i];
7229 int jx = player->jx, jy = player->jy;
7231 if (!player->active)
7235 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7243 if (element == EL_ROBOT &&
7244 game.robot_wheel_x >= 0 &&
7245 game.robot_wheel_y >= 0 &&
7246 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7247 game.engine_version < VERSION_IDENT(3,1,0,0)))
7249 attr_x = game.robot_wheel_x;
7250 attr_y = game.robot_wheel_y;
7253 if (element == EL_PENGUIN)
7256 static int xy[4][2] =
7264 for (i = 0; i < NUM_DIRECTIONS; i++)
7266 int ex = x + xy[i][0];
7267 int ey = y + xy[i][1];
7269 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7270 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7271 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7272 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7281 MovDir[x][y] = MV_NONE;
7283 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7284 else if (attr_x > x)
7285 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7287 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7288 else if (attr_y > y)
7289 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7291 if (element == EL_ROBOT)
7295 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7296 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7297 Moving2Blocked(x, y, &newx, &newy);
7299 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7300 MovDelay[x][y] = 8 + 8 * !RND(3);
7302 MovDelay[x][y] = 16;
7304 else if (element == EL_PENGUIN)
7310 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7312 boolean first_horiz = RND(2);
7313 int new_move_dir = MovDir[x][y];
7316 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7317 Moving2Blocked(x, y, &newx, &newy);
7319 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7323 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7324 Moving2Blocked(x, y, &newx, &newy);
7326 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7329 MovDir[x][y] = old_move_dir;
7333 else if (element == EL_SATELLITE)
7339 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7341 boolean first_horiz = RND(2);
7342 int new_move_dir = MovDir[x][y];
7345 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7346 Moving2Blocked(x, y, &newx, &newy);
7348 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7352 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7353 Moving2Blocked(x, y, &newx, &newy);
7355 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7358 MovDir[x][y] = old_move_dir;
7362 else if (element == EL_EMC_ANDROID)
7364 static int check_pos[16] =
7366 -1, // 0 => (invalid)
7369 -1, // 3 => (invalid)
7371 0, // 5 => MV_LEFT | MV_UP
7372 2, // 6 => MV_RIGHT | MV_UP
7373 -1, // 7 => (invalid)
7375 6, // 9 => MV_LEFT | MV_DOWN
7376 4, // 10 => MV_RIGHT | MV_DOWN
7377 -1, // 11 => (invalid)
7378 -1, // 12 => (invalid)
7379 -1, // 13 => (invalid)
7380 -1, // 14 => (invalid)
7381 -1, // 15 => (invalid)
7389 { -1, -1, MV_LEFT | MV_UP },
7391 { +1, -1, MV_RIGHT | MV_UP },
7392 { +1, 0, MV_RIGHT },
7393 { +1, +1, MV_RIGHT | MV_DOWN },
7395 { -1, +1, MV_LEFT | MV_DOWN },
7398 int start_pos, check_order;
7399 boolean can_clone = FALSE;
7402 // check if there is any free field around current position
7403 for (i = 0; i < 8; i++)
7405 int newx = x + check_xy[i].dx;
7406 int newy = y + check_xy[i].dy;
7408 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7416 if (can_clone) // randomly find an element to clone
7420 start_pos = check_pos[RND(8)];
7421 check_order = (RND(2) ? -1 : +1);
7423 for (i = 0; i < 8; i++)
7425 int pos_raw = start_pos + i * check_order;
7426 int pos = (pos_raw + 8) % 8;
7427 int newx = x + check_xy[pos].dx;
7428 int newy = y + check_xy[pos].dy;
7430 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7432 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7433 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7435 Store[x][y] = Tile[newx][newy];
7444 if (can_clone) // randomly find a direction to move
7448 start_pos = check_pos[RND(8)];
7449 check_order = (RND(2) ? -1 : +1);
7451 for (i = 0; i < 8; i++)
7453 int pos_raw = start_pos + i * check_order;
7454 int pos = (pos_raw + 8) % 8;
7455 int newx = x + check_xy[pos].dx;
7456 int newy = y + check_xy[pos].dy;
7457 int new_move_dir = check_xy[pos].dir;
7459 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7461 MovDir[x][y] = new_move_dir;
7462 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7471 if (can_clone) // cloning and moving successful
7474 // cannot clone -- try to move towards player
7476 start_pos = check_pos[MovDir[x][y] & 0x0f];
7477 check_order = (RND(2) ? -1 : +1);
7479 for (i = 0; i < 3; i++)
7481 // first check start_pos, then previous/next or (next/previous) pos
7482 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7483 int pos = (pos_raw + 8) % 8;
7484 int newx = x + check_xy[pos].dx;
7485 int newy = y + check_xy[pos].dy;
7486 int new_move_dir = check_xy[pos].dir;
7488 if (IS_PLAYER(newx, newy))
7491 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7493 MovDir[x][y] = new_move_dir;
7494 MovDelay[x][y] = level.android_move_time * 8 + 1;
7501 else if (move_pattern == MV_TURNING_LEFT ||
7502 move_pattern == MV_TURNING_RIGHT ||
7503 move_pattern == MV_TURNING_LEFT_RIGHT ||
7504 move_pattern == MV_TURNING_RIGHT_LEFT ||
7505 move_pattern == MV_TURNING_RANDOM ||
7506 move_pattern == MV_ALL_DIRECTIONS)
7508 boolean can_turn_left =
7509 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7510 boolean can_turn_right =
7511 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7513 if (element_info[element].move_stepsize == 0) // "not moving"
7516 if (move_pattern == MV_TURNING_LEFT)
7517 MovDir[x][y] = left_dir;
7518 else if (move_pattern == MV_TURNING_RIGHT)
7519 MovDir[x][y] = right_dir;
7520 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7521 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7522 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7523 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7524 else if (move_pattern == MV_TURNING_RANDOM)
7525 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7526 can_turn_right && !can_turn_left ? right_dir :
7527 RND(2) ? left_dir : right_dir);
7528 else if (can_turn_left && can_turn_right)
7529 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7530 else if (can_turn_left)
7531 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7532 else if (can_turn_right)
7533 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7535 MovDir[x][y] = back_dir;
7537 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7539 else if (move_pattern == MV_HORIZONTAL ||
7540 move_pattern == MV_VERTICAL)
7542 if (move_pattern & old_move_dir)
7543 MovDir[x][y] = back_dir;
7544 else if (move_pattern == MV_HORIZONTAL)
7545 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7546 else if (move_pattern == MV_VERTICAL)
7547 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7549 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7551 else if (move_pattern & MV_ANY_DIRECTION)
7553 MovDir[x][y] = move_pattern;
7554 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7556 else if (move_pattern & MV_WIND_DIRECTION)
7558 MovDir[x][y] = game.wind_direction;
7559 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7561 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7563 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7564 MovDir[x][y] = left_dir;
7565 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7566 MovDir[x][y] = right_dir;
7568 if (MovDir[x][y] != old_move_dir)
7569 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7571 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7573 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7574 MovDir[x][y] = right_dir;
7575 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7576 MovDir[x][y] = left_dir;
7578 if (MovDir[x][y] != old_move_dir)
7579 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7581 else if (move_pattern == MV_TOWARDS_PLAYER ||
7582 move_pattern == MV_AWAY_FROM_PLAYER)
7584 int attr_x = -1, attr_y = -1;
7586 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7588 if (game.all_players_gone)
7590 attr_x = game.exit_x;
7591 attr_y = game.exit_y;
7597 for (i = 0; i < MAX_PLAYERS; i++)
7599 struct PlayerInfo *player = &stored_player[i];
7600 int jx = player->jx, jy = player->jy;
7602 if (!player->active)
7606 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7614 MovDir[x][y] = MV_NONE;
7616 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7617 else if (attr_x > x)
7618 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7620 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7621 else if (attr_y > y)
7622 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7624 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7626 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7628 boolean first_horiz = RND(2);
7629 int new_move_dir = MovDir[x][y];
7631 if (element_info[element].move_stepsize == 0) // "not moving"
7633 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7634 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7640 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7641 Moving2Blocked(x, y, &newx, &newy);
7643 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7647 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7648 Moving2Blocked(x, y, &newx, &newy);
7650 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7653 MovDir[x][y] = old_move_dir;
7656 else if (move_pattern == MV_WHEN_PUSHED ||
7657 move_pattern == MV_WHEN_DROPPED)
7659 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7660 MovDir[x][y] = MV_NONE;
7664 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7666 static int test_xy[7][2] =
7676 static int test_dir[7] =
7686 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7687 int move_preference = -1000000; // start with very low preference
7688 int new_move_dir = MV_NONE;
7689 int start_test = RND(4);
7692 for (i = 0; i < NUM_DIRECTIONS; i++)
7694 int move_dir = test_dir[start_test + i];
7695 int move_dir_preference;
7697 xx = x + test_xy[start_test + i][0];
7698 yy = y + test_xy[start_test + i][1];
7700 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7701 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7703 new_move_dir = move_dir;
7708 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7711 move_dir_preference = -1 * RunnerVisit[xx][yy];
7712 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7713 move_dir_preference = PlayerVisit[xx][yy];
7715 if (move_dir_preference > move_preference)
7717 // prefer field that has not been visited for the longest time
7718 move_preference = move_dir_preference;
7719 new_move_dir = move_dir;
7721 else if (move_dir_preference == move_preference &&
7722 move_dir == old_move_dir)
7724 // prefer last direction when all directions are preferred equally
7725 move_preference = move_dir_preference;
7726 new_move_dir = move_dir;
7730 MovDir[x][y] = new_move_dir;
7731 if (old_move_dir != new_move_dir)
7732 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7736 static void TurnRound(int x, int y)
7738 int direction = MovDir[x][y];
7742 GfxDir[x][y] = MovDir[x][y];
7744 if (direction != MovDir[x][y])
7748 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7750 ResetGfxFrame(x, y);
7753 static boolean JustBeingPushed(int x, int y)
7757 for (i = 0; i < MAX_PLAYERS; i++)
7759 struct PlayerInfo *player = &stored_player[i];
7761 if (player->active && player->is_pushing && player->MovPos)
7763 int next_jx = player->jx + (player->jx - player->last_jx);
7764 int next_jy = player->jy + (player->jy - player->last_jy);
7766 if (x == next_jx && y == next_jy)
7774 static void StartMoving(int x, int y)
7776 boolean started_moving = FALSE; // some elements can fall _and_ move
7777 int element = Tile[x][y];
7782 if (MovDelay[x][y] == 0)
7783 GfxAction[x][y] = ACTION_DEFAULT;
7785 if (CAN_FALL(element) && y < lev_fieldy - 1)
7787 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7788 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7789 if (JustBeingPushed(x, y))
7792 if (element == EL_QUICKSAND_FULL)
7794 if (IS_FREE(x, y + 1))
7796 InitMovingField(x, y, MV_DOWN);
7797 started_moving = TRUE;
7799 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7800 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7801 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7802 Store[x][y] = EL_ROCK;
7804 Store[x][y] = EL_ROCK;
7807 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7809 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7811 if (!MovDelay[x][y])
7813 MovDelay[x][y] = TILEY + 1;
7815 ResetGfxAnimation(x, y);
7816 ResetGfxAnimation(x, y + 1);
7821 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7822 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7829 Tile[x][y] = EL_QUICKSAND_EMPTY;
7830 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7831 Store[x][y + 1] = Store[x][y];
7834 PlayLevelSoundAction(x, y, ACTION_FILLING);
7836 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7838 if (!MovDelay[x][y])
7840 MovDelay[x][y] = TILEY + 1;
7842 ResetGfxAnimation(x, y);
7843 ResetGfxAnimation(x, y + 1);
7848 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7849 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7856 Tile[x][y] = EL_QUICKSAND_EMPTY;
7857 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7858 Store[x][y + 1] = Store[x][y];
7861 PlayLevelSoundAction(x, y, ACTION_FILLING);
7864 else if (element == EL_QUICKSAND_FAST_FULL)
7866 if (IS_FREE(x, y + 1))
7868 InitMovingField(x, y, MV_DOWN);
7869 started_moving = TRUE;
7871 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7872 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7873 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7874 Store[x][y] = EL_ROCK;
7876 Store[x][y] = EL_ROCK;
7879 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7881 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7883 if (!MovDelay[x][y])
7885 MovDelay[x][y] = TILEY + 1;
7887 ResetGfxAnimation(x, y);
7888 ResetGfxAnimation(x, y + 1);
7893 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7894 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7901 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7902 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7903 Store[x][y + 1] = Store[x][y];
7906 PlayLevelSoundAction(x, y, ACTION_FILLING);
7908 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7910 if (!MovDelay[x][y])
7912 MovDelay[x][y] = TILEY + 1;
7914 ResetGfxAnimation(x, y);
7915 ResetGfxAnimation(x, y + 1);
7920 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7921 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7928 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7929 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7930 Store[x][y + 1] = Store[x][y];
7933 PlayLevelSoundAction(x, y, ACTION_FILLING);
7936 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7937 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7939 InitMovingField(x, y, MV_DOWN);
7940 started_moving = TRUE;
7942 Tile[x][y] = EL_QUICKSAND_FILLING;
7943 Store[x][y] = element;
7945 PlayLevelSoundAction(x, y, ACTION_FILLING);
7947 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7948 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7950 InitMovingField(x, y, MV_DOWN);
7951 started_moving = TRUE;
7953 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7954 Store[x][y] = element;
7956 PlayLevelSoundAction(x, y, ACTION_FILLING);
7958 else if (element == EL_MAGIC_WALL_FULL)
7960 if (IS_FREE(x, y + 1))
7962 InitMovingField(x, y, MV_DOWN);
7963 started_moving = TRUE;
7965 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7966 Store[x][y] = EL_CHANGED(Store[x][y]);
7968 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7970 if (!MovDelay[x][y])
7971 MovDelay[x][y] = TILEY / 4 + 1;
7980 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7981 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7982 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
7986 else if (element == EL_BD_MAGIC_WALL_FULL)
7988 if (IS_FREE(x, y + 1))
7990 InitMovingField(x, y, MV_DOWN);
7991 started_moving = TRUE;
7993 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
7994 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
7996 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
7998 if (!MovDelay[x][y])
7999 MovDelay[x][y] = TILEY / 4 + 1;
8008 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8009 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8010 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8014 else if (element == EL_DC_MAGIC_WALL_FULL)
8016 if (IS_FREE(x, y + 1))
8018 InitMovingField(x, y, MV_DOWN);
8019 started_moving = TRUE;
8021 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8022 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8024 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8026 if (!MovDelay[x][y])
8027 MovDelay[x][y] = TILEY / 4 + 1;
8036 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8037 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8038 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8042 else if ((CAN_PASS_MAGIC_WALL(element) &&
8043 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8044 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8045 (CAN_PASS_DC_MAGIC_WALL(element) &&
8046 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8049 InitMovingField(x, y, MV_DOWN);
8050 started_moving = TRUE;
8053 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8054 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8055 EL_DC_MAGIC_WALL_FILLING);
8056 Store[x][y] = element;
8058 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8060 SplashAcid(x, y + 1);
8062 InitMovingField(x, y, MV_DOWN);
8063 started_moving = TRUE;
8065 Store[x][y] = EL_ACID;
8068 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8069 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8070 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8071 CAN_FALL(element) && WasJustFalling[x][y] &&
8072 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8074 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8075 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8076 (Tile[x][y + 1] == EL_BLOCKED)))
8078 /* this is needed for a special case not covered by calling "Impact()"
8079 from "ContinueMoving()": if an element moves to a tile directly below
8080 another element which was just falling on that tile (which was empty
8081 in the previous frame), the falling element above would just stop
8082 instead of smashing the element below (in previous version, the above
8083 element was just checked for "moving" instead of "falling", resulting
8084 in incorrect smashes caused by horizontal movement of the above
8085 element; also, the case of the player being the element to smash was
8086 simply not covered here... :-/ ) */
8088 CheckCollision[x][y] = 0;
8089 CheckImpact[x][y] = 0;
8093 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8095 if (MovDir[x][y] == MV_NONE)
8097 InitMovingField(x, y, MV_DOWN);
8098 started_moving = TRUE;
8101 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8103 if (WasJustFalling[x][y]) // prevent animation from being restarted
8104 MovDir[x][y] = MV_DOWN;
8106 InitMovingField(x, y, MV_DOWN);
8107 started_moving = TRUE;
8109 else if (element == EL_AMOEBA_DROP)
8111 Tile[x][y] = EL_AMOEBA_GROWING;
8112 Store[x][y] = EL_AMOEBA_WET;
8114 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8115 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8116 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8117 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8119 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8120 (IS_FREE(x - 1, y + 1) ||
8121 Tile[x - 1][y + 1] == EL_ACID));
8122 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8123 (IS_FREE(x + 1, y + 1) ||
8124 Tile[x + 1][y + 1] == EL_ACID));
8125 boolean can_fall_any = (can_fall_left || can_fall_right);
8126 boolean can_fall_both = (can_fall_left && can_fall_right);
8127 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8129 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8131 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8132 can_fall_right = FALSE;
8133 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8134 can_fall_left = FALSE;
8135 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8136 can_fall_right = FALSE;
8137 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8138 can_fall_left = FALSE;
8140 can_fall_any = (can_fall_left || can_fall_right);
8141 can_fall_both = FALSE;
8146 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8147 can_fall_right = FALSE; // slip down on left side
8149 can_fall_left = !(can_fall_right = RND(2));
8151 can_fall_both = FALSE;
8156 // if not determined otherwise, prefer left side for slipping down
8157 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8158 started_moving = TRUE;
8161 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8163 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8164 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8165 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8166 int belt_dir = game.belt_dir[belt_nr];
8168 if ((belt_dir == MV_LEFT && left_is_free) ||
8169 (belt_dir == MV_RIGHT && right_is_free))
8171 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8173 InitMovingField(x, y, belt_dir);
8174 started_moving = TRUE;
8176 Pushed[x][y] = TRUE;
8177 Pushed[nextx][y] = TRUE;
8179 GfxAction[x][y] = ACTION_DEFAULT;
8183 MovDir[x][y] = 0; // if element was moving, stop it
8188 // not "else if" because of elements that can fall and move (EL_SPRING)
8189 if (CAN_MOVE(element) && !started_moving)
8191 int move_pattern = element_info[element].move_pattern;
8194 Moving2Blocked(x, y, &newx, &newy);
8196 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8199 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8200 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8202 WasJustMoving[x][y] = 0;
8203 CheckCollision[x][y] = 0;
8205 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8207 if (Tile[x][y] != element) // element has changed
8211 if (!MovDelay[x][y]) // start new movement phase
8213 // all objects that can change their move direction after each step
8214 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8216 if (element != EL_YAMYAM &&
8217 element != EL_DARK_YAMYAM &&
8218 element != EL_PACMAN &&
8219 !(move_pattern & MV_ANY_DIRECTION) &&
8220 move_pattern != MV_TURNING_LEFT &&
8221 move_pattern != MV_TURNING_RIGHT &&
8222 move_pattern != MV_TURNING_LEFT_RIGHT &&
8223 move_pattern != MV_TURNING_RIGHT_LEFT &&
8224 move_pattern != MV_TURNING_RANDOM)
8228 if (MovDelay[x][y] && (element == EL_BUG ||
8229 element == EL_SPACESHIP ||
8230 element == EL_SP_SNIKSNAK ||
8231 element == EL_SP_ELECTRON ||
8232 element == EL_MOLE))
8233 TEST_DrawLevelField(x, y);
8237 if (MovDelay[x][y]) // wait some time before next movement
8241 if (element == EL_ROBOT ||
8242 element == EL_YAMYAM ||
8243 element == EL_DARK_YAMYAM)
8245 DrawLevelElementAnimationIfNeeded(x, y, element);
8246 PlayLevelSoundAction(x, y, ACTION_WAITING);
8248 else if (element == EL_SP_ELECTRON)
8249 DrawLevelElementAnimationIfNeeded(x, y, element);
8250 else if (element == EL_DRAGON)
8253 int dir = MovDir[x][y];
8254 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8255 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8256 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8257 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8258 dir == MV_UP ? IMG_FLAMES_1_UP :
8259 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8260 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8262 GfxAction[x][y] = ACTION_ATTACKING;
8264 if (IS_PLAYER(x, y))
8265 DrawPlayerField(x, y);
8267 TEST_DrawLevelField(x, y);
8269 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8271 for (i = 1; i <= 3; i++)
8273 int xx = x + i * dx;
8274 int yy = y + i * dy;
8275 int sx = SCREENX(xx);
8276 int sy = SCREENY(yy);
8277 int flame_graphic = graphic + (i - 1);
8279 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8284 int flamed = MovingOrBlocked2Element(xx, yy);
8286 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8289 RemoveMovingField(xx, yy);
8291 ChangeDelay[xx][yy] = 0;
8293 Tile[xx][yy] = EL_FLAMES;
8295 if (IN_SCR_FIELD(sx, sy))
8297 TEST_DrawLevelFieldCrumbled(xx, yy);
8298 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8303 if (Tile[xx][yy] == EL_FLAMES)
8304 Tile[xx][yy] = EL_EMPTY;
8305 TEST_DrawLevelField(xx, yy);
8310 if (MovDelay[x][y]) // element still has to wait some time
8312 PlayLevelSoundAction(x, y, ACTION_WAITING);
8318 // now make next step
8320 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8322 if (DONT_COLLIDE_WITH(element) &&
8323 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8324 !PLAYER_ENEMY_PROTECTED(newx, newy))
8326 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8331 else if (CAN_MOVE_INTO_ACID(element) &&
8332 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8333 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8334 (MovDir[x][y] == MV_DOWN ||
8335 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8337 SplashAcid(newx, newy);
8338 Store[x][y] = EL_ACID;
8340 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8342 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8343 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8344 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8345 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8348 TEST_DrawLevelField(x, y);
8350 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8351 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8352 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8354 game.friends_still_needed--;
8355 if (!game.friends_still_needed &&
8357 game.all_players_gone)
8362 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8364 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8365 TEST_DrawLevelField(newx, newy);
8367 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8369 else if (!IS_FREE(newx, newy))
8371 GfxAction[x][y] = ACTION_WAITING;
8373 if (IS_PLAYER(x, y))
8374 DrawPlayerField(x, y);
8376 TEST_DrawLevelField(x, y);
8381 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8383 if (IS_FOOD_PIG(Tile[newx][newy]))
8385 if (IS_MOVING(newx, newy))
8386 RemoveMovingField(newx, newy);
8389 Tile[newx][newy] = EL_EMPTY;
8390 TEST_DrawLevelField(newx, newy);
8393 PlayLevelSound(x, y, SND_PIG_DIGGING);
8395 else if (!IS_FREE(newx, newy))
8397 if (IS_PLAYER(x, y))
8398 DrawPlayerField(x, y);
8400 TEST_DrawLevelField(x, y);
8405 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8407 if (Store[x][y] != EL_EMPTY)
8409 boolean can_clone = FALSE;
8412 // check if element to clone is still there
8413 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8415 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8423 // cannot clone or target field not free anymore -- do not clone
8424 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8425 Store[x][y] = EL_EMPTY;
8428 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8430 if (IS_MV_DIAGONAL(MovDir[x][y]))
8432 int diagonal_move_dir = MovDir[x][y];
8433 int stored = Store[x][y];
8434 int change_delay = 8;
8437 // android is moving diagonally
8439 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8441 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8442 GfxElement[x][y] = EL_EMC_ANDROID;
8443 GfxAction[x][y] = ACTION_SHRINKING;
8444 GfxDir[x][y] = diagonal_move_dir;
8445 ChangeDelay[x][y] = change_delay;
8447 if (Store[x][y] == EL_EMPTY)
8448 Store[x][y] = GfxElementEmpty[x][y];
8450 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8453 DrawLevelGraphicAnimation(x, y, graphic);
8454 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8456 if (Tile[newx][newy] == EL_ACID)
8458 SplashAcid(newx, newy);
8463 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8465 Store[newx][newy] = EL_EMC_ANDROID;
8466 GfxElement[newx][newy] = EL_EMC_ANDROID;
8467 GfxAction[newx][newy] = ACTION_GROWING;
8468 GfxDir[newx][newy] = diagonal_move_dir;
8469 ChangeDelay[newx][newy] = change_delay;
8471 graphic = el_act_dir2img(GfxElement[newx][newy],
8472 GfxAction[newx][newy], GfxDir[newx][newy]);
8474 DrawLevelGraphicAnimation(newx, newy, graphic);
8475 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8481 Tile[newx][newy] = EL_EMPTY;
8482 TEST_DrawLevelField(newx, newy);
8484 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8487 else if (!IS_FREE(newx, newy))
8492 else if (IS_CUSTOM_ELEMENT(element) &&
8493 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8495 if (!DigFieldByCE(newx, newy, element))
8498 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8500 RunnerVisit[x][y] = FrameCounter;
8501 PlayerVisit[x][y] /= 8; // expire player visit path
8504 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8506 if (!IS_FREE(newx, newy))
8508 if (IS_PLAYER(x, y))
8509 DrawPlayerField(x, y);
8511 TEST_DrawLevelField(x, y);
8517 boolean wanna_flame = !RND(10);
8518 int dx = newx - x, dy = newy - y;
8519 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8520 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8521 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8522 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8523 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8524 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8527 IS_CLASSIC_ENEMY(element1) ||
8528 IS_CLASSIC_ENEMY(element2)) &&
8529 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8530 element1 != EL_FLAMES && element2 != EL_FLAMES)
8532 ResetGfxAnimation(x, y);
8533 GfxAction[x][y] = ACTION_ATTACKING;
8535 if (IS_PLAYER(x, y))
8536 DrawPlayerField(x, y);
8538 TEST_DrawLevelField(x, y);
8540 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8542 MovDelay[x][y] = 50;
8544 Tile[newx][newy] = EL_FLAMES;
8545 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8546 Tile[newx1][newy1] = EL_FLAMES;
8547 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8548 Tile[newx2][newy2] = EL_FLAMES;
8554 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8555 Tile[newx][newy] == EL_DIAMOND)
8557 if (IS_MOVING(newx, newy))
8558 RemoveMovingField(newx, newy);
8561 Tile[newx][newy] = EL_EMPTY;
8562 TEST_DrawLevelField(newx, newy);
8565 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8567 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8568 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8570 if (AmoebaNr[newx][newy])
8572 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8573 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8574 Tile[newx][newy] == EL_BD_AMOEBA)
8575 AmoebaCnt[AmoebaNr[newx][newy]]--;
8578 if (IS_MOVING(newx, newy))
8580 RemoveMovingField(newx, newy);
8584 Tile[newx][newy] = EL_EMPTY;
8585 TEST_DrawLevelField(newx, newy);
8588 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8590 else if ((element == EL_PACMAN || element == EL_MOLE)
8591 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8593 if (AmoebaNr[newx][newy])
8595 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8596 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8597 Tile[newx][newy] == EL_BD_AMOEBA)
8598 AmoebaCnt[AmoebaNr[newx][newy]]--;
8601 if (element == EL_MOLE)
8603 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8604 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8606 ResetGfxAnimation(x, y);
8607 GfxAction[x][y] = ACTION_DIGGING;
8608 TEST_DrawLevelField(x, y);
8610 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8612 return; // wait for shrinking amoeba
8614 else // element == EL_PACMAN
8616 Tile[newx][newy] = EL_EMPTY;
8617 TEST_DrawLevelField(newx, newy);
8618 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8621 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8622 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8623 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8625 // wait for shrinking amoeba to completely disappear
8628 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8630 // object was running against a wall
8634 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8635 DrawLevelElementAnimation(x, y, element);
8637 if (DONT_TOUCH(element))
8638 TestIfBadThingTouchesPlayer(x, y);
8643 InitMovingField(x, y, MovDir[x][y]);
8645 PlayLevelSoundAction(x, y, ACTION_MOVING);
8649 ContinueMoving(x, y);
8652 void ContinueMoving(int x, int y)
8654 int element = Tile[x][y];
8655 struct ElementInfo *ei = &element_info[element];
8656 int direction = MovDir[x][y];
8657 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8658 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8659 int newx = x + dx, newy = y + dy;
8660 int stored = Store[x][y];
8661 int stored_new = Store[newx][newy];
8662 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8663 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8664 boolean last_line = (newy == lev_fieldy - 1);
8665 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8667 if (pushed_by_player) // special case: moving object pushed by player
8669 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8671 else if (use_step_delay) // special case: moving object has step delay
8673 if (!MovDelay[x][y])
8674 MovPos[x][y] += getElementMoveStepsize(x, y);
8679 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8683 TEST_DrawLevelField(x, y);
8685 return; // element is still waiting
8688 else // normal case: generically moving object
8690 MovPos[x][y] += getElementMoveStepsize(x, y);
8693 if (ABS(MovPos[x][y]) < TILEX)
8695 TEST_DrawLevelField(x, y);
8697 return; // element is still moving
8700 // element reached destination field
8702 Tile[x][y] = EL_EMPTY;
8703 Tile[newx][newy] = element;
8704 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8706 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8708 element = Tile[newx][newy] = EL_ACID;
8710 else if (element == EL_MOLE)
8712 Tile[x][y] = EL_SAND;
8714 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8716 else if (element == EL_QUICKSAND_FILLING)
8718 element = Tile[newx][newy] = get_next_element(element);
8719 Store[newx][newy] = Store[x][y];
8721 else if (element == EL_QUICKSAND_EMPTYING)
8723 Tile[x][y] = get_next_element(element);
8724 element = Tile[newx][newy] = Store[x][y];
8726 else if (element == EL_QUICKSAND_FAST_FILLING)
8728 element = Tile[newx][newy] = get_next_element(element);
8729 Store[newx][newy] = Store[x][y];
8731 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8733 Tile[x][y] = get_next_element(element);
8734 element = Tile[newx][newy] = Store[x][y];
8736 else if (element == EL_MAGIC_WALL_FILLING)
8738 element = Tile[newx][newy] = get_next_element(element);
8739 if (!game.magic_wall_active)
8740 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8741 Store[newx][newy] = Store[x][y];
8743 else if (element == EL_MAGIC_WALL_EMPTYING)
8745 Tile[x][y] = get_next_element(element);
8746 if (!game.magic_wall_active)
8747 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8748 element = Tile[newx][newy] = Store[x][y];
8750 InitField(newx, newy, FALSE);
8752 else if (element == EL_BD_MAGIC_WALL_FILLING)
8754 element = Tile[newx][newy] = get_next_element(element);
8755 if (!game.magic_wall_active)
8756 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8757 Store[newx][newy] = Store[x][y];
8759 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8761 Tile[x][y] = get_next_element(element);
8762 if (!game.magic_wall_active)
8763 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8764 element = Tile[newx][newy] = Store[x][y];
8766 InitField(newx, newy, FALSE);
8768 else if (element == EL_DC_MAGIC_WALL_FILLING)
8770 element = Tile[newx][newy] = get_next_element(element);
8771 if (!game.magic_wall_active)
8772 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8773 Store[newx][newy] = Store[x][y];
8775 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8777 Tile[x][y] = get_next_element(element);
8778 if (!game.magic_wall_active)
8779 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8780 element = Tile[newx][newy] = Store[x][y];
8782 InitField(newx, newy, FALSE);
8784 else if (element == EL_AMOEBA_DROPPING)
8786 Tile[x][y] = get_next_element(element);
8787 element = Tile[newx][newy] = Store[x][y];
8789 else if (element == EL_SOKOBAN_OBJECT)
8792 Tile[x][y] = Back[x][y];
8794 if (Back[newx][newy])
8795 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8797 Back[x][y] = Back[newx][newy] = 0;
8800 Store[x][y] = EL_EMPTY;
8805 MovDelay[newx][newy] = 0;
8807 if (CAN_CHANGE_OR_HAS_ACTION(element))
8809 // copy element change control values to new field
8810 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8811 ChangePage[newx][newy] = ChangePage[x][y];
8812 ChangeCount[newx][newy] = ChangeCount[x][y];
8813 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8816 CustomValue[newx][newy] = CustomValue[x][y];
8818 ChangeDelay[x][y] = 0;
8819 ChangePage[x][y] = -1;
8820 ChangeCount[x][y] = 0;
8821 ChangeEvent[x][y] = -1;
8823 CustomValue[x][y] = 0;
8825 // copy animation control values to new field
8826 GfxFrame[newx][newy] = GfxFrame[x][y];
8827 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8828 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8829 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8831 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8833 // some elements can leave other elements behind after moving
8834 if (ei->move_leave_element != EL_EMPTY &&
8835 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8836 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8838 int move_leave_element = ei->move_leave_element;
8840 // this makes it possible to leave the removed element again
8841 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8842 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8844 Tile[x][y] = move_leave_element;
8846 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8847 MovDir[x][y] = direction;
8849 InitField(x, y, FALSE);
8851 if (GFX_CRUMBLED(Tile[x][y]))
8852 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8854 if (IS_PLAYER_ELEMENT(move_leave_element))
8855 RelocatePlayer(x, y, move_leave_element);
8858 // do this after checking for left-behind element
8859 ResetGfxAnimation(x, y); // reset animation values for old field
8861 if (!CAN_MOVE(element) ||
8862 (CAN_FALL(element) && direction == MV_DOWN &&
8863 (element == EL_SPRING ||
8864 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8865 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8866 GfxDir[x][y] = MovDir[newx][newy] = 0;
8868 TEST_DrawLevelField(x, y);
8869 TEST_DrawLevelField(newx, newy);
8871 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8873 // prevent pushed element from moving on in pushed direction
8874 if (pushed_by_player && CAN_MOVE(element) &&
8875 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8876 !(element_info[element].move_pattern & direction))
8877 TurnRound(newx, newy);
8879 // prevent elements on conveyor belt from moving on in last direction
8880 if (pushed_by_conveyor && CAN_FALL(element) &&
8881 direction & MV_HORIZONTAL)
8882 MovDir[newx][newy] = 0;
8884 if (!pushed_by_player)
8886 int nextx = newx + dx, nexty = newy + dy;
8887 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8889 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8891 if (CAN_FALL(element) && direction == MV_DOWN)
8892 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8894 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8895 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8897 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8898 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8901 if (DONT_TOUCH(element)) // object may be nasty to player or others
8903 TestIfBadThingTouchesPlayer(newx, newy);
8904 TestIfBadThingTouchesFriend(newx, newy);
8906 if (!IS_CUSTOM_ELEMENT(element))
8907 TestIfBadThingTouchesOtherBadThing(newx, newy);
8909 else if (element == EL_PENGUIN)
8910 TestIfFriendTouchesBadThing(newx, newy);
8912 if (DONT_GET_HIT_BY(element))
8914 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8917 // give the player one last chance (one more frame) to move away
8918 if (CAN_FALL(element) && direction == MV_DOWN &&
8919 (last_line || (!IS_FREE(x, newy + 1) &&
8920 (!IS_PLAYER(x, newy + 1) ||
8921 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8924 if (pushed_by_player && !game.use_change_when_pushing_bug)
8926 int push_side = MV_DIR_OPPOSITE(direction);
8927 struct PlayerInfo *player = PLAYERINFO(x, y);
8929 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8930 player->index_bit, push_side);
8931 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8932 player->index_bit, push_side);
8935 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8936 MovDelay[newx][newy] = 1;
8938 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8940 TestIfElementTouchesCustomElement(x, y); // empty or new element
8941 TestIfElementHitsCustomElement(newx, newy, direction);
8942 TestIfPlayerTouchesCustomElement(newx, newy);
8943 TestIfElementTouchesCustomElement(newx, newy);
8945 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8946 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8947 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8948 MV_DIR_OPPOSITE(direction));
8951 int AmoebaNeighbourNr(int ax, int ay)
8954 int element = Tile[ax][ay];
8956 static int xy[4][2] =
8964 for (i = 0; i < NUM_DIRECTIONS; i++)
8966 int x = ax + xy[i][0];
8967 int y = ay + xy[i][1];
8969 if (!IN_LEV_FIELD(x, y))
8972 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8973 group_nr = AmoebaNr[x][y];
8979 static void AmoebaMerge(int ax, int ay)
8981 int i, x, y, xx, yy;
8982 int new_group_nr = AmoebaNr[ax][ay];
8983 static int xy[4][2] =
8991 if (new_group_nr == 0)
8994 for (i = 0; i < NUM_DIRECTIONS; i++)
8999 if (!IN_LEV_FIELD(x, y))
9002 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9003 Tile[x][y] == EL_BD_AMOEBA ||
9004 Tile[x][y] == EL_AMOEBA_DEAD) &&
9005 AmoebaNr[x][y] != new_group_nr)
9007 int old_group_nr = AmoebaNr[x][y];
9009 if (old_group_nr == 0)
9012 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9013 AmoebaCnt[old_group_nr] = 0;
9014 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9015 AmoebaCnt2[old_group_nr] = 0;
9017 SCAN_PLAYFIELD(xx, yy)
9019 if (AmoebaNr[xx][yy] == old_group_nr)
9020 AmoebaNr[xx][yy] = new_group_nr;
9026 void AmoebaToDiamond(int ax, int ay)
9030 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9032 int group_nr = AmoebaNr[ax][ay];
9037 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9038 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9044 SCAN_PLAYFIELD(x, y)
9046 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9049 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9053 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9054 SND_AMOEBA_TURNING_TO_GEM :
9055 SND_AMOEBA_TURNING_TO_ROCK));
9060 static int xy[4][2] =
9068 for (i = 0; i < NUM_DIRECTIONS; i++)
9073 if (!IN_LEV_FIELD(x, y))
9076 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9078 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9079 SND_AMOEBA_TURNING_TO_GEM :
9080 SND_AMOEBA_TURNING_TO_ROCK));
9087 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9090 int group_nr = AmoebaNr[ax][ay];
9091 boolean done = FALSE;
9096 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9097 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9103 SCAN_PLAYFIELD(x, y)
9105 if (AmoebaNr[x][y] == group_nr &&
9106 (Tile[x][y] == EL_AMOEBA_DEAD ||
9107 Tile[x][y] == EL_BD_AMOEBA ||
9108 Tile[x][y] == EL_AMOEBA_GROWING))
9111 Tile[x][y] = new_element;
9112 InitField(x, y, FALSE);
9113 TEST_DrawLevelField(x, y);
9119 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9120 SND_BD_AMOEBA_TURNING_TO_ROCK :
9121 SND_BD_AMOEBA_TURNING_TO_GEM));
9124 static void AmoebaGrowing(int x, int y)
9126 static unsigned int sound_delay = 0;
9127 static unsigned int sound_delay_value = 0;
9129 if (!MovDelay[x][y]) // start new growing cycle
9133 if (DelayReached(&sound_delay, sound_delay_value))
9135 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9136 sound_delay_value = 30;
9140 if (MovDelay[x][y]) // wait some time before growing bigger
9143 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9145 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9146 6 - MovDelay[x][y]);
9148 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9151 if (!MovDelay[x][y])
9153 Tile[x][y] = Store[x][y];
9155 TEST_DrawLevelField(x, y);
9160 static void AmoebaShrinking(int x, int y)
9162 static unsigned int sound_delay = 0;
9163 static unsigned int sound_delay_value = 0;
9165 if (!MovDelay[x][y]) // start new shrinking cycle
9169 if (DelayReached(&sound_delay, sound_delay_value))
9170 sound_delay_value = 30;
9173 if (MovDelay[x][y]) // wait some time before shrinking
9176 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9178 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9179 6 - MovDelay[x][y]);
9181 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9184 if (!MovDelay[x][y])
9186 Tile[x][y] = EL_EMPTY;
9187 TEST_DrawLevelField(x, y);
9189 // don't let mole enter this field in this cycle;
9190 // (give priority to objects falling to this field from above)
9196 static void AmoebaReproduce(int ax, int ay)
9199 int element = Tile[ax][ay];
9200 int graphic = el2img(element);
9201 int newax = ax, neway = ay;
9202 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9203 static int xy[4][2] =
9211 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9213 Tile[ax][ay] = EL_AMOEBA_DEAD;
9214 TEST_DrawLevelField(ax, ay);
9218 if (IS_ANIMATED(graphic))
9219 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9221 if (!MovDelay[ax][ay]) // start making new amoeba field
9222 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9224 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9227 if (MovDelay[ax][ay])
9231 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9234 int x = ax + xy[start][0];
9235 int y = ay + xy[start][1];
9237 if (!IN_LEV_FIELD(x, y))
9240 if (IS_FREE(x, y) ||
9241 CAN_GROW_INTO(Tile[x][y]) ||
9242 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9243 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9249 if (newax == ax && neway == ay)
9252 else // normal or "filled" (BD style) amoeba
9255 boolean waiting_for_player = FALSE;
9257 for (i = 0; i < NUM_DIRECTIONS; i++)
9259 int j = (start + i) % 4;
9260 int x = ax + xy[j][0];
9261 int y = ay + xy[j][1];
9263 if (!IN_LEV_FIELD(x, y))
9266 if (IS_FREE(x, y) ||
9267 CAN_GROW_INTO(Tile[x][y]) ||
9268 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9269 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9275 else if (IS_PLAYER(x, y))
9276 waiting_for_player = TRUE;
9279 if (newax == ax && neway == ay) // amoeba cannot grow
9281 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9283 Tile[ax][ay] = EL_AMOEBA_DEAD;
9284 TEST_DrawLevelField(ax, ay);
9285 AmoebaCnt[AmoebaNr[ax][ay]]--;
9287 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9289 if (element == EL_AMOEBA_FULL)
9290 AmoebaToDiamond(ax, ay);
9291 else if (element == EL_BD_AMOEBA)
9292 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9297 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9299 // amoeba gets larger by growing in some direction
9301 int new_group_nr = AmoebaNr[ax][ay];
9304 if (new_group_nr == 0)
9306 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9308 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9314 AmoebaNr[newax][neway] = new_group_nr;
9315 AmoebaCnt[new_group_nr]++;
9316 AmoebaCnt2[new_group_nr]++;
9318 // if amoeba touches other amoeba(s) after growing, unify them
9319 AmoebaMerge(newax, neway);
9321 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9323 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9329 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9330 (neway == lev_fieldy - 1 && newax != ax))
9332 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9333 Store[newax][neway] = element;
9335 else if (neway == ay || element == EL_EMC_DRIPPER)
9337 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9339 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9343 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9344 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9345 Store[ax][ay] = EL_AMOEBA_DROP;
9346 ContinueMoving(ax, ay);
9350 TEST_DrawLevelField(newax, neway);
9353 static void Life(int ax, int ay)
9357 int element = Tile[ax][ay];
9358 int graphic = el2img(element);
9359 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9361 boolean changed = FALSE;
9363 if (IS_ANIMATED(graphic))
9364 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9369 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9370 MovDelay[ax][ay] = life_time;
9372 if (MovDelay[ax][ay]) // wait some time before next cycle
9375 if (MovDelay[ax][ay])
9379 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9381 int xx = ax+x1, yy = ay+y1;
9382 int old_element = Tile[xx][yy];
9383 int num_neighbours = 0;
9385 if (!IN_LEV_FIELD(xx, yy))
9388 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9390 int x = xx+x2, y = yy+y2;
9392 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9395 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9396 boolean is_neighbour = FALSE;
9398 if (level.use_life_bugs)
9400 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9401 (IS_FREE(x, y) && Stop[x][y]));
9404 (Last[x][y] == element || is_player_cell);
9410 boolean is_free = FALSE;
9412 if (level.use_life_bugs)
9413 is_free = (IS_FREE(xx, yy));
9415 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9417 if (xx == ax && yy == ay) // field in the middle
9419 if (num_neighbours < life_parameter[0] ||
9420 num_neighbours > life_parameter[1])
9422 Tile[xx][yy] = EL_EMPTY;
9423 if (Tile[xx][yy] != old_element)
9424 TEST_DrawLevelField(xx, yy);
9425 Stop[xx][yy] = TRUE;
9429 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9430 { // free border field
9431 if (num_neighbours >= life_parameter[2] &&
9432 num_neighbours <= life_parameter[3])
9434 Tile[xx][yy] = element;
9435 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9436 if (Tile[xx][yy] != old_element)
9437 TEST_DrawLevelField(xx, yy);
9438 Stop[xx][yy] = TRUE;
9445 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9446 SND_GAME_OF_LIFE_GROWING);
9449 static void InitRobotWheel(int x, int y)
9451 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9454 static void RunRobotWheel(int x, int y)
9456 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9459 static void StopRobotWheel(int x, int y)
9461 if (game.robot_wheel_x == x &&
9462 game.robot_wheel_y == y)
9464 game.robot_wheel_x = -1;
9465 game.robot_wheel_y = -1;
9466 game.robot_wheel_active = FALSE;
9470 static void InitTimegateWheel(int x, int y)
9472 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9475 static void RunTimegateWheel(int x, int y)
9477 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9480 static void InitMagicBallDelay(int x, int y)
9482 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9485 static void ActivateMagicBall(int bx, int by)
9489 if (level.ball_random)
9491 int pos_border = RND(8); // select one of the eight border elements
9492 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9493 int xx = pos_content % 3;
9494 int yy = pos_content / 3;
9499 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9500 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9504 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9506 int xx = x - bx + 1;
9507 int yy = y - by + 1;
9509 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9510 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9514 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9517 static void CheckExit(int x, int y)
9519 if (game.gems_still_needed > 0 ||
9520 game.sokoban_fields_still_needed > 0 ||
9521 game.sokoban_objects_still_needed > 0 ||
9522 game.lights_still_needed > 0)
9524 int element = Tile[x][y];
9525 int graphic = el2img(element);
9527 if (IS_ANIMATED(graphic))
9528 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9533 // do not re-open exit door closed after last player
9534 if (game.all_players_gone)
9537 Tile[x][y] = EL_EXIT_OPENING;
9539 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9542 static void CheckExitEM(int x, int y)
9544 if (game.gems_still_needed > 0 ||
9545 game.sokoban_fields_still_needed > 0 ||
9546 game.sokoban_objects_still_needed > 0 ||
9547 game.lights_still_needed > 0)
9549 int element = Tile[x][y];
9550 int graphic = el2img(element);
9552 if (IS_ANIMATED(graphic))
9553 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9558 // do not re-open exit door closed after last player
9559 if (game.all_players_gone)
9562 Tile[x][y] = EL_EM_EXIT_OPENING;
9564 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9567 static void CheckExitSteel(int x, int y)
9569 if (game.gems_still_needed > 0 ||
9570 game.sokoban_fields_still_needed > 0 ||
9571 game.sokoban_objects_still_needed > 0 ||
9572 game.lights_still_needed > 0)
9574 int element = Tile[x][y];
9575 int graphic = el2img(element);
9577 if (IS_ANIMATED(graphic))
9578 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9583 // do not re-open exit door closed after last player
9584 if (game.all_players_gone)
9587 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9589 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9592 static void CheckExitSteelEM(int x, int y)
9594 if (game.gems_still_needed > 0 ||
9595 game.sokoban_fields_still_needed > 0 ||
9596 game.sokoban_objects_still_needed > 0 ||
9597 game.lights_still_needed > 0)
9599 int element = Tile[x][y];
9600 int graphic = el2img(element);
9602 if (IS_ANIMATED(graphic))
9603 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9608 // do not re-open exit door closed after last player
9609 if (game.all_players_gone)
9612 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9614 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9617 static void CheckExitSP(int x, int y)
9619 if (game.gems_still_needed > 0)
9621 int element = Tile[x][y];
9622 int graphic = el2img(element);
9624 if (IS_ANIMATED(graphic))
9625 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9630 // do not re-open exit door closed after last player
9631 if (game.all_players_gone)
9634 Tile[x][y] = EL_SP_EXIT_OPENING;
9636 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9639 static void CloseAllOpenTimegates(void)
9643 SCAN_PLAYFIELD(x, y)
9645 int element = Tile[x][y];
9647 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9649 Tile[x][y] = EL_TIMEGATE_CLOSING;
9651 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9656 static void DrawTwinkleOnField(int x, int y)
9658 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9661 if (Tile[x][y] == EL_BD_DIAMOND)
9664 if (MovDelay[x][y] == 0) // next animation frame
9665 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9667 if (MovDelay[x][y] != 0) // wait some time before next frame
9671 DrawLevelElementAnimation(x, y, Tile[x][y]);
9673 if (MovDelay[x][y] != 0)
9675 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9676 10 - MovDelay[x][y]);
9678 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9683 static void MauerWaechst(int x, int y)
9687 if (!MovDelay[x][y]) // next animation frame
9688 MovDelay[x][y] = 3 * delay;
9690 if (MovDelay[x][y]) // wait some time before next frame
9694 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9696 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9697 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9699 DrawLevelGraphic(x, y, graphic, frame);
9702 if (!MovDelay[x][y])
9704 if (MovDir[x][y] == MV_LEFT)
9706 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9707 TEST_DrawLevelField(x - 1, y);
9709 else if (MovDir[x][y] == MV_RIGHT)
9711 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9712 TEST_DrawLevelField(x + 1, y);
9714 else if (MovDir[x][y] == MV_UP)
9716 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9717 TEST_DrawLevelField(x, y - 1);
9721 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9722 TEST_DrawLevelField(x, y + 1);
9725 Tile[x][y] = Store[x][y];
9727 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9728 TEST_DrawLevelField(x, y);
9733 static void MauerAbleger(int ax, int ay)
9735 int element = Tile[ax][ay];
9736 int graphic = el2img(element);
9737 boolean oben_frei = FALSE, unten_frei = FALSE;
9738 boolean links_frei = FALSE, rechts_frei = FALSE;
9739 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9740 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9741 boolean new_wall = FALSE;
9743 if (IS_ANIMATED(graphic))
9744 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9746 if (!MovDelay[ax][ay]) // start building new wall
9747 MovDelay[ax][ay] = 6;
9749 if (MovDelay[ax][ay]) // wait some time before building new wall
9752 if (MovDelay[ax][ay])
9756 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9758 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9760 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9762 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9765 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9766 element == EL_EXPANDABLE_WALL_ANY)
9770 Tile[ax][ay - 1] = EL_EXPANDABLE_WALL_GROWING;
9771 Store[ax][ay - 1] = element;
9772 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9773 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9774 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9779 Tile[ax][ay + 1] = EL_EXPANDABLE_WALL_GROWING;
9780 Store[ax][ay + 1] = element;
9781 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9782 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9783 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9788 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9789 element == EL_EXPANDABLE_WALL_ANY ||
9790 element == EL_EXPANDABLE_WALL ||
9791 element == EL_BD_EXPANDABLE_WALL)
9795 Tile[ax - 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9796 Store[ax - 1][ay] = element;
9797 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9798 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9799 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9805 Tile[ax + 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9806 Store[ax + 1][ay] = element;
9807 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9808 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9809 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9814 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9815 TEST_DrawLevelField(ax, ay);
9817 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9819 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9820 unten_massiv = TRUE;
9821 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9822 links_massiv = TRUE;
9823 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9824 rechts_massiv = TRUE;
9826 if (((oben_massiv && unten_massiv) ||
9827 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9828 element == EL_EXPANDABLE_WALL) &&
9829 ((links_massiv && rechts_massiv) ||
9830 element == EL_EXPANDABLE_WALL_VERTICAL))
9831 Tile[ax][ay] = EL_WALL;
9834 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9837 static void MauerAblegerStahl(int ax, int ay)
9839 int element = Tile[ax][ay];
9840 int graphic = el2img(element);
9841 boolean oben_frei = FALSE, unten_frei = FALSE;
9842 boolean links_frei = FALSE, rechts_frei = FALSE;
9843 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9844 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9845 boolean new_wall = FALSE;
9847 if (IS_ANIMATED(graphic))
9848 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9850 if (!MovDelay[ax][ay]) // start building new wall
9851 MovDelay[ax][ay] = 6;
9853 if (MovDelay[ax][ay]) // wait some time before building new wall
9856 if (MovDelay[ax][ay])
9860 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9862 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9864 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9866 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9869 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9870 element == EL_EXPANDABLE_STEELWALL_ANY)
9874 Tile[ax][ay - 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9875 Store[ax][ay - 1] = element;
9876 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9877 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9878 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9883 Tile[ax][ay + 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9884 Store[ax][ay + 1] = element;
9885 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9886 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9887 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9892 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9893 element == EL_EXPANDABLE_STEELWALL_ANY)
9897 Tile[ax - 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9898 Store[ax - 1][ay] = element;
9899 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9900 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9901 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9907 Tile[ax + 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9908 Store[ax + 1][ay] = element;
9909 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9910 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9911 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9916 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9918 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9919 unten_massiv = TRUE;
9920 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9921 links_massiv = TRUE;
9922 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9923 rechts_massiv = TRUE;
9925 if (((oben_massiv && unten_massiv) ||
9926 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9927 ((links_massiv && rechts_massiv) ||
9928 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9929 Tile[ax][ay] = EL_STEELWALL;
9932 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9935 static void CheckForDragon(int x, int y)
9938 boolean dragon_found = FALSE;
9939 static int xy[4][2] =
9947 for (i = 0; i < NUM_DIRECTIONS; i++)
9949 for (j = 0; j < 4; j++)
9951 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9953 if (IN_LEV_FIELD(xx, yy) &&
9954 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9956 if (Tile[xx][yy] == EL_DRAGON)
9957 dragon_found = TRUE;
9966 for (i = 0; i < NUM_DIRECTIONS; i++)
9968 for (j = 0; j < 3; j++)
9970 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9972 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9974 Tile[xx][yy] = EL_EMPTY;
9975 TEST_DrawLevelField(xx, yy);
9984 static void InitBuggyBase(int x, int y)
9986 int element = Tile[x][y];
9987 int activating_delay = FRAMES_PER_SECOND / 4;
9990 (element == EL_SP_BUGGY_BASE ?
9991 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
9992 element == EL_SP_BUGGY_BASE_ACTIVATING ?
9994 element == EL_SP_BUGGY_BASE_ACTIVE ?
9995 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
9998 static void WarnBuggyBase(int x, int y)
10001 static int xy[4][2] =
10009 for (i = 0; i < NUM_DIRECTIONS; i++)
10011 int xx = x + xy[i][0];
10012 int yy = y + xy[i][1];
10014 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10016 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10023 static void InitTrap(int x, int y)
10025 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10028 static void ActivateTrap(int x, int y)
10030 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10033 static void ChangeActiveTrap(int x, int y)
10035 int graphic = IMG_TRAP_ACTIVE;
10037 // if new animation frame was drawn, correct crumbled sand border
10038 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10039 TEST_DrawLevelFieldCrumbled(x, y);
10042 static int getSpecialActionElement(int element, int number, int base_element)
10044 return (element != EL_EMPTY ? element :
10045 number != -1 ? base_element + number - 1 :
10049 static int getModifiedActionNumber(int value_old, int operator, int operand,
10050 int value_min, int value_max)
10052 int value_new = (operator == CA_MODE_SET ? operand :
10053 operator == CA_MODE_ADD ? value_old + operand :
10054 operator == CA_MODE_SUBTRACT ? value_old - operand :
10055 operator == CA_MODE_MULTIPLY ? value_old * operand :
10056 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10057 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10060 return (value_new < value_min ? value_min :
10061 value_new > value_max ? value_max :
10065 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10067 struct ElementInfo *ei = &element_info[element];
10068 struct ElementChangeInfo *change = &ei->change_page[page];
10069 int target_element = change->target_element;
10070 int action_type = change->action_type;
10071 int action_mode = change->action_mode;
10072 int action_arg = change->action_arg;
10073 int action_element = change->action_element;
10076 if (!change->has_action)
10079 // ---------- determine action paramater values -----------------------------
10081 int level_time_value =
10082 (level.time > 0 ? TimeLeft :
10085 int action_arg_element_raw =
10086 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10087 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10088 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10089 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10090 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10091 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10092 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10094 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10096 int action_arg_direction =
10097 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10098 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10099 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10100 change->actual_trigger_side :
10101 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10102 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10105 int action_arg_number_min =
10106 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10109 int action_arg_number_max =
10110 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10111 action_type == CA_SET_LEVEL_GEMS ? 999 :
10112 action_type == CA_SET_LEVEL_TIME ? 9999 :
10113 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10114 action_type == CA_SET_CE_VALUE ? 9999 :
10115 action_type == CA_SET_CE_SCORE ? 9999 :
10118 int action_arg_number_reset =
10119 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10120 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10121 action_type == CA_SET_LEVEL_TIME ? level.time :
10122 action_type == CA_SET_LEVEL_SCORE ? 0 :
10123 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10124 action_type == CA_SET_CE_SCORE ? 0 :
10127 int action_arg_number =
10128 (action_arg <= CA_ARG_MAX ? action_arg :
10129 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10130 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10131 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10132 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10133 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10134 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10135 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10136 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10137 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10138 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10139 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10140 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10141 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10142 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10143 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10144 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10145 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10146 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10147 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10148 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10149 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10152 int action_arg_number_old =
10153 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10154 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10155 action_type == CA_SET_LEVEL_SCORE ? game.score :
10156 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10157 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10160 int action_arg_number_new =
10161 getModifiedActionNumber(action_arg_number_old,
10162 action_mode, action_arg_number,
10163 action_arg_number_min, action_arg_number_max);
10165 int trigger_player_bits =
10166 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10167 change->actual_trigger_player_bits : change->trigger_player);
10169 int action_arg_player_bits =
10170 (action_arg >= CA_ARG_PLAYER_1 &&
10171 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10172 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10173 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10176 // ---------- execute action -----------------------------------------------
10178 switch (action_type)
10185 // ---------- level actions ----------------------------------------------
10187 case CA_RESTART_LEVEL:
10189 game.restart_level = TRUE;
10194 case CA_SHOW_ENVELOPE:
10196 int element = getSpecialActionElement(action_arg_element,
10197 action_arg_number, EL_ENVELOPE_1);
10199 if (IS_ENVELOPE(element))
10200 local_player->show_envelope = element;
10205 case CA_SET_LEVEL_TIME:
10207 if (level.time > 0) // only modify limited time value
10209 TimeLeft = action_arg_number_new;
10211 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10213 DisplayGameControlValues();
10215 if (!TimeLeft && setup.time_limit)
10216 for (i = 0; i < MAX_PLAYERS; i++)
10217 KillPlayer(&stored_player[i]);
10223 case CA_SET_LEVEL_SCORE:
10225 game.score = action_arg_number_new;
10227 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10229 DisplayGameControlValues();
10234 case CA_SET_LEVEL_GEMS:
10236 game.gems_still_needed = action_arg_number_new;
10238 game.snapshot.collected_item = TRUE;
10240 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10242 DisplayGameControlValues();
10247 case CA_SET_LEVEL_WIND:
10249 game.wind_direction = action_arg_direction;
10254 case CA_SET_LEVEL_RANDOM_SEED:
10256 // ensure that setting a new random seed while playing is predictable
10257 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10262 // ---------- player actions ---------------------------------------------
10264 case CA_MOVE_PLAYER:
10265 case CA_MOVE_PLAYER_NEW:
10267 // automatically move to the next field in specified direction
10268 for (i = 0; i < MAX_PLAYERS; i++)
10269 if (trigger_player_bits & (1 << i))
10270 if (action_type == CA_MOVE_PLAYER ||
10271 stored_player[i].MovPos == 0)
10272 stored_player[i].programmed_action = action_arg_direction;
10277 case CA_EXIT_PLAYER:
10279 for (i = 0; i < MAX_PLAYERS; i++)
10280 if (action_arg_player_bits & (1 << i))
10281 ExitPlayer(&stored_player[i]);
10283 if (game.players_still_needed == 0)
10289 case CA_KILL_PLAYER:
10291 for (i = 0; i < MAX_PLAYERS; i++)
10292 if (action_arg_player_bits & (1 << i))
10293 KillPlayer(&stored_player[i]);
10298 case CA_SET_PLAYER_KEYS:
10300 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10301 int element = getSpecialActionElement(action_arg_element,
10302 action_arg_number, EL_KEY_1);
10304 if (IS_KEY(element))
10306 for (i = 0; i < MAX_PLAYERS; i++)
10308 if (trigger_player_bits & (1 << i))
10310 stored_player[i].key[KEY_NR(element)] = key_state;
10312 DrawGameDoorValues();
10320 case CA_SET_PLAYER_SPEED:
10322 for (i = 0; i < MAX_PLAYERS; i++)
10324 if (trigger_player_bits & (1 << i))
10326 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10328 if (action_arg == CA_ARG_SPEED_FASTER &&
10329 stored_player[i].cannot_move)
10331 action_arg_number = STEPSIZE_VERY_SLOW;
10333 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10334 action_arg == CA_ARG_SPEED_FASTER)
10336 action_arg_number = 2;
10337 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10340 else if (action_arg == CA_ARG_NUMBER_RESET)
10342 action_arg_number = level.initial_player_stepsize[i];
10346 getModifiedActionNumber(move_stepsize,
10349 action_arg_number_min,
10350 action_arg_number_max);
10352 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10359 case CA_SET_PLAYER_SHIELD:
10361 for (i = 0; i < MAX_PLAYERS; i++)
10363 if (trigger_player_bits & (1 << i))
10365 if (action_arg == CA_ARG_SHIELD_OFF)
10367 stored_player[i].shield_normal_time_left = 0;
10368 stored_player[i].shield_deadly_time_left = 0;
10370 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10372 stored_player[i].shield_normal_time_left = 999999;
10374 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10376 stored_player[i].shield_normal_time_left = 999999;
10377 stored_player[i].shield_deadly_time_left = 999999;
10385 case CA_SET_PLAYER_GRAVITY:
10387 for (i = 0; i < MAX_PLAYERS; i++)
10389 if (trigger_player_bits & (1 << i))
10391 stored_player[i].gravity =
10392 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10393 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10394 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10395 stored_player[i].gravity);
10402 case CA_SET_PLAYER_ARTWORK:
10404 for (i = 0; i < MAX_PLAYERS; i++)
10406 if (trigger_player_bits & (1 << i))
10408 int artwork_element = action_arg_element;
10410 if (action_arg == CA_ARG_ELEMENT_RESET)
10412 (level.use_artwork_element[i] ? level.artwork_element[i] :
10413 stored_player[i].element_nr);
10415 if (stored_player[i].artwork_element != artwork_element)
10416 stored_player[i].Frame = 0;
10418 stored_player[i].artwork_element = artwork_element;
10420 SetPlayerWaiting(&stored_player[i], FALSE);
10422 // set number of special actions for bored and sleeping animation
10423 stored_player[i].num_special_action_bored =
10424 get_num_special_action(artwork_element,
10425 ACTION_BORING_1, ACTION_BORING_LAST);
10426 stored_player[i].num_special_action_sleeping =
10427 get_num_special_action(artwork_element,
10428 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10435 case CA_SET_PLAYER_INVENTORY:
10437 for (i = 0; i < MAX_PLAYERS; i++)
10439 struct PlayerInfo *player = &stored_player[i];
10442 if (trigger_player_bits & (1 << i))
10444 int inventory_element = action_arg_element;
10446 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10447 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10448 action_arg == CA_ARG_ELEMENT_ACTION)
10450 int element = inventory_element;
10451 int collect_count = element_info[element].collect_count_initial;
10453 if (!IS_CUSTOM_ELEMENT(element))
10456 if (collect_count == 0)
10457 player->inventory_infinite_element = element;
10459 for (k = 0; k < collect_count; k++)
10460 if (player->inventory_size < MAX_INVENTORY_SIZE)
10461 player->inventory_element[player->inventory_size++] =
10464 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10465 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10466 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10468 if (player->inventory_infinite_element != EL_UNDEFINED &&
10469 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10470 action_arg_element_raw))
10471 player->inventory_infinite_element = EL_UNDEFINED;
10473 for (k = 0, j = 0; j < player->inventory_size; j++)
10475 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10476 action_arg_element_raw))
10477 player->inventory_element[k++] = player->inventory_element[j];
10480 player->inventory_size = k;
10482 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10484 if (player->inventory_size > 0)
10486 for (j = 0; j < player->inventory_size - 1; j++)
10487 player->inventory_element[j] = player->inventory_element[j + 1];
10489 player->inventory_size--;
10492 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10494 if (player->inventory_size > 0)
10495 player->inventory_size--;
10497 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10499 player->inventory_infinite_element = EL_UNDEFINED;
10500 player->inventory_size = 0;
10502 else if (action_arg == CA_ARG_INVENTORY_RESET)
10504 player->inventory_infinite_element = EL_UNDEFINED;
10505 player->inventory_size = 0;
10507 if (level.use_initial_inventory[i])
10509 for (j = 0; j < level.initial_inventory_size[i]; j++)
10511 int element = level.initial_inventory_content[i][j];
10512 int collect_count = element_info[element].collect_count_initial;
10514 if (!IS_CUSTOM_ELEMENT(element))
10517 if (collect_count == 0)
10518 player->inventory_infinite_element = element;
10520 for (k = 0; k < collect_count; k++)
10521 if (player->inventory_size < MAX_INVENTORY_SIZE)
10522 player->inventory_element[player->inventory_size++] =
10533 // ---------- CE actions -------------------------------------------------
10535 case CA_SET_CE_VALUE:
10537 int last_ce_value = CustomValue[x][y];
10539 CustomValue[x][y] = action_arg_number_new;
10541 if (CustomValue[x][y] != last_ce_value)
10543 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10544 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10546 if (CustomValue[x][y] == 0)
10548 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10549 ChangeCount[x][y] = 0; // allow at least one more change
10551 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10552 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10559 case CA_SET_CE_SCORE:
10561 int last_ce_score = ei->collect_score;
10563 ei->collect_score = action_arg_number_new;
10565 if (ei->collect_score != last_ce_score)
10567 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10568 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10570 if (ei->collect_score == 0)
10574 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10575 ChangeCount[x][y] = 0; // allow at least one more change
10577 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10578 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10581 This is a very special case that seems to be a mixture between
10582 CheckElementChange() and CheckTriggeredElementChange(): while
10583 the first one only affects single elements that are triggered
10584 directly, the second one affects multiple elements in the playfield
10585 that are triggered indirectly by another element. This is a third
10586 case: Changing the CE score always affects multiple identical CEs,
10587 so every affected CE must be checked, not only the single CE for
10588 which the CE score was changed in the first place (as every instance
10589 of that CE shares the same CE score, and therefore also can change)!
10591 SCAN_PLAYFIELD(xx, yy)
10593 if (Tile[xx][yy] == element)
10594 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10595 CE_SCORE_GETS_ZERO);
10603 case CA_SET_CE_ARTWORK:
10605 int artwork_element = action_arg_element;
10606 boolean reset_frame = FALSE;
10609 if (action_arg == CA_ARG_ELEMENT_RESET)
10610 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10613 if (ei->gfx_element != artwork_element)
10614 reset_frame = TRUE;
10616 ei->gfx_element = artwork_element;
10618 SCAN_PLAYFIELD(xx, yy)
10620 if (Tile[xx][yy] == element)
10624 ResetGfxAnimation(xx, yy);
10625 ResetRandomAnimationValue(xx, yy);
10628 TEST_DrawLevelField(xx, yy);
10635 // ---------- engine actions ---------------------------------------------
10637 case CA_SET_ENGINE_SCAN_MODE:
10639 InitPlayfieldScanMode(action_arg);
10649 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10651 int old_element = Tile[x][y];
10652 int new_element = GetElementFromGroupElement(element);
10653 int previous_move_direction = MovDir[x][y];
10654 int last_ce_value = CustomValue[x][y];
10655 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10656 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10657 boolean add_player_onto_element = (new_element_is_player &&
10658 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10659 IS_WALKABLE(old_element));
10661 if (!add_player_onto_element)
10663 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10664 RemoveMovingField(x, y);
10668 Tile[x][y] = new_element;
10670 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10671 MovDir[x][y] = previous_move_direction;
10673 if (element_info[new_element].use_last_ce_value)
10674 CustomValue[x][y] = last_ce_value;
10676 InitField_WithBug1(x, y, FALSE);
10678 new_element = Tile[x][y]; // element may have changed
10680 ResetGfxAnimation(x, y);
10681 ResetRandomAnimationValue(x, y);
10683 TEST_DrawLevelField(x, y);
10685 if (GFX_CRUMBLED(new_element))
10686 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10689 // check if element under the player changes from accessible to unaccessible
10690 // (needed for special case of dropping element which then changes)
10691 // (must be checked after creating new element for walkable group elements)
10692 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10693 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10700 // "ChangeCount" not set yet to allow "entered by player" change one time
10701 if (new_element_is_player)
10702 RelocatePlayer(x, y, new_element);
10705 ChangeCount[x][y]++; // count number of changes in the same frame
10707 TestIfBadThingTouchesPlayer(x, y);
10708 TestIfPlayerTouchesCustomElement(x, y);
10709 TestIfElementTouchesCustomElement(x, y);
10712 static void CreateField(int x, int y, int element)
10714 CreateFieldExt(x, y, element, FALSE);
10717 static void CreateElementFromChange(int x, int y, int element)
10719 element = GET_VALID_RUNTIME_ELEMENT(element);
10721 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10723 int old_element = Tile[x][y];
10725 // prevent changed element from moving in same engine frame
10726 // unless both old and new element can either fall or move
10727 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10728 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10732 CreateFieldExt(x, y, element, TRUE);
10735 static boolean ChangeElement(int x, int y, int element, int page)
10737 struct ElementInfo *ei = &element_info[element];
10738 struct ElementChangeInfo *change = &ei->change_page[page];
10739 int ce_value = CustomValue[x][y];
10740 int ce_score = ei->collect_score;
10741 int target_element;
10742 int old_element = Tile[x][y];
10744 // always use default change event to prevent running into a loop
10745 if (ChangeEvent[x][y] == -1)
10746 ChangeEvent[x][y] = CE_DELAY;
10748 if (ChangeEvent[x][y] == CE_DELAY)
10750 // reset actual trigger element, trigger player and action element
10751 change->actual_trigger_element = EL_EMPTY;
10752 change->actual_trigger_player = EL_EMPTY;
10753 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10754 change->actual_trigger_side = CH_SIDE_NONE;
10755 change->actual_trigger_ce_value = 0;
10756 change->actual_trigger_ce_score = 0;
10759 // do not change elements more than a specified maximum number of changes
10760 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10763 ChangeCount[x][y]++; // count number of changes in the same frame
10765 if (change->explode)
10772 if (change->use_target_content)
10774 boolean complete_replace = TRUE;
10775 boolean can_replace[3][3];
10778 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10781 boolean is_walkable;
10782 boolean is_diggable;
10783 boolean is_collectible;
10784 boolean is_removable;
10785 boolean is_destructible;
10786 int ex = x + xx - 1;
10787 int ey = y + yy - 1;
10788 int content_element = change->target_content.e[xx][yy];
10791 can_replace[xx][yy] = TRUE;
10793 if (ex == x && ey == y) // do not check changing element itself
10796 if (content_element == EL_EMPTY_SPACE)
10798 can_replace[xx][yy] = FALSE; // do not replace border with space
10803 if (!IN_LEV_FIELD(ex, ey))
10805 can_replace[xx][yy] = FALSE;
10806 complete_replace = FALSE;
10813 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10814 e = MovingOrBlocked2Element(ex, ey);
10816 is_empty = (IS_FREE(ex, ey) ||
10817 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10819 is_walkable = (is_empty || IS_WALKABLE(e));
10820 is_diggable = (is_empty || IS_DIGGABLE(e));
10821 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10822 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10823 is_removable = (is_diggable || is_collectible);
10825 can_replace[xx][yy] =
10826 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10827 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10828 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10829 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10830 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10831 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10832 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10834 if (!can_replace[xx][yy])
10835 complete_replace = FALSE;
10838 if (!change->only_if_complete || complete_replace)
10840 boolean something_has_changed = FALSE;
10842 if (change->only_if_complete && change->use_random_replace &&
10843 RND(100) < change->random_percentage)
10846 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10848 int ex = x + xx - 1;
10849 int ey = y + yy - 1;
10850 int content_element;
10852 if (can_replace[xx][yy] && (!change->use_random_replace ||
10853 RND(100) < change->random_percentage))
10855 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10856 RemoveMovingField(ex, ey);
10858 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10860 content_element = change->target_content.e[xx][yy];
10861 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10862 ce_value, ce_score);
10864 CreateElementFromChange(ex, ey, target_element);
10866 something_has_changed = TRUE;
10868 // for symmetry reasons, freeze newly created border elements
10869 if (ex != x || ey != y)
10870 Stop[ex][ey] = TRUE; // no more moving in this frame
10874 if (something_has_changed)
10876 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10877 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10883 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10884 ce_value, ce_score);
10886 if (element == EL_DIAGONAL_GROWING ||
10887 element == EL_DIAGONAL_SHRINKING)
10889 target_element = Store[x][y];
10891 Store[x][y] = EL_EMPTY;
10894 // special case: element changes to player (and may be kept if walkable)
10895 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10896 CreateElementFromChange(x, y, EL_EMPTY);
10898 CreateElementFromChange(x, y, target_element);
10900 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10901 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10904 // this uses direct change before indirect change
10905 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10910 static void HandleElementChange(int x, int y, int page)
10912 int element = MovingOrBlocked2Element(x, y);
10913 struct ElementInfo *ei = &element_info[element];
10914 struct ElementChangeInfo *change = &ei->change_page[page];
10915 boolean handle_action_before_change = FALSE;
10918 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10919 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10921 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10922 x, y, element, element_info[element].token_name);
10923 Debug("game:playing:HandleElementChange", "This should never happen!");
10927 // this can happen with classic bombs on walkable, changing elements
10928 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10933 if (ChangeDelay[x][y] == 0) // initialize element change
10935 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10937 if (change->can_change)
10939 // !!! not clear why graphic animation should be reset at all here !!!
10940 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10941 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10944 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10946 When using an animation frame delay of 1 (this only happens with
10947 "sp_zonk.moving.left/right" in the classic graphics), the default
10948 (non-moving) animation shows wrong animation frames (while the
10949 moving animation, like "sp_zonk.moving.left/right", is correct,
10950 so this graphical bug never shows up with the classic graphics).
10951 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10952 be drawn instead of the correct frames 0,1,2,3. This is caused by
10953 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10954 an element change: First when the change delay ("ChangeDelay[][]")
10955 counter has reached zero after decrementing, then a second time in
10956 the next frame (after "GfxFrame[][]" was already incremented) when
10957 "ChangeDelay[][]" is reset to the initial delay value again.
10959 This causes frame 0 to be drawn twice, while the last frame won't
10960 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10962 As some animations may already be cleverly designed around this bug
10963 (at least the "Snake Bite" snake tail animation does this), it cannot
10964 simply be fixed here without breaking such existing animations.
10965 Unfortunately, it cannot easily be detected if a graphics set was
10966 designed "before" or "after" the bug was fixed. As a workaround,
10967 a new graphics set option "game.graphics_engine_version" was added
10968 to be able to specify the game's major release version for which the
10969 graphics set was designed, which can then be used to decide if the
10970 bugfix should be used (version 4 and above) or not (version 3 or
10971 below, or if no version was specified at all, as with old sets).
10973 (The wrong/fixed animation frames can be tested with the test level set
10974 "test_gfxframe" and level "000", which contains a specially prepared
10975 custom element at level position (x/y) == (11/9) which uses the zonk
10976 animation mentioned above. Using "game.graphics_engine_version: 4"
10977 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10978 This can also be seen from the debug output for this test element.)
10981 // when a custom element is about to change (for example by change delay),
10982 // do not reset graphic animation when the custom element is moving
10983 if (game.graphics_engine_version < 4 &&
10986 ResetGfxAnimation(x, y);
10987 ResetRandomAnimationValue(x, y);
10990 if (change->pre_change_function)
10991 change->pre_change_function(x, y);
10995 ChangeDelay[x][y]--;
10997 if (ChangeDelay[x][y] != 0) // continue element change
10999 if (change->can_change)
11001 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11003 if (IS_ANIMATED(graphic))
11004 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11006 if (change->change_function)
11007 change->change_function(x, y);
11010 else // finish element change
11012 if (ChangePage[x][y] != -1) // remember page from delayed change
11014 page = ChangePage[x][y];
11015 ChangePage[x][y] = -1;
11017 change = &ei->change_page[page];
11020 if (IS_MOVING(x, y)) // never change a running system ;-)
11022 ChangeDelay[x][y] = 1; // try change after next move step
11023 ChangePage[x][y] = page; // remember page to use for change
11028 // special case: set new level random seed before changing element
11029 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11030 handle_action_before_change = TRUE;
11032 if (change->has_action && handle_action_before_change)
11033 ExecuteCustomElementAction(x, y, element, page);
11035 if (change->can_change)
11037 if (ChangeElement(x, y, element, page))
11039 if (change->post_change_function)
11040 change->post_change_function(x, y);
11044 if (change->has_action && !handle_action_before_change)
11045 ExecuteCustomElementAction(x, y, element, page);
11049 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11050 int trigger_element,
11052 int trigger_player,
11056 boolean change_done_any = FALSE;
11057 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11060 if (!(trigger_events[trigger_element][trigger_event]))
11063 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11065 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11067 int element = EL_CUSTOM_START + i;
11068 boolean change_done = FALSE;
11071 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11072 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11075 for (p = 0; p < element_info[element].num_change_pages; p++)
11077 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11079 if (change->can_change_or_has_action &&
11080 change->has_event[trigger_event] &&
11081 change->trigger_side & trigger_side &&
11082 change->trigger_player & trigger_player &&
11083 change->trigger_page & trigger_page_bits &&
11084 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11086 change->actual_trigger_element = trigger_element;
11087 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11088 change->actual_trigger_player_bits = trigger_player;
11089 change->actual_trigger_side = trigger_side;
11090 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11091 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11093 if ((change->can_change && !change_done) || change->has_action)
11097 SCAN_PLAYFIELD(x, y)
11099 if (Tile[x][y] == element)
11101 if (change->can_change && !change_done)
11103 // if element already changed in this frame, not only prevent
11104 // another element change (checked in ChangeElement()), but
11105 // also prevent additional element actions for this element
11107 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11108 !level.use_action_after_change_bug)
11111 ChangeDelay[x][y] = 1;
11112 ChangeEvent[x][y] = trigger_event;
11114 HandleElementChange(x, y, p);
11116 else if (change->has_action)
11118 // if element already changed in this frame, not only prevent
11119 // another element change (checked in ChangeElement()), but
11120 // also prevent additional element actions for this element
11122 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11123 !level.use_action_after_change_bug)
11126 ExecuteCustomElementAction(x, y, element, p);
11127 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11132 if (change->can_change)
11134 change_done = TRUE;
11135 change_done_any = TRUE;
11142 RECURSION_LOOP_DETECTION_END();
11144 return change_done_any;
11147 static boolean CheckElementChangeExt(int x, int y,
11149 int trigger_element,
11151 int trigger_player,
11154 boolean change_done = FALSE;
11157 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11158 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11161 if (Tile[x][y] == EL_BLOCKED)
11163 Blocked2Moving(x, y, &x, &y);
11164 element = Tile[x][y];
11167 // check if element has already changed or is about to change after moving
11168 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11169 Tile[x][y] != element) ||
11171 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11172 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11173 ChangePage[x][y] != -1)))
11176 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11178 for (p = 0; p < element_info[element].num_change_pages; p++)
11180 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11182 /* check trigger element for all events where the element that is checked
11183 for changing interacts with a directly adjacent element -- this is
11184 different to element changes that affect other elements to change on the
11185 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11186 boolean check_trigger_element =
11187 (trigger_event == CE_NEXT_TO_X ||
11188 trigger_event == CE_TOUCHING_X ||
11189 trigger_event == CE_HITTING_X ||
11190 trigger_event == CE_HIT_BY_X ||
11191 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11193 if (change->can_change_or_has_action &&
11194 change->has_event[trigger_event] &&
11195 change->trigger_side & trigger_side &&
11196 change->trigger_player & trigger_player &&
11197 (!check_trigger_element ||
11198 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11200 change->actual_trigger_element = trigger_element;
11201 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11202 change->actual_trigger_player_bits = trigger_player;
11203 change->actual_trigger_side = trigger_side;
11204 change->actual_trigger_ce_value = CustomValue[x][y];
11205 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11207 // special case: trigger element not at (x,y) position for some events
11208 if (check_trigger_element)
11220 { 0, 0 }, { 0, 0 }, { 0, 0 },
11224 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11225 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11227 change->actual_trigger_ce_value = CustomValue[xx][yy];
11228 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11231 if (change->can_change && !change_done)
11233 ChangeDelay[x][y] = 1;
11234 ChangeEvent[x][y] = trigger_event;
11236 HandleElementChange(x, y, p);
11238 change_done = TRUE;
11240 else if (change->has_action)
11242 ExecuteCustomElementAction(x, y, element, p);
11243 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11248 RECURSION_LOOP_DETECTION_END();
11250 return change_done;
11253 static void PlayPlayerSound(struct PlayerInfo *player)
11255 int jx = player->jx, jy = player->jy;
11256 int sound_element = player->artwork_element;
11257 int last_action = player->last_action_waiting;
11258 int action = player->action_waiting;
11260 if (player->is_waiting)
11262 if (action != last_action)
11263 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11265 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11269 if (action != last_action)
11270 StopSound(element_info[sound_element].sound[last_action]);
11272 if (last_action == ACTION_SLEEPING)
11273 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11277 static void PlayAllPlayersSound(void)
11281 for (i = 0; i < MAX_PLAYERS; i++)
11282 if (stored_player[i].active)
11283 PlayPlayerSound(&stored_player[i]);
11286 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11288 boolean last_waiting = player->is_waiting;
11289 int move_dir = player->MovDir;
11291 player->dir_waiting = move_dir;
11292 player->last_action_waiting = player->action_waiting;
11296 if (!last_waiting) // not waiting -> waiting
11298 player->is_waiting = TRUE;
11300 player->frame_counter_bored =
11302 game.player_boring_delay_fixed +
11303 GetSimpleRandom(game.player_boring_delay_random);
11304 player->frame_counter_sleeping =
11306 game.player_sleeping_delay_fixed +
11307 GetSimpleRandom(game.player_sleeping_delay_random);
11309 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11312 if (game.player_sleeping_delay_fixed +
11313 game.player_sleeping_delay_random > 0 &&
11314 player->anim_delay_counter == 0 &&
11315 player->post_delay_counter == 0 &&
11316 FrameCounter >= player->frame_counter_sleeping)
11317 player->is_sleeping = TRUE;
11318 else if (game.player_boring_delay_fixed +
11319 game.player_boring_delay_random > 0 &&
11320 FrameCounter >= player->frame_counter_bored)
11321 player->is_bored = TRUE;
11323 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11324 player->is_bored ? ACTION_BORING :
11327 if (player->is_sleeping && player->use_murphy)
11329 // special case for sleeping Murphy when leaning against non-free tile
11331 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11332 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11333 !IS_MOVING(player->jx - 1, player->jy)))
11334 move_dir = MV_LEFT;
11335 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11336 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11337 !IS_MOVING(player->jx + 1, player->jy)))
11338 move_dir = MV_RIGHT;
11340 player->is_sleeping = FALSE;
11342 player->dir_waiting = move_dir;
11345 if (player->is_sleeping)
11347 if (player->num_special_action_sleeping > 0)
11349 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11351 int last_special_action = player->special_action_sleeping;
11352 int num_special_action = player->num_special_action_sleeping;
11353 int special_action =
11354 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11355 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11356 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11357 last_special_action + 1 : ACTION_SLEEPING);
11358 int special_graphic =
11359 el_act_dir2img(player->artwork_element, special_action, move_dir);
11361 player->anim_delay_counter =
11362 graphic_info[special_graphic].anim_delay_fixed +
11363 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11364 player->post_delay_counter =
11365 graphic_info[special_graphic].post_delay_fixed +
11366 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11368 player->special_action_sleeping = special_action;
11371 if (player->anim_delay_counter > 0)
11373 player->action_waiting = player->special_action_sleeping;
11374 player->anim_delay_counter--;
11376 else if (player->post_delay_counter > 0)
11378 player->post_delay_counter--;
11382 else if (player->is_bored)
11384 if (player->num_special_action_bored > 0)
11386 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11388 int special_action =
11389 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11390 int special_graphic =
11391 el_act_dir2img(player->artwork_element, special_action, move_dir);
11393 player->anim_delay_counter =
11394 graphic_info[special_graphic].anim_delay_fixed +
11395 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11396 player->post_delay_counter =
11397 graphic_info[special_graphic].post_delay_fixed +
11398 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11400 player->special_action_bored = special_action;
11403 if (player->anim_delay_counter > 0)
11405 player->action_waiting = player->special_action_bored;
11406 player->anim_delay_counter--;
11408 else if (player->post_delay_counter > 0)
11410 player->post_delay_counter--;
11415 else if (last_waiting) // waiting -> not waiting
11417 player->is_waiting = FALSE;
11418 player->is_bored = FALSE;
11419 player->is_sleeping = FALSE;
11421 player->frame_counter_bored = -1;
11422 player->frame_counter_sleeping = -1;
11424 player->anim_delay_counter = 0;
11425 player->post_delay_counter = 0;
11427 player->dir_waiting = player->MovDir;
11428 player->action_waiting = ACTION_DEFAULT;
11430 player->special_action_bored = ACTION_DEFAULT;
11431 player->special_action_sleeping = ACTION_DEFAULT;
11435 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11437 if ((!player->is_moving && player->was_moving) ||
11438 (player->MovPos == 0 && player->was_moving) ||
11439 (player->is_snapping && !player->was_snapping) ||
11440 (player->is_dropping && !player->was_dropping))
11442 if (!CheckSaveEngineSnapshotToList())
11445 player->was_moving = FALSE;
11446 player->was_snapping = TRUE;
11447 player->was_dropping = TRUE;
11451 if (player->is_moving)
11452 player->was_moving = TRUE;
11454 if (!player->is_snapping)
11455 player->was_snapping = FALSE;
11457 if (!player->is_dropping)
11458 player->was_dropping = FALSE;
11461 static struct MouseActionInfo mouse_action_last = { 0 };
11462 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11463 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11466 CheckSaveEngineSnapshotToList();
11468 mouse_action_last = mouse_action;
11471 static void CheckSingleStepMode(struct PlayerInfo *player)
11473 if (tape.single_step && tape.recording && !tape.pausing)
11475 // as it is called "single step mode", just return to pause mode when the
11476 // player stopped moving after one tile (or never starts moving at all)
11477 // (reverse logic needed here in case single step mode used in team mode)
11478 if (player->is_moving ||
11479 player->is_pushing ||
11480 player->is_dropping_pressed ||
11481 player->effective_mouse_action.button)
11482 game.enter_single_step_mode = FALSE;
11485 CheckSaveEngineSnapshot(player);
11488 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11490 int left = player_action & JOY_LEFT;
11491 int right = player_action & JOY_RIGHT;
11492 int up = player_action & JOY_UP;
11493 int down = player_action & JOY_DOWN;
11494 int button1 = player_action & JOY_BUTTON_1;
11495 int button2 = player_action & JOY_BUTTON_2;
11496 int dx = (left ? -1 : right ? 1 : 0);
11497 int dy = (up ? -1 : down ? 1 : 0);
11499 if (!player->active || tape.pausing)
11505 SnapField(player, dx, dy);
11509 DropElement(player);
11511 MovePlayer(player, dx, dy);
11514 CheckSingleStepMode(player);
11516 SetPlayerWaiting(player, FALSE);
11518 return player_action;
11522 // no actions for this player (no input at player's configured device)
11524 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11525 SnapField(player, 0, 0);
11526 CheckGravityMovementWhenNotMoving(player);
11528 if (player->MovPos == 0)
11529 SetPlayerWaiting(player, TRUE);
11531 if (player->MovPos == 0) // needed for tape.playing
11532 player->is_moving = FALSE;
11534 player->is_dropping = FALSE;
11535 player->is_dropping_pressed = FALSE;
11536 player->drop_pressed_delay = 0;
11538 CheckSingleStepMode(player);
11544 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11547 if (!tape.use_mouse_actions)
11550 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11551 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11552 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11555 static void SetTapeActionFromMouseAction(byte *tape_action,
11556 struct MouseActionInfo *mouse_action)
11558 if (!tape.use_mouse_actions)
11561 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11562 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11563 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11566 static void CheckLevelSolved(void)
11568 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11570 if (game_em.level_solved &&
11571 !game_em.game_over) // game won
11575 game_em.game_over = TRUE;
11577 game.all_players_gone = TRUE;
11580 if (game_em.game_over) // game lost
11581 game.all_players_gone = TRUE;
11583 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11585 if (game_sp.level_solved &&
11586 !game_sp.game_over) // game won
11590 game_sp.game_over = TRUE;
11592 game.all_players_gone = TRUE;
11595 if (game_sp.game_over) // game lost
11596 game.all_players_gone = TRUE;
11598 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11600 if (game_mm.level_solved &&
11601 !game_mm.game_over) // game won
11605 game_mm.game_over = TRUE;
11607 game.all_players_gone = TRUE;
11610 if (game_mm.game_over) // game lost
11611 game.all_players_gone = TRUE;
11615 static void CheckLevelTime_StepCounter(void)
11625 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
11626 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11628 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11630 DisplayGameControlValues();
11632 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
11633 for (i = 0; i < MAX_PLAYERS; i++)
11634 KillPlayer(&stored_player[i]);
11636 else if (game.no_time_limit && !game.all_players_gone)
11638 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11640 DisplayGameControlValues();
11644 static void CheckLevelTime(void)
11648 if (TimeFrames >= FRAMES_PER_SECOND)
11653 for (i = 0; i < MAX_PLAYERS; i++)
11655 struct PlayerInfo *player = &stored_player[i];
11657 if (SHIELD_ON(player))
11659 player->shield_normal_time_left--;
11661 if (player->shield_deadly_time_left > 0)
11662 player->shield_deadly_time_left--;
11666 if (!game.LevelSolved && !level.use_step_counter)
11674 if (TimeLeft <= 10 && setup.time_limit)
11675 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11677 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11678 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11680 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11682 if (!TimeLeft && setup.time_limit)
11684 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11685 game_em.lev->killed_out_of_time = TRUE;
11687 for (i = 0; i < MAX_PLAYERS; i++)
11688 KillPlayer(&stored_player[i]);
11691 else if (game.no_time_limit && !game.all_players_gone)
11693 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11696 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11699 if (tape.recording || tape.playing)
11700 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11703 if (tape.recording || tape.playing)
11704 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11706 UpdateAndDisplayGameControlValues();
11709 void AdvanceFrameAndPlayerCounters(int player_nr)
11713 // advance frame counters (global frame counter and time frame counter)
11717 // advance player counters (counters for move delay, move animation etc.)
11718 for (i = 0; i < MAX_PLAYERS; i++)
11720 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11721 int move_delay_value = stored_player[i].move_delay_value;
11722 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11724 if (!advance_player_counters) // not all players may be affected
11727 if (move_frames == 0) // less than one move per game frame
11729 int stepsize = TILEX / move_delay_value;
11730 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11731 int count = (stored_player[i].is_moving ?
11732 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11734 if (count % delay == 0)
11738 stored_player[i].Frame += move_frames;
11740 if (stored_player[i].MovPos != 0)
11741 stored_player[i].StepFrame += move_frames;
11743 if (stored_player[i].move_delay > 0)
11744 stored_player[i].move_delay--;
11746 // due to bugs in previous versions, counter must count up, not down
11747 if (stored_player[i].push_delay != -1)
11748 stored_player[i].push_delay++;
11750 if (stored_player[i].drop_delay > 0)
11751 stored_player[i].drop_delay--;
11753 if (stored_player[i].is_dropping_pressed)
11754 stored_player[i].drop_pressed_delay++;
11758 void StartGameActions(boolean init_network_game, boolean record_tape,
11761 unsigned int new_random_seed = InitRND(random_seed);
11764 TapeStartRecording(new_random_seed);
11766 if (setup.auto_pause_on_start && !tape.pausing)
11767 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11769 if (init_network_game)
11771 SendToServer_LevelFile();
11772 SendToServer_StartPlaying();
11780 static void GameActionsExt(void)
11783 static unsigned int game_frame_delay = 0;
11785 unsigned int game_frame_delay_value;
11786 byte *recorded_player_action;
11787 byte summarized_player_action = 0;
11788 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11791 // detect endless loops, caused by custom element programming
11792 if (recursion_loop_detected && recursion_loop_depth == 0)
11794 char *message = getStringCat3("Internal Error! Element ",
11795 EL_NAME(recursion_loop_element),
11796 " caused endless loop! Quit the game?");
11798 Warn("element '%s' caused endless loop in game engine",
11799 EL_NAME(recursion_loop_element));
11801 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11803 recursion_loop_detected = FALSE; // if game should be continued
11810 if (game.restart_level)
11811 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11813 CheckLevelSolved();
11815 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11818 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11821 if (game_status != GAME_MODE_PLAYING) // status might have changed
11824 game_frame_delay_value =
11825 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11827 if (tape.playing && tape.warp_forward && !tape.pausing)
11828 game_frame_delay_value = 0;
11830 SetVideoFrameDelay(game_frame_delay_value);
11832 // (de)activate virtual buttons depending on current game status
11833 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11835 if (game.all_players_gone) // if no players there to be controlled anymore
11836 SetOverlayActive(FALSE);
11837 else if (!tape.playing) // if game continues after tape stopped playing
11838 SetOverlayActive(TRUE);
11843 // ---------- main game synchronization point ----------
11845 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11847 Debug("game:playing:skip", "skip == %d", skip);
11850 // ---------- main game synchronization point ----------
11852 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11856 if (network_playing && !network_player_action_received)
11858 // try to get network player actions in time
11860 // last chance to get network player actions without main loop delay
11861 HandleNetworking();
11863 // game was quit by network peer
11864 if (game_status != GAME_MODE_PLAYING)
11867 // check if network player actions still missing and game still running
11868 if (!network_player_action_received && !checkGameEnded())
11869 return; // failed to get network player actions in time
11871 // do not yet reset "network_player_action_received" (for tape.pausing)
11877 // at this point we know that we really continue executing the game
11879 network_player_action_received = FALSE;
11881 // when playing tape, read previously recorded player input from tape data
11882 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11884 local_player->effective_mouse_action = local_player->mouse_action;
11886 if (recorded_player_action != NULL)
11887 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11888 recorded_player_action);
11890 // TapePlayAction() may return NULL when toggling to "pause before death"
11894 if (tape.set_centered_player)
11896 game.centered_player_nr_next = tape.centered_player_nr_next;
11897 game.set_centered_player = TRUE;
11900 for (i = 0; i < MAX_PLAYERS; i++)
11902 summarized_player_action |= stored_player[i].action;
11904 if (!network_playing && (game.team_mode || tape.playing))
11905 stored_player[i].effective_action = stored_player[i].action;
11908 if (network_playing && !checkGameEnded())
11909 SendToServer_MovePlayer(summarized_player_action);
11911 // summarize all actions at local players mapped input device position
11912 // (this allows using different input devices in single player mode)
11913 if (!network.enabled && !game.team_mode)
11914 stored_player[map_player_action[local_player->index_nr]].effective_action =
11915 summarized_player_action;
11917 // summarize all actions at centered player in local team mode
11918 if (tape.recording &&
11919 setup.team_mode && !network.enabled &&
11920 setup.input_on_focus &&
11921 game.centered_player_nr != -1)
11923 for (i = 0; i < MAX_PLAYERS; i++)
11924 stored_player[map_player_action[i]].effective_action =
11925 (i == game.centered_player_nr ? summarized_player_action : 0);
11928 if (recorded_player_action != NULL)
11929 for (i = 0; i < MAX_PLAYERS; i++)
11930 stored_player[i].effective_action = recorded_player_action[i];
11932 for (i = 0; i < MAX_PLAYERS; i++)
11934 tape_action[i] = stored_player[i].effective_action;
11936 /* (this may happen in the RND game engine if a player was not present on
11937 the playfield on level start, but appeared later from a custom element */
11938 if (setup.team_mode &&
11941 !tape.player_participates[i])
11942 tape.player_participates[i] = TRUE;
11945 SetTapeActionFromMouseAction(tape_action,
11946 &local_player->effective_mouse_action);
11948 // only record actions from input devices, but not programmed actions
11949 if (tape.recording)
11950 TapeRecordAction(tape_action);
11952 // remember if game was played (especially after tape stopped playing)
11953 if (!tape.playing && summarized_player_action)
11954 game.GamePlayed = TRUE;
11956 #if USE_NEW_PLAYER_ASSIGNMENTS
11957 // !!! also map player actions in single player mode !!!
11958 // if (game.team_mode)
11961 byte mapped_action[MAX_PLAYERS];
11963 #if DEBUG_PLAYER_ACTIONS
11964 for (i = 0; i < MAX_PLAYERS; i++)
11965 DebugContinued("", "%d, ", stored_player[i].effective_action);
11968 for (i = 0; i < MAX_PLAYERS; i++)
11969 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11971 for (i = 0; i < MAX_PLAYERS; i++)
11972 stored_player[i].effective_action = mapped_action[i];
11974 #if DEBUG_PLAYER_ACTIONS
11975 DebugContinued("", "=> ");
11976 for (i = 0; i < MAX_PLAYERS; i++)
11977 DebugContinued("", "%d, ", stored_player[i].effective_action);
11978 DebugContinued("game:playing:player", "\n");
11981 #if DEBUG_PLAYER_ACTIONS
11984 for (i = 0; i < MAX_PLAYERS; i++)
11985 DebugContinued("", "%d, ", stored_player[i].effective_action);
11986 DebugContinued("game:playing:player", "\n");
11991 for (i = 0; i < MAX_PLAYERS; i++)
11993 // allow engine snapshot in case of changed movement attempt
11994 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
11995 (stored_player[i].effective_action & KEY_MOTION))
11996 game.snapshot.changed_action = TRUE;
11998 // allow engine snapshot in case of snapping/dropping attempt
11999 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12000 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12001 game.snapshot.changed_action = TRUE;
12003 game.snapshot.last_action[i] = stored_player[i].effective_action;
12006 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12008 GameActions_EM_Main();
12010 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12012 GameActions_SP_Main();
12014 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12016 GameActions_MM_Main();
12020 GameActions_RND_Main();
12023 BlitScreenToBitmap(backbuffer);
12025 CheckLevelSolved();
12028 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12030 if (global.show_frames_per_second)
12032 static unsigned int fps_counter = 0;
12033 static int fps_frames = 0;
12034 unsigned int fps_delay_ms = Counter() - fps_counter;
12038 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12040 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12043 fps_counter = Counter();
12045 // always draw FPS to screen after FPS value was updated
12046 redraw_mask |= REDRAW_FPS;
12049 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12050 if (GetDrawDeactivationMask() == REDRAW_NONE)
12051 redraw_mask |= REDRAW_FPS;
12055 static void GameActions_CheckSaveEngineSnapshot(void)
12057 if (!game.snapshot.save_snapshot)
12060 // clear flag for saving snapshot _before_ saving snapshot
12061 game.snapshot.save_snapshot = FALSE;
12063 SaveEngineSnapshotToList();
12066 void GameActions(void)
12070 GameActions_CheckSaveEngineSnapshot();
12073 void GameActions_EM_Main(void)
12075 byte effective_action[MAX_PLAYERS];
12076 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12079 for (i = 0; i < MAX_PLAYERS; i++)
12080 effective_action[i] = stored_player[i].effective_action;
12082 GameActions_EM(effective_action, warp_mode);
12085 void GameActions_SP_Main(void)
12087 byte effective_action[MAX_PLAYERS];
12088 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12091 for (i = 0; i < MAX_PLAYERS; i++)
12092 effective_action[i] = stored_player[i].effective_action;
12094 GameActions_SP(effective_action, warp_mode);
12096 for (i = 0; i < MAX_PLAYERS; i++)
12098 if (stored_player[i].force_dropping)
12099 stored_player[i].action |= KEY_BUTTON_DROP;
12101 stored_player[i].force_dropping = FALSE;
12105 void GameActions_MM_Main(void)
12107 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12109 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12112 void GameActions_RND_Main(void)
12117 void GameActions_RND(void)
12119 static struct MouseActionInfo mouse_action_last = { 0 };
12120 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12121 int magic_wall_x = 0, magic_wall_y = 0;
12122 int i, x, y, element, graphic, last_gfx_frame;
12124 InitPlayfieldScanModeVars();
12126 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12128 SCAN_PLAYFIELD(x, y)
12130 ChangeCount[x][y] = 0;
12131 ChangeEvent[x][y] = -1;
12135 if (game.set_centered_player)
12137 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12139 // switching to "all players" only possible if all players fit to screen
12140 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12142 game.centered_player_nr_next = game.centered_player_nr;
12143 game.set_centered_player = FALSE;
12146 // do not switch focus to non-existing (or non-active) player
12147 if (game.centered_player_nr_next >= 0 &&
12148 !stored_player[game.centered_player_nr_next].active)
12150 game.centered_player_nr_next = game.centered_player_nr;
12151 game.set_centered_player = FALSE;
12155 if (game.set_centered_player &&
12156 ScreenMovPos == 0) // screen currently aligned at tile position
12160 if (game.centered_player_nr_next == -1)
12162 setScreenCenteredToAllPlayers(&sx, &sy);
12166 sx = stored_player[game.centered_player_nr_next].jx;
12167 sy = stored_player[game.centered_player_nr_next].jy;
12170 game.centered_player_nr = game.centered_player_nr_next;
12171 game.set_centered_player = FALSE;
12173 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12174 DrawGameDoorValues();
12177 // check single step mode (set flag and clear again if any player is active)
12178 game.enter_single_step_mode =
12179 (tape.single_step && tape.recording && !tape.pausing);
12181 for (i = 0; i < MAX_PLAYERS; i++)
12183 int actual_player_action = stored_player[i].effective_action;
12186 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12187 - rnd_equinox_tetrachloride 048
12188 - rnd_equinox_tetrachloride_ii 096
12189 - rnd_emanuel_schmieg 002
12190 - doctor_sloan_ww 001, 020
12192 if (stored_player[i].MovPos == 0)
12193 CheckGravityMovement(&stored_player[i]);
12196 // overwrite programmed action with tape action
12197 if (stored_player[i].programmed_action)
12198 actual_player_action = stored_player[i].programmed_action;
12200 PlayerActions(&stored_player[i], actual_player_action);
12202 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12205 // single step pause mode may already have been toggled by "ScrollPlayer()"
12206 if (game.enter_single_step_mode && !tape.pausing)
12207 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12209 ScrollScreen(NULL, SCROLL_GO_ON);
12211 /* for backwards compatibility, the following code emulates a fixed bug that
12212 occured when pushing elements (causing elements that just made their last
12213 pushing step to already (if possible) make their first falling step in the
12214 same game frame, which is bad); this code is also needed to use the famous
12215 "spring push bug" which is used in older levels and might be wanted to be
12216 used also in newer levels, but in this case the buggy pushing code is only
12217 affecting the "spring" element and no other elements */
12219 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12221 for (i = 0; i < MAX_PLAYERS; i++)
12223 struct PlayerInfo *player = &stored_player[i];
12224 int x = player->jx;
12225 int y = player->jy;
12227 if (player->active && player->is_pushing && player->is_moving &&
12229 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12230 Tile[x][y] == EL_SPRING))
12232 ContinueMoving(x, y);
12234 // continue moving after pushing (this is actually a bug)
12235 if (!IS_MOVING(x, y))
12236 Stop[x][y] = FALSE;
12241 SCAN_PLAYFIELD(x, y)
12243 Last[x][y] = Tile[x][y];
12245 ChangeCount[x][y] = 0;
12246 ChangeEvent[x][y] = -1;
12248 // this must be handled before main playfield loop
12249 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12252 if (MovDelay[x][y] <= 0)
12256 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12259 if (MovDelay[x][y] <= 0)
12261 int element = Store[x][y];
12262 int move_direction = MovDir[x][y];
12263 int player_index_bit = Store2[x][y];
12269 TEST_DrawLevelField(x, y);
12271 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12273 if (IS_ENVELOPE(element))
12274 local_player->show_envelope = element;
12279 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12281 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12283 Debug("game:playing:GameActions_RND", "This should never happen!");
12285 ChangePage[x][y] = -1;
12289 Stop[x][y] = FALSE;
12290 if (WasJustMoving[x][y] > 0)
12291 WasJustMoving[x][y]--;
12292 if (WasJustFalling[x][y] > 0)
12293 WasJustFalling[x][y]--;
12294 if (CheckCollision[x][y] > 0)
12295 CheckCollision[x][y]--;
12296 if (CheckImpact[x][y] > 0)
12297 CheckImpact[x][y]--;
12301 /* reset finished pushing action (not done in ContinueMoving() to allow
12302 continuous pushing animation for elements with zero push delay) */
12303 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12305 ResetGfxAnimation(x, y);
12306 TEST_DrawLevelField(x, y);
12310 if (IS_BLOCKED(x, y))
12314 Blocked2Moving(x, y, &oldx, &oldy);
12315 if (!IS_MOVING(oldx, oldy))
12317 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12318 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12319 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12320 Debug("game:playing:GameActions_RND", "This should never happen!");
12326 if (mouse_action.button)
12328 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12329 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12331 x = mouse_action.lx;
12332 y = mouse_action.ly;
12333 element = Tile[x][y];
12337 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12338 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12342 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12343 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12346 if (level.use_step_counter)
12348 boolean counted_click = FALSE;
12350 // element clicked that can change when clicked/pressed
12351 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12352 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12353 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12354 counted_click = TRUE;
12356 // element clicked that can trigger change when clicked/pressed
12357 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12358 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12359 counted_click = TRUE;
12361 if (new_button && counted_click)
12362 CheckLevelTime_StepCounter();
12366 SCAN_PLAYFIELD(x, y)
12368 element = Tile[x][y];
12369 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12370 last_gfx_frame = GfxFrame[x][y];
12372 if (element == EL_EMPTY)
12373 graphic = el2img(GfxElementEmpty[x][y]);
12375 ResetGfxFrame(x, y);
12377 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12378 DrawLevelGraphicAnimation(x, y, graphic);
12380 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12381 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12382 ResetRandomAnimationValue(x, y);
12384 SetRandomAnimationValue(x, y);
12386 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12388 if (IS_INACTIVE(element))
12390 if (IS_ANIMATED(graphic))
12391 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12396 // this may take place after moving, so 'element' may have changed
12397 if (IS_CHANGING(x, y) &&
12398 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12400 int page = element_info[element].event_page_nr[CE_DELAY];
12402 HandleElementChange(x, y, page);
12404 element = Tile[x][y];
12405 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12408 CheckNextToConditions(x, y);
12410 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12414 element = Tile[x][y];
12415 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12417 if (IS_ANIMATED(graphic) &&
12418 !IS_MOVING(x, y) &&
12420 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12422 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12423 TEST_DrawTwinkleOnField(x, y);
12425 else if (element == EL_ACID)
12428 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12430 else if ((element == EL_EXIT_OPEN ||
12431 element == EL_EM_EXIT_OPEN ||
12432 element == EL_SP_EXIT_OPEN ||
12433 element == EL_STEEL_EXIT_OPEN ||
12434 element == EL_EM_STEEL_EXIT_OPEN ||
12435 element == EL_SP_TERMINAL ||
12436 element == EL_SP_TERMINAL_ACTIVE ||
12437 element == EL_EXTRA_TIME ||
12438 element == EL_SHIELD_NORMAL ||
12439 element == EL_SHIELD_DEADLY) &&
12440 IS_ANIMATED(graphic))
12441 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12442 else if (IS_MOVING(x, y))
12443 ContinueMoving(x, y);
12444 else if (IS_ACTIVE_BOMB(element))
12445 CheckDynamite(x, y);
12446 else if (element == EL_AMOEBA_GROWING)
12447 AmoebaGrowing(x, y);
12448 else if (element == EL_AMOEBA_SHRINKING)
12449 AmoebaShrinking(x, y);
12451 #if !USE_NEW_AMOEBA_CODE
12452 else if (IS_AMOEBALIVE(element))
12453 AmoebaReproduce(x, y);
12456 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12458 else if (element == EL_EXIT_CLOSED)
12460 else if (element == EL_EM_EXIT_CLOSED)
12462 else if (element == EL_STEEL_EXIT_CLOSED)
12463 CheckExitSteel(x, y);
12464 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12465 CheckExitSteelEM(x, y);
12466 else if (element == EL_SP_EXIT_CLOSED)
12468 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12469 element == EL_EXPANDABLE_STEELWALL_GROWING)
12470 MauerWaechst(x, y);
12471 else if (element == EL_EXPANDABLE_WALL ||
12472 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12473 element == EL_EXPANDABLE_WALL_VERTICAL ||
12474 element == EL_EXPANDABLE_WALL_ANY ||
12475 element == EL_BD_EXPANDABLE_WALL)
12476 MauerAbleger(x, y);
12477 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12478 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12479 element == EL_EXPANDABLE_STEELWALL_ANY)
12480 MauerAblegerStahl(x, y);
12481 else if (element == EL_FLAMES)
12482 CheckForDragon(x, y);
12483 else if (element == EL_EXPLOSION)
12484 ; // drawing of correct explosion animation is handled separately
12485 else if (element == EL_ELEMENT_SNAPPING ||
12486 element == EL_DIAGONAL_SHRINKING ||
12487 element == EL_DIAGONAL_GROWING)
12489 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12491 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12493 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12494 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12496 if (IS_BELT_ACTIVE(element))
12497 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12499 if (game.magic_wall_active)
12501 int jx = local_player->jx, jy = local_player->jy;
12503 // play the element sound at the position nearest to the player
12504 if ((element == EL_MAGIC_WALL_FULL ||
12505 element == EL_MAGIC_WALL_ACTIVE ||
12506 element == EL_MAGIC_WALL_EMPTYING ||
12507 element == EL_BD_MAGIC_WALL_FULL ||
12508 element == EL_BD_MAGIC_WALL_ACTIVE ||
12509 element == EL_BD_MAGIC_WALL_EMPTYING ||
12510 element == EL_DC_MAGIC_WALL_FULL ||
12511 element == EL_DC_MAGIC_WALL_ACTIVE ||
12512 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12513 ABS(x - jx) + ABS(y - jy) <
12514 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12522 #if USE_NEW_AMOEBA_CODE
12523 // new experimental amoeba growth stuff
12524 if (!(FrameCounter % 8))
12526 static unsigned int random = 1684108901;
12528 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12530 x = RND(lev_fieldx);
12531 y = RND(lev_fieldy);
12532 element = Tile[x][y];
12534 if (!IS_PLAYER(x,y) &&
12535 (element == EL_EMPTY ||
12536 CAN_GROW_INTO(element) ||
12537 element == EL_QUICKSAND_EMPTY ||
12538 element == EL_QUICKSAND_FAST_EMPTY ||
12539 element == EL_ACID_SPLASH_LEFT ||
12540 element == EL_ACID_SPLASH_RIGHT))
12542 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12543 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12544 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12545 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12546 Tile[x][y] = EL_AMOEBA_DROP;
12549 random = random * 129 + 1;
12554 game.explosions_delayed = FALSE;
12556 SCAN_PLAYFIELD(x, y)
12558 element = Tile[x][y];
12560 if (ExplodeField[x][y])
12561 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12562 else if (element == EL_EXPLOSION)
12563 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12565 ExplodeField[x][y] = EX_TYPE_NONE;
12568 game.explosions_delayed = TRUE;
12570 if (game.magic_wall_active)
12572 if (!(game.magic_wall_time_left % 4))
12574 int element = Tile[magic_wall_x][magic_wall_y];
12576 if (element == EL_BD_MAGIC_WALL_FULL ||
12577 element == EL_BD_MAGIC_WALL_ACTIVE ||
12578 element == EL_BD_MAGIC_WALL_EMPTYING)
12579 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12580 else if (element == EL_DC_MAGIC_WALL_FULL ||
12581 element == EL_DC_MAGIC_WALL_ACTIVE ||
12582 element == EL_DC_MAGIC_WALL_EMPTYING)
12583 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12585 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12588 if (game.magic_wall_time_left > 0)
12590 game.magic_wall_time_left--;
12592 if (!game.magic_wall_time_left)
12594 SCAN_PLAYFIELD(x, y)
12596 element = Tile[x][y];
12598 if (element == EL_MAGIC_WALL_ACTIVE ||
12599 element == EL_MAGIC_WALL_FULL)
12601 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12602 TEST_DrawLevelField(x, y);
12604 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12605 element == EL_BD_MAGIC_WALL_FULL)
12607 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12608 TEST_DrawLevelField(x, y);
12610 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12611 element == EL_DC_MAGIC_WALL_FULL)
12613 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12614 TEST_DrawLevelField(x, y);
12618 game.magic_wall_active = FALSE;
12623 if (game.light_time_left > 0)
12625 game.light_time_left--;
12627 if (game.light_time_left == 0)
12628 RedrawAllLightSwitchesAndInvisibleElements();
12631 if (game.timegate_time_left > 0)
12633 game.timegate_time_left--;
12635 if (game.timegate_time_left == 0)
12636 CloseAllOpenTimegates();
12639 if (game.lenses_time_left > 0)
12641 game.lenses_time_left--;
12643 if (game.lenses_time_left == 0)
12644 RedrawAllInvisibleElementsForLenses();
12647 if (game.magnify_time_left > 0)
12649 game.magnify_time_left--;
12651 if (game.magnify_time_left == 0)
12652 RedrawAllInvisibleElementsForMagnifier();
12655 for (i = 0; i < MAX_PLAYERS; i++)
12657 struct PlayerInfo *player = &stored_player[i];
12659 if (SHIELD_ON(player))
12661 if (player->shield_deadly_time_left)
12662 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12663 else if (player->shield_normal_time_left)
12664 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12668 #if USE_DELAYED_GFX_REDRAW
12669 SCAN_PLAYFIELD(x, y)
12671 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12673 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12674 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12676 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12677 DrawLevelField(x, y);
12679 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12680 DrawLevelFieldCrumbled(x, y);
12682 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12683 DrawLevelFieldCrumbledNeighbours(x, y);
12685 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12686 DrawTwinkleOnField(x, y);
12689 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12694 PlayAllPlayersSound();
12696 for (i = 0; i < MAX_PLAYERS; i++)
12698 struct PlayerInfo *player = &stored_player[i];
12700 if (player->show_envelope != 0 && (!player->active ||
12701 player->MovPos == 0))
12703 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12705 player->show_envelope = 0;
12709 // use random number generator in every frame to make it less predictable
12710 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12713 mouse_action_last = mouse_action;
12716 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12718 int min_x = x, min_y = y, max_x = x, max_y = y;
12719 int scr_fieldx = getScreenFieldSizeX();
12720 int scr_fieldy = getScreenFieldSizeY();
12723 for (i = 0; i < MAX_PLAYERS; i++)
12725 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12727 if (!stored_player[i].active || &stored_player[i] == player)
12730 min_x = MIN(min_x, jx);
12731 min_y = MIN(min_y, jy);
12732 max_x = MAX(max_x, jx);
12733 max_y = MAX(max_y, jy);
12736 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12739 static boolean AllPlayersInVisibleScreen(void)
12743 for (i = 0; i < MAX_PLAYERS; i++)
12745 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12747 if (!stored_player[i].active)
12750 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12757 void ScrollLevel(int dx, int dy)
12759 int scroll_offset = 2 * TILEX_VAR;
12762 BlitBitmap(drawto_field, drawto_field,
12763 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12764 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12765 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12766 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12767 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12768 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12772 x = (dx == 1 ? BX1 : BX2);
12773 for (y = BY1; y <= BY2; y++)
12774 DrawScreenField(x, y);
12779 y = (dy == 1 ? BY1 : BY2);
12780 for (x = BX1; x <= BX2; x++)
12781 DrawScreenField(x, y);
12784 redraw_mask |= REDRAW_FIELD;
12787 static boolean canFallDown(struct PlayerInfo *player)
12789 int jx = player->jx, jy = player->jy;
12791 return (IN_LEV_FIELD(jx, jy + 1) &&
12792 (IS_FREE(jx, jy + 1) ||
12793 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12794 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12795 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12798 static boolean canPassField(int x, int y, int move_dir)
12800 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12801 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12802 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12803 int nextx = x + dx;
12804 int nexty = y + dy;
12805 int element = Tile[x][y];
12807 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12808 !CAN_MOVE(element) &&
12809 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12810 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12811 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12814 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12816 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12817 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12818 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12822 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12823 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12824 (IS_DIGGABLE(Tile[newx][newy]) ||
12825 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12826 canPassField(newx, newy, move_dir)));
12829 static void CheckGravityMovement(struct PlayerInfo *player)
12831 if (player->gravity && !player->programmed_action)
12833 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12834 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12835 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12836 int jx = player->jx, jy = player->jy;
12837 boolean player_is_moving_to_valid_field =
12838 (!player_is_snapping &&
12839 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12840 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12841 boolean player_can_fall_down = canFallDown(player);
12843 if (player_can_fall_down &&
12844 !player_is_moving_to_valid_field)
12845 player->programmed_action = MV_DOWN;
12849 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12851 return CheckGravityMovement(player);
12853 if (player->gravity && !player->programmed_action)
12855 int jx = player->jx, jy = player->jy;
12856 boolean field_under_player_is_free =
12857 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12858 boolean player_is_standing_on_valid_field =
12859 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12860 (IS_WALKABLE(Tile[jx][jy]) &&
12861 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12863 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12864 player->programmed_action = MV_DOWN;
12869 MovePlayerOneStep()
12870 -----------------------------------------------------------------------------
12871 dx, dy: direction (non-diagonal) to try to move the player to
12872 real_dx, real_dy: direction as read from input device (can be diagonal)
12875 boolean MovePlayerOneStep(struct PlayerInfo *player,
12876 int dx, int dy, int real_dx, int real_dy)
12878 int jx = player->jx, jy = player->jy;
12879 int new_jx = jx + dx, new_jy = jy + dy;
12881 boolean player_can_move = !player->cannot_move;
12883 if (!player->active || (!dx && !dy))
12884 return MP_NO_ACTION;
12886 player->MovDir = (dx < 0 ? MV_LEFT :
12887 dx > 0 ? MV_RIGHT :
12889 dy > 0 ? MV_DOWN : MV_NONE);
12891 if (!IN_LEV_FIELD(new_jx, new_jy))
12892 return MP_NO_ACTION;
12894 if (!player_can_move)
12896 if (player->MovPos == 0)
12898 player->is_moving = FALSE;
12899 player->is_digging = FALSE;
12900 player->is_collecting = FALSE;
12901 player->is_snapping = FALSE;
12902 player->is_pushing = FALSE;
12906 if (!network.enabled && game.centered_player_nr == -1 &&
12907 !AllPlayersInSight(player, new_jx, new_jy))
12908 return MP_NO_ACTION;
12910 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12911 if (can_move != MP_MOVING)
12914 // check if DigField() has caused relocation of the player
12915 if (player->jx != jx || player->jy != jy)
12916 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12918 StorePlayer[jx][jy] = 0;
12919 player->last_jx = jx;
12920 player->last_jy = jy;
12921 player->jx = new_jx;
12922 player->jy = new_jy;
12923 StorePlayer[new_jx][new_jy] = player->element_nr;
12925 if (player->move_delay_value_next != -1)
12927 player->move_delay_value = player->move_delay_value_next;
12928 player->move_delay_value_next = -1;
12932 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12934 player->step_counter++;
12936 PlayerVisit[jx][jy] = FrameCounter;
12938 player->is_moving = TRUE;
12941 // should better be called in MovePlayer(), but this breaks some tapes
12942 ScrollPlayer(player, SCROLL_INIT);
12948 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12950 int jx = player->jx, jy = player->jy;
12951 int old_jx = jx, old_jy = jy;
12952 int moved = MP_NO_ACTION;
12954 if (!player->active)
12959 if (player->MovPos == 0)
12961 player->is_moving = FALSE;
12962 player->is_digging = FALSE;
12963 player->is_collecting = FALSE;
12964 player->is_snapping = FALSE;
12965 player->is_pushing = FALSE;
12971 if (player->move_delay > 0)
12974 player->move_delay = -1; // set to "uninitialized" value
12976 // store if player is automatically moved to next field
12977 player->is_auto_moving = (player->programmed_action != MV_NONE);
12979 // remove the last programmed player action
12980 player->programmed_action = 0;
12982 if (player->MovPos)
12984 // should only happen if pre-1.2 tape recordings are played
12985 // this is only for backward compatibility
12987 int original_move_delay_value = player->move_delay_value;
12990 Debug("game:playing:MovePlayer",
12991 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
12995 // scroll remaining steps with finest movement resolution
12996 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
12998 while (player->MovPos)
13000 ScrollPlayer(player, SCROLL_GO_ON);
13001 ScrollScreen(NULL, SCROLL_GO_ON);
13003 AdvanceFrameAndPlayerCounters(player->index_nr);
13006 BackToFront_WithFrameDelay(0);
13009 player->move_delay_value = original_move_delay_value;
13012 player->is_active = FALSE;
13014 if (player->last_move_dir & MV_HORIZONTAL)
13016 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13017 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13021 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13022 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13025 if (!moved && !player->is_active)
13027 player->is_moving = FALSE;
13028 player->is_digging = FALSE;
13029 player->is_collecting = FALSE;
13030 player->is_snapping = FALSE;
13031 player->is_pushing = FALSE;
13037 if (moved & MP_MOVING && !ScreenMovPos &&
13038 (player->index_nr == game.centered_player_nr ||
13039 game.centered_player_nr == -1))
13041 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13043 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13045 // actual player has left the screen -- scroll in that direction
13046 if (jx != old_jx) // player has moved horizontally
13047 scroll_x += (jx - old_jx);
13048 else // player has moved vertically
13049 scroll_y += (jy - old_jy);
13053 int offset_raw = game.scroll_delay_value;
13055 if (jx != old_jx) // player has moved horizontally
13057 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13058 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13059 int new_scroll_x = jx - MIDPOSX + offset_x;
13061 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13062 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13063 scroll_x = new_scroll_x;
13065 // don't scroll over playfield boundaries
13066 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13068 // don't scroll more than one field at a time
13069 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13071 // don't scroll against the player's moving direction
13072 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13073 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13074 scroll_x = old_scroll_x;
13076 else // player has moved vertically
13078 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13079 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13080 int new_scroll_y = jy - MIDPOSY + offset_y;
13082 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13083 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13084 scroll_y = new_scroll_y;
13086 // don't scroll over playfield boundaries
13087 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13089 // don't scroll more than one field at a time
13090 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13092 // don't scroll against the player's moving direction
13093 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13094 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13095 scroll_y = old_scroll_y;
13099 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13101 if (!network.enabled && game.centered_player_nr == -1 &&
13102 !AllPlayersInVisibleScreen())
13104 scroll_x = old_scroll_x;
13105 scroll_y = old_scroll_y;
13109 ScrollScreen(player, SCROLL_INIT);
13110 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13115 player->StepFrame = 0;
13117 if (moved & MP_MOVING)
13119 if (old_jx != jx && old_jy == jy)
13120 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13121 else if (old_jx == jx && old_jy != jy)
13122 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13124 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13126 player->last_move_dir = player->MovDir;
13127 player->is_moving = TRUE;
13128 player->is_snapping = FALSE;
13129 player->is_switching = FALSE;
13130 player->is_dropping = FALSE;
13131 player->is_dropping_pressed = FALSE;
13132 player->drop_pressed_delay = 0;
13135 // should better be called here than above, but this breaks some tapes
13136 ScrollPlayer(player, SCROLL_INIT);
13141 CheckGravityMovementWhenNotMoving(player);
13143 player->is_moving = FALSE;
13145 /* at this point, the player is allowed to move, but cannot move right now
13146 (e.g. because of something blocking the way) -- ensure that the player
13147 is also allowed to move in the next frame (in old versions before 3.1.1,
13148 the player was forced to wait again for eight frames before next try) */
13150 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13151 player->move_delay = 0; // allow direct movement in the next frame
13154 if (player->move_delay == -1) // not yet initialized by DigField()
13155 player->move_delay = player->move_delay_value;
13157 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13159 TestIfPlayerTouchesBadThing(jx, jy);
13160 TestIfPlayerTouchesCustomElement(jx, jy);
13163 if (!player->active)
13164 RemovePlayer(player);
13169 void ScrollPlayer(struct PlayerInfo *player, int mode)
13171 int jx = player->jx, jy = player->jy;
13172 int last_jx = player->last_jx, last_jy = player->last_jy;
13173 int move_stepsize = TILEX / player->move_delay_value;
13175 if (!player->active)
13178 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13181 if (mode == SCROLL_INIT)
13183 player->actual_frame_counter = FrameCounter;
13184 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13186 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13187 Tile[last_jx][last_jy] == EL_EMPTY)
13189 int last_field_block_delay = 0; // start with no blocking at all
13190 int block_delay_adjustment = player->block_delay_adjustment;
13192 // if player blocks last field, add delay for exactly one move
13193 if (player->block_last_field)
13195 last_field_block_delay += player->move_delay_value;
13197 // when blocking enabled, prevent moving up despite gravity
13198 if (player->gravity && player->MovDir == MV_UP)
13199 block_delay_adjustment = -1;
13202 // add block delay adjustment (also possible when not blocking)
13203 last_field_block_delay += block_delay_adjustment;
13205 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13206 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13209 if (player->MovPos != 0) // player has not yet reached destination
13212 else if (!FrameReached(&player->actual_frame_counter, 1))
13215 if (player->MovPos != 0)
13217 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13218 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13220 // before DrawPlayer() to draw correct player graphic for this case
13221 if (player->MovPos == 0)
13222 CheckGravityMovement(player);
13225 if (player->MovPos == 0) // player reached destination field
13227 if (player->move_delay_reset_counter > 0)
13229 player->move_delay_reset_counter--;
13231 if (player->move_delay_reset_counter == 0)
13233 // continue with normal speed after quickly moving through gate
13234 HALVE_PLAYER_SPEED(player);
13236 // be able to make the next move without delay
13237 player->move_delay = 0;
13241 player->last_jx = jx;
13242 player->last_jy = jy;
13244 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13245 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13246 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13247 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13248 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13249 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13250 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13251 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13253 ExitPlayer(player);
13255 if (game.players_still_needed == 0 &&
13256 (game.friends_still_needed == 0 ||
13257 IS_SP_ELEMENT(Tile[jx][jy])))
13261 // this breaks one level: "machine", level 000
13263 int move_direction = player->MovDir;
13264 int enter_side = MV_DIR_OPPOSITE(move_direction);
13265 int leave_side = move_direction;
13266 int old_jx = last_jx;
13267 int old_jy = last_jy;
13268 int old_element = Tile[old_jx][old_jy];
13269 int new_element = Tile[jx][jy];
13271 if (IS_CUSTOM_ELEMENT(old_element))
13272 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13274 player->index_bit, leave_side);
13276 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13277 CE_PLAYER_LEAVES_X,
13278 player->index_bit, leave_side);
13280 if (IS_CUSTOM_ELEMENT(new_element))
13281 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13282 player->index_bit, enter_side);
13284 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13285 CE_PLAYER_ENTERS_X,
13286 player->index_bit, enter_side);
13288 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13289 CE_MOVE_OF_X, move_direction);
13292 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13294 TestIfPlayerTouchesBadThing(jx, jy);
13295 TestIfPlayerTouchesCustomElement(jx, jy);
13297 /* needed because pushed element has not yet reached its destination,
13298 so it would trigger a change event at its previous field location */
13299 if (!player->is_pushing)
13300 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13302 if (level.finish_dig_collect &&
13303 (player->is_digging || player->is_collecting))
13305 int last_element = player->last_removed_element;
13306 int move_direction = player->MovDir;
13307 int enter_side = MV_DIR_OPPOSITE(move_direction);
13308 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13309 CE_PLAYER_COLLECTS_X);
13311 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13312 player->index_bit, enter_side);
13314 player->last_removed_element = EL_UNDEFINED;
13317 if (!player->active)
13318 RemovePlayer(player);
13321 if (level.use_step_counter)
13322 CheckLevelTime_StepCounter();
13324 if (tape.single_step && tape.recording && !tape.pausing &&
13325 !player->programmed_action)
13326 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13328 if (!player->programmed_action)
13329 CheckSaveEngineSnapshot(player);
13333 void ScrollScreen(struct PlayerInfo *player, int mode)
13335 static unsigned int screen_frame_counter = 0;
13337 if (mode == SCROLL_INIT)
13339 // set scrolling step size according to actual player's moving speed
13340 ScrollStepSize = TILEX / player->move_delay_value;
13342 screen_frame_counter = FrameCounter;
13343 ScreenMovDir = player->MovDir;
13344 ScreenMovPos = player->MovPos;
13345 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13348 else if (!FrameReached(&screen_frame_counter, 1))
13353 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13354 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13355 redraw_mask |= REDRAW_FIELD;
13358 ScreenMovDir = MV_NONE;
13361 void CheckNextToConditions(int x, int y)
13363 int element = Tile[x][y];
13365 if (IS_PLAYER(x, y))
13366 TestIfPlayerNextToCustomElement(x, y);
13368 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13369 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13370 TestIfElementNextToCustomElement(x, y);
13373 void TestIfPlayerNextToCustomElement(int x, int y)
13375 static int xy[4][2] =
13382 static int trigger_sides[4][2] =
13384 // center side border side
13385 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13386 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13387 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13388 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13392 if (!IS_PLAYER(x, y))
13395 struct PlayerInfo *player = PLAYERINFO(x, y);
13397 if (player->is_moving)
13400 for (i = 0; i < NUM_DIRECTIONS; i++)
13402 int xx = x + xy[i][0];
13403 int yy = y + xy[i][1];
13404 int border_side = trigger_sides[i][1];
13405 int border_element;
13407 if (!IN_LEV_FIELD(xx, yy))
13410 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13411 continue; // center and border element not connected
13413 border_element = Tile[xx][yy];
13415 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13416 player->index_bit, border_side);
13417 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13418 CE_PLAYER_NEXT_TO_X,
13419 player->index_bit, border_side);
13421 /* use player element that is initially defined in the level playfield,
13422 not the player element that corresponds to the runtime player number
13423 (example: a level that contains EL_PLAYER_3 as the only player would
13424 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13426 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13427 CE_NEXT_TO_X, border_side);
13431 void TestIfPlayerTouchesCustomElement(int x, int y)
13433 static int xy[4][2] =
13440 static int trigger_sides[4][2] =
13442 // center side border side
13443 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13444 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13445 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13446 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13448 static int touch_dir[4] =
13450 MV_LEFT | MV_RIGHT,
13455 int center_element = Tile[x][y]; // should always be non-moving!
13458 for (i = 0; i < NUM_DIRECTIONS; i++)
13460 int xx = x + xy[i][0];
13461 int yy = y + xy[i][1];
13462 int center_side = trigger_sides[i][0];
13463 int border_side = trigger_sides[i][1];
13464 int border_element;
13466 if (!IN_LEV_FIELD(xx, yy))
13469 if (IS_PLAYER(x, y)) // player found at center element
13471 struct PlayerInfo *player = PLAYERINFO(x, y);
13473 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13474 border_element = Tile[xx][yy]; // may be moving!
13475 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13476 border_element = Tile[xx][yy];
13477 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13478 border_element = MovingOrBlocked2Element(xx, yy);
13480 continue; // center and border element do not touch
13482 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13483 player->index_bit, border_side);
13484 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13485 CE_PLAYER_TOUCHES_X,
13486 player->index_bit, border_side);
13489 /* use player element that is initially defined in the level playfield,
13490 not the player element that corresponds to the runtime player number
13491 (example: a level that contains EL_PLAYER_3 as the only player would
13492 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13493 int player_element = PLAYERINFO(x, y)->initial_element;
13495 CheckElementChangeBySide(xx, yy, border_element, player_element,
13496 CE_TOUCHING_X, border_side);
13499 else if (IS_PLAYER(xx, yy)) // player found at border element
13501 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13503 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13505 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13506 continue; // center and border element do not touch
13509 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13510 player->index_bit, center_side);
13511 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13512 CE_PLAYER_TOUCHES_X,
13513 player->index_bit, center_side);
13516 /* use player element that is initially defined in the level playfield,
13517 not the player element that corresponds to the runtime player number
13518 (example: a level that contains EL_PLAYER_3 as the only player would
13519 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13520 int player_element = PLAYERINFO(xx, yy)->initial_element;
13522 CheckElementChangeBySide(x, y, center_element, player_element,
13523 CE_TOUCHING_X, center_side);
13531 void TestIfElementNextToCustomElement(int x, int y)
13533 static int xy[4][2] =
13540 static int trigger_sides[4][2] =
13542 // center side border side
13543 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13544 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13545 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13546 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13548 int center_element = Tile[x][y]; // should always be non-moving!
13551 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13554 for (i = 0; i < NUM_DIRECTIONS; i++)
13556 int xx = x + xy[i][0];
13557 int yy = y + xy[i][1];
13558 int border_side = trigger_sides[i][1];
13559 int border_element;
13561 if (!IN_LEV_FIELD(xx, yy))
13564 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13565 continue; // center and border element not connected
13567 border_element = Tile[xx][yy];
13569 // check for change of center element (but change it only once)
13570 if (CheckElementChangeBySide(x, y, center_element, border_element,
13571 CE_NEXT_TO_X, border_side))
13576 void TestIfElementTouchesCustomElement(int x, int y)
13578 static int xy[4][2] =
13585 static int trigger_sides[4][2] =
13587 // center side border side
13588 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13589 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13590 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13591 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13593 static int touch_dir[4] =
13595 MV_LEFT | MV_RIGHT,
13600 boolean change_center_element = FALSE;
13601 int center_element = Tile[x][y]; // should always be non-moving!
13602 int border_element_old[NUM_DIRECTIONS];
13605 for (i = 0; i < NUM_DIRECTIONS; i++)
13607 int xx = x + xy[i][0];
13608 int yy = y + xy[i][1];
13609 int border_element;
13611 border_element_old[i] = -1;
13613 if (!IN_LEV_FIELD(xx, yy))
13616 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13617 border_element = Tile[xx][yy]; // may be moving!
13618 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13619 border_element = Tile[xx][yy];
13620 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13621 border_element = MovingOrBlocked2Element(xx, yy);
13623 continue; // center and border element do not touch
13625 border_element_old[i] = border_element;
13628 for (i = 0; i < NUM_DIRECTIONS; i++)
13630 int xx = x + xy[i][0];
13631 int yy = y + xy[i][1];
13632 int center_side = trigger_sides[i][0];
13633 int border_element = border_element_old[i];
13635 if (border_element == -1)
13638 // check for change of border element
13639 CheckElementChangeBySide(xx, yy, border_element, center_element,
13640 CE_TOUCHING_X, center_side);
13642 // (center element cannot be player, so we dont have to check this here)
13645 for (i = 0; i < NUM_DIRECTIONS; i++)
13647 int xx = x + xy[i][0];
13648 int yy = y + xy[i][1];
13649 int border_side = trigger_sides[i][1];
13650 int border_element = border_element_old[i];
13652 if (border_element == -1)
13655 // check for change of center element (but change it only once)
13656 if (!change_center_element)
13657 change_center_element =
13658 CheckElementChangeBySide(x, y, center_element, border_element,
13659 CE_TOUCHING_X, border_side);
13661 if (IS_PLAYER(xx, yy))
13663 /* use player element that is initially defined in the level playfield,
13664 not the player element that corresponds to the runtime player number
13665 (example: a level that contains EL_PLAYER_3 as the only player would
13666 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13667 int player_element = PLAYERINFO(xx, yy)->initial_element;
13669 CheckElementChangeBySide(x, y, center_element, player_element,
13670 CE_TOUCHING_X, border_side);
13675 void TestIfElementHitsCustomElement(int x, int y, int direction)
13677 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13678 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13679 int hitx = x + dx, hity = y + dy;
13680 int hitting_element = Tile[x][y];
13681 int touched_element;
13683 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13686 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13687 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13689 if (IN_LEV_FIELD(hitx, hity))
13691 int opposite_direction = MV_DIR_OPPOSITE(direction);
13692 int hitting_side = direction;
13693 int touched_side = opposite_direction;
13694 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13695 MovDir[hitx][hity] != direction ||
13696 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13702 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13703 CE_HITTING_X, touched_side);
13705 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13706 CE_HIT_BY_X, hitting_side);
13708 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13709 CE_HIT_BY_SOMETHING, opposite_direction);
13711 if (IS_PLAYER(hitx, hity))
13713 /* use player element that is initially defined in the level playfield,
13714 not the player element that corresponds to the runtime player number
13715 (example: a level that contains EL_PLAYER_3 as the only player would
13716 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13717 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13719 CheckElementChangeBySide(x, y, hitting_element, player_element,
13720 CE_HITTING_X, touched_side);
13725 // "hitting something" is also true when hitting the playfield border
13726 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13727 CE_HITTING_SOMETHING, direction);
13730 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13732 int i, kill_x = -1, kill_y = -1;
13734 int bad_element = -1;
13735 static int test_xy[4][2] =
13742 static int test_dir[4] =
13750 for (i = 0; i < NUM_DIRECTIONS; i++)
13752 int test_x, test_y, test_move_dir, test_element;
13754 test_x = good_x + test_xy[i][0];
13755 test_y = good_y + test_xy[i][1];
13757 if (!IN_LEV_FIELD(test_x, test_y))
13761 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13763 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13765 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13766 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13768 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13769 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13773 bad_element = test_element;
13779 if (kill_x != -1 || kill_y != -1)
13781 if (IS_PLAYER(good_x, good_y))
13783 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13785 if (player->shield_deadly_time_left > 0 &&
13786 !IS_INDESTRUCTIBLE(bad_element))
13787 Bang(kill_x, kill_y);
13788 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13789 KillPlayer(player);
13792 Bang(good_x, good_y);
13796 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13798 int i, kill_x = -1, kill_y = -1;
13799 int bad_element = Tile[bad_x][bad_y];
13800 static int test_xy[4][2] =
13807 static int touch_dir[4] =
13809 MV_LEFT | MV_RIGHT,
13814 static int test_dir[4] =
13822 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13825 for (i = 0; i < NUM_DIRECTIONS; i++)
13827 int test_x, test_y, test_move_dir, test_element;
13829 test_x = bad_x + test_xy[i][0];
13830 test_y = bad_y + test_xy[i][1];
13832 if (!IN_LEV_FIELD(test_x, test_y))
13836 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13838 test_element = Tile[test_x][test_y];
13840 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13841 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13843 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13844 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13846 // good thing is player or penguin that does not move away
13847 if (IS_PLAYER(test_x, test_y))
13849 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13851 if (bad_element == EL_ROBOT && player->is_moving)
13852 continue; // robot does not kill player if he is moving
13854 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13856 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13857 continue; // center and border element do not touch
13865 else if (test_element == EL_PENGUIN)
13875 if (kill_x != -1 || kill_y != -1)
13877 if (IS_PLAYER(kill_x, kill_y))
13879 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13881 if (player->shield_deadly_time_left > 0 &&
13882 !IS_INDESTRUCTIBLE(bad_element))
13883 Bang(bad_x, bad_y);
13884 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13885 KillPlayer(player);
13888 Bang(kill_x, kill_y);
13892 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13894 int bad_element = Tile[bad_x][bad_y];
13895 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13896 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13897 int test_x = bad_x + dx, test_y = bad_y + dy;
13898 int test_move_dir, test_element;
13899 int kill_x = -1, kill_y = -1;
13901 if (!IN_LEV_FIELD(test_x, test_y))
13905 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13907 test_element = Tile[test_x][test_y];
13909 if (test_move_dir != bad_move_dir)
13911 // good thing can be player or penguin that does not move away
13912 if (IS_PLAYER(test_x, test_y))
13914 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13916 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13917 player as being hit when he is moving towards the bad thing, because
13918 the "get hit by" condition would be lost after the player stops) */
13919 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13920 return; // player moves away from bad thing
13925 else if (test_element == EL_PENGUIN)
13932 if (kill_x != -1 || kill_y != -1)
13934 if (IS_PLAYER(kill_x, kill_y))
13936 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13938 if (player->shield_deadly_time_left > 0 &&
13939 !IS_INDESTRUCTIBLE(bad_element))
13940 Bang(bad_x, bad_y);
13941 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13942 KillPlayer(player);
13945 Bang(kill_x, kill_y);
13949 void TestIfPlayerTouchesBadThing(int x, int y)
13951 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13954 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13956 TestIfGoodThingHitsBadThing(x, y, move_dir);
13959 void TestIfBadThingTouchesPlayer(int x, int y)
13961 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13964 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13966 TestIfBadThingHitsGoodThing(x, y, move_dir);
13969 void TestIfFriendTouchesBadThing(int x, int y)
13971 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13974 void TestIfBadThingTouchesFriend(int x, int y)
13976 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13979 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13981 int i, kill_x = bad_x, kill_y = bad_y;
13982 static int xy[4][2] =
13990 for (i = 0; i < NUM_DIRECTIONS; i++)
13994 x = bad_x + xy[i][0];
13995 y = bad_y + xy[i][1];
13996 if (!IN_LEV_FIELD(x, y))
13999 element = Tile[x][y];
14000 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14001 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14009 if (kill_x != bad_x || kill_y != bad_y)
14010 Bang(bad_x, bad_y);
14013 void KillPlayer(struct PlayerInfo *player)
14015 int jx = player->jx, jy = player->jy;
14017 if (!player->active)
14021 Debug("game:playing:KillPlayer",
14022 "0: killed == %d, active == %d, reanimated == %d",
14023 player->killed, player->active, player->reanimated);
14026 /* the following code was introduced to prevent an infinite loop when calling
14028 -> CheckTriggeredElementChangeExt()
14029 -> ExecuteCustomElementAction()
14031 -> (infinitely repeating the above sequence of function calls)
14032 which occurs when killing the player while having a CE with the setting
14033 "kill player X when explosion of <player X>"; the solution using a new
14034 field "player->killed" was chosen for backwards compatibility, although
14035 clever use of the fields "player->active" etc. would probably also work */
14037 if (player->killed)
14041 player->killed = TRUE;
14043 // remove accessible field at the player's position
14044 Tile[jx][jy] = EL_EMPTY;
14046 // deactivate shield (else Bang()/Explode() would not work right)
14047 player->shield_normal_time_left = 0;
14048 player->shield_deadly_time_left = 0;
14051 Debug("game:playing:KillPlayer",
14052 "1: killed == %d, active == %d, reanimated == %d",
14053 player->killed, player->active, player->reanimated);
14059 Debug("game:playing:KillPlayer",
14060 "2: killed == %d, active == %d, reanimated == %d",
14061 player->killed, player->active, player->reanimated);
14064 if (player->reanimated) // killed player may have been reanimated
14065 player->killed = player->reanimated = FALSE;
14067 BuryPlayer(player);
14070 static void KillPlayerUnlessEnemyProtected(int x, int y)
14072 if (!PLAYER_ENEMY_PROTECTED(x, y))
14073 KillPlayer(PLAYERINFO(x, y));
14076 static void KillPlayerUnlessExplosionProtected(int x, int y)
14078 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14079 KillPlayer(PLAYERINFO(x, y));
14082 void BuryPlayer(struct PlayerInfo *player)
14084 int jx = player->jx, jy = player->jy;
14086 if (!player->active)
14089 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14090 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14092 RemovePlayer(player);
14094 player->buried = TRUE;
14096 if (game.all_players_gone)
14097 game.GameOver = TRUE;
14100 void RemovePlayer(struct PlayerInfo *player)
14102 int jx = player->jx, jy = player->jy;
14103 int i, found = FALSE;
14105 player->present = FALSE;
14106 player->active = FALSE;
14108 // required for some CE actions (even if the player is not active anymore)
14109 player->MovPos = 0;
14111 if (!ExplodeField[jx][jy])
14112 StorePlayer[jx][jy] = 0;
14114 if (player->is_moving)
14115 TEST_DrawLevelField(player->last_jx, player->last_jy);
14117 for (i = 0; i < MAX_PLAYERS; i++)
14118 if (stored_player[i].active)
14123 game.all_players_gone = TRUE;
14124 game.GameOver = TRUE;
14127 game.exit_x = game.robot_wheel_x = jx;
14128 game.exit_y = game.robot_wheel_y = jy;
14131 void ExitPlayer(struct PlayerInfo *player)
14133 DrawPlayer(player); // needed here only to cleanup last field
14134 RemovePlayer(player);
14136 if (game.players_still_needed > 0)
14137 game.players_still_needed--;
14140 static void SetFieldForSnapping(int x, int y, int element, int direction,
14141 int player_index_bit)
14143 struct ElementInfo *ei = &element_info[element];
14144 int direction_bit = MV_DIR_TO_BIT(direction);
14145 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14146 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14147 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14149 Tile[x][y] = EL_ELEMENT_SNAPPING;
14150 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14151 MovDir[x][y] = direction;
14152 Store[x][y] = element;
14153 Store2[x][y] = player_index_bit;
14155 ResetGfxAnimation(x, y);
14157 GfxElement[x][y] = element;
14158 GfxAction[x][y] = action;
14159 GfxDir[x][y] = direction;
14160 GfxFrame[x][y] = -1;
14163 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14164 int player_index_bit)
14166 TestIfElementTouchesCustomElement(x, y); // for empty space
14168 if (level.finish_dig_collect)
14170 int dig_side = MV_DIR_OPPOSITE(direction);
14171 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14172 CE_PLAYER_COLLECTS_X);
14174 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14175 player_index_bit, dig_side);
14176 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14177 player_index_bit, dig_side);
14182 =============================================================================
14183 checkDiagonalPushing()
14184 -----------------------------------------------------------------------------
14185 check if diagonal input device direction results in pushing of object
14186 (by checking if the alternative direction is walkable, diggable, ...)
14187 =============================================================================
14190 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14191 int x, int y, int real_dx, int real_dy)
14193 int jx, jy, dx, dy, xx, yy;
14195 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14198 // diagonal direction: check alternative direction
14203 xx = jx + (dx == 0 ? real_dx : 0);
14204 yy = jy + (dy == 0 ? real_dy : 0);
14206 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14210 =============================================================================
14212 -----------------------------------------------------------------------------
14213 x, y: field next to player (non-diagonal) to try to dig to
14214 real_dx, real_dy: direction as read from input device (can be diagonal)
14215 =============================================================================
14218 static int DigField(struct PlayerInfo *player,
14219 int oldx, int oldy, int x, int y,
14220 int real_dx, int real_dy, int mode)
14222 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14223 boolean player_was_pushing = player->is_pushing;
14224 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14225 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14226 int jx = oldx, jy = oldy;
14227 int dx = x - jx, dy = y - jy;
14228 int nextx = x + dx, nexty = y + dy;
14229 int move_direction = (dx == -1 ? MV_LEFT :
14230 dx == +1 ? MV_RIGHT :
14232 dy == +1 ? MV_DOWN : MV_NONE);
14233 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14234 int dig_side = MV_DIR_OPPOSITE(move_direction);
14235 int old_element = Tile[jx][jy];
14236 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14239 if (is_player) // function can also be called by EL_PENGUIN
14241 if (player->MovPos == 0)
14243 player->is_digging = FALSE;
14244 player->is_collecting = FALSE;
14247 if (player->MovPos == 0) // last pushing move finished
14248 player->is_pushing = FALSE;
14250 if (mode == DF_NO_PUSH) // player just stopped pushing
14252 player->is_switching = FALSE;
14253 player->push_delay = -1;
14255 return MP_NO_ACTION;
14258 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14259 old_element = Back[jx][jy];
14261 // in case of element dropped at player position, check background
14262 else if (Back[jx][jy] != EL_EMPTY &&
14263 game.engine_version >= VERSION_IDENT(2,2,0,0))
14264 old_element = Back[jx][jy];
14266 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14267 return MP_NO_ACTION; // field has no opening in this direction
14269 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14270 return MP_NO_ACTION; // field has no opening in this direction
14272 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14276 Tile[jx][jy] = player->artwork_element;
14277 InitMovingField(jx, jy, MV_DOWN);
14278 Store[jx][jy] = EL_ACID;
14279 ContinueMoving(jx, jy);
14280 BuryPlayer(player);
14282 return MP_DONT_RUN_INTO;
14285 if (player_can_move && DONT_RUN_INTO(element))
14287 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14289 return MP_DONT_RUN_INTO;
14292 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14293 return MP_NO_ACTION;
14295 collect_count = element_info[element].collect_count_initial;
14297 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14298 return MP_NO_ACTION;
14300 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14301 player_can_move = player_can_move_or_snap;
14303 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14304 game.engine_version >= VERSION_IDENT(2,2,0,0))
14306 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14307 player->index_bit, dig_side);
14308 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14309 player->index_bit, dig_side);
14311 if (element == EL_DC_LANDMINE)
14314 if (Tile[x][y] != element) // field changed by snapping
14317 return MP_NO_ACTION;
14320 if (player->gravity && is_player && !player->is_auto_moving &&
14321 canFallDown(player) && move_direction != MV_DOWN &&
14322 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14323 return MP_NO_ACTION; // player cannot walk here due to gravity
14325 if (player_can_move &&
14326 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14328 int sound_element = SND_ELEMENT(element);
14329 int sound_action = ACTION_WALKING;
14331 if (IS_RND_GATE(element))
14333 if (!player->key[RND_GATE_NR(element)])
14334 return MP_NO_ACTION;
14336 else if (IS_RND_GATE_GRAY(element))
14338 if (!player->key[RND_GATE_GRAY_NR(element)])
14339 return MP_NO_ACTION;
14341 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14343 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14344 return MP_NO_ACTION;
14346 else if (element == EL_EXIT_OPEN ||
14347 element == EL_EM_EXIT_OPEN ||
14348 element == EL_EM_EXIT_OPENING ||
14349 element == EL_STEEL_EXIT_OPEN ||
14350 element == EL_EM_STEEL_EXIT_OPEN ||
14351 element == EL_EM_STEEL_EXIT_OPENING ||
14352 element == EL_SP_EXIT_OPEN ||
14353 element == EL_SP_EXIT_OPENING)
14355 sound_action = ACTION_PASSING; // player is passing exit
14357 else if (element == EL_EMPTY)
14359 sound_action = ACTION_MOVING; // nothing to walk on
14362 // play sound from background or player, whatever is available
14363 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14364 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14366 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14368 else if (player_can_move &&
14369 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14371 if (!ACCESS_FROM(element, opposite_direction))
14372 return MP_NO_ACTION; // field not accessible from this direction
14374 if (CAN_MOVE(element)) // only fixed elements can be passed!
14375 return MP_NO_ACTION;
14377 if (IS_EM_GATE(element))
14379 if (!player->key[EM_GATE_NR(element)])
14380 return MP_NO_ACTION;
14382 else if (IS_EM_GATE_GRAY(element))
14384 if (!player->key[EM_GATE_GRAY_NR(element)])
14385 return MP_NO_ACTION;
14387 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14389 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14390 return MP_NO_ACTION;
14392 else if (IS_EMC_GATE(element))
14394 if (!player->key[EMC_GATE_NR(element)])
14395 return MP_NO_ACTION;
14397 else if (IS_EMC_GATE_GRAY(element))
14399 if (!player->key[EMC_GATE_GRAY_NR(element)])
14400 return MP_NO_ACTION;
14402 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14404 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (element == EL_DC_GATE_WHITE ||
14408 element == EL_DC_GATE_WHITE_GRAY ||
14409 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14411 if (player->num_white_keys == 0)
14412 return MP_NO_ACTION;
14414 player->num_white_keys--;
14416 else if (IS_SP_PORT(element))
14418 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14419 element == EL_SP_GRAVITY_PORT_RIGHT ||
14420 element == EL_SP_GRAVITY_PORT_UP ||
14421 element == EL_SP_GRAVITY_PORT_DOWN)
14422 player->gravity = !player->gravity;
14423 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14424 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14425 element == EL_SP_GRAVITY_ON_PORT_UP ||
14426 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14427 player->gravity = TRUE;
14428 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14429 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14430 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14431 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14432 player->gravity = FALSE;
14435 // automatically move to the next field with double speed
14436 player->programmed_action = move_direction;
14438 if (player->move_delay_reset_counter == 0)
14440 player->move_delay_reset_counter = 2; // two double speed steps
14442 DOUBLE_PLAYER_SPEED(player);
14445 PlayLevelSoundAction(x, y, ACTION_PASSING);
14447 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14451 if (mode != DF_SNAP)
14453 GfxElement[x][y] = GFX_ELEMENT(element);
14454 player->is_digging = TRUE;
14457 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14459 // use old behaviour for old levels (digging)
14460 if (!level.finish_dig_collect)
14462 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14463 player->index_bit, dig_side);
14465 // if digging triggered player relocation, finish digging tile
14466 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14467 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14470 if (mode == DF_SNAP)
14472 if (level.block_snap_field)
14473 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14475 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14477 // use old behaviour for old levels (snapping)
14478 if (!level.finish_dig_collect)
14479 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14480 player->index_bit, dig_side);
14483 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14487 if (is_player && mode != DF_SNAP)
14489 GfxElement[x][y] = element;
14490 player->is_collecting = TRUE;
14493 if (element == EL_SPEED_PILL)
14495 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14497 else if (element == EL_EXTRA_TIME && level.time > 0)
14499 TimeLeft += level.extra_time;
14501 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14503 DisplayGameControlValues();
14505 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14507 player->shield_normal_time_left += level.shield_normal_time;
14508 if (element == EL_SHIELD_DEADLY)
14509 player->shield_deadly_time_left += level.shield_deadly_time;
14511 else if (element == EL_DYNAMITE ||
14512 element == EL_EM_DYNAMITE ||
14513 element == EL_SP_DISK_RED)
14515 if (player->inventory_size < MAX_INVENTORY_SIZE)
14516 player->inventory_element[player->inventory_size++] = element;
14518 DrawGameDoorValues();
14520 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14522 player->dynabomb_count++;
14523 player->dynabombs_left++;
14525 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14527 player->dynabomb_size++;
14529 else if (element == EL_DYNABOMB_INCREASE_POWER)
14531 player->dynabomb_xl = TRUE;
14533 else if (IS_KEY(element))
14535 player->key[KEY_NR(element)] = TRUE;
14537 DrawGameDoorValues();
14539 else if (element == EL_DC_KEY_WHITE)
14541 player->num_white_keys++;
14543 // display white keys?
14544 // DrawGameDoorValues();
14546 else if (IS_ENVELOPE(element))
14548 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14550 if (!wait_for_snapping)
14551 player->show_envelope = element;
14553 else if (element == EL_EMC_LENSES)
14555 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14557 RedrawAllInvisibleElementsForLenses();
14559 else if (element == EL_EMC_MAGNIFIER)
14561 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14563 RedrawAllInvisibleElementsForMagnifier();
14565 else if (IS_DROPPABLE(element) ||
14566 IS_THROWABLE(element)) // can be collected and dropped
14570 if (collect_count == 0)
14571 player->inventory_infinite_element = element;
14573 for (i = 0; i < collect_count; i++)
14574 if (player->inventory_size < MAX_INVENTORY_SIZE)
14575 player->inventory_element[player->inventory_size++] = element;
14577 DrawGameDoorValues();
14579 else if (collect_count > 0)
14581 game.gems_still_needed -= collect_count;
14582 if (game.gems_still_needed < 0)
14583 game.gems_still_needed = 0;
14585 game.snapshot.collected_item = TRUE;
14587 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14589 DisplayGameControlValues();
14592 RaiseScoreElement(element);
14593 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14595 // use old behaviour for old levels (collecting)
14596 if (!level.finish_dig_collect && is_player)
14598 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14599 player->index_bit, dig_side);
14601 // if collecting triggered player relocation, finish collecting tile
14602 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14603 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14606 if (mode == DF_SNAP)
14608 if (level.block_snap_field)
14609 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14611 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14613 // use old behaviour for old levels (snapping)
14614 if (!level.finish_dig_collect)
14615 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14616 player->index_bit, dig_side);
14619 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14621 if (mode == DF_SNAP && element != EL_BD_ROCK)
14622 return MP_NO_ACTION;
14624 if (CAN_FALL(element) && dy)
14625 return MP_NO_ACTION;
14627 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14628 !(element == EL_SPRING && level.use_spring_bug))
14629 return MP_NO_ACTION;
14631 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14632 ((move_direction & MV_VERTICAL &&
14633 ((element_info[element].move_pattern & MV_LEFT &&
14634 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14635 (element_info[element].move_pattern & MV_RIGHT &&
14636 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14637 (move_direction & MV_HORIZONTAL &&
14638 ((element_info[element].move_pattern & MV_UP &&
14639 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14640 (element_info[element].move_pattern & MV_DOWN &&
14641 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14642 return MP_NO_ACTION;
14644 // do not push elements already moving away faster than player
14645 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14646 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14647 return MP_NO_ACTION;
14649 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14651 if (player->push_delay_value == -1 || !player_was_pushing)
14652 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14654 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14656 if (player->push_delay_value == -1)
14657 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14659 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14661 if (!player->is_pushing)
14662 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14665 player->is_pushing = TRUE;
14666 player->is_active = TRUE;
14668 if (!(IN_LEV_FIELD(nextx, nexty) &&
14669 (IS_FREE(nextx, nexty) ||
14670 (IS_SB_ELEMENT(element) &&
14671 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14672 (IS_CUSTOM_ELEMENT(element) &&
14673 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14674 return MP_NO_ACTION;
14676 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14677 return MP_NO_ACTION;
14679 if (player->push_delay == -1) // new pushing; restart delay
14680 player->push_delay = 0;
14682 if (player->push_delay < player->push_delay_value &&
14683 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14684 element != EL_SPRING && element != EL_BALLOON)
14686 // make sure that there is no move delay before next try to push
14687 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14688 player->move_delay = 0;
14690 return MP_NO_ACTION;
14693 if (IS_CUSTOM_ELEMENT(element) &&
14694 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14696 if (!DigFieldByCE(nextx, nexty, element))
14697 return MP_NO_ACTION;
14700 if (IS_SB_ELEMENT(element))
14702 boolean sokoban_task_solved = FALSE;
14704 if (element == EL_SOKOBAN_FIELD_FULL)
14706 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14708 IncrementSokobanFieldsNeeded();
14709 IncrementSokobanObjectsNeeded();
14712 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14714 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14716 DecrementSokobanFieldsNeeded();
14717 DecrementSokobanObjectsNeeded();
14719 // sokoban object was pushed from empty field to sokoban field
14720 if (Back[x][y] == EL_EMPTY)
14721 sokoban_task_solved = TRUE;
14724 Tile[x][y] = EL_SOKOBAN_OBJECT;
14726 if (Back[x][y] == Back[nextx][nexty])
14727 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14728 else if (Back[x][y] != 0)
14729 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14732 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14735 if (sokoban_task_solved &&
14736 game.sokoban_fields_still_needed == 0 &&
14737 game.sokoban_objects_still_needed == 0 &&
14738 level.auto_exit_sokoban)
14740 game.players_still_needed = 0;
14744 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14748 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14750 InitMovingField(x, y, move_direction);
14751 GfxAction[x][y] = ACTION_PUSHING;
14753 if (mode == DF_SNAP)
14754 ContinueMoving(x, y);
14756 MovPos[x][y] = (dx != 0 ? dx : dy);
14758 Pushed[x][y] = TRUE;
14759 Pushed[nextx][nexty] = TRUE;
14761 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14762 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14764 player->push_delay_value = -1; // get new value later
14766 // check for element change _after_ element has been pushed
14767 if (game.use_change_when_pushing_bug)
14769 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14770 player->index_bit, dig_side);
14771 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14772 player->index_bit, dig_side);
14775 else if (IS_SWITCHABLE(element))
14777 if (PLAYER_SWITCHING(player, x, y))
14779 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14780 player->index_bit, dig_side);
14785 player->is_switching = TRUE;
14786 player->switch_x = x;
14787 player->switch_y = y;
14789 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14791 if (element == EL_ROBOT_WHEEL)
14793 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14795 game.robot_wheel_x = x;
14796 game.robot_wheel_y = y;
14797 game.robot_wheel_active = TRUE;
14799 TEST_DrawLevelField(x, y);
14801 else if (element == EL_SP_TERMINAL)
14805 SCAN_PLAYFIELD(xx, yy)
14807 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14811 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14813 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14815 ResetGfxAnimation(xx, yy);
14816 TEST_DrawLevelField(xx, yy);
14820 else if (IS_BELT_SWITCH(element))
14822 ToggleBeltSwitch(x, y);
14824 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14825 element == EL_SWITCHGATE_SWITCH_DOWN ||
14826 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14827 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14829 ToggleSwitchgateSwitch(x, y);
14831 else if (element == EL_LIGHT_SWITCH ||
14832 element == EL_LIGHT_SWITCH_ACTIVE)
14834 ToggleLightSwitch(x, y);
14836 else if (element == EL_TIMEGATE_SWITCH ||
14837 element == EL_DC_TIMEGATE_SWITCH)
14839 ActivateTimegateSwitch(x, y);
14841 else if (element == EL_BALLOON_SWITCH_LEFT ||
14842 element == EL_BALLOON_SWITCH_RIGHT ||
14843 element == EL_BALLOON_SWITCH_UP ||
14844 element == EL_BALLOON_SWITCH_DOWN ||
14845 element == EL_BALLOON_SWITCH_NONE ||
14846 element == EL_BALLOON_SWITCH_ANY)
14848 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14849 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14850 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14851 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14852 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14855 else if (element == EL_LAMP)
14857 Tile[x][y] = EL_LAMP_ACTIVE;
14858 game.lights_still_needed--;
14860 ResetGfxAnimation(x, y);
14861 TEST_DrawLevelField(x, y);
14863 else if (element == EL_TIME_ORB_FULL)
14865 Tile[x][y] = EL_TIME_ORB_EMPTY;
14867 if (level.time > 0 || level.use_time_orb_bug)
14869 TimeLeft += level.time_orb_time;
14870 game.no_time_limit = FALSE;
14872 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14874 DisplayGameControlValues();
14877 ResetGfxAnimation(x, y);
14878 TEST_DrawLevelField(x, y);
14880 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14881 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14885 game.ball_active = !game.ball_active;
14887 SCAN_PLAYFIELD(xx, yy)
14889 int e = Tile[xx][yy];
14891 if (game.ball_active)
14893 if (e == EL_EMC_MAGIC_BALL)
14894 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14895 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14896 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14900 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14901 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14902 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14903 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14908 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14909 player->index_bit, dig_side);
14911 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14912 player->index_bit, dig_side);
14914 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14915 player->index_bit, dig_side);
14921 if (!PLAYER_SWITCHING(player, x, y))
14923 player->is_switching = TRUE;
14924 player->switch_x = x;
14925 player->switch_y = y;
14927 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14928 player->index_bit, dig_side);
14929 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14930 player->index_bit, dig_side);
14932 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14933 player->index_bit, dig_side);
14934 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14935 player->index_bit, dig_side);
14938 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14939 player->index_bit, dig_side);
14940 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14941 player->index_bit, dig_side);
14943 return MP_NO_ACTION;
14946 player->push_delay = -1;
14948 if (is_player) // function can also be called by EL_PENGUIN
14950 if (Tile[x][y] != element) // really digged/collected something
14952 player->is_collecting = !player->is_digging;
14953 player->is_active = TRUE;
14955 player->last_removed_element = element;
14962 static boolean DigFieldByCE(int x, int y, int digging_element)
14964 int element = Tile[x][y];
14966 if (!IS_FREE(x, y))
14968 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14969 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14972 // no element can dig solid indestructible elements
14973 if (IS_INDESTRUCTIBLE(element) &&
14974 !IS_DIGGABLE(element) &&
14975 !IS_COLLECTIBLE(element))
14978 if (AmoebaNr[x][y] &&
14979 (element == EL_AMOEBA_FULL ||
14980 element == EL_BD_AMOEBA ||
14981 element == EL_AMOEBA_GROWING))
14983 AmoebaCnt[AmoebaNr[x][y]]--;
14984 AmoebaCnt2[AmoebaNr[x][y]]--;
14987 if (IS_MOVING(x, y))
14988 RemoveMovingField(x, y);
14992 TEST_DrawLevelField(x, y);
14995 // if digged element was about to explode, prevent the explosion
14996 ExplodeField[x][y] = EX_TYPE_NONE;
14998 PlayLevelSoundAction(x, y, action);
15001 Store[x][y] = EL_EMPTY;
15003 // this makes it possible to leave the removed element again
15004 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15005 Store[x][y] = element;
15010 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15012 int jx = player->jx, jy = player->jy;
15013 int x = jx + dx, y = jy + dy;
15014 int snap_direction = (dx == -1 ? MV_LEFT :
15015 dx == +1 ? MV_RIGHT :
15017 dy == +1 ? MV_DOWN : MV_NONE);
15018 boolean can_continue_snapping = (level.continuous_snapping &&
15019 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15021 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15024 if (!player->active || !IN_LEV_FIELD(x, y))
15032 if (player->MovPos == 0)
15033 player->is_pushing = FALSE;
15035 player->is_snapping = FALSE;
15037 if (player->MovPos == 0)
15039 player->is_moving = FALSE;
15040 player->is_digging = FALSE;
15041 player->is_collecting = FALSE;
15047 // prevent snapping with already pressed snap key when not allowed
15048 if (player->is_snapping && !can_continue_snapping)
15051 player->MovDir = snap_direction;
15053 if (player->MovPos == 0)
15055 player->is_moving = FALSE;
15056 player->is_digging = FALSE;
15057 player->is_collecting = FALSE;
15060 player->is_dropping = FALSE;
15061 player->is_dropping_pressed = FALSE;
15062 player->drop_pressed_delay = 0;
15064 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15067 player->is_snapping = TRUE;
15068 player->is_active = TRUE;
15070 if (player->MovPos == 0)
15072 player->is_moving = FALSE;
15073 player->is_digging = FALSE;
15074 player->is_collecting = FALSE;
15077 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15078 TEST_DrawLevelField(player->last_jx, player->last_jy);
15080 TEST_DrawLevelField(x, y);
15085 static boolean DropElement(struct PlayerInfo *player)
15087 int old_element, new_element;
15088 int dropx = player->jx, dropy = player->jy;
15089 int drop_direction = player->MovDir;
15090 int drop_side = drop_direction;
15091 int drop_element = get_next_dropped_element(player);
15093 /* do not drop an element on top of another element; when holding drop key
15094 pressed without moving, dropped element must move away before the next
15095 element can be dropped (this is especially important if the next element
15096 is dynamite, which can be placed on background for historical reasons) */
15097 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15100 if (IS_THROWABLE(drop_element))
15102 dropx += GET_DX_FROM_DIR(drop_direction);
15103 dropy += GET_DY_FROM_DIR(drop_direction);
15105 if (!IN_LEV_FIELD(dropx, dropy))
15109 old_element = Tile[dropx][dropy]; // old element at dropping position
15110 new_element = drop_element; // default: no change when dropping
15112 // check if player is active, not moving and ready to drop
15113 if (!player->active || player->MovPos || player->drop_delay > 0)
15116 // check if player has anything that can be dropped
15117 if (new_element == EL_UNDEFINED)
15120 // only set if player has anything that can be dropped
15121 player->is_dropping_pressed = TRUE;
15123 // check if drop key was pressed long enough for EM style dynamite
15124 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15127 // check if anything can be dropped at the current position
15128 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15131 // collected custom elements can only be dropped on empty fields
15132 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15135 if (old_element != EL_EMPTY)
15136 Back[dropx][dropy] = old_element; // store old element on this field
15138 ResetGfxAnimation(dropx, dropy);
15139 ResetRandomAnimationValue(dropx, dropy);
15141 if (player->inventory_size > 0 ||
15142 player->inventory_infinite_element != EL_UNDEFINED)
15144 if (player->inventory_size > 0)
15146 player->inventory_size--;
15148 DrawGameDoorValues();
15150 if (new_element == EL_DYNAMITE)
15151 new_element = EL_DYNAMITE_ACTIVE;
15152 else if (new_element == EL_EM_DYNAMITE)
15153 new_element = EL_EM_DYNAMITE_ACTIVE;
15154 else if (new_element == EL_SP_DISK_RED)
15155 new_element = EL_SP_DISK_RED_ACTIVE;
15158 Tile[dropx][dropy] = new_element;
15160 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15161 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15162 el2img(Tile[dropx][dropy]), 0);
15164 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15166 // needed if previous element just changed to "empty" in the last frame
15167 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15169 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15170 player->index_bit, drop_side);
15171 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15173 player->index_bit, drop_side);
15175 TestIfElementTouchesCustomElement(dropx, dropy);
15177 else // player is dropping a dyna bomb
15179 player->dynabombs_left--;
15181 Tile[dropx][dropy] = new_element;
15183 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15184 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15185 el2img(Tile[dropx][dropy]), 0);
15187 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15190 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15191 InitField_WithBug1(dropx, dropy, FALSE);
15193 new_element = Tile[dropx][dropy]; // element might have changed
15195 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15196 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15198 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15199 MovDir[dropx][dropy] = drop_direction;
15201 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15203 // do not cause impact style collision by dropping elements that can fall
15204 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15207 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15208 player->is_dropping = TRUE;
15210 player->drop_pressed_delay = 0;
15211 player->is_dropping_pressed = FALSE;
15213 player->drop_x = dropx;
15214 player->drop_y = dropy;
15219 // ----------------------------------------------------------------------------
15220 // game sound playing functions
15221 // ----------------------------------------------------------------------------
15223 static int *loop_sound_frame = NULL;
15224 static int *loop_sound_volume = NULL;
15226 void InitPlayLevelSound(void)
15228 int num_sounds = getSoundListSize();
15230 checked_free(loop_sound_frame);
15231 checked_free(loop_sound_volume);
15233 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15234 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15237 static void PlayLevelSound(int x, int y, int nr)
15239 int sx = SCREENX(x), sy = SCREENY(y);
15240 int volume, stereo_position;
15241 int max_distance = 8;
15242 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15244 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15245 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15248 if (!IN_LEV_FIELD(x, y) ||
15249 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15250 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15253 volume = SOUND_MAX_VOLUME;
15255 if (!IN_SCR_FIELD(sx, sy))
15257 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15258 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15260 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15263 stereo_position = (SOUND_MAX_LEFT +
15264 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15265 (SCR_FIELDX + 2 * max_distance));
15267 if (IS_LOOP_SOUND(nr))
15269 /* This assures that quieter loop sounds do not overwrite louder ones,
15270 while restarting sound volume comparison with each new game frame. */
15272 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15275 loop_sound_volume[nr] = volume;
15276 loop_sound_frame[nr] = FrameCounter;
15279 PlaySoundExt(nr, volume, stereo_position, type);
15282 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15284 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15285 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15286 y < LEVELY(BY1) ? LEVELY(BY1) :
15287 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15291 static void PlayLevelSoundAction(int x, int y, int action)
15293 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15296 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15298 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15300 if (sound_effect != SND_UNDEFINED)
15301 PlayLevelSound(x, y, sound_effect);
15304 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15307 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15309 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15310 PlayLevelSound(x, y, sound_effect);
15313 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15315 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15317 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15318 PlayLevelSound(x, y, sound_effect);
15321 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15323 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15325 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15326 StopSound(sound_effect);
15329 static int getLevelMusicNr(void)
15331 if (levelset.music[level_nr] != MUS_UNDEFINED)
15332 return levelset.music[level_nr]; // from config file
15334 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15337 static void FadeLevelSounds(void)
15342 static void FadeLevelMusic(void)
15344 int music_nr = getLevelMusicNr();
15345 char *curr_music = getCurrentlyPlayingMusicFilename();
15346 char *next_music = getMusicInfoEntryFilename(music_nr);
15348 if (!strEqual(curr_music, next_music))
15352 void FadeLevelSoundsAndMusic(void)
15358 static void PlayLevelMusic(void)
15360 int music_nr = getLevelMusicNr();
15361 char *curr_music = getCurrentlyPlayingMusicFilename();
15362 char *next_music = getMusicInfoEntryFilename(music_nr);
15364 if (!strEqual(curr_music, next_music))
15365 PlayMusicLoop(music_nr);
15368 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15370 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15372 int x = xx - offset;
15373 int y = yy - offset;
15378 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15382 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15386 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15390 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15394 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15398 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15402 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15405 case SOUND_android_clone:
15406 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15409 case SOUND_android_move:
15410 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15414 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15418 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15422 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15425 case SOUND_eater_eat:
15426 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15430 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15433 case SOUND_collect:
15434 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15437 case SOUND_diamond:
15438 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15442 // !!! CHECK THIS !!!
15444 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15446 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15450 case SOUND_wonderfall:
15451 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15455 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15459 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15463 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15467 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15471 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15475 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15479 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15483 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15486 case SOUND_exit_open:
15487 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15490 case SOUND_exit_leave:
15491 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15494 case SOUND_dynamite:
15495 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15499 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15503 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15507 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15511 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15515 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15519 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15523 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15528 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15530 int element = map_element_SP_to_RND(element_sp);
15531 int action = map_action_SP_to_RND(action_sp);
15532 int offset = (setup.sp_show_border_elements ? 0 : 1);
15533 int x = xx - offset;
15534 int y = yy - offset;
15536 PlayLevelSoundElementAction(x, y, element, action);
15539 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15541 int element = map_element_MM_to_RND(element_mm);
15542 int action = map_action_MM_to_RND(action_mm);
15544 int x = xx - offset;
15545 int y = yy - offset;
15547 if (!IS_MM_ELEMENT(element))
15548 element = EL_MM_DEFAULT;
15550 PlayLevelSoundElementAction(x, y, element, action);
15553 void PlaySound_MM(int sound_mm)
15555 int sound = map_sound_MM_to_RND(sound_mm);
15557 if (sound == SND_UNDEFINED)
15563 void PlaySoundLoop_MM(int sound_mm)
15565 int sound = map_sound_MM_to_RND(sound_mm);
15567 if (sound == SND_UNDEFINED)
15570 PlaySoundLoop(sound);
15573 void StopSound_MM(int sound_mm)
15575 int sound = map_sound_MM_to_RND(sound_mm);
15577 if (sound == SND_UNDEFINED)
15583 void RaiseScore(int value)
15585 game.score += value;
15587 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15589 DisplayGameControlValues();
15592 void RaiseScoreElement(int element)
15597 case EL_BD_DIAMOND:
15598 case EL_EMERALD_YELLOW:
15599 case EL_EMERALD_RED:
15600 case EL_EMERALD_PURPLE:
15601 case EL_SP_INFOTRON:
15602 RaiseScore(level.score[SC_EMERALD]);
15605 RaiseScore(level.score[SC_DIAMOND]);
15608 RaiseScore(level.score[SC_CRYSTAL]);
15611 RaiseScore(level.score[SC_PEARL]);
15614 case EL_BD_BUTTERFLY:
15615 case EL_SP_ELECTRON:
15616 RaiseScore(level.score[SC_BUG]);
15619 case EL_BD_FIREFLY:
15620 case EL_SP_SNIKSNAK:
15621 RaiseScore(level.score[SC_SPACESHIP]);
15624 case EL_DARK_YAMYAM:
15625 RaiseScore(level.score[SC_YAMYAM]);
15628 RaiseScore(level.score[SC_ROBOT]);
15631 RaiseScore(level.score[SC_PACMAN]);
15634 RaiseScore(level.score[SC_NUT]);
15637 case EL_EM_DYNAMITE:
15638 case EL_SP_DISK_RED:
15639 case EL_DYNABOMB_INCREASE_NUMBER:
15640 case EL_DYNABOMB_INCREASE_SIZE:
15641 case EL_DYNABOMB_INCREASE_POWER:
15642 RaiseScore(level.score[SC_DYNAMITE]);
15644 case EL_SHIELD_NORMAL:
15645 case EL_SHIELD_DEADLY:
15646 RaiseScore(level.score[SC_SHIELD]);
15648 case EL_EXTRA_TIME:
15649 RaiseScore(level.extra_time_score);
15663 case EL_DC_KEY_WHITE:
15664 RaiseScore(level.score[SC_KEY]);
15667 RaiseScore(element_info[element].collect_score);
15672 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15674 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15678 // prevent short reactivation of overlay buttons while closing door
15679 SetOverlayActive(FALSE);
15681 // door may still be open due to skipped or envelope style request
15682 CloseDoor(DOOR_CLOSE_1);
15685 if (network.enabled)
15686 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15690 FadeSkipNextFadeIn();
15692 SetGameStatus(GAME_MODE_MAIN);
15697 else // continue playing the game
15699 if (tape.playing && tape.deactivate_display)
15700 TapeDeactivateDisplayOff(TRUE);
15702 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15704 if (tape.playing && tape.deactivate_display)
15705 TapeDeactivateDisplayOn();
15709 void RequestQuitGame(boolean escape_key_pressed)
15711 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15712 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15713 level_editor_test_game);
15714 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15717 RequestQuitGameExt(skip_request, quick_quit,
15718 "Do you really want to quit the game?");
15721 void RequestRestartGame(char *message)
15723 game.restart_game_message = NULL;
15725 boolean has_started_game = hasStartedNetworkGame();
15726 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15728 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15730 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15734 // needed in case of envelope request to close game panel
15735 CloseDoor(DOOR_CLOSE_1);
15737 SetGameStatus(GAME_MODE_MAIN);
15743 void CheckGameOver(void)
15745 static boolean last_game_over = FALSE;
15746 static int game_over_delay = 0;
15747 int game_over_delay_value = 50;
15748 boolean game_over = checkGameFailed();
15750 // do not handle game over if request dialog is already active
15751 if (game.request_active)
15754 // do not ask to play again if game was never actually played
15755 if (!game.GamePlayed)
15760 last_game_over = FALSE;
15761 game_over_delay = game_over_delay_value;
15766 if (game_over_delay > 0)
15773 if (last_game_over != game_over)
15774 game.restart_game_message = (hasStartedNetworkGame() ?
15775 "Game over! Play it again?" :
15778 last_game_over = game_over;
15781 boolean checkGameSolved(void)
15783 // set for all game engines if level was solved
15784 return game.LevelSolved_GameEnd;
15787 boolean checkGameFailed(void)
15789 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15790 return (game_em.game_over && !game_em.level_solved);
15791 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15792 return (game_sp.game_over && !game_sp.level_solved);
15793 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15794 return (game_mm.game_over && !game_mm.level_solved);
15795 else // GAME_ENGINE_TYPE_RND
15796 return (game.GameOver && !game.LevelSolved);
15799 boolean checkGameEnded(void)
15801 return (checkGameSolved() || checkGameFailed());
15805 // ----------------------------------------------------------------------------
15806 // random generator functions
15807 // ----------------------------------------------------------------------------
15809 unsigned int InitEngineRandom_RND(int seed)
15811 game.num_random_calls = 0;
15813 return InitEngineRandom(seed);
15816 unsigned int RND(int max)
15820 game.num_random_calls++;
15822 return GetEngineRandom(max);
15829 // ----------------------------------------------------------------------------
15830 // game engine snapshot handling functions
15831 // ----------------------------------------------------------------------------
15833 struct EngineSnapshotInfo
15835 // runtime values for custom element collect score
15836 int collect_score[NUM_CUSTOM_ELEMENTS];
15838 // runtime values for group element choice position
15839 int choice_pos[NUM_GROUP_ELEMENTS];
15841 // runtime values for belt position animations
15842 int belt_graphic[4][NUM_BELT_PARTS];
15843 int belt_anim_mode[4][NUM_BELT_PARTS];
15846 static struct EngineSnapshotInfo engine_snapshot_rnd;
15847 static char *snapshot_level_identifier = NULL;
15848 static int snapshot_level_nr = -1;
15850 static void SaveEngineSnapshotValues_RND(void)
15852 static int belt_base_active_element[4] =
15854 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15855 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15856 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15857 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15861 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15863 int element = EL_CUSTOM_START + i;
15865 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15868 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15870 int element = EL_GROUP_START + i;
15872 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15875 for (i = 0; i < 4; i++)
15877 for (j = 0; j < NUM_BELT_PARTS; j++)
15879 int element = belt_base_active_element[i] + j;
15880 int graphic = el2img(element);
15881 int anim_mode = graphic_info[graphic].anim_mode;
15883 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15884 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15889 static void LoadEngineSnapshotValues_RND(void)
15891 unsigned int num_random_calls = game.num_random_calls;
15894 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15896 int element = EL_CUSTOM_START + i;
15898 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15901 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15903 int element = EL_GROUP_START + i;
15905 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15908 for (i = 0; i < 4; i++)
15910 for (j = 0; j < NUM_BELT_PARTS; j++)
15912 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15913 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15915 graphic_info[graphic].anim_mode = anim_mode;
15919 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15921 InitRND(tape.random_seed);
15922 for (i = 0; i < num_random_calls; i++)
15926 if (game.num_random_calls != num_random_calls)
15928 Error("number of random calls out of sync");
15929 Error("number of random calls should be %d", num_random_calls);
15930 Error("number of random calls is %d", game.num_random_calls);
15932 Fail("this should not happen -- please debug");
15936 void FreeEngineSnapshotSingle(void)
15938 FreeSnapshotSingle();
15940 setString(&snapshot_level_identifier, NULL);
15941 snapshot_level_nr = -1;
15944 void FreeEngineSnapshotList(void)
15946 FreeSnapshotList();
15949 static ListNode *SaveEngineSnapshotBuffers(void)
15951 ListNode *buffers = NULL;
15953 // copy some special values to a structure better suited for the snapshot
15955 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15956 SaveEngineSnapshotValues_RND();
15957 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15958 SaveEngineSnapshotValues_EM();
15959 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15960 SaveEngineSnapshotValues_SP(&buffers);
15961 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15962 SaveEngineSnapshotValues_MM(&buffers);
15964 // save values stored in special snapshot structure
15966 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15967 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15968 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15969 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15970 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15971 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15972 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15973 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15975 // save further RND engine values
15977 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15978 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15979 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15985 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
15987 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
15989 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
15993 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
15999 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16000 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16001 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16003 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16004 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16005 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16006 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16007 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16008 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16009 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16010 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16011 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16012 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16026 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16028 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16029 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16030 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16036 ListNode *node = engine_snapshot_list_rnd;
16039 while (node != NULL)
16041 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16046 Debug("game:playing:SaveEngineSnapshotBuffers",
16047 "size of engine snapshot: %d bytes", num_bytes);
16053 void SaveEngineSnapshotSingle(void)
16055 ListNode *buffers = SaveEngineSnapshotBuffers();
16057 // finally save all snapshot buffers to single snapshot
16058 SaveSnapshotSingle(buffers);
16060 // save level identification information
16061 setString(&snapshot_level_identifier, leveldir_current->identifier);
16062 snapshot_level_nr = level_nr;
16065 boolean CheckSaveEngineSnapshotToList(void)
16067 boolean save_snapshot =
16068 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16069 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16070 game.snapshot.changed_action) ||
16071 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16072 game.snapshot.collected_item));
16074 game.snapshot.changed_action = FALSE;
16075 game.snapshot.collected_item = FALSE;
16076 game.snapshot.save_snapshot = save_snapshot;
16078 return save_snapshot;
16081 void SaveEngineSnapshotToList(void)
16083 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16087 ListNode *buffers = SaveEngineSnapshotBuffers();
16089 // finally save all snapshot buffers to snapshot list
16090 SaveSnapshotToList(buffers);
16093 void SaveEngineSnapshotToListInitial(void)
16095 FreeEngineSnapshotList();
16097 SaveEngineSnapshotToList();
16100 static void LoadEngineSnapshotValues(void)
16102 // restore special values from snapshot structure
16104 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16105 LoadEngineSnapshotValues_RND();
16106 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16107 LoadEngineSnapshotValues_EM();
16108 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16109 LoadEngineSnapshotValues_SP();
16110 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16111 LoadEngineSnapshotValues_MM();
16114 void LoadEngineSnapshotSingle(void)
16116 LoadSnapshotSingle();
16118 LoadEngineSnapshotValues();
16121 static void LoadEngineSnapshot_Undo(int steps)
16123 LoadSnapshotFromList_Older(steps);
16125 LoadEngineSnapshotValues();
16128 static void LoadEngineSnapshot_Redo(int steps)
16130 LoadSnapshotFromList_Newer(steps);
16132 LoadEngineSnapshotValues();
16135 boolean CheckEngineSnapshotSingle(void)
16137 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16138 snapshot_level_nr == level_nr);
16141 boolean CheckEngineSnapshotList(void)
16143 return CheckSnapshotList();
16147 // ---------- new game button stuff -------------------------------------------
16154 boolean *setup_value;
16155 boolean allowed_on_tape;
16156 boolean is_touch_button;
16158 } gamebutton_info[NUM_GAME_BUTTONS] =
16161 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16162 GAME_CTRL_ID_STOP, NULL,
16163 TRUE, FALSE, "stop game"
16166 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16167 GAME_CTRL_ID_PAUSE, NULL,
16168 TRUE, FALSE, "pause game"
16171 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16172 GAME_CTRL_ID_PLAY, NULL,
16173 TRUE, FALSE, "play game"
16176 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16177 GAME_CTRL_ID_UNDO, NULL,
16178 TRUE, FALSE, "undo step"
16181 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16182 GAME_CTRL_ID_REDO, NULL,
16183 TRUE, FALSE, "redo step"
16186 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16187 GAME_CTRL_ID_SAVE, NULL,
16188 TRUE, FALSE, "save game"
16191 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16192 GAME_CTRL_ID_PAUSE2, NULL,
16193 TRUE, FALSE, "pause game"
16196 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16197 GAME_CTRL_ID_LOAD, NULL,
16198 TRUE, FALSE, "load game"
16201 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16202 GAME_CTRL_ID_PANEL_STOP, NULL,
16203 FALSE, FALSE, "stop game"
16206 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16207 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16208 FALSE, FALSE, "pause game"
16211 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16212 GAME_CTRL_ID_PANEL_PLAY, NULL,
16213 FALSE, FALSE, "play game"
16216 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16217 GAME_CTRL_ID_TOUCH_STOP, NULL,
16218 FALSE, TRUE, "stop game"
16221 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16222 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16223 FALSE, TRUE, "pause game"
16226 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16227 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16228 TRUE, FALSE, "background music on/off"
16231 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16232 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16233 TRUE, FALSE, "sound loops on/off"
16236 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16237 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16238 TRUE, FALSE, "normal sounds on/off"
16241 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16242 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16243 FALSE, FALSE, "background music on/off"
16246 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16247 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16248 FALSE, FALSE, "sound loops on/off"
16251 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16252 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16253 FALSE, FALSE, "normal sounds on/off"
16257 void CreateGameButtons(void)
16261 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16263 int graphic = gamebutton_info[i].graphic;
16264 struct GraphicInfo *gfx = &graphic_info[graphic];
16265 struct XY *pos = gamebutton_info[i].pos;
16266 struct GadgetInfo *gi;
16269 unsigned int event_mask;
16270 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16271 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16272 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16273 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16274 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16275 int gd_x = gfx->src_x;
16276 int gd_y = gfx->src_y;
16277 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16278 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16279 int gd_xa = gfx->src_x + gfx->active_xoffset;
16280 int gd_ya = gfx->src_y + gfx->active_yoffset;
16281 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16282 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16283 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16284 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16287 if (gfx->bitmap == NULL)
16289 game_gadget[id] = NULL;
16294 if (id == GAME_CTRL_ID_STOP ||
16295 id == GAME_CTRL_ID_PANEL_STOP ||
16296 id == GAME_CTRL_ID_TOUCH_STOP ||
16297 id == GAME_CTRL_ID_PLAY ||
16298 id == GAME_CTRL_ID_PANEL_PLAY ||
16299 id == GAME_CTRL_ID_SAVE ||
16300 id == GAME_CTRL_ID_LOAD)
16302 button_type = GD_TYPE_NORMAL_BUTTON;
16304 event_mask = GD_EVENT_RELEASED;
16306 else if (id == GAME_CTRL_ID_UNDO ||
16307 id == GAME_CTRL_ID_REDO)
16309 button_type = GD_TYPE_NORMAL_BUTTON;
16311 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16315 button_type = GD_TYPE_CHECK_BUTTON;
16316 checked = (gamebutton_info[i].setup_value != NULL ?
16317 *gamebutton_info[i].setup_value : FALSE);
16318 event_mask = GD_EVENT_PRESSED;
16321 gi = CreateGadget(GDI_CUSTOM_ID, id,
16322 GDI_IMAGE_ID, graphic,
16323 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16326 GDI_WIDTH, gfx->width,
16327 GDI_HEIGHT, gfx->height,
16328 GDI_TYPE, button_type,
16329 GDI_STATE, GD_BUTTON_UNPRESSED,
16330 GDI_CHECKED, checked,
16331 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16332 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16333 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16334 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16335 GDI_DIRECT_DRAW, FALSE,
16336 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16337 GDI_EVENT_MASK, event_mask,
16338 GDI_CALLBACK_ACTION, HandleGameButtons,
16342 Fail("cannot create gadget");
16344 game_gadget[id] = gi;
16348 void FreeGameButtons(void)
16352 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16353 FreeGadget(game_gadget[i]);
16356 static void UnmapGameButtonsAtSamePosition(int id)
16360 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16362 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16363 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16364 UnmapGadget(game_gadget[i]);
16367 static void UnmapGameButtonsAtSamePosition_All(void)
16369 if (setup.show_load_save_buttons)
16371 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16372 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16373 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16375 else if (setup.show_undo_redo_buttons)
16377 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16378 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16379 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16383 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16384 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16385 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16387 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16388 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16389 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16393 void MapLoadSaveButtons(void)
16395 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16396 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16398 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16399 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16402 void MapUndoRedoButtons(void)
16404 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16405 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16407 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16408 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16411 void ModifyPauseButtons(void)
16415 GAME_CTRL_ID_PAUSE,
16416 GAME_CTRL_ID_PAUSE2,
16417 GAME_CTRL_ID_PANEL_PAUSE,
16418 GAME_CTRL_ID_TOUCH_PAUSE,
16423 for (i = 0; ids[i] > -1; i++)
16424 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16427 static void MapGameButtonsExt(boolean on_tape)
16431 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16433 if ((i == GAME_CTRL_ID_UNDO ||
16434 i == GAME_CTRL_ID_REDO) &&
16435 game_status != GAME_MODE_PLAYING)
16438 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16439 MapGadget(game_gadget[i]);
16442 UnmapGameButtonsAtSamePosition_All();
16444 RedrawGameButtons();
16447 static void UnmapGameButtonsExt(boolean on_tape)
16451 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16452 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16453 UnmapGadget(game_gadget[i]);
16456 static void RedrawGameButtonsExt(boolean on_tape)
16460 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16461 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16462 RedrawGadget(game_gadget[i]);
16465 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16470 gi->checked = state;
16473 static void RedrawSoundButtonGadget(int id)
16475 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16476 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16477 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16478 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16479 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16480 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16483 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16484 RedrawGadget(game_gadget[id2]);
16487 void MapGameButtons(void)
16489 MapGameButtonsExt(FALSE);
16492 void UnmapGameButtons(void)
16494 UnmapGameButtonsExt(FALSE);
16497 void RedrawGameButtons(void)
16499 RedrawGameButtonsExt(FALSE);
16502 void MapGameButtonsOnTape(void)
16504 MapGameButtonsExt(TRUE);
16507 void UnmapGameButtonsOnTape(void)
16509 UnmapGameButtonsExt(TRUE);
16512 void RedrawGameButtonsOnTape(void)
16514 RedrawGameButtonsExt(TRUE);
16517 static void GameUndoRedoExt(void)
16519 ClearPlayerAction();
16521 tape.pausing = TRUE;
16524 UpdateAndDisplayGameControlValues();
16526 DrawCompleteVideoDisplay();
16527 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16528 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16529 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16531 ModifyPauseButtons();
16536 static void GameUndo(int steps)
16538 if (!CheckEngineSnapshotList())
16541 int tape_property_bits = tape.property_bits;
16543 LoadEngineSnapshot_Undo(steps);
16545 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16550 static void GameRedo(int steps)
16552 if (!CheckEngineSnapshotList())
16555 int tape_property_bits = tape.property_bits;
16557 LoadEngineSnapshot_Redo(steps);
16559 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16564 static void HandleGameButtonsExt(int id, int button)
16566 static boolean game_undo_executed = FALSE;
16567 int steps = BUTTON_STEPSIZE(button);
16568 boolean handle_game_buttons =
16569 (game_status == GAME_MODE_PLAYING ||
16570 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16572 if (!handle_game_buttons)
16577 case GAME_CTRL_ID_STOP:
16578 case GAME_CTRL_ID_PANEL_STOP:
16579 case GAME_CTRL_ID_TOUCH_STOP:
16584 case GAME_CTRL_ID_PAUSE:
16585 case GAME_CTRL_ID_PAUSE2:
16586 case GAME_CTRL_ID_PANEL_PAUSE:
16587 case GAME_CTRL_ID_TOUCH_PAUSE:
16588 if (network.enabled && game_status == GAME_MODE_PLAYING)
16591 SendToServer_ContinuePlaying();
16593 SendToServer_PausePlaying();
16596 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16598 game_undo_executed = FALSE;
16602 case GAME_CTRL_ID_PLAY:
16603 case GAME_CTRL_ID_PANEL_PLAY:
16604 if (game_status == GAME_MODE_MAIN)
16606 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16608 else if (tape.pausing)
16610 if (network.enabled)
16611 SendToServer_ContinuePlaying();
16613 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16617 case GAME_CTRL_ID_UNDO:
16618 // Important: When using "save snapshot when collecting an item" mode,
16619 // load last (current) snapshot for first "undo" after pressing "pause"
16620 // (else the last-but-one snapshot would be loaded, because the snapshot
16621 // pointer already points to the last snapshot when pressing "pause",
16622 // which is fine for "every step/move" mode, but not for "every collect")
16623 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16624 !game_undo_executed)
16627 game_undo_executed = TRUE;
16632 case GAME_CTRL_ID_REDO:
16636 case GAME_CTRL_ID_SAVE:
16640 case GAME_CTRL_ID_LOAD:
16644 case SOUND_CTRL_ID_MUSIC:
16645 case SOUND_CTRL_ID_PANEL_MUSIC:
16646 if (setup.sound_music)
16648 setup.sound_music = FALSE;
16652 else if (audio.music_available)
16654 setup.sound = setup.sound_music = TRUE;
16656 SetAudioMode(setup.sound);
16658 if (game_status == GAME_MODE_PLAYING)
16662 RedrawSoundButtonGadget(id);
16666 case SOUND_CTRL_ID_LOOPS:
16667 case SOUND_CTRL_ID_PANEL_LOOPS:
16668 if (setup.sound_loops)
16669 setup.sound_loops = FALSE;
16670 else if (audio.loops_available)
16672 setup.sound = setup.sound_loops = TRUE;
16674 SetAudioMode(setup.sound);
16677 RedrawSoundButtonGadget(id);
16681 case SOUND_CTRL_ID_SIMPLE:
16682 case SOUND_CTRL_ID_PANEL_SIMPLE:
16683 if (setup.sound_simple)
16684 setup.sound_simple = FALSE;
16685 else if (audio.sound_available)
16687 setup.sound = setup.sound_simple = TRUE;
16689 SetAudioMode(setup.sound);
16692 RedrawSoundButtonGadget(id);
16701 static void HandleGameButtons(struct GadgetInfo *gi)
16703 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16706 void HandleSoundButtonKeys(Key key)
16708 if (key == setup.shortcut.sound_simple)
16709 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16710 else if (key == setup.shortcut.sound_loops)
16711 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16712 else if (key == setup.shortcut.sound_music)
16713 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);