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 && !score_info_tape_play)
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 || score_info_tape_play)
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
5154 // special case: new score is beyond the last high score list position
5155 return MAX_SCORE_ENTRIES;
5158 void NewHighScore(int level_nr, boolean tape_saved)
5160 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5161 boolean one_per_name = FALSE;
5163 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5164 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5166 new_entry.score = game.score_final;
5167 new_entry.time = game.score_time_final;
5169 LoadScore(level_nr);
5171 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5173 if (scores.last_added >= MAX_SCORE_ENTRIES)
5175 scores.last_added = MAX_SCORE_ENTRIES - 1;
5176 scores.force_last_added = TRUE;
5178 scores.entry[scores.last_added] = new_entry;
5180 // store last added local score entry (before merging server scores)
5181 scores.last_added_local = scores.last_added;
5186 if (scores.last_added < 0)
5189 SaveScore(level_nr);
5191 // store last added local score entry (before merging server scores)
5192 scores.last_added_local = scores.last_added;
5194 if (!game.LevelSolved_SaveTape)
5197 SaveScoreTape(level_nr);
5199 if (setup.ask_for_using_api_server)
5201 setup.use_api_server =
5202 Request("Upload your score and tape to the high score server?", REQ_ASK);
5204 if (!setup.use_api_server)
5205 Request("Not using high score server! Use setup menu to enable again!",
5208 runtime.use_api_server = setup.use_api_server;
5210 // after asking for using API server once, do not ask again
5211 setup.ask_for_using_api_server = FALSE;
5213 SaveSetup_ServerSetup();
5216 SaveServerScore(level_nr, tape_saved);
5219 void MergeServerScore(void)
5221 struct ScoreEntry last_added_entry;
5222 boolean one_per_name = FALSE;
5225 if (scores.last_added >= 0)
5226 last_added_entry = scores.entry[scores.last_added];
5228 for (i = 0; i < server_scores.num_entries; i++)
5230 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5232 if (pos >= 0 && pos <= scores.last_added)
5233 scores.last_added++;
5236 if (scores.last_added >= MAX_SCORE_ENTRIES)
5238 scores.last_added = MAX_SCORE_ENTRIES - 1;
5239 scores.force_last_added = TRUE;
5241 scores.entry[scores.last_added] = last_added_entry;
5245 static int getElementMoveStepsizeExt(int x, int y, int direction)
5247 int element = Tile[x][y];
5248 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5249 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5250 int horiz_move = (dx != 0);
5251 int sign = (horiz_move ? dx : dy);
5252 int step = sign * element_info[element].move_stepsize;
5254 // special values for move stepsize for spring and things on conveyor belt
5257 if (CAN_FALL(element) &&
5258 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5259 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5260 else if (element == EL_SPRING)
5261 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5267 static int getElementMoveStepsize(int x, int y)
5269 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5272 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5274 if (player->GfxAction != action || player->GfxDir != dir)
5276 player->GfxAction = action;
5277 player->GfxDir = dir;
5279 player->StepFrame = 0;
5283 static void ResetGfxFrame(int x, int y)
5285 // profiling showed that "autotest" spends 10~20% of its time in this function
5286 if (DrawingDeactivatedField())
5289 int element = Tile[x][y];
5290 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5292 if (graphic_info[graphic].anim_global_sync)
5293 GfxFrame[x][y] = FrameCounter;
5294 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5295 GfxFrame[x][y] = CustomValue[x][y];
5296 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5297 GfxFrame[x][y] = element_info[element].collect_score;
5298 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5299 GfxFrame[x][y] = ChangeDelay[x][y];
5302 static void ResetGfxAnimation(int x, int y)
5304 GfxAction[x][y] = ACTION_DEFAULT;
5305 GfxDir[x][y] = MovDir[x][y];
5308 ResetGfxFrame(x, y);
5311 static void ResetRandomAnimationValue(int x, int y)
5313 GfxRandom[x][y] = INIT_GFX_RANDOM();
5316 static void InitMovingField(int x, int y, int direction)
5318 int element = Tile[x][y];
5319 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5320 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5323 boolean is_moving_before, is_moving_after;
5325 // check if element was/is moving or being moved before/after mode change
5326 is_moving_before = (WasJustMoving[x][y] != 0);
5327 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5329 // reset animation only for moving elements which change direction of moving
5330 // or which just started or stopped moving
5331 // (else CEs with property "can move" / "not moving" are reset each frame)
5332 if (is_moving_before != is_moving_after ||
5333 direction != MovDir[x][y])
5334 ResetGfxAnimation(x, y);
5336 MovDir[x][y] = direction;
5337 GfxDir[x][y] = direction;
5339 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5340 direction == MV_DOWN && CAN_FALL(element) ?
5341 ACTION_FALLING : ACTION_MOVING);
5343 // this is needed for CEs with property "can move" / "not moving"
5345 if (is_moving_after)
5347 if (Tile[newx][newy] == EL_EMPTY)
5348 Tile[newx][newy] = EL_BLOCKED;
5350 MovDir[newx][newy] = MovDir[x][y];
5352 CustomValue[newx][newy] = CustomValue[x][y];
5354 GfxFrame[newx][newy] = GfxFrame[x][y];
5355 GfxRandom[newx][newy] = GfxRandom[x][y];
5356 GfxAction[newx][newy] = GfxAction[x][y];
5357 GfxDir[newx][newy] = GfxDir[x][y];
5361 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5363 int direction = MovDir[x][y];
5364 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5365 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5371 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5373 int oldx = x, oldy = y;
5374 int direction = MovDir[x][y];
5376 if (direction == MV_LEFT)
5378 else if (direction == MV_RIGHT)
5380 else if (direction == MV_UP)
5382 else if (direction == MV_DOWN)
5385 *comes_from_x = oldx;
5386 *comes_from_y = oldy;
5389 static int MovingOrBlocked2Element(int x, int y)
5391 int element = Tile[x][y];
5393 if (element == EL_BLOCKED)
5397 Blocked2Moving(x, y, &oldx, &oldy);
5398 return Tile[oldx][oldy];
5404 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5406 // like MovingOrBlocked2Element(), but if element is moving
5407 // and (x,y) is the field the moving element is just leaving,
5408 // return EL_BLOCKED instead of the element value
5409 int element = Tile[x][y];
5411 if (IS_MOVING(x, y))
5413 if (element == EL_BLOCKED)
5417 Blocked2Moving(x, y, &oldx, &oldy);
5418 return Tile[oldx][oldy];
5427 static void RemoveField(int x, int y)
5429 Tile[x][y] = EL_EMPTY;
5435 CustomValue[x][y] = 0;
5438 ChangeDelay[x][y] = 0;
5439 ChangePage[x][y] = -1;
5440 Pushed[x][y] = FALSE;
5442 GfxElement[x][y] = EL_UNDEFINED;
5443 GfxAction[x][y] = ACTION_DEFAULT;
5444 GfxDir[x][y] = MV_NONE;
5447 static void RemoveMovingField(int x, int y)
5449 int oldx = x, oldy = y, newx = x, newy = y;
5450 int element = Tile[x][y];
5451 int next_element = EL_UNDEFINED;
5453 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5456 if (IS_MOVING(x, y))
5458 Moving2Blocked(x, y, &newx, &newy);
5460 if (Tile[newx][newy] != EL_BLOCKED)
5462 // element is moving, but target field is not free (blocked), but
5463 // already occupied by something different (example: acid pool);
5464 // in this case, only remove the moving field, but not the target
5466 RemoveField(oldx, oldy);
5468 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5470 TEST_DrawLevelField(oldx, oldy);
5475 else if (element == EL_BLOCKED)
5477 Blocked2Moving(x, y, &oldx, &oldy);
5478 if (!IS_MOVING(oldx, oldy))
5482 if (element == EL_BLOCKED &&
5483 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5484 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5485 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5486 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5487 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5488 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5489 next_element = get_next_element(Tile[oldx][oldy]);
5491 RemoveField(oldx, oldy);
5492 RemoveField(newx, newy);
5494 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5496 if (next_element != EL_UNDEFINED)
5497 Tile[oldx][oldy] = next_element;
5499 TEST_DrawLevelField(oldx, oldy);
5500 TEST_DrawLevelField(newx, newy);
5503 void DrawDynamite(int x, int y)
5505 int sx = SCREENX(x), sy = SCREENY(y);
5506 int graphic = el2img(Tile[x][y]);
5509 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5512 if (IS_WALKABLE_INSIDE(Back[x][y]))
5516 DrawLevelElement(x, y, Back[x][y]);
5517 else if (Store[x][y])
5518 DrawLevelElement(x, y, Store[x][y]);
5519 else if (game.use_masked_elements)
5520 DrawLevelElement(x, y, EL_EMPTY);
5522 frame = getGraphicAnimationFrameXY(graphic, x, y);
5524 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5525 DrawGraphicThruMask(sx, sy, graphic, frame);
5527 DrawGraphic(sx, sy, graphic, frame);
5530 static void CheckDynamite(int x, int y)
5532 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5536 if (MovDelay[x][y] != 0)
5539 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5545 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5550 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5552 boolean num_checked_players = 0;
5555 for (i = 0; i < MAX_PLAYERS; i++)
5557 if (stored_player[i].active)
5559 int sx = stored_player[i].jx;
5560 int sy = stored_player[i].jy;
5562 if (num_checked_players == 0)
5569 *sx1 = MIN(*sx1, sx);
5570 *sy1 = MIN(*sy1, sy);
5571 *sx2 = MAX(*sx2, sx);
5572 *sy2 = MAX(*sy2, sy);
5575 num_checked_players++;
5580 static boolean checkIfAllPlayersFitToScreen_RND(void)
5582 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5584 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5586 return (sx2 - sx1 < SCR_FIELDX &&
5587 sy2 - sy1 < SCR_FIELDY);
5590 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5592 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5594 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5596 *sx = (sx1 + sx2) / 2;
5597 *sy = (sy1 + sy2) / 2;
5600 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5601 boolean center_screen, boolean quick_relocation)
5603 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5604 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5605 boolean no_delay = (tape.warp_forward);
5606 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5607 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5608 int new_scroll_x, new_scroll_y;
5610 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5612 // case 1: quick relocation inside visible screen (without scrolling)
5619 if (!level.shifted_relocation || center_screen)
5621 // relocation _with_ centering of screen
5623 new_scroll_x = SCROLL_POSITION_X(x);
5624 new_scroll_y = SCROLL_POSITION_Y(y);
5628 // relocation _without_ centering of screen
5630 int center_scroll_x = SCROLL_POSITION_X(old_x);
5631 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5632 int offset_x = x + (scroll_x - center_scroll_x);
5633 int offset_y = y + (scroll_y - center_scroll_y);
5635 // for new screen position, apply previous offset to center position
5636 new_scroll_x = SCROLL_POSITION_X(offset_x);
5637 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5640 if (quick_relocation)
5642 // case 2: quick relocation (redraw without visible scrolling)
5644 scroll_x = new_scroll_x;
5645 scroll_y = new_scroll_y;
5652 // case 3: visible relocation (with scrolling to new position)
5654 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5656 SetVideoFrameDelay(wait_delay_value);
5658 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5660 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5661 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5663 if (dx == 0 && dy == 0) // no scrolling needed at all
5669 // set values for horizontal/vertical screen scrolling (half tile size)
5670 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5671 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5672 int pos_x = dx * TILEX / 2;
5673 int pos_y = dy * TILEY / 2;
5674 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5675 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5677 ScrollLevel(dx, dy);
5680 // scroll in two steps of half tile size to make things smoother
5681 BlitScreenToBitmapExt_RND(window, fx, fy);
5683 // scroll second step to align at full tile size
5684 BlitScreenToBitmap(window);
5690 SetVideoFrameDelay(frame_delay_value_old);
5693 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5695 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5696 int player_nr = GET_PLAYER_NR(el_player);
5697 struct PlayerInfo *player = &stored_player[player_nr];
5698 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5699 boolean no_delay = (tape.warp_forward);
5700 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5701 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5702 int old_jx = player->jx;
5703 int old_jy = player->jy;
5704 int old_element = Tile[old_jx][old_jy];
5705 int element = Tile[jx][jy];
5706 boolean player_relocated = (old_jx != jx || old_jy != jy);
5708 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5709 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5710 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5711 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5712 int leave_side_horiz = move_dir_horiz;
5713 int leave_side_vert = move_dir_vert;
5714 int enter_side = enter_side_horiz | enter_side_vert;
5715 int leave_side = leave_side_horiz | leave_side_vert;
5717 if (player->buried) // do not reanimate dead player
5720 if (!player_relocated) // no need to relocate the player
5723 if (IS_PLAYER(jx, jy)) // player already placed at new position
5725 RemoveField(jx, jy); // temporarily remove newly placed player
5726 DrawLevelField(jx, jy);
5729 if (player->present)
5731 while (player->MovPos)
5733 ScrollPlayer(player, SCROLL_GO_ON);
5734 ScrollScreen(NULL, SCROLL_GO_ON);
5736 AdvanceFrameAndPlayerCounters(player->index_nr);
5740 BackToFront_WithFrameDelay(wait_delay_value);
5743 DrawPlayer(player); // needed here only to cleanup last field
5744 DrawLevelField(player->jx, player->jy); // remove player graphic
5746 player->is_moving = FALSE;
5749 if (IS_CUSTOM_ELEMENT(old_element))
5750 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5752 player->index_bit, leave_side);
5754 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5756 player->index_bit, leave_side);
5758 Tile[jx][jy] = el_player;
5759 InitPlayerField(jx, jy, el_player, TRUE);
5761 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5762 possible that the relocation target field did not contain a player element,
5763 but a walkable element, to which the new player was relocated -- in this
5764 case, restore that (already initialized!) element on the player field */
5765 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5767 Tile[jx][jy] = element; // restore previously existing element
5770 // only visually relocate centered player
5771 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5772 FALSE, level.instant_relocation);
5774 TestIfPlayerTouchesBadThing(jx, jy);
5775 TestIfPlayerTouchesCustomElement(jx, jy);
5777 if (IS_CUSTOM_ELEMENT(element))
5778 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5779 player->index_bit, enter_side);
5781 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5782 player->index_bit, enter_side);
5784 if (player->is_switching)
5786 /* ensure that relocation while still switching an element does not cause
5787 a new element to be treated as also switched directly after relocation
5788 (this is important for teleporter switches that teleport the player to
5789 a place where another teleporter switch is in the same direction, which
5790 would then incorrectly be treated as immediately switched before the
5791 direction key that caused the switch was released) */
5793 player->switch_x += jx - old_jx;
5794 player->switch_y += jy - old_jy;
5798 static void Explode(int ex, int ey, int phase, int mode)
5804 // !!! eliminate this variable !!!
5805 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5807 if (game.explosions_delayed)
5809 ExplodeField[ex][ey] = mode;
5813 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5815 int center_element = Tile[ex][ey];
5816 int artwork_element, explosion_element; // set these values later
5818 // remove things displayed in background while burning dynamite
5819 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5822 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5824 // put moving element to center field (and let it explode there)
5825 center_element = MovingOrBlocked2Element(ex, ey);
5826 RemoveMovingField(ex, ey);
5827 Tile[ex][ey] = center_element;
5830 // now "center_element" is finally determined -- set related values now
5831 artwork_element = center_element; // for custom player artwork
5832 explosion_element = center_element; // for custom player artwork
5834 if (IS_PLAYER(ex, ey))
5836 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5838 artwork_element = stored_player[player_nr].artwork_element;
5840 if (level.use_explosion_element[player_nr])
5842 explosion_element = level.explosion_element[player_nr];
5843 artwork_element = explosion_element;
5847 if (mode == EX_TYPE_NORMAL ||
5848 mode == EX_TYPE_CENTER ||
5849 mode == EX_TYPE_CROSS)
5850 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5852 last_phase = element_info[explosion_element].explosion_delay + 1;
5854 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5856 int xx = x - ex + 1;
5857 int yy = y - ey + 1;
5860 if (!IN_LEV_FIELD(x, y) ||
5861 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5862 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5865 element = Tile[x][y];
5867 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5869 element = MovingOrBlocked2Element(x, y);
5871 if (!IS_EXPLOSION_PROOF(element))
5872 RemoveMovingField(x, y);
5875 // indestructible elements can only explode in center (but not flames)
5876 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5877 mode == EX_TYPE_BORDER)) ||
5878 element == EL_FLAMES)
5881 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5882 behaviour, for example when touching a yamyam that explodes to rocks
5883 with active deadly shield, a rock is created under the player !!! */
5884 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5886 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5887 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5888 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5890 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5893 if (IS_ACTIVE_BOMB(element))
5895 // re-activate things under the bomb like gate or penguin
5896 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5903 // save walkable background elements while explosion on same tile
5904 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5905 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5906 Back[x][y] = element;
5908 // ignite explodable elements reached by other explosion
5909 if (element == EL_EXPLOSION)
5910 element = Store2[x][y];
5912 if (AmoebaNr[x][y] &&
5913 (element == EL_AMOEBA_FULL ||
5914 element == EL_BD_AMOEBA ||
5915 element == EL_AMOEBA_GROWING))
5917 AmoebaCnt[AmoebaNr[x][y]]--;
5918 AmoebaCnt2[AmoebaNr[x][y]]--;
5923 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5925 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5927 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5929 if (PLAYERINFO(ex, ey)->use_murphy)
5930 Store[x][y] = EL_EMPTY;
5933 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5934 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5935 else if (IS_PLAYER_ELEMENT(center_element))
5936 Store[x][y] = EL_EMPTY;
5937 else if (center_element == EL_YAMYAM)
5938 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5939 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5940 Store[x][y] = element_info[center_element].content.e[xx][yy];
5942 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5943 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5944 // otherwise) -- FIX THIS !!!
5945 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5946 Store[x][y] = element_info[element].content.e[1][1];
5948 else if (!CAN_EXPLODE(element))
5949 Store[x][y] = element_info[element].content.e[1][1];
5952 Store[x][y] = EL_EMPTY;
5954 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5955 center_element == EL_AMOEBA_TO_DIAMOND)
5956 Store2[x][y] = element;
5958 Tile[x][y] = EL_EXPLOSION;
5959 GfxElement[x][y] = artwork_element;
5961 ExplodePhase[x][y] = 1;
5962 ExplodeDelay[x][y] = last_phase;
5967 if (center_element == EL_YAMYAM)
5968 game.yamyam_content_nr =
5969 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5981 GfxFrame[x][y] = 0; // restart explosion animation
5983 last_phase = ExplodeDelay[x][y];
5985 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5987 // this can happen if the player leaves an explosion just in time
5988 if (GfxElement[x][y] == EL_UNDEFINED)
5989 GfxElement[x][y] = EL_EMPTY;
5991 border_element = Store2[x][y];
5992 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5993 border_element = StorePlayer[x][y];
5995 if (phase == element_info[border_element].ignition_delay ||
5996 phase == last_phase)
5998 boolean border_explosion = FALSE;
6000 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6001 !PLAYER_EXPLOSION_PROTECTED(x, y))
6003 KillPlayerUnlessExplosionProtected(x, y);
6004 border_explosion = TRUE;
6006 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6008 Tile[x][y] = Store2[x][y];
6011 border_explosion = TRUE;
6013 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6015 AmoebaToDiamond(x, y);
6017 border_explosion = TRUE;
6020 // if an element just explodes due to another explosion (chain-reaction),
6021 // do not immediately end the new explosion when it was the last frame of
6022 // the explosion (as it would be done in the following "if"-statement!)
6023 if (border_explosion && phase == last_phase)
6027 // this can happen if the player was just killed by an explosion
6028 if (GfxElement[x][y] == EL_UNDEFINED)
6029 GfxElement[x][y] = EL_EMPTY;
6031 if (phase == last_phase)
6035 element = Tile[x][y] = Store[x][y];
6036 Store[x][y] = Store2[x][y] = 0;
6037 GfxElement[x][y] = EL_UNDEFINED;
6039 // player can escape from explosions and might therefore be still alive
6040 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6041 element <= EL_PLAYER_IS_EXPLODING_4)
6043 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6044 int explosion_element = EL_PLAYER_1 + player_nr;
6045 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6046 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6048 if (level.use_explosion_element[player_nr])
6049 explosion_element = level.explosion_element[player_nr];
6051 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6052 element_info[explosion_element].content.e[xx][yy]);
6055 // restore probably existing indestructible background element
6056 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6057 element = Tile[x][y] = Back[x][y];
6060 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6061 GfxDir[x][y] = MV_NONE;
6062 ChangeDelay[x][y] = 0;
6063 ChangePage[x][y] = -1;
6065 CustomValue[x][y] = 0;
6067 InitField_WithBug2(x, y, FALSE);
6069 TEST_DrawLevelField(x, y);
6071 TestIfElementTouchesCustomElement(x, y);
6073 if (GFX_CRUMBLED(element))
6074 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6076 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6077 StorePlayer[x][y] = 0;
6079 if (IS_PLAYER_ELEMENT(element))
6080 RelocatePlayer(x, y, element);
6082 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6084 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6085 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6088 TEST_DrawLevelFieldCrumbled(x, y);
6090 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6092 DrawLevelElement(x, y, Back[x][y]);
6093 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6095 else if (IS_WALKABLE_UNDER(Back[x][y]))
6097 DrawLevelGraphic(x, y, graphic, frame);
6098 DrawLevelElementThruMask(x, y, Back[x][y]);
6100 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6101 DrawLevelGraphic(x, y, graphic, frame);
6105 static void DynaExplode(int ex, int ey)
6108 int dynabomb_element = Tile[ex][ey];
6109 int dynabomb_size = 1;
6110 boolean dynabomb_xl = FALSE;
6111 struct PlayerInfo *player;
6112 static int xy[4][2] =
6120 if (IS_ACTIVE_BOMB(dynabomb_element))
6122 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6123 dynabomb_size = player->dynabomb_size;
6124 dynabomb_xl = player->dynabomb_xl;
6125 player->dynabombs_left++;
6128 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6130 for (i = 0; i < NUM_DIRECTIONS; i++)
6132 for (j = 1; j <= dynabomb_size; j++)
6134 int x = ex + j * xy[i][0];
6135 int y = ey + j * xy[i][1];
6138 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6141 element = Tile[x][y];
6143 // do not restart explosions of fields with active bombs
6144 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6147 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6149 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6150 !IS_DIGGABLE(element) && !dynabomb_xl)
6156 void Bang(int x, int y)
6158 int element = MovingOrBlocked2Element(x, y);
6159 int explosion_type = EX_TYPE_NORMAL;
6161 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6163 struct PlayerInfo *player = PLAYERINFO(x, y);
6165 element = Tile[x][y] = player->initial_element;
6167 if (level.use_explosion_element[player->index_nr])
6169 int explosion_element = level.explosion_element[player->index_nr];
6171 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6172 explosion_type = EX_TYPE_CROSS;
6173 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6174 explosion_type = EX_TYPE_CENTER;
6182 case EL_BD_BUTTERFLY:
6185 case EL_DARK_YAMYAM:
6189 RaiseScoreElement(element);
6192 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6193 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6194 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6195 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6196 case EL_DYNABOMB_INCREASE_NUMBER:
6197 case EL_DYNABOMB_INCREASE_SIZE:
6198 case EL_DYNABOMB_INCREASE_POWER:
6199 explosion_type = EX_TYPE_DYNA;
6202 case EL_DC_LANDMINE:
6203 explosion_type = EX_TYPE_CENTER;
6208 case EL_LAMP_ACTIVE:
6209 case EL_AMOEBA_TO_DIAMOND:
6210 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6211 explosion_type = EX_TYPE_CENTER;
6215 if (element_info[element].explosion_type == EXPLODES_CROSS)
6216 explosion_type = EX_TYPE_CROSS;
6217 else if (element_info[element].explosion_type == EXPLODES_1X1)
6218 explosion_type = EX_TYPE_CENTER;
6222 if (explosion_type == EX_TYPE_DYNA)
6225 Explode(x, y, EX_PHASE_START, explosion_type);
6227 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6230 static void SplashAcid(int x, int y)
6232 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6233 (!IN_LEV_FIELD(x - 1, y - 2) ||
6234 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6235 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6237 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6238 (!IN_LEV_FIELD(x + 1, y - 2) ||
6239 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6240 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6242 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6245 static void InitBeltMovement(void)
6247 static int belt_base_element[4] =
6249 EL_CONVEYOR_BELT_1_LEFT,
6250 EL_CONVEYOR_BELT_2_LEFT,
6251 EL_CONVEYOR_BELT_3_LEFT,
6252 EL_CONVEYOR_BELT_4_LEFT
6254 static int belt_base_active_element[4] =
6256 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6257 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6258 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6259 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6264 // set frame order for belt animation graphic according to belt direction
6265 for (i = 0; i < NUM_BELTS; i++)
6269 for (j = 0; j < NUM_BELT_PARTS; j++)
6271 int element = belt_base_active_element[belt_nr] + j;
6272 int graphic_1 = el2img(element);
6273 int graphic_2 = el2panelimg(element);
6275 if (game.belt_dir[i] == MV_LEFT)
6277 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6278 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6282 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6283 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6288 SCAN_PLAYFIELD(x, y)
6290 int element = Tile[x][y];
6292 for (i = 0; i < NUM_BELTS; i++)
6294 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6296 int e_belt_nr = getBeltNrFromBeltElement(element);
6299 if (e_belt_nr == belt_nr)
6301 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6303 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6310 static void ToggleBeltSwitch(int x, int y)
6312 static int belt_base_element[4] =
6314 EL_CONVEYOR_BELT_1_LEFT,
6315 EL_CONVEYOR_BELT_2_LEFT,
6316 EL_CONVEYOR_BELT_3_LEFT,
6317 EL_CONVEYOR_BELT_4_LEFT
6319 static int belt_base_active_element[4] =
6321 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6322 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6323 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6324 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6326 static int belt_base_switch_element[4] =
6328 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6329 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6330 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6331 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6333 static int belt_move_dir[4] =
6341 int element = Tile[x][y];
6342 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6343 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6344 int belt_dir = belt_move_dir[belt_dir_nr];
6347 if (!IS_BELT_SWITCH(element))
6350 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6351 game.belt_dir[belt_nr] = belt_dir;
6353 if (belt_dir_nr == 3)
6356 // set frame order for belt animation graphic according to belt direction
6357 for (i = 0; i < NUM_BELT_PARTS; i++)
6359 int element = belt_base_active_element[belt_nr] + i;
6360 int graphic_1 = el2img(element);
6361 int graphic_2 = el2panelimg(element);
6363 if (belt_dir == MV_LEFT)
6365 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6366 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6370 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6371 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6375 SCAN_PLAYFIELD(xx, yy)
6377 int element = Tile[xx][yy];
6379 if (IS_BELT_SWITCH(element))
6381 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6383 if (e_belt_nr == belt_nr)
6385 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6386 TEST_DrawLevelField(xx, yy);
6389 else if (IS_BELT(element) && belt_dir != MV_NONE)
6391 int e_belt_nr = getBeltNrFromBeltElement(element);
6393 if (e_belt_nr == belt_nr)
6395 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6397 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6398 TEST_DrawLevelField(xx, yy);
6401 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6403 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6405 if (e_belt_nr == belt_nr)
6407 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6409 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6410 TEST_DrawLevelField(xx, yy);
6416 static void ToggleSwitchgateSwitch(int x, int y)
6420 game.switchgate_pos = !game.switchgate_pos;
6422 SCAN_PLAYFIELD(xx, yy)
6424 int element = Tile[xx][yy];
6426 if (element == EL_SWITCHGATE_SWITCH_UP)
6428 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6429 TEST_DrawLevelField(xx, yy);
6431 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6433 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6434 TEST_DrawLevelField(xx, yy);
6436 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6438 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6439 TEST_DrawLevelField(xx, yy);
6441 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6443 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6444 TEST_DrawLevelField(xx, yy);
6446 else if (element == EL_SWITCHGATE_OPEN ||
6447 element == EL_SWITCHGATE_OPENING)
6449 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6451 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6453 else if (element == EL_SWITCHGATE_CLOSED ||
6454 element == EL_SWITCHGATE_CLOSING)
6456 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6458 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6463 static int getInvisibleActiveFromInvisibleElement(int element)
6465 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6466 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6467 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6471 static int getInvisibleFromInvisibleActiveElement(int element)
6473 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6474 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6475 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6479 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6483 SCAN_PLAYFIELD(x, y)
6485 int element = Tile[x][y];
6487 if (element == EL_LIGHT_SWITCH &&
6488 game.light_time_left > 0)
6490 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6491 TEST_DrawLevelField(x, y);
6493 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6494 game.light_time_left == 0)
6496 Tile[x][y] = EL_LIGHT_SWITCH;
6497 TEST_DrawLevelField(x, y);
6499 else if (element == EL_EMC_DRIPPER &&
6500 game.light_time_left > 0)
6502 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6503 TEST_DrawLevelField(x, y);
6505 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6506 game.light_time_left == 0)
6508 Tile[x][y] = EL_EMC_DRIPPER;
6509 TEST_DrawLevelField(x, y);
6511 else if (element == EL_INVISIBLE_STEELWALL ||
6512 element == EL_INVISIBLE_WALL ||
6513 element == EL_INVISIBLE_SAND)
6515 if (game.light_time_left > 0)
6516 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6518 TEST_DrawLevelField(x, y);
6520 // uncrumble neighbour fields, if needed
6521 if (element == EL_INVISIBLE_SAND)
6522 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6524 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6525 element == EL_INVISIBLE_WALL_ACTIVE ||
6526 element == EL_INVISIBLE_SAND_ACTIVE)
6528 if (game.light_time_left == 0)
6529 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6531 TEST_DrawLevelField(x, y);
6533 // re-crumble neighbour fields, if needed
6534 if (element == EL_INVISIBLE_SAND)
6535 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6540 static void RedrawAllInvisibleElementsForLenses(void)
6544 SCAN_PLAYFIELD(x, y)
6546 int element = Tile[x][y];
6548 if (element == EL_EMC_DRIPPER &&
6549 game.lenses_time_left > 0)
6551 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6552 TEST_DrawLevelField(x, y);
6554 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6555 game.lenses_time_left == 0)
6557 Tile[x][y] = EL_EMC_DRIPPER;
6558 TEST_DrawLevelField(x, y);
6560 else if (element == EL_INVISIBLE_STEELWALL ||
6561 element == EL_INVISIBLE_WALL ||
6562 element == EL_INVISIBLE_SAND)
6564 if (game.lenses_time_left > 0)
6565 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6567 TEST_DrawLevelField(x, y);
6569 // uncrumble neighbour fields, if needed
6570 if (element == EL_INVISIBLE_SAND)
6571 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6573 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6574 element == EL_INVISIBLE_WALL_ACTIVE ||
6575 element == EL_INVISIBLE_SAND_ACTIVE)
6577 if (game.lenses_time_left == 0)
6578 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6580 TEST_DrawLevelField(x, y);
6582 // re-crumble neighbour fields, if needed
6583 if (element == EL_INVISIBLE_SAND)
6584 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6589 static void RedrawAllInvisibleElementsForMagnifier(void)
6593 SCAN_PLAYFIELD(x, y)
6595 int element = Tile[x][y];
6597 if (element == EL_EMC_FAKE_GRASS &&
6598 game.magnify_time_left > 0)
6600 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6601 TEST_DrawLevelField(x, y);
6603 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6604 game.magnify_time_left == 0)
6606 Tile[x][y] = EL_EMC_FAKE_GRASS;
6607 TEST_DrawLevelField(x, y);
6609 else if (IS_GATE_GRAY(element) &&
6610 game.magnify_time_left > 0)
6612 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6613 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6614 IS_EM_GATE_GRAY(element) ?
6615 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6616 IS_EMC_GATE_GRAY(element) ?
6617 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6618 IS_DC_GATE_GRAY(element) ?
6619 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6621 TEST_DrawLevelField(x, y);
6623 else if (IS_GATE_GRAY_ACTIVE(element) &&
6624 game.magnify_time_left == 0)
6626 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6627 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6628 IS_EM_GATE_GRAY_ACTIVE(element) ?
6629 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6630 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6631 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6632 IS_DC_GATE_GRAY_ACTIVE(element) ?
6633 EL_DC_GATE_WHITE_GRAY :
6635 TEST_DrawLevelField(x, y);
6640 static void ToggleLightSwitch(int x, int y)
6642 int element = Tile[x][y];
6644 game.light_time_left =
6645 (element == EL_LIGHT_SWITCH ?
6646 level.time_light * FRAMES_PER_SECOND : 0);
6648 RedrawAllLightSwitchesAndInvisibleElements();
6651 static void ActivateTimegateSwitch(int x, int y)
6655 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6657 SCAN_PLAYFIELD(xx, yy)
6659 int element = Tile[xx][yy];
6661 if (element == EL_TIMEGATE_CLOSED ||
6662 element == EL_TIMEGATE_CLOSING)
6664 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6665 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6669 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6671 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6672 TEST_DrawLevelField(xx, yy);
6678 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6679 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6682 static void Impact(int x, int y)
6684 boolean last_line = (y == lev_fieldy - 1);
6685 boolean object_hit = FALSE;
6686 boolean impact = (last_line || object_hit);
6687 int element = Tile[x][y];
6688 int smashed = EL_STEELWALL;
6690 if (!last_line) // check if element below was hit
6692 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6695 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6696 MovDir[x][y + 1] != MV_DOWN ||
6697 MovPos[x][y + 1] <= TILEY / 2));
6699 // do not smash moving elements that left the smashed field in time
6700 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6701 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6704 #if USE_QUICKSAND_IMPACT_BUGFIX
6705 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6707 RemoveMovingField(x, y + 1);
6708 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6709 Tile[x][y + 2] = EL_ROCK;
6710 TEST_DrawLevelField(x, y + 2);
6715 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6717 RemoveMovingField(x, y + 1);
6718 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6719 Tile[x][y + 2] = EL_ROCK;
6720 TEST_DrawLevelField(x, y + 2);
6727 smashed = MovingOrBlocked2Element(x, y + 1);
6729 impact = (last_line || object_hit);
6732 if (!last_line && smashed == EL_ACID) // element falls into acid
6734 SplashAcid(x, y + 1);
6738 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6739 // only reset graphic animation if graphic really changes after impact
6741 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6743 ResetGfxAnimation(x, y);
6744 TEST_DrawLevelField(x, y);
6747 if (impact && CAN_EXPLODE_IMPACT(element))
6752 else if (impact && element == EL_PEARL &&
6753 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6755 ResetGfxAnimation(x, y);
6757 Tile[x][y] = EL_PEARL_BREAKING;
6758 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6761 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6763 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6768 if (impact && element == EL_AMOEBA_DROP)
6770 if (object_hit && IS_PLAYER(x, y + 1))
6771 KillPlayerUnlessEnemyProtected(x, y + 1);
6772 else if (object_hit && smashed == EL_PENGUIN)
6776 Tile[x][y] = EL_AMOEBA_GROWING;
6777 Store[x][y] = EL_AMOEBA_WET;
6779 ResetRandomAnimationValue(x, y);
6784 if (object_hit) // check which object was hit
6786 if ((CAN_PASS_MAGIC_WALL(element) &&
6787 (smashed == EL_MAGIC_WALL ||
6788 smashed == EL_BD_MAGIC_WALL)) ||
6789 (CAN_PASS_DC_MAGIC_WALL(element) &&
6790 smashed == EL_DC_MAGIC_WALL))
6793 int activated_magic_wall =
6794 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6795 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6796 EL_DC_MAGIC_WALL_ACTIVE);
6798 // activate magic wall / mill
6799 SCAN_PLAYFIELD(xx, yy)
6801 if (Tile[xx][yy] == smashed)
6802 Tile[xx][yy] = activated_magic_wall;
6805 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6806 game.magic_wall_active = TRUE;
6808 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6809 SND_MAGIC_WALL_ACTIVATING :
6810 smashed == EL_BD_MAGIC_WALL ?
6811 SND_BD_MAGIC_WALL_ACTIVATING :
6812 SND_DC_MAGIC_WALL_ACTIVATING));
6815 if (IS_PLAYER(x, y + 1))
6817 if (CAN_SMASH_PLAYER(element))
6819 KillPlayerUnlessEnemyProtected(x, y + 1);
6823 else if (smashed == EL_PENGUIN)
6825 if (CAN_SMASH_PLAYER(element))
6831 else if (element == EL_BD_DIAMOND)
6833 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6839 else if (((element == EL_SP_INFOTRON ||
6840 element == EL_SP_ZONK) &&
6841 (smashed == EL_SP_SNIKSNAK ||
6842 smashed == EL_SP_ELECTRON ||
6843 smashed == EL_SP_DISK_ORANGE)) ||
6844 (element == EL_SP_INFOTRON &&
6845 smashed == EL_SP_DISK_YELLOW))
6850 else if (CAN_SMASH_EVERYTHING(element))
6852 if (IS_CLASSIC_ENEMY(smashed) ||
6853 CAN_EXPLODE_SMASHED(smashed))
6858 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6860 if (smashed == EL_LAMP ||
6861 smashed == EL_LAMP_ACTIVE)
6866 else if (smashed == EL_NUT)
6868 Tile[x][y + 1] = EL_NUT_BREAKING;
6869 PlayLevelSound(x, y, SND_NUT_BREAKING);
6870 RaiseScoreElement(EL_NUT);
6873 else if (smashed == EL_PEARL)
6875 ResetGfxAnimation(x, y);
6877 Tile[x][y + 1] = EL_PEARL_BREAKING;
6878 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6881 else if (smashed == EL_DIAMOND)
6883 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6884 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6887 else if (IS_BELT_SWITCH(smashed))
6889 ToggleBeltSwitch(x, y + 1);
6891 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6892 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6893 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6894 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6896 ToggleSwitchgateSwitch(x, y + 1);
6898 else if (smashed == EL_LIGHT_SWITCH ||
6899 smashed == EL_LIGHT_SWITCH_ACTIVE)
6901 ToggleLightSwitch(x, y + 1);
6905 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6907 CheckElementChangeBySide(x, y + 1, smashed, element,
6908 CE_SWITCHED, CH_SIDE_TOP);
6909 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6915 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6920 // play sound of magic wall / mill
6922 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6923 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6924 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6926 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6927 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6928 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6929 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6930 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6931 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6936 // play sound of object that hits the ground
6937 if (last_line || object_hit)
6938 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6941 static void TurnRoundExt(int x, int y)
6953 { 0, 0 }, { 0, 0 }, { 0, 0 },
6958 int left, right, back;
6962 { MV_DOWN, MV_UP, MV_RIGHT },
6963 { MV_UP, MV_DOWN, MV_LEFT },
6965 { MV_LEFT, MV_RIGHT, MV_DOWN },
6969 { MV_RIGHT, MV_LEFT, MV_UP }
6972 int element = Tile[x][y];
6973 int move_pattern = element_info[element].move_pattern;
6975 int old_move_dir = MovDir[x][y];
6976 int left_dir = turn[old_move_dir].left;
6977 int right_dir = turn[old_move_dir].right;
6978 int back_dir = turn[old_move_dir].back;
6980 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6981 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6982 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6983 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6985 int left_x = x + left_dx, left_y = y + left_dy;
6986 int right_x = x + right_dx, right_y = y + right_dy;
6987 int move_x = x + move_dx, move_y = y + move_dy;
6991 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6993 TestIfBadThingTouchesOtherBadThing(x, y);
6995 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6996 MovDir[x][y] = right_dir;
6997 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6998 MovDir[x][y] = left_dir;
7000 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7002 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7005 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7007 TestIfBadThingTouchesOtherBadThing(x, y);
7009 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7010 MovDir[x][y] = left_dir;
7011 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7012 MovDir[x][y] = right_dir;
7014 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7016 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7019 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7021 TestIfBadThingTouchesOtherBadThing(x, y);
7023 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7024 MovDir[x][y] = left_dir;
7025 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7026 MovDir[x][y] = right_dir;
7028 if (MovDir[x][y] != old_move_dir)
7031 else if (element == EL_YAMYAM)
7033 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7034 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7036 if (can_turn_left && can_turn_right)
7037 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7038 else if (can_turn_left)
7039 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7040 else if (can_turn_right)
7041 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7043 MovDir[x][y] = back_dir;
7045 MovDelay[x][y] = 16 + 16 * RND(3);
7047 else if (element == EL_DARK_YAMYAM)
7049 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7051 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7054 if (can_turn_left && can_turn_right)
7055 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7056 else if (can_turn_left)
7057 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7058 else if (can_turn_right)
7059 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7061 MovDir[x][y] = back_dir;
7063 MovDelay[x][y] = 16 + 16 * RND(3);
7065 else if (element == EL_PACMAN)
7067 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7068 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7070 if (can_turn_left && can_turn_right)
7071 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7072 else if (can_turn_left)
7073 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7074 else if (can_turn_right)
7075 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7077 MovDir[x][y] = back_dir;
7079 MovDelay[x][y] = 6 + RND(40);
7081 else if (element == EL_PIG)
7083 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7084 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7085 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7086 boolean should_turn_left, should_turn_right, should_move_on;
7088 int rnd = RND(rnd_value);
7090 should_turn_left = (can_turn_left &&
7092 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7093 y + back_dy + left_dy)));
7094 should_turn_right = (can_turn_right &&
7096 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7097 y + back_dy + right_dy)));
7098 should_move_on = (can_move_on &&
7101 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7102 y + move_dy + left_dy) ||
7103 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7104 y + move_dy + right_dy)));
7106 if (should_turn_left || should_turn_right || should_move_on)
7108 if (should_turn_left && should_turn_right && should_move_on)
7109 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7110 rnd < 2 * rnd_value / 3 ? right_dir :
7112 else if (should_turn_left && should_turn_right)
7113 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7114 else if (should_turn_left && should_move_on)
7115 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7116 else if (should_turn_right && should_move_on)
7117 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7118 else if (should_turn_left)
7119 MovDir[x][y] = left_dir;
7120 else if (should_turn_right)
7121 MovDir[x][y] = right_dir;
7122 else if (should_move_on)
7123 MovDir[x][y] = old_move_dir;
7125 else if (can_move_on && rnd > rnd_value / 8)
7126 MovDir[x][y] = old_move_dir;
7127 else if (can_turn_left && can_turn_right)
7128 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7129 else if (can_turn_left && rnd > rnd_value / 8)
7130 MovDir[x][y] = left_dir;
7131 else if (can_turn_right && rnd > rnd_value/8)
7132 MovDir[x][y] = right_dir;
7134 MovDir[x][y] = back_dir;
7136 xx = x + move_xy[MovDir[x][y]].dx;
7137 yy = y + move_xy[MovDir[x][y]].dy;
7139 if (!IN_LEV_FIELD(xx, yy) ||
7140 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7141 MovDir[x][y] = old_move_dir;
7145 else if (element == EL_DRAGON)
7147 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7148 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7149 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7151 int rnd = RND(rnd_value);
7153 if (can_move_on && rnd > rnd_value / 8)
7154 MovDir[x][y] = old_move_dir;
7155 else if (can_turn_left && can_turn_right)
7156 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7157 else if (can_turn_left && rnd > rnd_value / 8)
7158 MovDir[x][y] = left_dir;
7159 else if (can_turn_right && rnd > rnd_value / 8)
7160 MovDir[x][y] = right_dir;
7162 MovDir[x][y] = back_dir;
7164 xx = x + move_xy[MovDir[x][y]].dx;
7165 yy = y + move_xy[MovDir[x][y]].dy;
7167 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7168 MovDir[x][y] = old_move_dir;
7172 else if (element == EL_MOLE)
7174 boolean can_move_on =
7175 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7176 IS_AMOEBOID(Tile[move_x][move_y]) ||
7177 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7180 boolean can_turn_left =
7181 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7182 IS_AMOEBOID(Tile[left_x][left_y])));
7184 boolean can_turn_right =
7185 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7186 IS_AMOEBOID(Tile[right_x][right_y])));
7188 if (can_turn_left && can_turn_right)
7189 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7190 else if (can_turn_left)
7191 MovDir[x][y] = left_dir;
7193 MovDir[x][y] = right_dir;
7196 if (MovDir[x][y] != old_move_dir)
7199 else if (element == EL_BALLOON)
7201 MovDir[x][y] = game.wind_direction;
7204 else if (element == EL_SPRING)
7206 if (MovDir[x][y] & MV_HORIZONTAL)
7208 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7209 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7211 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7212 ResetGfxAnimation(move_x, move_y);
7213 TEST_DrawLevelField(move_x, move_y);
7215 MovDir[x][y] = back_dir;
7217 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7218 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7219 MovDir[x][y] = MV_NONE;
7224 else if (element == EL_ROBOT ||
7225 element == EL_SATELLITE ||
7226 element == EL_PENGUIN ||
7227 element == EL_EMC_ANDROID)
7229 int attr_x = -1, attr_y = -1;
7231 if (game.all_players_gone)
7233 attr_x = game.exit_x;
7234 attr_y = game.exit_y;
7240 for (i = 0; i < MAX_PLAYERS; i++)
7242 struct PlayerInfo *player = &stored_player[i];
7243 int jx = player->jx, jy = player->jy;
7245 if (!player->active)
7249 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7257 if (element == EL_ROBOT &&
7258 game.robot_wheel_x >= 0 &&
7259 game.robot_wheel_y >= 0 &&
7260 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7261 game.engine_version < VERSION_IDENT(3,1,0,0)))
7263 attr_x = game.robot_wheel_x;
7264 attr_y = game.robot_wheel_y;
7267 if (element == EL_PENGUIN)
7270 static int xy[4][2] =
7278 for (i = 0; i < NUM_DIRECTIONS; i++)
7280 int ex = x + xy[i][0];
7281 int ey = y + xy[i][1];
7283 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7284 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7285 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7286 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7295 MovDir[x][y] = MV_NONE;
7297 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7298 else if (attr_x > x)
7299 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7301 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7302 else if (attr_y > y)
7303 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7305 if (element == EL_ROBOT)
7309 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7310 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7311 Moving2Blocked(x, y, &newx, &newy);
7313 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7314 MovDelay[x][y] = 8 + 8 * !RND(3);
7316 MovDelay[x][y] = 16;
7318 else if (element == EL_PENGUIN)
7324 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7326 boolean first_horiz = RND(2);
7327 int new_move_dir = MovDir[x][y];
7330 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7331 Moving2Blocked(x, y, &newx, &newy);
7333 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7337 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7338 Moving2Blocked(x, y, &newx, &newy);
7340 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7343 MovDir[x][y] = old_move_dir;
7347 else if (element == EL_SATELLITE)
7353 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7355 boolean first_horiz = RND(2);
7356 int new_move_dir = MovDir[x][y];
7359 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7360 Moving2Blocked(x, y, &newx, &newy);
7362 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7366 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7367 Moving2Blocked(x, y, &newx, &newy);
7369 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7372 MovDir[x][y] = old_move_dir;
7376 else if (element == EL_EMC_ANDROID)
7378 static int check_pos[16] =
7380 -1, // 0 => (invalid)
7383 -1, // 3 => (invalid)
7385 0, // 5 => MV_LEFT | MV_UP
7386 2, // 6 => MV_RIGHT | MV_UP
7387 -1, // 7 => (invalid)
7389 6, // 9 => MV_LEFT | MV_DOWN
7390 4, // 10 => MV_RIGHT | MV_DOWN
7391 -1, // 11 => (invalid)
7392 -1, // 12 => (invalid)
7393 -1, // 13 => (invalid)
7394 -1, // 14 => (invalid)
7395 -1, // 15 => (invalid)
7403 { -1, -1, MV_LEFT | MV_UP },
7405 { +1, -1, MV_RIGHT | MV_UP },
7406 { +1, 0, MV_RIGHT },
7407 { +1, +1, MV_RIGHT | MV_DOWN },
7409 { -1, +1, MV_LEFT | MV_DOWN },
7412 int start_pos, check_order;
7413 boolean can_clone = FALSE;
7416 // check if there is any free field around current position
7417 for (i = 0; i < 8; i++)
7419 int newx = x + check_xy[i].dx;
7420 int newy = y + check_xy[i].dy;
7422 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7430 if (can_clone) // randomly find an element to clone
7434 start_pos = check_pos[RND(8)];
7435 check_order = (RND(2) ? -1 : +1);
7437 for (i = 0; i < 8; i++)
7439 int pos_raw = start_pos + i * check_order;
7440 int pos = (pos_raw + 8) % 8;
7441 int newx = x + check_xy[pos].dx;
7442 int newy = y + check_xy[pos].dy;
7444 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7446 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7447 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7449 Store[x][y] = Tile[newx][newy];
7458 if (can_clone) // randomly find a direction to move
7462 start_pos = check_pos[RND(8)];
7463 check_order = (RND(2) ? -1 : +1);
7465 for (i = 0; i < 8; i++)
7467 int pos_raw = start_pos + i * check_order;
7468 int pos = (pos_raw + 8) % 8;
7469 int newx = x + check_xy[pos].dx;
7470 int newy = y + check_xy[pos].dy;
7471 int new_move_dir = check_xy[pos].dir;
7473 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7475 MovDir[x][y] = new_move_dir;
7476 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7485 if (can_clone) // cloning and moving successful
7488 // cannot clone -- try to move towards player
7490 start_pos = check_pos[MovDir[x][y] & 0x0f];
7491 check_order = (RND(2) ? -1 : +1);
7493 for (i = 0; i < 3; i++)
7495 // first check start_pos, then previous/next or (next/previous) pos
7496 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7497 int pos = (pos_raw + 8) % 8;
7498 int newx = x + check_xy[pos].dx;
7499 int newy = y + check_xy[pos].dy;
7500 int new_move_dir = check_xy[pos].dir;
7502 if (IS_PLAYER(newx, newy))
7505 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7507 MovDir[x][y] = new_move_dir;
7508 MovDelay[x][y] = level.android_move_time * 8 + 1;
7515 else if (move_pattern == MV_TURNING_LEFT ||
7516 move_pattern == MV_TURNING_RIGHT ||
7517 move_pattern == MV_TURNING_LEFT_RIGHT ||
7518 move_pattern == MV_TURNING_RIGHT_LEFT ||
7519 move_pattern == MV_TURNING_RANDOM ||
7520 move_pattern == MV_ALL_DIRECTIONS)
7522 boolean can_turn_left =
7523 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7524 boolean can_turn_right =
7525 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7527 if (element_info[element].move_stepsize == 0) // "not moving"
7530 if (move_pattern == MV_TURNING_LEFT)
7531 MovDir[x][y] = left_dir;
7532 else if (move_pattern == MV_TURNING_RIGHT)
7533 MovDir[x][y] = right_dir;
7534 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7535 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7536 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7537 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7538 else if (move_pattern == MV_TURNING_RANDOM)
7539 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7540 can_turn_right && !can_turn_left ? right_dir :
7541 RND(2) ? left_dir : right_dir);
7542 else if (can_turn_left && can_turn_right)
7543 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7544 else if (can_turn_left)
7545 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7546 else if (can_turn_right)
7547 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7549 MovDir[x][y] = back_dir;
7551 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7553 else if (move_pattern == MV_HORIZONTAL ||
7554 move_pattern == MV_VERTICAL)
7556 if (move_pattern & old_move_dir)
7557 MovDir[x][y] = back_dir;
7558 else if (move_pattern == MV_HORIZONTAL)
7559 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7560 else if (move_pattern == MV_VERTICAL)
7561 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7563 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7565 else if (move_pattern & MV_ANY_DIRECTION)
7567 MovDir[x][y] = move_pattern;
7568 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7570 else if (move_pattern & MV_WIND_DIRECTION)
7572 MovDir[x][y] = game.wind_direction;
7573 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7575 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7577 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7578 MovDir[x][y] = left_dir;
7579 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7580 MovDir[x][y] = right_dir;
7582 if (MovDir[x][y] != old_move_dir)
7583 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7585 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7587 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7588 MovDir[x][y] = right_dir;
7589 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7590 MovDir[x][y] = left_dir;
7592 if (MovDir[x][y] != old_move_dir)
7593 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7595 else if (move_pattern == MV_TOWARDS_PLAYER ||
7596 move_pattern == MV_AWAY_FROM_PLAYER)
7598 int attr_x = -1, attr_y = -1;
7600 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7602 if (game.all_players_gone)
7604 attr_x = game.exit_x;
7605 attr_y = game.exit_y;
7611 for (i = 0; i < MAX_PLAYERS; i++)
7613 struct PlayerInfo *player = &stored_player[i];
7614 int jx = player->jx, jy = player->jy;
7616 if (!player->active)
7620 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7628 MovDir[x][y] = MV_NONE;
7630 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7631 else if (attr_x > x)
7632 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7634 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7635 else if (attr_y > y)
7636 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7638 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7640 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7642 boolean first_horiz = RND(2);
7643 int new_move_dir = MovDir[x][y];
7645 if (element_info[element].move_stepsize == 0) // "not moving"
7647 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7648 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7654 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7655 Moving2Blocked(x, y, &newx, &newy);
7657 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7661 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7662 Moving2Blocked(x, y, &newx, &newy);
7664 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7667 MovDir[x][y] = old_move_dir;
7670 else if (move_pattern == MV_WHEN_PUSHED ||
7671 move_pattern == MV_WHEN_DROPPED)
7673 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7674 MovDir[x][y] = MV_NONE;
7678 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7680 static int test_xy[7][2] =
7690 static int test_dir[7] =
7700 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7701 int move_preference = -1000000; // start with very low preference
7702 int new_move_dir = MV_NONE;
7703 int start_test = RND(4);
7706 for (i = 0; i < NUM_DIRECTIONS; i++)
7708 int move_dir = test_dir[start_test + i];
7709 int move_dir_preference;
7711 xx = x + test_xy[start_test + i][0];
7712 yy = y + test_xy[start_test + i][1];
7714 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7715 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7717 new_move_dir = move_dir;
7722 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7725 move_dir_preference = -1 * RunnerVisit[xx][yy];
7726 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7727 move_dir_preference = PlayerVisit[xx][yy];
7729 if (move_dir_preference > move_preference)
7731 // prefer field that has not been visited for the longest time
7732 move_preference = move_dir_preference;
7733 new_move_dir = move_dir;
7735 else if (move_dir_preference == move_preference &&
7736 move_dir == old_move_dir)
7738 // prefer last direction when all directions are preferred equally
7739 move_preference = move_dir_preference;
7740 new_move_dir = move_dir;
7744 MovDir[x][y] = new_move_dir;
7745 if (old_move_dir != new_move_dir)
7746 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7750 static void TurnRound(int x, int y)
7752 int direction = MovDir[x][y];
7756 GfxDir[x][y] = MovDir[x][y];
7758 if (direction != MovDir[x][y])
7762 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7764 ResetGfxFrame(x, y);
7767 static boolean JustBeingPushed(int x, int y)
7771 for (i = 0; i < MAX_PLAYERS; i++)
7773 struct PlayerInfo *player = &stored_player[i];
7775 if (player->active && player->is_pushing && player->MovPos)
7777 int next_jx = player->jx + (player->jx - player->last_jx);
7778 int next_jy = player->jy + (player->jy - player->last_jy);
7780 if (x == next_jx && y == next_jy)
7788 static void StartMoving(int x, int y)
7790 boolean started_moving = FALSE; // some elements can fall _and_ move
7791 int element = Tile[x][y];
7796 if (MovDelay[x][y] == 0)
7797 GfxAction[x][y] = ACTION_DEFAULT;
7799 if (CAN_FALL(element) && y < lev_fieldy - 1)
7801 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7802 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7803 if (JustBeingPushed(x, y))
7806 if (element == EL_QUICKSAND_FULL)
7808 if (IS_FREE(x, y + 1))
7810 InitMovingField(x, y, MV_DOWN);
7811 started_moving = TRUE;
7813 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7814 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7815 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7816 Store[x][y] = EL_ROCK;
7818 Store[x][y] = EL_ROCK;
7821 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7823 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7825 if (!MovDelay[x][y])
7827 MovDelay[x][y] = TILEY + 1;
7829 ResetGfxAnimation(x, y);
7830 ResetGfxAnimation(x, y + 1);
7835 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7836 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7843 Tile[x][y] = EL_QUICKSAND_EMPTY;
7844 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7845 Store[x][y + 1] = Store[x][y];
7848 PlayLevelSoundAction(x, y, ACTION_FILLING);
7850 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7852 if (!MovDelay[x][y])
7854 MovDelay[x][y] = TILEY + 1;
7856 ResetGfxAnimation(x, y);
7857 ResetGfxAnimation(x, y + 1);
7862 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7863 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7870 Tile[x][y] = EL_QUICKSAND_EMPTY;
7871 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7872 Store[x][y + 1] = Store[x][y];
7875 PlayLevelSoundAction(x, y, ACTION_FILLING);
7878 else if (element == EL_QUICKSAND_FAST_FULL)
7880 if (IS_FREE(x, y + 1))
7882 InitMovingField(x, y, MV_DOWN);
7883 started_moving = TRUE;
7885 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7886 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7887 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7888 Store[x][y] = EL_ROCK;
7890 Store[x][y] = EL_ROCK;
7893 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7895 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7897 if (!MovDelay[x][y])
7899 MovDelay[x][y] = TILEY + 1;
7901 ResetGfxAnimation(x, y);
7902 ResetGfxAnimation(x, y + 1);
7907 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7908 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7915 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7916 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7917 Store[x][y + 1] = Store[x][y];
7920 PlayLevelSoundAction(x, y, ACTION_FILLING);
7922 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7924 if (!MovDelay[x][y])
7926 MovDelay[x][y] = TILEY + 1;
7928 ResetGfxAnimation(x, y);
7929 ResetGfxAnimation(x, y + 1);
7934 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7935 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7942 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7943 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7944 Store[x][y + 1] = Store[x][y];
7947 PlayLevelSoundAction(x, y, ACTION_FILLING);
7950 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7951 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7953 InitMovingField(x, y, MV_DOWN);
7954 started_moving = TRUE;
7956 Tile[x][y] = EL_QUICKSAND_FILLING;
7957 Store[x][y] = element;
7959 PlayLevelSoundAction(x, y, ACTION_FILLING);
7961 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7962 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7964 InitMovingField(x, y, MV_DOWN);
7965 started_moving = TRUE;
7967 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7968 Store[x][y] = element;
7970 PlayLevelSoundAction(x, y, ACTION_FILLING);
7972 else if (element == EL_MAGIC_WALL_FULL)
7974 if (IS_FREE(x, y + 1))
7976 InitMovingField(x, y, MV_DOWN);
7977 started_moving = TRUE;
7979 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7980 Store[x][y] = EL_CHANGED(Store[x][y]);
7982 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7984 if (!MovDelay[x][y])
7985 MovDelay[x][y] = TILEY / 4 + 1;
7994 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7995 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7996 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8000 else if (element == EL_BD_MAGIC_WALL_FULL)
8002 if (IS_FREE(x, y + 1))
8004 InitMovingField(x, y, MV_DOWN);
8005 started_moving = TRUE;
8007 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8008 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8010 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8012 if (!MovDelay[x][y])
8013 MovDelay[x][y] = TILEY / 4 + 1;
8022 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8023 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8024 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8028 else if (element == EL_DC_MAGIC_WALL_FULL)
8030 if (IS_FREE(x, y + 1))
8032 InitMovingField(x, y, MV_DOWN);
8033 started_moving = TRUE;
8035 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8036 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8038 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8040 if (!MovDelay[x][y])
8041 MovDelay[x][y] = TILEY / 4 + 1;
8050 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8051 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8052 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8056 else if ((CAN_PASS_MAGIC_WALL(element) &&
8057 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8058 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8059 (CAN_PASS_DC_MAGIC_WALL(element) &&
8060 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8063 InitMovingField(x, y, MV_DOWN);
8064 started_moving = TRUE;
8067 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8068 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8069 EL_DC_MAGIC_WALL_FILLING);
8070 Store[x][y] = element;
8072 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8074 SplashAcid(x, y + 1);
8076 InitMovingField(x, y, MV_DOWN);
8077 started_moving = TRUE;
8079 Store[x][y] = EL_ACID;
8082 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8083 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8084 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8085 CAN_FALL(element) && WasJustFalling[x][y] &&
8086 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8088 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8089 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8090 (Tile[x][y + 1] == EL_BLOCKED)))
8092 /* this is needed for a special case not covered by calling "Impact()"
8093 from "ContinueMoving()": if an element moves to a tile directly below
8094 another element which was just falling on that tile (which was empty
8095 in the previous frame), the falling element above would just stop
8096 instead of smashing the element below (in previous version, the above
8097 element was just checked for "moving" instead of "falling", resulting
8098 in incorrect smashes caused by horizontal movement of the above
8099 element; also, the case of the player being the element to smash was
8100 simply not covered here... :-/ ) */
8102 CheckCollision[x][y] = 0;
8103 CheckImpact[x][y] = 0;
8107 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8109 if (MovDir[x][y] == MV_NONE)
8111 InitMovingField(x, y, MV_DOWN);
8112 started_moving = TRUE;
8115 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8117 if (WasJustFalling[x][y]) // prevent animation from being restarted
8118 MovDir[x][y] = MV_DOWN;
8120 InitMovingField(x, y, MV_DOWN);
8121 started_moving = TRUE;
8123 else if (element == EL_AMOEBA_DROP)
8125 Tile[x][y] = EL_AMOEBA_GROWING;
8126 Store[x][y] = EL_AMOEBA_WET;
8128 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8129 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8130 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8131 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8133 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8134 (IS_FREE(x - 1, y + 1) ||
8135 Tile[x - 1][y + 1] == EL_ACID));
8136 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8137 (IS_FREE(x + 1, y + 1) ||
8138 Tile[x + 1][y + 1] == EL_ACID));
8139 boolean can_fall_any = (can_fall_left || can_fall_right);
8140 boolean can_fall_both = (can_fall_left && can_fall_right);
8141 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8143 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8145 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8146 can_fall_right = FALSE;
8147 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8148 can_fall_left = FALSE;
8149 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8150 can_fall_right = FALSE;
8151 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8152 can_fall_left = FALSE;
8154 can_fall_any = (can_fall_left || can_fall_right);
8155 can_fall_both = FALSE;
8160 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8161 can_fall_right = FALSE; // slip down on left side
8163 can_fall_left = !(can_fall_right = RND(2));
8165 can_fall_both = FALSE;
8170 // if not determined otherwise, prefer left side for slipping down
8171 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8172 started_moving = TRUE;
8175 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8177 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8178 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8179 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8180 int belt_dir = game.belt_dir[belt_nr];
8182 if ((belt_dir == MV_LEFT && left_is_free) ||
8183 (belt_dir == MV_RIGHT && right_is_free))
8185 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8187 InitMovingField(x, y, belt_dir);
8188 started_moving = TRUE;
8190 Pushed[x][y] = TRUE;
8191 Pushed[nextx][y] = TRUE;
8193 GfxAction[x][y] = ACTION_DEFAULT;
8197 MovDir[x][y] = 0; // if element was moving, stop it
8202 // not "else if" because of elements that can fall and move (EL_SPRING)
8203 if (CAN_MOVE(element) && !started_moving)
8205 int move_pattern = element_info[element].move_pattern;
8208 Moving2Blocked(x, y, &newx, &newy);
8210 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8213 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8214 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8216 WasJustMoving[x][y] = 0;
8217 CheckCollision[x][y] = 0;
8219 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8221 if (Tile[x][y] != element) // element has changed
8225 if (!MovDelay[x][y]) // start new movement phase
8227 // all objects that can change their move direction after each step
8228 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8230 if (element != EL_YAMYAM &&
8231 element != EL_DARK_YAMYAM &&
8232 element != EL_PACMAN &&
8233 !(move_pattern & MV_ANY_DIRECTION) &&
8234 move_pattern != MV_TURNING_LEFT &&
8235 move_pattern != MV_TURNING_RIGHT &&
8236 move_pattern != MV_TURNING_LEFT_RIGHT &&
8237 move_pattern != MV_TURNING_RIGHT_LEFT &&
8238 move_pattern != MV_TURNING_RANDOM)
8242 if (MovDelay[x][y] && (element == EL_BUG ||
8243 element == EL_SPACESHIP ||
8244 element == EL_SP_SNIKSNAK ||
8245 element == EL_SP_ELECTRON ||
8246 element == EL_MOLE))
8247 TEST_DrawLevelField(x, y);
8251 if (MovDelay[x][y]) // wait some time before next movement
8255 if (element == EL_ROBOT ||
8256 element == EL_YAMYAM ||
8257 element == EL_DARK_YAMYAM)
8259 DrawLevelElementAnimationIfNeeded(x, y, element);
8260 PlayLevelSoundAction(x, y, ACTION_WAITING);
8262 else if (element == EL_SP_ELECTRON)
8263 DrawLevelElementAnimationIfNeeded(x, y, element);
8264 else if (element == EL_DRAGON)
8267 int dir = MovDir[x][y];
8268 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8269 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8270 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8271 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8272 dir == MV_UP ? IMG_FLAMES_1_UP :
8273 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8274 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8276 GfxAction[x][y] = ACTION_ATTACKING;
8278 if (IS_PLAYER(x, y))
8279 DrawPlayerField(x, y);
8281 TEST_DrawLevelField(x, y);
8283 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8285 for (i = 1; i <= 3; i++)
8287 int xx = x + i * dx;
8288 int yy = y + i * dy;
8289 int sx = SCREENX(xx);
8290 int sy = SCREENY(yy);
8291 int flame_graphic = graphic + (i - 1);
8293 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8298 int flamed = MovingOrBlocked2Element(xx, yy);
8300 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8303 RemoveMovingField(xx, yy);
8305 ChangeDelay[xx][yy] = 0;
8307 Tile[xx][yy] = EL_FLAMES;
8309 if (IN_SCR_FIELD(sx, sy))
8311 TEST_DrawLevelFieldCrumbled(xx, yy);
8312 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8317 if (Tile[xx][yy] == EL_FLAMES)
8318 Tile[xx][yy] = EL_EMPTY;
8319 TEST_DrawLevelField(xx, yy);
8324 if (MovDelay[x][y]) // element still has to wait some time
8326 PlayLevelSoundAction(x, y, ACTION_WAITING);
8332 // now make next step
8334 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8336 if (DONT_COLLIDE_WITH(element) &&
8337 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8338 !PLAYER_ENEMY_PROTECTED(newx, newy))
8340 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8345 else if (CAN_MOVE_INTO_ACID(element) &&
8346 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8347 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8348 (MovDir[x][y] == MV_DOWN ||
8349 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8351 SplashAcid(newx, newy);
8352 Store[x][y] = EL_ACID;
8354 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8356 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8357 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8358 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8359 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8362 TEST_DrawLevelField(x, y);
8364 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8365 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8366 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8368 game.friends_still_needed--;
8369 if (!game.friends_still_needed &&
8371 game.all_players_gone)
8376 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8378 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8379 TEST_DrawLevelField(newx, newy);
8381 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8383 else if (!IS_FREE(newx, newy))
8385 GfxAction[x][y] = ACTION_WAITING;
8387 if (IS_PLAYER(x, y))
8388 DrawPlayerField(x, y);
8390 TEST_DrawLevelField(x, y);
8395 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8397 if (IS_FOOD_PIG(Tile[newx][newy]))
8399 if (IS_MOVING(newx, newy))
8400 RemoveMovingField(newx, newy);
8403 Tile[newx][newy] = EL_EMPTY;
8404 TEST_DrawLevelField(newx, newy);
8407 PlayLevelSound(x, y, SND_PIG_DIGGING);
8409 else if (!IS_FREE(newx, newy))
8411 if (IS_PLAYER(x, y))
8412 DrawPlayerField(x, y);
8414 TEST_DrawLevelField(x, y);
8419 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8421 if (Store[x][y] != EL_EMPTY)
8423 boolean can_clone = FALSE;
8426 // check if element to clone is still there
8427 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8429 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8437 // cannot clone or target field not free anymore -- do not clone
8438 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8439 Store[x][y] = EL_EMPTY;
8442 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8444 if (IS_MV_DIAGONAL(MovDir[x][y]))
8446 int diagonal_move_dir = MovDir[x][y];
8447 int stored = Store[x][y];
8448 int change_delay = 8;
8451 // android is moving diagonally
8453 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8455 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8456 GfxElement[x][y] = EL_EMC_ANDROID;
8457 GfxAction[x][y] = ACTION_SHRINKING;
8458 GfxDir[x][y] = diagonal_move_dir;
8459 ChangeDelay[x][y] = change_delay;
8461 if (Store[x][y] == EL_EMPTY)
8462 Store[x][y] = GfxElementEmpty[x][y];
8464 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8467 DrawLevelGraphicAnimation(x, y, graphic);
8468 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8470 if (Tile[newx][newy] == EL_ACID)
8472 SplashAcid(newx, newy);
8477 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8479 Store[newx][newy] = EL_EMC_ANDROID;
8480 GfxElement[newx][newy] = EL_EMC_ANDROID;
8481 GfxAction[newx][newy] = ACTION_GROWING;
8482 GfxDir[newx][newy] = diagonal_move_dir;
8483 ChangeDelay[newx][newy] = change_delay;
8485 graphic = el_act_dir2img(GfxElement[newx][newy],
8486 GfxAction[newx][newy], GfxDir[newx][newy]);
8488 DrawLevelGraphicAnimation(newx, newy, graphic);
8489 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8495 Tile[newx][newy] = EL_EMPTY;
8496 TEST_DrawLevelField(newx, newy);
8498 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8501 else if (!IS_FREE(newx, newy))
8506 else if (IS_CUSTOM_ELEMENT(element) &&
8507 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8509 if (!DigFieldByCE(newx, newy, element))
8512 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8514 RunnerVisit[x][y] = FrameCounter;
8515 PlayerVisit[x][y] /= 8; // expire player visit path
8518 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8520 if (!IS_FREE(newx, newy))
8522 if (IS_PLAYER(x, y))
8523 DrawPlayerField(x, y);
8525 TEST_DrawLevelField(x, y);
8531 boolean wanna_flame = !RND(10);
8532 int dx = newx - x, dy = newy - y;
8533 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8534 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8535 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8536 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8537 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8538 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8541 IS_CLASSIC_ENEMY(element1) ||
8542 IS_CLASSIC_ENEMY(element2)) &&
8543 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8544 element1 != EL_FLAMES && element2 != EL_FLAMES)
8546 ResetGfxAnimation(x, y);
8547 GfxAction[x][y] = ACTION_ATTACKING;
8549 if (IS_PLAYER(x, y))
8550 DrawPlayerField(x, y);
8552 TEST_DrawLevelField(x, y);
8554 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8556 MovDelay[x][y] = 50;
8558 Tile[newx][newy] = EL_FLAMES;
8559 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8560 Tile[newx1][newy1] = EL_FLAMES;
8561 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8562 Tile[newx2][newy2] = EL_FLAMES;
8568 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8569 Tile[newx][newy] == EL_DIAMOND)
8571 if (IS_MOVING(newx, newy))
8572 RemoveMovingField(newx, newy);
8575 Tile[newx][newy] = EL_EMPTY;
8576 TEST_DrawLevelField(newx, newy);
8579 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8581 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8582 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8584 if (AmoebaNr[newx][newy])
8586 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8587 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8588 Tile[newx][newy] == EL_BD_AMOEBA)
8589 AmoebaCnt[AmoebaNr[newx][newy]]--;
8592 if (IS_MOVING(newx, newy))
8594 RemoveMovingField(newx, newy);
8598 Tile[newx][newy] = EL_EMPTY;
8599 TEST_DrawLevelField(newx, newy);
8602 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8604 else if ((element == EL_PACMAN || element == EL_MOLE)
8605 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8607 if (AmoebaNr[newx][newy])
8609 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8610 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8611 Tile[newx][newy] == EL_BD_AMOEBA)
8612 AmoebaCnt[AmoebaNr[newx][newy]]--;
8615 if (element == EL_MOLE)
8617 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8618 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8620 ResetGfxAnimation(x, y);
8621 GfxAction[x][y] = ACTION_DIGGING;
8622 TEST_DrawLevelField(x, y);
8624 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8626 return; // wait for shrinking amoeba
8628 else // element == EL_PACMAN
8630 Tile[newx][newy] = EL_EMPTY;
8631 TEST_DrawLevelField(newx, newy);
8632 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8635 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8636 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8637 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8639 // wait for shrinking amoeba to completely disappear
8642 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8644 // object was running against a wall
8648 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8649 DrawLevelElementAnimation(x, y, element);
8651 if (DONT_TOUCH(element))
8652 TestIfBadThingTouchesPlayer(x, y);
8657 InitMovingField(x, y, MovDir[x][y]);
8659 PlayLevelSoundAction(x, y, ACTION_MOVING);
8663 ContinueMoving(x, y);
8666 void ContinueMoving(int x, int y)
8668 int element = Tile[x][y];
8669 struct ElementInfo *ei = &element_info[element];
8670 int direction = MovDir[x][y];
8671 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8672 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8673 int newx = x + dx, newy = y + dy;
8674 int stored = Store[x][y];
8675 int stored_new = Store[newx][newy];
8676 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8677 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8678 boolean last_line = (newy == lev_fieldy - 1);
8679 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8681 if (pushed_by_player) // special case: moving object pushed by player
8683 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8685 else if (use_step_delay) // special case: moving object has step delay
8687 if (!MovDelay[x][y])
8688 MovPos[x][y] += getElementMoveStepsize(x, y);
8693 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8697 TEST_DrawLevelField(x, y);
8699 return; // element is still waiting
8702 else // normal case: generically moving object
8704 MovPos[x][y] += getElementMoveStepsize(x, y);
8707 if (ABS(MovPos[x][y]) < TILEX)
8709 TEST_DrawLevelField(x, y);
8711 return; // element is still moving
8714 // element reached destination field
8716 Tile[x][y] = EL_EMPTY;
8717 Tile[newx][newy] = element;
8718 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8720 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8722 element = Tile[newx][newy] = EL_ACID;
8724 else if (element == EL_MOLE)
8726 Tile[x][y] = EL_SAND;
8728 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8730 else if (element == EL_QUICKSAND_FILLING)
8732 element = Tile[newx][newy] = get_next_element(element);
8733 Store[newx][newy] = Store[x][y];
8735 else if (element == EL_QUICKSAND_EMPTYING)
8737 Tile[x][y] = get_next_element(element);
8738 element = Tile[newx][newy] = Store[x][y];
8740 else if (element == EL_QUICKSAND_FAST_FILLING)
8742 element = Tile[newx][newy] = get_next_element(element);
8743 Store[newx][newy] = Store[x][y];
8745 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8747 Tile[x][y] = get_next_element(element);
8748 element = Tile[newx][newy] = Store[x][y];
8750 else if (element == EL_MAGIC_WALL_FILLING)
8752 element = Tile[newx][newy] = get_next_element(element);
8753 if (!game.magic_wall_active)
8754 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8755 Store[newx][newy] = Store[x][y];
8757 else if (element == EL_MAGIC_WALL_EMPTYING)
8759 Tile[x][y] = get_next_element(element);
8760 if (!game.magic_wall_active)
8761 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8762 element = Tile[newx][newy] = Store[x][y];
8764 InitField(newx, newy, FALSE);
8766 else if (element == EL_BD_MAGIC_WALL_FILLING)
8768 element = Tile[newx][newy] = get_next_element(element);
8769 if (!game.magic_wall_active)
8770 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8771 Store[newx][newy] = Store[x][y];
8773 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8775 Tile[x][y] = get_next_element(element);
8776 if (!game.magic_wall_active)
8777 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8778 element = Tile[newx][newy] = Store[x][y];
8780 InitField(newx, newy, FALSE);
8782 else if (element == EL_DC_MAGIC_WALL_FILLING)
8784 element = Tile[newx][newy] = get_next_element(element);
8785 if (!game.magic_wall_active)
8786 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8787 Store[newx][newy] = Store[x][y];
8789 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8791 Tile[x][y] = get_next_element(element);
8792 if (!game.magic_wall_active)
8793 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8794 element = Tile[newx][newy] = Store[x][y];
8796 InitField(newx, newy, FALSE);
8798 else if (element == EL_AMOEBA_DROPPING)
8800 Tile[x][y] = get_next_element(element);
8801 element = Tile[newx][newy] = Store[x][y];
8803 else if (element == EL_SOKOBAN_OBJECT)
8806 Tile[x][y] = Back[x][y];
8808 if (Back[newx][newy])
8809 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8811 Back[x][y] = Back[newx][newy] = 0;
8814 Store[x][y] = EL_EMPTY;
8819 MovDelay[newx][newy] = 0;
8821 if (CAN_CHANGE_OR_HAS_ACTION(element))
8823 // copy element change control values to new field
8824 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8825 ChangePage[newx][newy] = ChangePage[x][y];
8826 ChangeCount[newx][newy] = ChangeCount[x][y];
8827 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8830 CustomValue[newx][newy] = CustomValue[x][y];
8832 ChangeDelay[x][y] = 0;
8833 ChangePage[x][y] = -1;
8834 ChangeCount[x][y] = 0;
8835 ChangeEvent[x][y] = -1;
8837 CustomValue[x][y] = 0;
8839 // copy animation control values to new field
8840 GfxFrame[newx][newy] = GfxFrame[x][y];
8841 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8842 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8843 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8845 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8847 // some elements can leave other elements behind after moving
8848 if (ei->move_leave_element != EL_EMPTY &&
8849 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8850 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8852 int move_leave_element = ei->move_leave_element;
8854 // this makes it possible to leave the removed element again
8855 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8856 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8858 Tile[x][y] = move_leave_element;
8860 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8861 MovDir[x][y] = direction;
8863 InitField(x, y, FALSE);
8865 if (GFX_CRUMBLED(Tile[x][y]))
8866 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8868 if (IS_PLAYER_ELEMENT(move_leave_element))
8869 RelocatePlayer(x, y, move_leave_element);
8872 // do this after checking for left-behind element
8873 ResetGfxAnimation(x, y); // reset animation values for old field
8875 if (!CAN_MOVE(element) ||
8876 (CAN_FALL(element) && direction == MV_DOWN &&
8877 (element == EL_SPRING ||
8878 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8879 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8880 GfxDir[x][y] = MovDir[newx][newy] = 0;
8882 TEST_DrawLevelField(x, y);
8883 TEST_DrawLevelField(newx, newy);
8885 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8887 // prevent pushed element from moving on in pushed direction
8888 if (pushed_by_player && CAN_MOVE(element) &&
8889 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8890 !(element_info[element].move_pattern & direction))
8891 TurnRound(newx, newy);
8893 // prevent elements on conveyor belt from moving on in last direction
8894 if (pushed_by_conveyor && CAN_FALL(element) &&
8895 direction & MV_HORIZONTAL)
8896 MovDir[newx][newy] = 0;
8898 if (!pushed_by_player)
8900 int nextx = newx + dx, nexty = newy + dy;
8901 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8903 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8905 if (CAN_FALL(element) && direction == MV_DOWN)
8906 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8908 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8909 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8911 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8912 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8915 if (DONT_TOUCH(element)) // object may be nasty to player or others
8917 TestIfBadThingTouchesPlayer(newx, newy);
8918 TestIfBadThingTouchesFriend(newx, newy);
8920 if (!IS_CUSTOM_ELEMENT(element))
8921 TestIfBadThingTouchesOtherBadThing(newx, newy);
8923 else if (element == EL_PENGUIN)
8924 TestIfFriendTouchesBadThing(newx, newy);
8926 if (DONT_GET_HIT_BY(element))
8928 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8931 // give the player one last chance (one more frame) to move away
8932 if (CAN_FALL(element) && direction == MV_DOWN &&
8933 (last_line || (!IS_FREE(x, newy + 1) &&
8934 (!IS_PLAYER(x, newy + 1) ||
8935 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8938 if (pushed_by_player && !game.use_change_when_pushing_bug)
8940 int push_side = MV_DIR_OPPOSITE(direction);
8941 struct PlayerInfo *player = PLAYERINFO(x, y);
8943 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8944 player->index_bit, push_side);
8945 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8946 player->index_bit, push_side);
8949 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8950 MovDelay[newx][newy] = 1;
8952 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8954 TestIfElementTouchesCustomElement(x, y); // empty or new element
8955 TestIfElementHitsCustomElement(newx, newy, direction);
8956 TestIfPlayerTouchesCustomElement(newx, newy);
8957 TestIfElementTouchesCustomElement(newx, newy);
8959 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8960 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8961 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8962 MV_DIR_OPPOSITE(direction));
8965 int AmoebaNeighbourNr(int ax, int ay)
8968 int element = Tile[ax][ay];
8970 static int xy[4][2] =
8978 for (i = 0; i < NUM_DIRECTIONS; i++)
8980 int x = ax + xy[i][0];
8981 int y = ay + xy[i][1];
8983 if (!IN_LEV_FIELD(x, y))
8986 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8987 group_nr = AmoebaNr[x][y];
8993 static void AmoebaMerge(int ax, int ay)
8995 int i, x, y, xx, yy;
8996 int new_group_nr = AmoebaNr[ax][ay];
8997 static int xy[4][2] =
9005 if (new_group_nr == 0)
9008 for (i = 0; i < NUM_DIRECTIONS; i++)
9013 if (!IN_LEV_FIELD(x, y))
9016 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9017 Tile[x][y] == EL_BD_AMOEBA ||
9018 Tile[x][y] == EL_AMOEBA_DEAD) &&
9019 AmoebaNr[x][y] != new_group_nr)
9021 int old_group_nr = AmoebaNr[x][y];
9023 if (old_group_nr == 0)
9026 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9027 AmoebaCnt[old_group_nr] = 0;
9028 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9029 AmoebaCnt2[old_group_nr] = 0;
9031 SCAN_PLAYFIELD(xx, yy)
9033 if (AmoebaNr[xx][yy] == old_group_nr)
9034 AmoebaNr[xx][yy] = new_group_nr;
9040 void AmoebaToDiamond(int ax, int ay)
9044 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9046 int group_nr = AmoebaNr[ax][ay];
9051 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9052 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9058 SCAN_PLAYFIELD(x, y)
9060 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9063 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9067 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9068 SND_AMOEBA_TURNING_TO_GEM :
9069 SND_AMOEBA_TURNING_TO_ROCK));
9074 static int xy[4][2] =
9082 for (i = 0; i < NUM_DIRECTIONS; i++)
9087 if (!IN_LEV_FIELD(x, y))
9090 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9092 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9093 SND_AMOEBA_TURNING_TO_GEM :
9094 SND_AMOEBA_TURNING_TO_ROCK));
9101 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9104 int group_nr = AmoebaNr[ax][ay];
9105 boolean done = FALSE;
9110 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9111 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9117 SCAN_PLAYFIELD(x, y)
9119 if (AmoebaNr[x][y] == group_nr &&
9120 (Tile[x][y] == EL_AMOEBA_DEAD ||
9121 Tile[x][y] == EL_BD_AMOEBA ||
9122 Tile[x][y] == EL_AMOEBA_GROWING))
9125 Tile[x][y] = new_element;
9126 InitField(x, y, FALSE);
9127 TEST_DrawLevelField(x, y);
9133 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9134 SND_BD_AMOEBA_TURNING_TO_ROCK :
9135 SND_BD_AMOEBA_TURNING_TO_GEM));
9138 static void AmoebaGrowing(int x, int y)
9140 static unsigned int sound_delay = 0;
9141 static unsigned int sound_delay_value = 0;
9143 if (!MovDelay[x][y]) // start new growing cycle
9147 if (DelayReached(&sound_delay, sound_delay_value))
9149 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9150 sound_delay_value = 30;
9154 if (MovDelay[x][y]) // wait some time before growing bigger
9157 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9159 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9160 6 - MovDelay[x][y]);
9162 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9165 if (!MovDelay[x][y])
9167 Tile[x][y] = Store[x][y];
9169 TEST_DrawLevelField(x, y);
9174 static void AmoebaShrinking(int x, int y)
9176 static unsigned int sound_delay = 0;
9177 static unsigned int sound_delay_value = 0;
9179 if (!MovDelay[x][y]) // start new shrinking cycle
9183 if (DelayReached(&sound_delay, sound_delay_value))
9184 sound_delay_value = 30;
9187 if (MovDelay[x][y]) // wait some time before shrinking
9190 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9192 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9193 6 - MovDelay[x][y]);
9195 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9198 if (!MovDelay[x][y])
9200 Tile[x][y] = EL_EMPTY;
9201 TEST_DrawLevelField(x, y);
9203 // don't let mole enter this field in this cycle;
9204 // (give priority to objects falling to this field from above)
9210 static void AmoebaReproduce(int ax, int ay)
9213 int element = Tile[ax][ay];
9214 int graphic = el2img(element);
9215 int newax = ax, neway = ay;
9216 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9217 static int xy[4][2] =
9225 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9227 Tile[ax][ay] = EL_AMOEBA_DEAD;
9228 TEST_DrawLevelField(ax, ay);
9232 if (IS_ANIMATED(graphic))
9233 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9235 if (!MovDelay[ax][ay]) // start making new amoeba field
9236 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9238 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9241 if (MovDelay[ax][ay])
9245 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9248 int x = ax + xy[start][0];
9249 int y = ay + xy[start][1];
9251 if (!IN_LEV_FIELD(x, y))
9254 if (IS_FREE(x, y) ||
9255 CAN_GROW_INTO(Tile[x][y]) ||
9256 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9257 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9263 if (newax == ax && neway == ay)
9266 else // normal or "filled" (BD style) amoeba
9269 boolean waiting_for_player = FALSE;
9271 for (i = 0; i < NUM_DIRECTIONS; i++)
9273 int j = (start + i) % 4;
9274 int x = ax + xy[j][0];
9275 int y = ay + xy[j][1];
9277 if (!IN_LEV_FIELD(x, y))
9280 if (IS_FREE(x, y) ||
9281 CAN_GROW_INTO(Tile[x][y]) ||
9282 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9283 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9289 else if (IS_PLAYER(x, y))
9290 waiting_for_player = TRUE;
9293 if (newax == ax && neway == ay) // amoeba cannot grow
9295 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9297 Tile[ax][ay] = EL_AMOEBA_DEAD;
9298 TEST_DrawLevelField(ax, ay);
9299 AmoebaCnt[AmoebaNr[ax][ay]]--;
9301 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9303 if (element == EL_AMOEBA_FULL)
9304 AmoebaToDiamond(ax, ay);
9305 else if (element == EL_BD_AMOEBA)
9306 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9311 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9313 // amoeba gets larger by growing in some direction
9315 int new_group_nr = AmoebaNr[ax][ay];
9318 if (new_group_nr == 0)
9320 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9322 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9328 AmoebaNr[newax][neway] = new_group_nr;
9329 AmoebaCnt[new_group_nr]++;
9330 AmoebaCnt2[new_group_nr]++;
9332 // if amoeba touches other amoeba(s) after growing, unify them
9333 AmoebaMerge(newax, neway);
9335 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9337 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9343 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9344 (neway == lev_fieldy - 1 && newax != ax))
9346 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9347 Store[newax][neway] = element;
9349 else if (neway == ay || element == EL_EMC_DRIPPER)
9351 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9353 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9357 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9358 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9359 Store[ax][ay] = EL_AMOEBA_DROP;
9360 ContinueMoving(ax, ay);
9364 TEST_DrawLevelField(newax, neway);
9367 static void Life(int ax, int ay)
9371 int element = Tile[ax][ay];
9372 int graphic = el2img(element);
9373 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9375 boolean changed = FALSE;
9377 if (IS_ANIMATED(graphic))
9378 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9383 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9384 MovDelay[ax][ay] = life_time;
9386 if (MovDelay[ax][ay]) // wait some time before next cycle
9389 if (MovDelay[ax][ay])
9393 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9395 int xx = ax+x1, yy = ay+y1;
9396 int old_element = Tile[xx][yy];
9397 int num_neighbours = 0;
9399 if (!IN_LEV_FIELD(xx, yy))
9402 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9404 int x = xx+x2, y = yy+y2;
9406 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9409 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9410 boolean is_neighbour = FALSE;
9412 if (level.use_life_bugs)
9414 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9415 (IS_FREE(x, y) && Stop[x][y]));
9418 (Last[x][y] == element || is_player_cell);
9424 boolean is_free = FALSE;
9426 if (level.use_life_bugs)
9427 is_free = (IS_FREE(xx, yy));
9429 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9431 if (xx == ax && yy == ay) // field in the middle
9433 if (num_neighbours < life_parameter[0] ||
9434 num_neighbours > life_parameter[1])
9436 Tile[xx][yy] = EL_EMPTY;
9437 if (Tile[xx][yy] != old_element)
9438 TEST_DrawLevelField(xx, yy);
9439 Stop[xx][yy] = TRUE;
9443 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9444 { // free border field
9445 if (num_neighbours >= life_parameter[2] &&
9446 num_neighbours <= life_parameter[3])
9448 Tile[xx][yy] = element;
9449 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9450 if (Tile[xx][yy] != old_element)
9451 TEST_DrawLevelField(xx, yy);
9452 Stop[xx][yy] = TRUE;
9459 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9460 SND_GAME_OF_LIFE_GROWING);
9463 static void InitRobotWheel(int x, int y)
9465 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9468 static void RunRobotWheel(int x, int y)
9470 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9473 static void StopRobotWheel(int x, int y)
9475 if (game.robot_wheel_x == x &&
9476 game.robot_wheel_y == y)
9478 game.robot_wheel_x = -1;
9479 game.robot_wheel_y = -1;
9480 game.robot_wheel_active = FALSE;
9484 static void InitTimegateWheel(int x, int y)
9486 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9489 static void RunTimegateWheel(int x, int y)
9491 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9494 static void InitMagicBallDelay(int x, int y)
9496 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9499 static void ActivateMagicBall(int bx, int by)
9503 if (level.ball_random)
9505 int pos_border = RND(8); // select one of the eight border elements
9506 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9507 int xx = pos_content % 3;
9508 int yy = pos_content / 3;
9513 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9514 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9518 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9520 int xx = x - bx + 1;
9521 int yy = y - by + 1;
9523 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9524 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9528 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9531 static void CheckExit(int x, int y)
9533 if (game.gems_still_needed > 0 ||
9534 game.sokoban_fields_still_needed > 0 ||
9535 game.sokoban_objects_still_needed > 0 ||
9536 game.lights_still_needed > 0)
9538 int element = Tile[x][y];
9539 int graphic = el2img(element);
9541 if (IS_ANIMATED(graphic))
9542 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9547 // do not re-open exit door closed after last player
9548 if (game.all_players_gone)
9551 Tile[x][y] = EL_EXIT_OPENING;
9553 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9556 static void CheckExitEM(int x, int y)
9558 if (game.gems_still_needed > 0 ||
9559 game.sokoban_fields_still_needed > 0 ||
9560 game.sokoban_objects_still_needed > 0 ||
9561 game.lights_still_needed > 0)
9563 int element = Tile[x][y];
9564 int graphic = el2img(element);
9566 if (IS_ANIMATED(graphic))
9567 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9572 // do not re-open exit door closed after last player
9573 if (game.all_players_gone)
9576 Tile[x][y] = EL_EM_EXIT_OPENING;
9578 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9581 static void CheckExitSteel(int x, int y)
9583 if (game.gems_still_needed > 0 ||
9584 game.sokoban_fields_still_needed > 0 ||
9585 game.sokoban_objects_still_needed > 0 ||
9586 game.lights_still_needed > 0)
9588 int element = Tile[x][y];
9589 int graphic = el2img(element);
9591 if (IS_ANIMATED(graphic))
9592 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9597 // do not re-open exit door closed after last player
9598 if (game.all_players_gone)
9601 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9603 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9606 static void CheckExitSteelEM(int x, int y)
9608 if (game.gems_still_needed > 0 ||
9609 game.sokoban_fields_still_needed > 0 ||
9610 game.sokoban_objects_still_needed > 0 ||
9611 game.lights_still_needed > 0)
9613 int element = Tile[x][y];
9614 int graphic = el2img(element);
9616 if (IS_ANIMATED(graphic))
9617 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9622 // do not re-open exit door closed after last player
9623 if (game.all_players_gone)
9626 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9628 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9631 static void CheckExitSP(int x, int y)
9633 if (game.gems_still_needed > 0)
9635 int element = Tile[x][y];
9636 int graphic = el2img(element);
9638 if (IS_ANIMATED(graphic))
9639 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9644 // do not re-open exit door closed after last player
9645 if (game.all_players_gone)
9648 Tile[x][y] = EL_SP_EXIT_OPENING;
9650 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9653 static void CloseAllOpenTimegates(void)
9657 SCAN_PLAYFIELD(x, y)
9659 int element = Tile[x][y];
9661 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9663 Tile[x][y] = EL_TIMEGATE_CLOSING;
9665 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9670 static void DrawTwinkleOnField(int x, int y)
9672 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9675 if (Tile[x][y] == EL_BD_DIAMOND)
9678 if (MovDelay[x][y] == 0) // next animation frame
9679 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9681 if (MovDelay[x][y] != 0) // wait some time before next frame
9685 DrawLevelElementAnimation(x, y, Tile[x][y]);
9687 if (MovDelay[x][y] != 0)
9689 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9690 10 - MovDelay[x][y]);
9692 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9697 static void MauerWaechst(int x, int y)
9701 if (!MovDelay[x][y]) // next animation frame
9702 MovDelay[x][y] = 3 * delay;
9704 if (MovDelay[x][y]) // wait some time before next frame
9708 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9710 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9711 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9713 DrawLevelGraphic(x, y, graphic, frame);
9716 if (!MovDelay[x][y])
9718 if (MovDir[x][y] == MV_LEFT)
9720 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9721 TEST_DrawLevelField(x - 1, y);
9723 else if (MovDir[x][y] == MV_RIGHT)
9725 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9726 TEST_DrawLevelField(x + 1, y);
9728 else if (MovDir[x][y] == MV_UP)
9730 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9731 TEST_DrawLevelField(x, y - 1);
9735 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9736 TEST_DrawLevelField(x, y + 1);
9739 Tile[x][y] = Store[x][y];
9741 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9742 TEST_DrawLevelField(x, y);
9747 static void MauerAbleger(int ax, int ay)
9749 int element = Tile[ax][ay];
9750 int graphic = el2img(element);
9751 boolean oben_frei = FALSE, unten_frei = FALSE;
9752 boolean links_frei = FALSE, rechts_frei = FALSE;
9753 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9754 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9755 boolean new_wall = FALSE;
9757 if (IS_ANIMATED(graphic))
9758 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9760 if (!MovDelay[ax][ay]) // start building new wall
9761 MovDelay[ax][ay] = 6;
9763 if (MovDelay[ax][ay]) // wait some time before building new wall
9766 if (MovDelay[ax][ay])
9770 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9772 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9774 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9776 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9779 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9780 element == EL_EXPANDABLE_WALL_ANY)
9784 Tile[ax][ay - 1] = EL_EXPANDABLE_WALL_GROWING;
9785 Store[ax][ay - 1] = element;
9786 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9787 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9788 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9793 Tile[ax][ay + 1] = EL_EXPANDABLE_WALL_GROWING;
9794 Store[ax][ay + 1] = element;
9795 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9796 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9797 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9802 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9803 element == EL_EXPANDABLE_WALL_ANY ||
9804 element == EL_EXPANDABLE_WALL ||
9805 element == EL_BD_EXPANDABLE_WALL)
9809 Tile[ax - 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9810 Store[ax - 1][ay] = element;
9811 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9812 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9813 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9819 Tile[ax + 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9820 Store[ax + 1][ay] = element;
9821 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9822 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9823 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9828 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9829 TEST_DrawLevelField(ax, ay);
9831 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9833 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9834 unten_massiv = TRUE;
9835 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9836 links_massiv = TRUE;
9837 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9838 rechts_massiv = TRUE;
9840 if (((oben_massiv && unten_massiv) ||
9841 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9842 element == EL_EXPANDABLE_WALL) &&
9843 ((links_massiv && rechts_massiv) ||
9844 element == EL_EXPANDABLE_WALL_VERTICAL))
9845 Tile[ax][ay] = EL_WALL;
9848 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9851 static void MauerAblegerStahl(int ax, int ay)
9853 int element = Tile[ax][ay];
9854 int graphic = el2img(element);
9855 boolean oben_frei = FALSE, unten_frei = FALSE;
9856 boolean links_frei = FALSE, rechts_frei = FALSE;
9857 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9858 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9859 boolean new_wall = FALSE;
9861 if (IS_ANIMATED(graphic))
9862 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9864 if (!MovDelay[ax][ay]) // start building new wall
9865 MovDelay[ax][ay] = 6;
9867 if (MovDelay[ax][ay]) // wait some time before building new wall
9870 if (MovDelay[ax][ay])
9874 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9876 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9878 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9880 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9883 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9884 element == EL_EXPANDABLE_STEELWALL_ANY)
9888 Tile[ax][ay - 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9889 Store[ax][ay - 1] = element;
9890 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9891 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9892 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9897 Tile[ax][ay + 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9898 Store[ax][ay + 1] = element;
9899 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9900 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9901 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9906 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9907 element == EL_EXPANDABLE_STEELWALL_ANY)
9911 Tile[ax - 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9912 Store[ax - 1][ay] = element;
9913 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9914 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9915 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9921 Tile[ax + 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9922 Store[ax + 1][ay] = element;
9923 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9924 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9925 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9930 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9932 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9933 unten_massiv = TRUE;
9934 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9935 links_massiv = TRUE;
9936 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9937 rechts_massiv = TRUE;
9939 if (((oben_massiv && unten_massiv) ||
9940 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9941 ((links_massiv && rechts_massiv) ||
9942 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9943 Tile[ax][ay] = EL_STEELWALL;
9946 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9949 static void CheckForDragon(int x, int y)
9952 boolean dragon_found = FALSE;
9953 static int xy[4][2] =
9961 for (i = 0; i < NUM_DIRECTIONS; i++)
9963 for (j = 0; j < 4; j++)
9965 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9967 if (IN_LEV_FIELD(xx, yy) &&
9968 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9970 if (Tile[xx][yy] == EL_DRAGON)
9971 dragon_found = TRUE;
9980 for (i = 0; i < NUM_DIRECTIONS; i++)
9982 for (j = 0; j < 3; j++)
9984 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9986 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9988 Tile[xx][yy] = EL_EMPTY;
9989 TEST_DrawLevelField(xx, yy);
9998 static void InitBuggyBase(int x, int y)
10000 int element = Tile[x][y];
10001 int activating_delay = FRAMES_PER_SECOND / 4;
10003 ChangeDelay[x][y] =
10004 (element == EL_SP_BUGGY_BASE ?
10005 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10006 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10008 element == EL_SP_BUGGY_BASE_ACTIVE ?
10009 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10012 static void WarnBuggyBase(int x, int y)
10015 static int xy[4][2] =
10023 for (i = 0; i < NUM_DIRECTIONS; i++)
10025 int xx = x + xy[i][0];
10026 int yy = y + xy[i][1];
10028 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10030 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10037 static void InitTrap(int x, int y)
10039 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10042 static void ActivateTrap(int x, int y)
10044 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10047 static void ChangeActiveTrap(int x, int y)
10049 int graphic = IMG_TRAP_ACTIVE;
10051 // if new animation frame was drawn, correct crumbled sand border
10052 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10053 TEST_DrawLevelFieldCrumbled(x, y);
10056 static int getSpecialActionElement(int element, int number, int base_element)
10058 return (element != EL_EMPTY ? element :
10059 number != -1 ? base_element + number - 1 :
10063 static int getModifiedActionNumber(int value_old, int operator, int operand,
10064 int value_min, int value_max)
10066 int value_new = (operator == CA_MODE_SET ? operand :
10067 operator == CA_MODE_ADD ? value_old + operand :
10068 operator == CA_MODE_SUBTRACT ? value_old - operand :
10069 operator == CA_MODE_MULTIPLY ? value_old * operand :
10070 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10071 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10074 return (value_new < value_min ? value_min :
10075 value_new > value_max ? value_max :
10079 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10081 struct ElementInfo *ei = &element_info[element];
10082 struct ElementChangeInfo *change = &ei->change_page[page];
10083 int target_element = change->target_element;
10084 int action_type = change->action_type;
10085 int action_mode = change->action_mode;
10086 int action_arg = change->action_arg;
10087 int action_element = change->action_element;
10090 if (!change->has_action)
10093 // ---------- determine action paramater values -----------------------------
10095 int level_time_value =
10096 (level.time > 0 ? TimeLeft :
10099 int action_arg_element_raw =
10100 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10101 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10102 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10103 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10104 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10105 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10106 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10108 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10110 int action_arg_direction =
10111 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10112 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10113 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10114 change->actual_trigger_side :
10115 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10116 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10119 int action_arg_number_min =
10120 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10123 int action_arg_number_max =
10124 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10125 action_type == CA_SET_LEVEL_GEMS ? 999 :
10126 action_type == CA_SET_LEVEL_TIME ? 9999 :
10127 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10128 action_type == CA_SET_CE_VALUE ? 9999 :
10129 action_type == CA_SET_CE_SCORE ? 9999 :
10132 int action_arg_number_reset =
10133 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10134 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10135 action_type == CA_SET_LEVEL_TIME ? level.time :
10136 action_type == CA_SET_LEVEL_SCORE ? 0 :
10137 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10138 action_type == CA_SET_CE_SCORE ? 0 :
10141 int action_arg_number =
10142 (action_arg <= CA_ARG_MAX ? action_arg :
10143 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10144 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10145 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10146 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10147 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10148 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10149 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10150 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10151 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10152 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10153 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10154 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10155 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10156 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10157 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10158 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10159 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10160 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10161 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10162 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10163 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10166 int action_arg_number_old =
10167 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10168 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10169 action_type == CA_SET_LEVEL_SCORE ? game.score :
10170 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10171 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10174 int action_arg_number_new =
10175 getModifiedActionNumber(action_arg_number_old,
10176 action_mode, action_arg_number,
10177 action_arg_number_min, action_arg_number_max);
10179 int trigger_player_bits =
10180 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10181 change->actual_trigger_player_bits : change->trigger_player);
10183 int action_arg_player_bits =
10184 (action_arg >= CA_ARG_PLAYER_1 &&
10185 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10186 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10187 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10190 // ---------- execute action -----------------------------------------------
10192 switch (action_type)
10199 // ---------- level actions ----------------------------------------------
10201 case CA_RESTART_LEVEL:
10203 game.restart_level = TRUE;
10208 case CA_SHOW_ENVELOPE:
10210 int element = getSpecialActionElement(action_arg_element,
10211 action_arg_number, EL_ENVELOPE_1);
10213 if (IS_ENVELOPE(element))
10214 local_player->show_envelope = element;
10219 case CA_SET_LEVEL_TIME:
10221 if (level.time > 0) // only modify limited time value
10223 TimeLeft = action_arg_number_new;
10225 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10227 DisplayGameControlValues();
10229 if (!TimeLeft && setup.time_limit)
10230 for (i = 0; i < MAX_PLAYERS; i++)
10231 KillPlayer(&stored_player[i]);
10237 case CA_SET_LEVEL_SCORE:
10239 game.score = action_arg_number_new;
10241 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10243 DisplayGameControlValues();
10248 case CA_SET_LEVEL_GEMS:
10250 game.gems_still_needed = action_arg_number_new;
10252 game.snapshot.collected_item = TRUE;
10254 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10256 DisplayGameControlValues();
10261 case CA_SET_LEVEL_WIND:
10263 game.wind_direction = action_arg_direction;
10268 case CA_SET_LEVEL_RANDOM_SEED:
10270 // ensure that setting a new random seed while playing is predictable
10271 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10276 // ---------- player actions ---------------------------------------------
10278 case CA_MOVE_PLAYER:
10279 case CA_MOVE_PLAYER_NEW:
10281 // automatically move to the next field in specified direction
10282 for (i = 0; i < MAX_PLAYERS; i++)
10283 if (trigger_player_bits & (1 << i))
10284 if (action_type == CA_MOVE_PLAYER ||
10285 stored_player[i].MovPos == 0)
10286 stored_player[i].programmed_action = action_arg_direction;
10291 case CA_EXIT_PLAYER:
10293 for (i = 0; i < MAX_PLAYERS; i++)
10294 if (action_arg_player_bits & (1 << i))
10295 ExitPlayer(&stored_player[i]);
10297 if (game.players_still_needed == 0)
10303 case CA_KILL_PLAYER:
10305 for (i = 0; i < MAX_PLAYERS; i++)
10306 if (action_arg_player_bits & (1 << i))
10307 KillPlayer(&stored_player[i]);
10312 case CA_SET_PLAYER_KEYS:
10314 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10315 int element = getSpecialActionElement(action_arg_element,
10316 action_arg_number, EL_KEY_1);
10318 if (IS_KEY(element))
10320 for (i = 0; i < MAX_PLAYERS; i++)
10322 if (trigger_player_bits & (1 << i))
10324 stored_player[i].key[KEY_NR(element)] = key_state;
10326 DrawGameDoorValues();
10334 case CA_SET_PLAYER_SPEED:
10336 for (i = 0; i < MAX_PLAYERS; i++)
10338 if (trigger_player_bits & (1 << i))
10340 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10342 if (action_arg == CA_ARG_SPEED_FASTER &&
10343 stored_player[i].cannot_move)
10345 action_arg_number = STEPSIZE_VERY_SLOW;
10347 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10348 action_arg == CA_ARG_SPEED_FASTER)
10350 action_arg_number = 2;
10351 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10354 else if (action_arg == CA_ARG_NUMBER_RESET)
10356 action_arg_number = level.initial_player_stepsize[i];
10360 getModifiedActionNumber(move_stepsize,
10363 action_arg_number_min,
10364 action_arg_number_max);
10366 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10373 case CA_SET_PLAYER_SHIELD:
10375 for (i = 0; i < MAX_PLAYERS; i++)
10377 if (trigger_player_bits & (1 << i))
10379 if (action_arg == CA_ARG_SHIELD_OFF)
10381 stored_player[i].shield_normal_time_left = 0;
10382 stored_player[i].shield_deadly_time_left = 0;
10384 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10386 stored_player[i].shield_normal_time_left = 999999;
10388 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10390 stored_player[i].shield_normal_time_left = 999999;
10391 stored_player[i].shield_deadly_time_left = 999999;
10399 case CA_SET_PLAYER_GRAVITY:
10401 for (i = 0; i < MAX_PLAYERS; i++)
10403 if (trigger_player_bits & (1 << i))
10405 stored_player[i].gravity =
10406 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10407 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10408 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10409 stored_player[i].gravity);
10416 case CA_SET_PLAYER_ARTWORK:
10418 for (i = 0; i < MAX_PLAYERS; i++)
10420 if (trigger_player_bits & (1 << i))
10422 int artwork_element = action_arg_element;
10424 if (action_arg == CA_ARG_ELEMENT_RESET)
10426 (level.use_artwork_element[i] ? level.artwork_element[i] :
10427 stored_player[i].element_nr);
10429 if (stored_player[i].artwork_element != artwork_element)
10430 stored_player[i].Frame = 0;
10432 stored_player[i].artwork_element = artwork_element;
10434 SetPlayerWaiting(&stored_player[i], FALSE);
10436 // set number of special actions for bored and sleeping animation
10437 stored_player[i].num_special_action_bored =
10438 get_num_special_action(artwork_element,
10439 ACTION_BORING_1, ACTION_BORING_LAST);
10440 stored_player[i].num_special_action_sleeping =
10441 get_num_special_action(artwork_element,
10442 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10449 case CA_SET_PLAYER_INVENTORY:
10451 for (i = 0; i < MAX_PLAYERS; i++)
10453 struct PlayerInfo *player = &stored_player[i];
10456 if (trigger_player_bits & (1 << i))
10458 int inventory_element = action_arg_element;
10460 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10461 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10462 action_arg == CA_ARG_ELEMENT_ACTION)
10464 int element = inventory_element;
10465 int collect_count = element_info[element].collect_count_initial;
10467 if (!IS_CUSTOM_ELEMENT(element))
10470 if (collect_count == 0)
10471 player->inventory_infinite_element = element;
10473 for (k = 0; k < collect_count; k++)
10474 if (player->inventory_size < MAX_INVENTORY_SIZE)
10475 player->inventory_element[player->inventory_size++] =
10478 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10479 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10480 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10482 if (player->inventory_infinite_element != EL_UNDEFINED &&
10483 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10484 action_arg_element_raw))
10485 player->inventory_infinite_element = EL_UNDEFINED;
10487 for (k = 0, j = 0; j < player->inventory_size; j++)
10489 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10490 action_arg_element_raw))
10491 player->inventory_element[k++] = player->inventory_element[j];
10494 player->inventory_size = k;
10496 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10498 if (player->inventory_size > 0)
10500 for (j = 0; j < player->inventory_size - 1; j++)
10501 player->inventory_element[j] = player->inventory_element[j + 1];
10503 player->inventory_size--;
10506 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10508 if (player->inventory_size > 0)
10509 player->inventory_size--;
10511 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10513 player->inventory_infinite_element = EL_UNDEFINED;
10514 player->inventory_size = 0;
10516 else if (action_arg == CA_ARG_INVENTORY_RESET)
10518 player->inventory_infinite_element = EL_UNDEFINED;
10519 player->inventory_size = 0;
10521 if (level.use_initial_inventory[i])
10523 for (j = 0; j < level.initial_inventory_size[i]; j++)
10525 int element = level.initial_inventory_content[i][j];
10526 int collect_count = element_info[element].collect_count_initial;
10528 if (!IS_CUSTOM_ELEMENT(element))
10531 if (collect_count == 0)
10532 player->inventory_infinite_element = element;
10534 for (k = 0; k < collect_count; k++)
10535 if (player->inventory_size < MAX_INVENTORY_SIZE)
10536 player->inventory_element[player->inventory_size++] =
10547 // ---------- CE actions -------------------------------------------------
10549 case CA_SET_CE_VALUE:
10551 int last_ce_value = CustomValue[x][y];
10553 CustomValue[x][y] = action_arg_number_new;
10555 if (CustomValue[x][y] != last_ce_value)
10557 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10558 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10560 if (CustomValue[x][y] == 0)
10562 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10563 ChangeCount[x][y] = 0; // allow at least one more change
10565 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10566 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10573 case CA_SET_CE_SCORE:
10575 int last_ce_score = ei->collect_score;
10577 ei->collect_score = action_arg_number_new;
10579 if (ei->collect_score != last_ce_score)
10581 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10582 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10584 if (ei->collect_score == 0)
10588 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10589 ChangeCount[x][y] = 0; // allow at least one more change
10591 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10592 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10595 This is a very special case that seems to be a mixture between
10596 CheckElementChange() and CheckTriggeredElementChange(): while
10597 the first one only affects single elements that are triggered
10598 directly, the second one affects multiple elements in the playfield
10599 that are triggered indirectly by another element. This is a third
10600 case: Changing the CE score always affects multiple identical CEs,
10601 so every affected CE must be checked, not only the single CE for
10602 which the CE score was changed in the first place (as every instance
10603 of that CE shares the same CE score, and therefore also can change)!
10605 SCAN_PLAYFIELD(xx, yy)
10607 if (Tile[xx][yy] == element)
10608 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10609 CE_SCORE_GETS_ZERO);
10617 case CA_SET_CE_ARTWORK:
10619 int artwork_element = action_arg_element;
10620 boolean reset_frame = FALSE;
10623 if (action_arg == CA_ARG_ELEMENT_RESET)
10624 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10627 if (ei->gfx_element != artwork_element)
10628 reset_frame = TRUE;
10630 ei->gfx_element = artwork_element;
10632 SCAN_PLAYFIELD(xx, yy)
10634 if (Tile[xx][yy] == element)
10638 ResetGfxAnimation(xx, yy);
10639 ResetRandomAnimationValue(xx, yy);
10642 TEST_DrawLevelField(xx, yy);
10649 // ---------- engine actions ---------------------------------------------
10651 case CA_SET_ENGINE_SCAN_MODE:
10653 InitPlayfieldScanMode(action_arg);
10663 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10665 int old_element = Tile[x][y];
10666 int new_element = GetElementFromGroupElement(element);
10667 int previous_move_direction = MovDir[x][y];
10668 int last_ce_value = CustomValue[x][y];
10669 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10670 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10671 boolean add_player_onto_element = (new_element_is_player &&
10672 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10673 IS_WALKABLE(old_element));
10675 if (!add_player_onto_element)
10677 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10678 RemoveMovingField(x, y);
10682 Tile[x][y] = new_element;
10684 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10685 MovDir[x][y] = previous_move_direction;
10687 if (element_info[new_element].use_last_ce_value)
10688 CustomValue[x][y] = last_ce_value;
10690 InitField_WithBug1(x, y, FALSE);
10692 new_element = Tile[x][y]; // element may have changed
10694 ResetGfxAnimation(x, y);
10695 ResetRandomAnimationValue(x, y);
10697 TEST_DrawLevelField(x, y);
10699 if (GFX_CRUMBLED(new_element))
10700 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10703 // check if element under the player changes from accessible to unaccessible
10704 // (needed for special case of dropping element which then changes)
10705 // (must be checked after creating new element for walkable group elements)
10706 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10707 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10714 // "ChangeCount" not set yet to allow "entered by player" change one time
10715 if (new_element_is_player)
10716 RelocatePlayer(x, y, new_element);
10719 ChangeCount[x][y]++; // count number of changes in the same frame
10721 TestIfBadThingTouchesPlayer(x, y);
10722 TestIfPlayerTouchesCustomElement(x, y);
10723 TestIfElementTouchesCustomElement(x, y);
10726 static void CreateField(int x, int y, int element)
10728 CreateFieldExt(x, y, element, FALSE);
10731 static void CreateElementFromChange(int x, int y, int element)
10733 element = GET_VALID_RUNTIME_ELEMENT(element);
10735 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10737 int old_element = Tile[x][y];
10739 // prevent changed element from moving in same engine frame
10740 // unless both old and new element can either fall or move
10741 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10742 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10746 CreateFieldExt(x, y, element, TRUE);
10749 static boolean ChangeElement(int x, int y, int element, int page)
10751 struct ElementInfo *ei = &element_info[element];
10752 struct ElementChangeInfo *change = &ei->change_page[page];
10753 int ce_value = CustomValue[x][y];
10754 int ce_score = ei->collect_score;
10755 int target_element;
10756 int old_element = Tile[x][y];
10758 // always use default change event to prevent running into a loop
10759 if (ChangeEvent[x][y] == -1)
10760 ChangeEvent[x][y] = CE_DELAY;
10762 if (ChangeEvent[x][y] == CE_DELAY)
10764 // reset actual trigger element, trigger player and action element
10765 change->actual_trigger_element = EL_EMPTY;
10766 change->actual_trigger_player = EL_EMPTY;
10767 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10768 change->actual_trigger_side = CH_SIDE_NONE;
10769 change->actual_trigger_ce_value = 0;
10770 change->actual_trigger_ce_score = 0;
10773 // do not change elements more than a specified maximum number of changes
10774 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10777 ChangeCount[x][y]++; // count number of changes in the same frame
10779 if (change->explode)
10786 if (change->use_target_content)
10788 boolean complete_replace = TRUE;
10789 boolean can_replace[3][3];
10792 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10795 boolean is_walkable;
10796 boolean is_diggable;
10797 boolean is_collectible;
10798 boolean is_removable;
10799 boolean is_destructible;
10800 int ex = x + xx - 1;
10801 int ey = y + yy - 1;
10802 int content_element = change->target_content.e[xx][yy];
10805 can_replace[xx][yy] = TRUE;
10807 if (ex == x && ey == y) // do not check changing element itself
10810 if (content_element == EL_EMPTY_SPACE)
10812 can_replace[xx][yy] = FALSE; // do not replace border with space
10817 if (!IN_LEV_FIELD(ex, ey))
10819 can_replace[xx][yy] = FALSE;
10820 complete_replace = FALSE;
10827 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10828 e = MovingOrBlocked2Element(ex, ey);
10830 is_empty = (IS_FREE(ex, ey) ||
10831 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10833 is_walkable = (is_empty || IS_WALKABLE(e));
10834 is_diggable = (is_empty || IS_DIGGABLE(e));
10835 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10836 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10837 is_removable = (is_diggable || is_collectible);
10839 can_replace[xx][yy] =
10840 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10841 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10842 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10843 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10844 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10845 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10846 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10848 if (!can_replace[xx][yy])
10849 complete_replace = FALSE;
10852 if (!change->only_if_complete || complete_replace)
10854 boolean something_has_changed = FALSE;
10856 if (change->only_if_complete && change->use_random_replace &&
10857 RND(100) < change->random_percentage)
10860 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10862 int ex = x + xx - 1;
10863 int ey = y + yy - 1;
10864 int content_element;
10866 if (can_replace[xx][yy] && (!change->use_random_replace ||
10867 RND(100) < change->random_percentage))
10869 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10870 RemoveMovingField(ex, ey);
10872 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10874 content_element = change->target_content.e[xx][yy];
10875 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10876 ce_value, ce_score);
10878 CreateElementFromChange(ex, ey, target_element);
10880 something_has_changed = TRUE;
10882 // for symmetry reasons, freeze newly created border elements
10883 if (ex != x || ey != y)
10884 Stop[ex][ey] = TRUE; // no more moving in this frame
10888 if (something_has_changed)
10890 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10891 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10897 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10898 ce_value, ce_score);
10900 if (element == EL_DIAGONAL_GROWING ||
10901 element == EL_DIAGONAL_SHRINKING)
10903 target_element = Store[x][y];
10905 Store[x][y] = EL_EMPTY;
10908 // special case: element changes to player (and may be kept if walkable)
10909 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10910 CreateElementFromChange(x, y, EL_EMPTY);
10912 CreateElementFromChange(x, y, target_element);
10914 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10915 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10918 // this uses direct change before indirect change
10919 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10924 static void HandleElementChange(int x, int y, int page)
10926 int element = MovingOrBlocked2Element(x, y);
10927 struct ElementInfo *ei = &element_info[element];
10928 struct ElementChangeInfo *change = &ei->change_page[page];
10929 boolean handle_action_before_change = FALSE;
10932 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10933 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10935 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10936 x, y, element, element_info[element].token_name);
10937 Debug("game:playing:HandleElementChange", "This should never happen!");
10941 // this can happen with classic bombs on walkable, changing elements
10942 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10947 if (ChangeDelay[x][y] == 0) // initialize element change
10949 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10951 if (change->can_change)
10953 // !!! not clear why graphic animation should be reset at all here !!!
10954 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10955 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10958 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10960 When using an animation frame delay of 1 (this only happens with
10961 "sp_zonk.moving.left/right" in the classic graphics), the default
10962 (non-moving) animation shows wrong animation frames (while the
10963 moving animation, like "sp_zonk.moving.left/right", is correct,
10964 so this graphical bug never shows up with the classic graphics).
10965 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10966 be drawn instead of the correct frames 0,1,2,3. This is caused by
10967 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10968 an element change: First when the change delay ("ChangeDelay[][]")
10969 counter has reached zero after decrementing, then a second time in
10970 the next frame (after "GfxFrame[][]" was already incremented) when
10971 "ChangeDelay[][]" is reset to the initial delay value again.
10973 This causes frame 0 to be drawn twice, while the last frame won't
10974 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10976 As some animations may already be cleverly designed around this bug
10977 (at least the "Snake Bite" snake tail animation does this), it cannot
10978 simply be fixed here without breaking such existing animations.
10979 Unfortunately, it cannot easily be detected if a graphics set was
10980 designed "before" or "after" the bug was fixed. As a workaround,
10981 a new graphics set option "game.graphics_engine_version" was added
10982 to be able to specify the game's major release version for which the
10983 graphics set was designed, which can then be used to decide if the
10984 bugfix should be used (version 4 and above) or not (version 3 or
10985 below, or if no version was specified at all, as with old sets).
10987 (The wrong/fixed animation frames can be tested with the test level set
10988 "test_gfxframe" and level "000", which contains a specially prepared
10989 custom element at level position (x/y) == (11/9) which uses the zonk
10990 animation mentioned above. Using "game.graphics_engine_version: 4"
10991 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10992 This can also be seen from the debug output for this test element.)
10995 // when a custom element is about to change (for example by change delay),
10996 // do not reset graphic animation when the custom element is moving
10997 if (game.graphics_engine_version < 4 &&
11000 ResetGfxAnimation(x, y);
11001 ResetRandomAnimationValue(x, y);
11004 if (change->pre_change_function)
11005 change->pre_change_function(x, y);
11009 ChangeDelay[x][y]--;
11011 if (ChangeDelay[x][y] != 0) // continue element change
11013 if (change->can_change)
11015 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11017 if (IS_ANIMATED(graphic))
11018 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11020 if (change->change_function)
11021 change->change_function(x, y);
11024 else // finish element change
11026 if (ChangePage[x][y] != -1) // remember page from delayed change
11028 page = ChangePage[x][y];
11029 ChangePage[x][y] = -1;
11031 change = &ei->change_page[page];
11034 if (IS_MOVING(x, y)) // never change a running system ;-)
11036 ChangeDelay[x][y] = 1; // try change after next move step
11037 ChangePage[x][y] = page; // remember page to use for change
11042 // special case: set new level random seed before changing element
11043 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11044 handle_action_before_change = TRUE;
11046 if (change->has_action && handle_action_before_change)
11047 ExecuteCustomElementAction(x, y, element, page);
11049 if (change->can_change)
11051 if (ChangeElement(x, y, element, page))
11053 if (change->post_change_function)
11054 change->post_change_function(x, y);
11058 if (change->has_action && !handle_action_before_change)
11059 ExecuteCustomElementAction(x, y, element, page);
11063 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11064 int trigger_element,
11066 int trigger_player,
11070 boolean change_done_any = FALSE;
11071 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11074 if (!(trigger_events[trigger_element][trigger_event]))
11077 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11079 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11081 int element = EL_CUSTOM_START + i;
11082 boolean change_done = FALSE;
11085 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11086 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11089 for (p = 0; p < element_info[element].num_change_pages; p++)
11091 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11093 if (change->can_change_or_has_action &&
11094 change->has_event[trigger_event] &&
11095 change->trigger_side & trigger_side &&
11096 change->trigger_player & trigger_player &&
11097 change->trigger_page & trigger_page_bits &&
11098 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11100 change->actual_trigger_element = trigger_element;
11101 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11102 change->actual_trigger_player_bits = trigger_player;
11103 change->actual_trigger_side = trigger_side;
11104 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11105 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11107 if ((change->can_change && !change_done) || change->has_action)
11111 SCAN_PLAYFIELD(x, y)
11113 if (Tile[x][y] == element)
11115 if (change->can_change && !change_done)
11117 // if element already changed in this frame, not only prevent
11118 // another element change (checked in ChangeElement()), but
11119 // also prevent additional element actions for this element
11121 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11122 !level.use_action_after_change_bug)
11125 ChangeDelay[x][y] = 1;
11126 ChangeEvent[x][y] = trigger_event;
11128 HandleElementChange(x, y, p);
11130 else if (change->has_action)
11132 // if element already changed in this frame, not only prevent
11133 // another element change (checked in ChangeElement()), but
11134 // also prevent additional element actions for this element
11136 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11137 !level.use_action_after_change_bug)
11140 ExecuteCustomElementAction(x, y, element, p);
11141 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11146 if (change->can_change)
11148 change_done = TRUE;
11149 change_done_any = TRUE;
11156 RECURSION_LOOP_DETECTION_END();
11158 return change_done_any;
11161 static boolean CheckElementChangeExt(int x, int y,
11163 int trigger_element,
11165 int trigger_player,
11168 boolean change_done = FALSE;
11171 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11172 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11175 if (Tile[x][y] == EL_BLOCKED)
11177 Blocked2Moving(x, y, &x, &y);
11178 element = Tile[x][y];
11181 // check if element has already changed or is about to change after moving
11182 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11183 Tile[x][y] != element) ||
11185 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11186 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11187 ChangePage[x][y] != -1)))
11190 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11192 for (p = 0; p < element_info[element].num_change_pages; p++)
11194 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11196 /* check trigger element for all events where the element that is checked
11197 for changing interacts with a directly adjacent element -- this is
11198 different to element changes that affect other elements to change on the
11199 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11200 boolean check_trigger_element =
11201 (trigger_event == CE_NEXT_TO_X ||
11202 trigger_event == CE_TOUCHING_X ||
11203 trigger_event == CE_HITTING_X ||
11204 trigger_event == CE_HIT_BY_X ||
11205 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11207 if (change->can_change_or_has_action &&
11208 change->has_event[trigger_event] &&
11209 change->trigger_side & trigger_side &&
11210 change->trigger_player & trigger_player &&
11211 (!check_trigger_element ||
11212 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11214 change->actual_trigger_element = trigger_element;
11215 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11216 change->actual_trigger_player_bits = trigger_player;
11217 change->actual_trigger_side = trigger_side;
11218 change->actual_trigger_ce_value = CustomValue[x][y];
11219 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11221 // special case: trigger element not at (x,y) position for some events
11222 if (check_trigger_element)
11234 { 0, 0 }, { 0, 0 }, { 0, 0 },
11238 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11239 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11241 change->actual_trigger_ce_value = CustomValue[xx][yy];
11242 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11245 if (change->can_change && !change_done)
11247 ChangeDelay[x][y] = 1;
11248 ChangeEvent[x][y] = trigger_event;
11250 HandleElementChange(x, y, p);
11252 change_done = TRUE;
11254 else if (change->has_action)
11256 ExecuteCustomElementAction(x, y, element, p);
11257 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11262 RECURSION_LOOP_DETECTION_END();
11264 return change_done;
11267 static void PlayPlayerSound(struct PlayerInfo *player)
11269 int jx = player->jx, jy = player->jy;
11270 int sound_element = player->artwork_element;
11271 int last_action = player->last_action_waiting;
11272 int action = player->action_waiting;
11274 if (player->is_waiting)
11276 if (action != last_action)
11277 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11279 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11283 if (action != last_action)
11284 StopSound(element_info[sound_element].sound[last_action]);
11286 if (last_action == ACTION_SLEEPING)
11287 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11291 static void PlayAllPlayersSound(void)
11295 for (i = 0; i < MAX_PLAYERS; i++)
11296 if (stored_player[i].active)
11297 PlayPlayerSound(&stored_player[i]);
11300 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11302 boolean last_waiting = player->is_waiting;
11303 int move_dir = player->MovDir;
11305 player->dir_waiting = move_dir;
11306 player->last_action_waiting = player->action_waiting;
11310 if (!last_waiting) // not waiting -> waiting
11312 player->is_waiting = TRUE;
11314 player->frame_counter_bored =
11316 game.player_boring_delay_fixed +
11317 GetSimpleRandom(game.player_boring_delay_random);
11318 player->frame_counter_sleeping =
11320 game.player_sleeping_delay_fixed +
11321 GetSimpleRandom(game.player_sleeping_delay_random);
11323 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11326 if (game.player_sleeping_delay_fixed +
11327 game.player_sleeping_delay_random > 0 &&
11328 player->anim_delay_counter == 0 &&
11329 player->post_delay_counter == 0 &&
11330 FrameCounter >= player->frame_counter_sleeping)
11331 player->is_sleeping = TRUE;
11332 else if (game.player_boring_delay_fixed +
11333 game.player_boring_delay_random > 0 &&
11334 FrameCounter >= player->frame_counter_bored)
11335 player->is_bored = TRUE;
11337 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11338 player->is_bored ? ACTION_BORING :
11341 if (player->is_sleeping && player->use_murphy)
11343 // special case for sleeping Murphy when leaning against non-free tile
11345 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11346 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11347 !IS_MOVING(player->jx - 1, player->jy)))
11348 move_dir = MV_LEFT;
11349 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11350 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11351 !IS_MOVING(player->jx + 1, player->jy)))
11352 move_dir = MV_RIGHT;
11354 player->is_sleeping = FALSE;
11356 player->dir_waiting = move_dir;
11359 if (player->is_sleeping)
11361 if (player->num_special_action_sleeping > 0)
11363 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11365 int last_special_action = player->special_action_sleeping;
11366 int num_special_action = player->num_special_action_sleeping;
11367 int special_action =
11368 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11369 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11370 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11371 last_special_action + 1 : ACTION_SLEEPING);
11372 int special_graphic =
11373 el_act_dir2img(player->artwork_element, special_action, move_dir);
11375 player->anim_delay_counter =
11376 graphic_info[special_graphic].anim_delay_fixed +
11377 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11378 player->post_delay_counter =
11379 graphic_info[special_graphic].post_delay_fixed +
11380 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11382 player->special_action_sleeping = special_action;
11385 if (player->anim_delay_counter > 0)
11387 player->action_waiting = player->special_action_sleeping;
11388 player->anim_delay_counter--;
11390 else if (player->post_delay_counter > 0)
11392 player->post_delay_counter--;
11396 else if (player->is_bored)
11398 if (player->num_special_action_bored > 0)
11400 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11402 int special_action =
11403 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11404 int special_graphic =
11405 el_act_dir2img(player->artwork_element, special_action, move_dir);
11407 player->anim_delay_counter =
11408 graphic_info[special_graphic].anim_delay_fixed +
11409 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11410 player->post_delay_counter =
11411 graphic_info[special_graphic].post_delay_fixed +
11412 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11414 player->special_action_bored = special_action;
11417 if (player->anim_delay_counter > 0)
11419 player->action_waiting = player->special_action_bored;
11420 player->anim_delay_counter--;
11422 else if (player->post_delay_counter > 0)
11424 player->post_delay_counter--;
11429 else if (last_waiting) // waiting -> not waiting
11431 player->is_waiting = FALSE;
11432 player->is_bored = FALSE;
11433 player->is_sleeping = FALSE;
11435 player->frame_counter_bored = -1;
11436 player->frame_counter_sleeping = -1;
11438 player->anim_delay_counter = 0;
11439 player->post_delay_counter = 0;
11441 player->dir_waiting = player->MovDir;
11442 player->action_waiting = ACTION_DEFAULT;
11444 player->special_action_bored = ACTION_DEFAULT;
11445 player->special_action_sleeping = ACTION_DEFAULT;
11449 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11451 if ((!player->is_moving && player->was_moving) ||
11452 (player->MovPos == 0 && player->was_moving) ||
11453 (player->is_snapping && !player->was_snapping) ||
11454 (player->is_dropping && !player->was_dropping))
11456 if (!CheckSaveEngineSnapshotToList())
11459 player->was_moving = FALSE;
11460 player->was_snapping = TRUE;
11461 player->was_dropping = TRUE;
11465 if (player->is_moving)
11466 player->was_moving = TRUE;
11468 if (!player->is_snapping)
11469 player->was_snapping = FALSE;
11471 if (!player->is_dropping)
11472 player->was_dropping = FALSE;
11475 static struct MouseActionInfo mouse_action_last = { 0 };
11476 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11477 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11480 CheckSaveEngineSnapshotToList();
11482 mouse_action_last = mouse_action;
11485 static void CheckSingleStepMode(struct PlayerInfo *player)
11487 if (tape.single_step && tape.recording && !tape.pausing)
11489 // as it is called "single step mode", just return to pause mode when the
11490 // player stopped moving after one tile (or never starts moving at all)
11491 // (reverse logic needed here in case single step mode used in team mode)
11492 if (player->is_moving ||
11493 player->is_pushing ||
11494 player->is_dropping_pressed ||
11495 player->effective_mouse_action.button)
11496 game.enter_single_step_mode = FALSE;
11499 CheckSaveEngineSnapshot(player);
11502 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11504 int left = player_action & JOY_LEFT;
11505 int right = player_action & JOY_RIGHT;
11506 int up = player_action & JOY_UP;
11507 int down = player_action & JOY_DOWN;
11508 int button1 = player_action & JOY_BUTTON_1;
11509 int button2 = player_action & JOY_BUTTON_2;
11510 int dx = (left ? -1 : right ? 1 : 0);
11511 int dy = (up ? -1 : down ? 1 : 0);
11513 if (!player->active || tape.pausing)
11519 SnapField(player, dx, dy);
11523 DropElement(player);
11525 MovePlayer(player, dx, dy);
11528 CheckSingleStepMode(player);
11530 SetPlayerWaiting(player, FALSE);
11532 return player_action;
11536 // no actions for this player (no input at player's configured device)
11538 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11539 SnapField(player, 0, 0);
11540 CheckGravityMovementWhenNotMoving(player);
11542 if (player->MovPos == 0)
11543 SetPlayerWaiting(player, TRUE);
11545 if (player->MovPos == 0) // needed for tape.playing
11546 player->is_moving = FALSE;
11548 player->is_dropping = FALSE;
11549 player->is_dropping_pressed = FALSE;
11550 player->drop_pressed_delay = 0;
11552 CheckSingleStepMode(player);
11558 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11561 if (!tape.use_mouse_actions)
11564 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11565 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11566 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11569 static void SetTapeActionFromMouseAction(byte *tape_action,
11570 struct MouseActionInfo *mouse_action)
11572 if (!tape.use_mouse_actions)
11575 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11576 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11577 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11580 static void CheckLevelSolved(void)
11582 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11584 if (game_em.level_solved &&
11585 !game_em.game_over) // game won
11589 game_em.game_over = TRUE;
11591 game.all_players_gone = TRUE;
11594 if (game_em.game_over) // game lost
11595 game.all_players_gone = TRUE;
11597 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11599 if (game_sp.level_solved &&
11600 !game_sp.game_over) // game won
11604 game_sp.game_over = TRUE;
11606 game.all_players_gone = TRUE;
11609 if (game_sp.game_over) // game lost
11610 game.all_players_gone = TRUE;
11612 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11614 if (game_mm.level_solved &&
11615 !game_mm.game_over) // game won
11619 game_mm.game_over = TRUE;
11621 game.all_players_gone = TRUE;
11624 if (game_mm.game_over) // game lost
11625 game.all_players_gone = TRUE;
11629 static void CheckLevelTime_StepCounter(void)
11639 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
11640 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11642 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11644 DisplayGameControlValues();
11646 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
11647 for (i = 0; i < MAX_PLAYERS; i++)
11648 KillPlayer(&stored_player[i]);
11650 else if (game.no_time_limit && !game.all_players_gone)
11652 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11654 DisplayGameControlValues();
11658 static void CheckLevelTime(void)
11662 if (TimeFrames >= FRAMES_PER_SECOND)
11667 for (i = 0; i < MAX_PLAYERS; i++)
11669 struct PlayerInfo *player = &stored_player[i];
11671 if (SHIELD_ON(player))
11673 player->shield_normal_time_left--;
11675 if (player->shield_deadly_time_left > 0)
11676 player->shield_deadly_time_left--;
11680 if (!game.LevelSolved && !level.use_step_counter)
11688 if (TimeLeft <= 10 && setup.time_limit)
11689 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11691 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11692 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11694 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11696 if (!TimeLeft && setup.time_limit)
11698 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11699 game_em.lev->killed_out_of_time = TRUE;
11701 for (i = 0; i < MAX_PLAYERS; i++)
11702 KillPlayer(&stored_player[i]);
11705 else if (game.no_time_limit && !game.all_players_gone)
11707 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11710 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11713 if (tape.recording || tape.playing)
11714 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11717 if (tape.recording || tape.playing)
11718 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11720 UpdateAndDisplayGameControlValues();
11723 void AdvanceFrameAndPlayerCounters(int player_nr)
11727 // advance frame counters (global frame counter and time frame counter)
11731 // advance player counters (counters for move delay, move animation etc.)
11732 for (i = 0; i < MAX_PLAYERS; i++)
11734 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11735 int move_delay_value = stored_player[i].move_delay_value;
11736 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11738 if (!advance_player_counters) // not all players may be affected
11741 if (move_frames == 0) // less than one move per game frame
11743 int stepsize = TILEX / move_delay_value;
11744 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11745 int count = (stored_player[i].is_moving ?
11746 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11748 if (count % delay == 0)
11752 stored_player[i].Frame += move_frames;
11754 if (stored_player[i].MovPos != 0)
11755 stored_player[i].StepFrame += move_frames;
11757 if (stored_player[i].move_delay > 0)
11758 stored_player[i].move_delay--;
11760 // due to bugs in previous versions, counter must count up, not down
11761 if (stored_player[i].push_delay != -1)
11762 stored_player[i].push_delay++;
11764 if (stored_player[i].drop_delay > 0)
11765 stored_player[i].drop_delay--;
11767 if (stored_player[i].is_dropping_pressed)
11768 stored_player[i].drop_pressed_delay++;
11772 void StartGameActions(boolean init_network_game, boolean record_tape,
11775 unsigned int new_random_seed = InitRND(random_seed);
11778 TapeStartRecording(new_random_seed);
11780 if (setup.auto_pause_on_start && !tape.pausing)
11781 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11783 if (init_network_game)
11785 SendToServer_LevelFile();
11786 SendToServer_StartPlaying();
11794 static void GameActionsExt(void)
11797 static unsigned int game_frame_delay = 0;
11799 unsigned int game_frame_delay_value;
11800 byte *recorded_player_action;
11801 byte summarized_player_action = 0;
11802 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11805 // detect endless loops, caused by custom element programming
11806 if (recursion_loop_detected && recursion_loop_depth == 0)
11808 char *message = getStringCat3("Internal Error! Element ",
11809 EL_NAME(recursion_loop_element),
11810 " caused endless loop! Quit the game?");
11812 Warn("element '%s' caused endless loop in game engine",
11813 EL_NAME(recursion_loop_element));
11815 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11817 recursion_loop_detected = FALSE; // if game should be continued
11824 if (game.restart_level)
11825 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11827 CheckLevelSolved();
11829 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11832 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11835 if (game_status != GAME_MODE_PLAYING) // status might have changed
11838 game_frame_delay_value =
11839 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11841 if (tape.playing && tape.warp_forward && !tape.pausing)
11842 game_frame_delay_value = 0;
11844 SetVideoFrameDelay(game_frame_delay_value);
11846 // (de)activate virtual buttons depending on current game status
11847 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11849 if (game.all_players_gone) // if no players there to be controlled anymore
11850 SetOverlayActive(FALSE);
11851 else if (!tape.playing) // if game continues after tape stopped playing
11852 SetOverlayActive(TRUE);
11857 // ---------- main game synchronization point ----------
11859 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11861 Debug("game:playing:skip", "skip == %d", skip);
11864 // ---------- main game synchronization point ----------
11866 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11870 if (network_playing && !network_player_action_received)
11872 // try to get network player actions in time
11874 // last chance to get network player actions without main loop delay
11875 HandleNetworking();
11877 // game was quit by network peer
11878 if (game_status != GAME_MODE_PLAYING)
11881 // check if network player actions still missing and game still running
11882 if (!network_player_action_received && !checkGameEnded())
11883 return; // failed to get network player actions in time
11885 // do not yet reset "network_player_action_received" (for tape.pausing)
11891 // at this point we know that we really continue executing the game
11893 network_player_action_received = FALSE;
11895 // when playing tape, read previously recorded player input from tape data
11896 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11898 local_player->effective_mouse_action = local_player->mouse_action;
11900 if (recorded_player_action != NULL)
11901 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11902 recorded_player_action);
11904 // TapePlayAction() may return NULL when toggling to "pause before death"
11908 if (tape.set_centered_player)
11910 game.centered_player_nr_next = tape.centered_player_nr_next;
11911 game.set_centered_player = TRUE;
11914 for (i = 0; i < MAX_PLAYERS; i++)
11916 summarized_player_action |= stored_player[i].action;
11918 if (!network_playing && (game.team_mode || tape.playing))
11919 stored_player[i].effective_action = stored_player[i].action;
11922 if (network_playing && !checkGameEnded())
11923 SendToServer_MovePlayer(summarized_player_action);
11925 // summarize all actions at local players mapped input device position
11926 // (this allows using different input devices in single player mode)
11927 if (!network.enabled && !game.team_mode)
11928 stored_player[map_player_action[local_player->index_nr]].effective_action =
11929 summarized_player_action;
11931 // summarize all actions at centered player in local team mode
11932 if (tape.recording &&
11933 setup.team_mode && !network.enabled &&
11934 setup.input_on_focus &&
11935 game.centered_player_nr != -1)
11937 for (i = 0; i < MAX_PLAYERS; i++)
11938 stored_player[map_player_action[i]].effective_action =
11939 (i == game.centered_player_nr ? summarized_player_action : 0);
11942 if (recorded_player_action != NULL)
11943 for (i = 0; i < MAX_PLAYERS; i++)
11944 stored_player[i].effective_action = recorded_player_action[i];
11946 for (i = 0; i < MAX_PLAYERS; i++)
11948 tape_action[i] = stored_player[i].effective_action;
11950 /* (this may happen in the RND game engine if a player was not present on
11951 the playfield on level start, but appeared later from a custom element */
11952 if (setup.team_mode &&
11955 !tape.player_participates[i])
11956 tape.player_participates[i] = TRUE;
11959 SetTapeActionFromMouseAction(tape_action,
11960 &local_player->effective_mouse_action);
11962 // only record actions from input devices, but not programmed actions
11963 if (tape.recording)
11964 TapeRecordAction(tape_action);
11966 // remember if game was played (especially after tape stopped playing)
11967 if (!tape.playing && summarized_player_action)
11968 game.GamePlayed = TRUE;
11970 #if USE_NEW_PLAYER_ASSIGNMENTS
11971 // !!! also map player actions in single player mode !!!
11972 // if (game.team_mode)
11975 byte mapped_action[MAX_PLAYERS];
11977 #if DEBUG_PLAYER_ACTIONS
11978 for (i = 0; i < MAX_PLAYERS; i++)
11979 DebugContinued("", "%d, ", stored_player[i].effective_action);
11982 for (i = 0; i < MAX_PLAYERS; i++)
11983 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11985 for (i = 0; i < MAX_PLAYERS; i++)
11986 stored_player[i].effective_action = mapped_action[i];
11988 #if DEBUG_PLAYER_ACTIONS
11989 DebugContinued("", "=> ");
11990 for (i = 0; i < MAX_PLAYERS; i++)
11991 DebugContinued("", "%d, ", stored_player[i].effective_action);
11992 DebugContinued("game:playing:player", "\n");
11995 #if DEBUG_PLAYER_ACTIONS
11998 for (i = 0; i < MAX_PLAYERS; i++)
11999 DebugContinued("", "%d, ", stored_player[i].effective_action);
12000 DebugContinued("game:playing:player", "\n");
12005 for (i = 0; i < MAX_PLAYERS; i++)
12007 // allow engine snapshot in case of changed movement attempt
12008 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12009 (stored_player[i].effective_action & KEY_MOTION))
12010 game.snapshot.changed_action = TRUE;
12012 // allow engine snapshot in case of snapping/dropping attempt
12013 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12014 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12015 game.snapshot.changed_action = TRUE;
12017 game.snapshot.last_action[i] = stored_player[i].effective_action;
12020 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12022 GameActions_EM_Main();
12024 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12026 GameActions_SP_Main();
12028 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12030 GameActions_MM_Main();
12034 GameActions_RND_Main();
12037 BlitScreenToBitmap(backbuffer);
12039 CheckLevelSolved();
12042 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12044 if (global.show_frames_per_second)
12046 static unsigned int fps_counter = 0;
12047 static int fps_frames = 0;
12048 unsigned int fps_delay_ms = Counter() - fps_counter;
12052 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12054 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12057 fps_counter = Counter();
12059 // always draw FPS to screen after FPS value was updated
12060 redraw_mask |= REDRAW_FPS;
12063 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12064 if (GetDrawDeactivationMask() == REDRAW_NONE)
12065 redraw_mask |= REDRAW_FPS;
12069 static void GameActions_CheckSaveEngineSnapshot(void)
12071 if (!game.snapshot.save_snapshot)
12074 // clear flag for saving snapshot _before_ saving snapshot
12075 game.snapshot.save_snapshot = FALSE;
12077 SaveEngineSnapshotToList();
12080 void GameActions(void)
12084 GameActions_CheckSaveEngineSnapshot();
12087 void GameActions_EM_Main(void)
12089 byte effective_action[MAX_PLAYERS];
12090 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12093 for (i = 0; i < MAX_PLAYERS; i++)
12094 effective_action[i] = stored_player[i].effective_action;
12096 GameActions_EM(effective_action, warp_mode);
12099 void GameActions_SP_Main(void)
12101 byte effective_action[MAX_PLAYERS];
12102 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12105 for (i = 0; i < MAX_PLAYERS; i++)
12106 effective_action[i] = stored_player[i].effective_action;
12108 GameActions_SP(effective_action, warp_mode);
12110 for (i = 0; i < MAX_PLAYERS; i++)
12112 if (stored_player[i].force_dropping)
12113 stored_player[i].action |= KEY_BUTTON_DROP;
12115 stored_player[i].force_dropping = FALSE;
12119 void GameActions_MM_Main(void)
12121 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12123 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12126 void GameActions_RND_Main(void)
12131 void GameActions_RND(void)
12133 static struct MouseActionInfo mouse_action_last = { 0 };
12134 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12135 int magic_wall_x = 0, magic_wall_y = 0;
12136 int i, x, y, element, graphic, last_gfx_frame;
12138 InitPlayfieldScanModeVars();
12140 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12142 SCAN_PLAYFIELD(x, y)
12144 ChangeCount[x][y] = 0;
12145 ChangeEvent[x][y] = -1;
12149 if (game.set_centered_player)
12151 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12153 // switching to "all players" only possible if all players fit to screen
12154 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12156 game.centered_player_nr_next = game.centered_player_nr;
12157 game.set_centered_player = FALSE;
12160 // do not switch focus to non-existing (or non-active) player
12161 if (game.centered_player_nr_next >= 0 &&
12162 !stored_player[game.centered_player_nr_next].active)
12164 game.centered_player_nr_next = game.centered_player_nr;
12165 game.set_centered_player = FALSE;
12169 if (game.set_centered_player &&
12170 ScreenMovPos == 0) // screen currently aligned at tile position
12174 if (game.centered_player_nr_next == -1)
12176 setScreenCenteredToAllPlayers(&sx, &sy);
12180 sx = stored_player[game.centered_player_nr_next].jx;
12181 sy = stored_player[game.centered_player_nr_next].jy;
12184 game.centered_player_nr = game.centered_player_nr_next;
12185 game.set_centered_player = FALSE;
12187 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12188 DrawGameDoorValues();
12191 // check single step mode (set flag and clear again if any player is active)
12192 game.enter_single_step_mode =
12193 (tape.single_step && tape.recording && !tape.pausing);
12195 for (i = 0; i < MAX_PLAYERS; i++)
12197 int actual_player_action = stored_player[i].effective_action;
12200 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12201 - rnd_equinox_tetrachloride 048
12202 - rnd_equinox_tetrachloride_ii 096
12203 - rnd_emanuel_schmieg 002
12204 - doctor_sloan_ww 001, 020
12206 if (stored_player[i].MovPos == 0)
12207 CheckGravityMovement(&stored_player[i]);
12210 // overwrite programmed action with tape action
12211 if (stored_player[i].programmed_action)
12212 actual_player_action = stored_player[i].programmed_action;
12214 PlayerActions(&stored_player[i], actual_player_action);
12216 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12219 // single step pause mode may already have been toggled by "ScrollPlayer()"
12220 if (game.enter_single_step_mode && !tape.pausing)
12221 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12223 ScrollScreen(NULL, SCROLL_GO_ON);
12225 /* for backwards compatibility, the following code emulates a fixed bug that
12226 occured when pushing elements (causing elements that just made their last
12227 pushing step to already (if possible) make their first falling step in the
12228 same game frame, which is bad); this code is also needed to use the famous
12229 "spring push bug" which is used in older levels and might be wanted to be
12230 used also in newer levels, but in this case the buggy pushing code is only
12231 affecting the "spring" element and no other elements */
12233 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12235 for (i = 0; i < MAX_PLAYERS; i++)
12237 struct PlayerInfo *player = &stored_player[i];
12238 int x = player->jx;
12239 int y = player->jy;
12241 if (player->active && player->is_pushing && player->is_moving &&
12243 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12244 Tile[x][y] == EL_SPRING))
12246 ContinueMoving(x, y);
12248 // continue moving after pushing (this is actually a bug)
12249 if (!IS_MOVING(x, y))
12250 Stop[x][y] = FALSE;
12255 SCAN_PLAYFIELD(x, y)
12257 Last[x][y] = Tile[x][y];
12259 ChangeCount[x][y] = 0;
12260 ChangeEvent[x][y] = -1;
12262 // this must be handled before main playfield loop
12263 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12266 if (MovDelay[x][y] <= 0)
12270 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12273 if (MovDelay[x][y] <= 0)
12275 int element = Store[x][y];
12276 int move_direction = MovDir[x][y];
12277 int player_index_bit = Store2[x][y];
12283 TEST_DrawLevelField(x, y);
12285 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12287 if (IS_ENVELOPE(element))
12288 local_player->show_envelope = element;
12293 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12295 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12297 Debug("game:playing:GameActions_RND", "This should never happen!");
12299 ChangePage[x][y] = -1;
12303 Stop[x][y] = FALSE;
12304 if (WasJustMoving[x][y] > 0)
12305 WasJustMoving[x][y]--;
12306 if (WasJustFalling[x][y] > 0)
12307 WasJustFalling[x][y]--;
12308 if (CheckCollision[x][y] > 0)
12309 CheckCollision[x][y]--;
12310 if (CheckImpact[x][y] > 0)
12311 CheckImpact[x][y]--;
12315 /* reset finished pushing action (not done in ContinueMoving() to allow
12316 continuous pushing animation for elements with zero push delay) */
12317 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12319 ResetGfxAnimation(x, y);
12320 TEST_DrawLevelField(x, y);
12324 if (IS_BLOCKED(x, y))
12328 Blocked2Moving(x, y, &oldx, &oldy);
12329 if (!IS_MOVING(oldx, oldy))
12331 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12332 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12333 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12334 Debug("game:playing:GameActions_RND", "This should never happen!");
12340 if (mouse_action.button)
12342 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12343 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12345 x = mouse_action.lx;
12346 y = mouse_action.ly;
12347 element = Tile[x][y];
12351 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12352 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12356 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12357 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12360 if (level.use_step_counter)
12362 boolean counted_click = FALSE;
12364 // element clicked that can change when clicked/pressed
12365 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12366 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12367 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12368 counted_click = TRUE;
12370 // element clicked that can trigger change when clicked/pressed
12371 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12372 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12373 counted_click = TRUE;
12375 if (new_button && counted_click)
12376 CheckLevelTime_StepCounter();
12380 SCAN_PLAYFIELD(x, y)
12382 element = Tile[x][y];
12383 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12384 last_gfx_frame = GfxFrame[x][y];
12386 if (element == EL_EMPTY)
12387 graphic = el2img(GfxElementEmpty[x][y]);
12389 ResetGfxFrame(x, y);
12391 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12392 DrawLevelGraphicAnimation(x, y, graphic);
12394 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12395 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12396 ResetRandomAnimationValue(x, y);
12398 SetRandomAnimationValue(x, y);
12400 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12402 if (IS_INACTIVE(element))
12404 if (IS_ANIMATED(graphic))
12405 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12410 // this may take place after moving, so 'element' may have changed
12411 if (IS_CHANGING(x, y) &&
12412 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12414 int page = element_info[element].event_page_nr[CE_DELAY];
12416 HandleElementChange(x, y, page);
12418 element = Tile[x][y];
12419 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12422 CheckNextToConditions(x, y);
12424 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12428 element = Tile[x][y];
12429 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12431 if (IS_ANIMATED(graphic) &&
12432 !IS_MOVING(x, y) &&
12434 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12436 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12437 TEST_DrawTwinkleOnField(x, y);
12439 else if (element == EL_ACID)
12442 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12444 else if ((element == EL_EXIT_OPEN ||
12445 element == EL_EM_EXIT_OPEN ||
12446 element == EL_SP_EXIT_OPEN ||
12447 element == EL_STEEL_EXIT_OPEN ||
12448 element == EL_EM_STEEL_EXIT_OPEN ||
12449 element == EL_SP_TERMINAL ||
12450 element == EL_SP_TERMINAL_ACTIVE ||
12451 element == EL_EXTRA_TIME ||
12452 element == EL_SHIELD_NORMAL ||
12453 element == EL_SHIELD_DEADLY) &&
12454 IS_ANIMATED(graphic))
12455 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12456 else if (IS_MOVING(x, y))
12457 ContinueMoving(x, y);
12458 else if (IS_ACTIVE_BOMB(element))
12459 CheckDynamite(x, y);
12460 else if (element == EL_AMOEBA_GROWING)
12461 AmoebaGrowing(x, y);
12462 else if (element == EL_AMOEBA_SHRINKING)
12463 AmoebaShrinking(x, y);
12465 #if !USE_NEW_AMOEBA_CODE
12466 else if (IS_AMOEBALIVE(element))
12467 AmoebaReproduce(x, y);
12470 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12472 else if (element == EL_EXIT_CLOSED)
12474 else if (element == EL_EM_EXIT_CLOSED)
12476 else if (element == EL_STEEL_EXIT_CLOSED)
12477 CheckExitSteel(x, y);
12478 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12479 CheckExitSteelEM(x, y);
12480 else if (element == EL_SP_EXIT_CLOSED)
12482 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12483 element == EL_EXPANDABLE_STEELWALL_GROWING)
12484 MauerWaechst(x, y);
12485 else if (element == EL_EXPANDABLE_WALL ||
12486 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12487 element == EL_EXPANDABLE_WALL_VERTICAL ||
12488 element == EL_EXPANDABLE_WALL_ANY ||
12489 element == EL_BD_EXPANDABLE_WALL)
12490 MauerAbleger(x, y);
12491 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12492 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12493 element == EL_EXPANDABLE_STEELWALL_ANY)
12494 MauerAblegerStahl(x, y);
12495 else if (element == EL_FLAMES)
12496 CheckForDragon(x, y);
12497 else if (element == EL_EXPLOSION)
12498 ; // drawing of correct explosion animation is handled separately
12499 else if (element == EL_ELEMENT_SNAPPING ||
12500 element == EL_DIAGONAL_SHRINKING ||
12501 element == EL_DIAGONAL_GROWING)
12503 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12505 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12507 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12508 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12510 if (IS_BELT_ACTIVE(element))
12511 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12513 if (game.magic_wall_active)
12515 int jx = local_player->jx, jy = local_player->jy;
12517 // play the element sound at the position nearest to the player
12518 if ((element == EL_MAGIC_WALL_FULL ||
12519 element == EL_MAGIC_WALL_ACTIVE ||
12520 element == EL_MAGIC_WALL_EMPTYING ||
12521 element == EL_BD_MAGIC_WALL_FULL ||
12522 element == EL_BD_MAGIC_WALL_ACTIVE ||
12523 element == EL_BD_MAGIC_WALL_EMPTYING ||
12524 element == EL_DC_MAGIC_WALL_FULL ||
12525 element == EL_DC_MAGIC_WALL_ACTIVE ||
12526 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12527 ABS(x - jx) + ABS(y - jy) <
12528 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12536 #if USE_NEW_AMOEBA_CODE
12537 // new experimental amoeba growth stuff
12538 if (!(FrameCounter % 8))
12540 static unsigned int random = 1684108901;
12542 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12544 x = RND(lev_fieldx);
12545 y = RND(lev_fieldy);
12546 element = Tile[x][y];
12548 if (!IS_PLAYER(x,y) &&
12549 (element == EL_EMPTY ||
12550 CAN_GROW_INTO(element) ||
12551 element == EL_QUICKSAND_EMPTY ||
12552 element == EL_QUICKSAND_FAST_EMPTY ||
12553 element == EL_ACID_SPLASH_LEFT ||
12554 element == EL_ACID_SPLASH_RIGHT))
12556 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12557 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12558 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12559 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12560 Tile[x][y] = EL_AMOEBA_DROP;
12563 random = random * 129 + 1;
12568 game.explosions_delayed = FALSE;
12570 SCAN_PLAYFIELD(x, y)
12572 element = Tile[x][y];
12574 if (ExplodeField[x][y])
12575 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12576 else if (element == EL_EXPLOSION)
12577 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12579 ExplodeField[x][y] = EX_TYPE_NONE;
12582 game.explosions_delayed = TRUE;
12584 if (game.magic_wall_active)
12586 if (!(game.magic_wall_time_left % 4))
12588 int element = Tile[magic_wall_x][magic_wall_y];
12590 if (element == EL_BD_MAGIC_WALL_FULL ||
12591 element == EL_BD_MAGIC_WALL_ACTIVE ||
12592 element == EL_BD_MAGIC_WALL_EMPTYING)
12593 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12594 else if (element == EL_DC_MAGIC_WALL_FULL ||
12595 element == EL_DC_MAGIC_WALL_ACTIVE ||
12596 element == EL_DC_MAGIC_WALL_EMPTYING)
12597 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12599 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12602 if (game.magic_wall_time_left > 0)
12604 game.magic_wall_time_left--;
12606 if (!game.magic_wall_time_left)
12608 SCAN_PLAYFIELD(x, y)
12610 element = Tile[x][y];
12612 if (element == EL_MAGIC_WALL_ACTIVE ||
12613 element == EL_MAGIC_WALL_FULL)
12615 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12616 TEST_DrawLevelField(x, y);
12618 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12619 element == EL_BD_MAGIC_WALL_FULL)
12621 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12622 TEST_DrawLevelField(x, y);
12624 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12625 element == EL_DC_MAGIC_WALL_FULL)
12627 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12628 TEST_DrawLevelField(x, y);
12632 game.magic_wall_active = FALSE;
12637 if (game.light_time_left > 0)
12639 game.light_time_left--;
12641 if (game.light_time_left == 0)
12642 RedrawAllLightSwitchesAndInvisibleElements();
12645 if (game.timegate_time_left > 0)
12647 game.timegate_time_left--;
12649 if (game.timegate_time_left == 0)
12650 CloseAllOpenTimegates();
12653 if (game.lenses_time_left > 0)
12655 game.lenses_time_left--;
12657 if (game.lenses_time_left == 0)
12658 RedrawAllInvisibleElementsForLenses();
12661 if (game.magnify_time_left > 0)
12663 game.magnify_time_left--;
12665 if (game.magnify_time_left == 0)
12666 RedrawAllInvisibleElementsForMagnifier();
12669 for (i = 0; i < MAX_PLAYERS; i++)
12671 struct PlayerInfo *player = &stored_player[i];
12673 if (SHIELD_ON(player))
12675 if (player->shield_deadly_time_left)
12676 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12677 else if (player->shield_normal_time_left)
12678 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12682 #if USE_DELAYED_GFX_REDRAW
12683 SCAN_PLAYFIELD(x, y)
12685 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12687 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12688 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12690 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12691 DrawLevelField(x, y);
12693 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12694 DrawLevelFieldCrumbled(x, y);
12696 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12697 DrawLevelFieldCrumbledNeighbours(x, y);
12699 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12700 DrawTwinkleOnField(x, y);
12703 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12708 PlayAllPlayersSound();
12710 for (i = 0; i < MAX_PLAYERS; i++)
12712 struct PlayerInfo *player = &stored_player[i];
12714 if (player->show_envelope != 0 && (!player->active ||
12715 player->MovPos == 0))
12717 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12719 player->show_envelope = 0;
12723 // use random number generator in every frame to make it less predictable
12724 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12727 mouse_action_last = mouse_action;
12730 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12732 int min_x = x, min_y = y, max_x = x, max_y = y;
12733 int scr_fieldx = getScreenFieldSizeX();
12734 int scr_fieldy = getScreenFieldSizeY();
12737 for (i = 0; i < MAX_PLAYERS; i++)
12739 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12741 if (!stored_player[i].active || &stored_player[i] == player)
12744 min_x = MIN(min_x, jx);
12745 min_y = MIN(min_y, jy);
12746 max_x = MAX(max_x, jx);
12747 max_y = MAX(max_y, jy);
12750 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12753 static boolean AllPlayersInVisibleScreen(void)
12757 for (i = 0; i < MAX_PLAYERS; i++)
12759 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12761 if (!stored_player[i].active)
12764 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12771 void ScrollLevel(int dx, int dy)
12773 int scroll_offset = 2 * TILEX_VAR;
12776 BlitBitmap(drawto_field, drawto_field,
12777 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12778 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12779 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12780 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12781 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12782 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12786 x = (dx == 1 ? BX1 : BX2);
12787 for (y = BY1; y <= BY2; y++)
12788 DrawScreenField(x, y);
12793 y = (dy == 1 ? BY1 : BY2);
12794 for (x = BX1; x <= BX2; x++)
12795 DrawScreenField(x, y);
12798 redraw_mask |= REDRAW_FIELD;
12801 static boolean canFallDown(struct PlayerInfo *player)
12803 int jx = player->jx, jy = player->jy;
12805 return (IN_LEV_FIELD(jx, jy + 1) &&
12806 (IS_FREE(jx, jy + 1) ||
12807 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12808 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12809 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12812 static boolean canPassField(int x, int y, int move_dir)
12814 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12815 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12816 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12817 int nextx = x + dx;
12818 int nexty = y + dy;
12819 int element = Tile[x][y];
12821 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12822 !CAN_MOVE(element) &&
12823 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12824 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12825 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12828 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12830 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12831 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12832 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12836 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12837 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12838 (IS_DIGGABLE(Tile[newx][newy]) ||
12839 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12840 canPassField(newx, newy, move_dir)));
12843 static void CheckGravityMovement(struct PlayerInfo *player)
12845 if (player->gravity && !player->programmed_action)
12847 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12848 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12849 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12850 int jx = player->jx, jy = player->jy;
12851 boolean player_is_moving_to_valid_field =
12852 (!player_is_snapping &&
12853 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12854 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12855 boolean player_can_fall_down = canFallDown(player);
12857 if (player_can_fall_down &&
12858 !player_is_moving_to_valid_field)
12859 player->programmed_action = MV_DOWN;
12863 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12865 return CheckGravityMovement(player);
12867 if (player->gravity && !player->programmed_action)
12869 int jx = player->jx, jy = player->jy;
12870 boolean field_under_player_is_free =
12871 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12872 boolean player_is_standing_on_valid_field =
12873 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12874 (IS_WALKABLE(Tile[jx][jy]) &&
12875 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12877 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12878 player->programmed_action = MV_DOWN;
12883 MovePlayerOneStep()
12884 -----------------------------------------------------------------------------
12885 dx, dy: direction (non-diagonal) to try to move the player to
12886 real_dx, real_dy: direction as read from input device (can be diagonal)
12889 boolean MovePlayerOneStep(struct PlayerInfo *player,
12890 int dx, int dy, int real_dx, int real_dy)
12892 int jx = player->jx, jy = player->jy;
12893 int new_jx = jx + dx, new_jy = jy + dy;
12895 boolean player_can_move = !player->cannot_move;
12897 if (!player->active || (!dx && !dy))
12898 return MP_NO_ACTION;
12900 player->MovDir = (dx < 0 ? MV_LEFT :
12901 dx > 0 ? MV_RIGHT :
12903 dy > 0 ? MV_DOWN : MV_NONE);
12905 if (!IN_LEV_FIELD(new_jx, new_jy))
12906 return MP_NO_ACTION;
12908 if (!player_can_move)
12910 if (player->MovPos == 0)
12912 player->is_moving = FALSE;
12913 player->is_digging = FALSE;
12914 player->is_collecting = FALSE;
12915 player->is_snapping = FALSE;
12916 player->is_pushing = FALSE;
12920 if (!network.enabled && game.centered_player_nr == -1 &&
12921 !AllPlayersInSight(player, new_jx, new_jy))
12922 return MP_NO_ACTION;
12924 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12925 if (can_move != MP_MOVING)
12928 // check if DigField() has caused relocation of the player
12929 if (player->jx != jx || player->jy != jy)
12930 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12932 StorePlayer[jx][jy] = 0;
12933 player->last_jx = jx;
12934 player->last_jy = jy;
12935 player->jx = new_jx;
12936 player->jy = new_jy;
12937 StorePlayer[new_jx][new_jy] = player->element_nr;
12939 if (player->move_delay_value_next != -1)
12941 player->move_delay_value = player->move_delay_value_next;
12942 player->move_delay_value_next = -1;
12946 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12948 player->step_counter++;
12950 PlayerVisit[jx][jy] = FrameCounter;
12952 player->is_moving = TRUE;
12955 // should better be called in MovePlayer(), but this breaks some tapes
12956 ScrollPlayer(player, SCROLL_INIT);
12962 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12964 int jx = player->jx, jy = player->jy;
12965 int old_jx = jx, old_jy = jy;
12966 int moved = MP_NO_ACTION;
12968 if (!player->active)
12973 if (player->MovPos == 0)
12975 player->is_moving = FALSE;
12976 player->is_digging = FALSE;
12977 player->is_collecting = FALSE;
12978 player->is_snapping = FALSE;
12979 player->is_pushing = FALSE;
12985 if (player->move_delay > 0)
12988 player->move_delay = -1; // set to "uninitialized" value
12990 // store if player is automatically moved to next field
12991 player->is_auto_moving = (player->programmed_action != MV_NONE);
12993 // remove the last programmed player action
12994 player->programmed_action = 0;
12996 if (player->MovPos)
12998 // should only happen if pre-1.2 tape recordings are played
12999 // this is only for backward compatibility
13001 int original_move_delay_value = player->move_delay_value;
13004 Debug("game:playing:MovePlayer",
13005 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13009 // scroll remaining steps with finest movement resolution
13010 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13012 while (player->MovPos)
13014 ScrollPlayer(player, SCROLL_GO_ON);
13015 ScrollScreen(NULL, SCROLL_GO_ON);
13017 AdvanceFrameAndPlayerCounters(player->index_nr);
13020 BackToFront_WithFrameDelay(0);
13023 player->move_delay_value = original_move_delay_value;
13026 player->is_active = FALSE;
13028 if (player->last_move_dir & MV_HORIZONTAL)
13030 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13031 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13035 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13036 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13039 if (!moved && !player->is_active)
13041 player->is_moving = FALSE;
13042 player->is_digging = FALSE;
13043 player->is_collecting = FALSE;
13044 player->is_snapping = FALSE;
13045 player->is_pushing = FALSE;
13051 if (moved & MP_MOVING && !ScreenMovPos &&
13052 (player->index_nr == game.centered_player_nr ||
13053 game.centered_player_nr == -1))
13055 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13057 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13059 // actual player has left the screen -- scroll in that direction
13060 if (jx != old_jx) // player has moved horizontally
13061 scroll_x += (jx - old_jx);
13062 else // player has moved vertically
13063 scroll_y += (jy - old_jy);
13067 int offset_raw = game.scroll_delay_value;
13069 if (jx != old_jx) // player has moved horizontally
13071 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13072 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13073 int new_scroll_x = jx - MIDPOSX + offset_x;
13075 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13076 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13077 scroll_x = new_scroll_x;
13079 // don't scroll over playfield boundaries
13080 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13082 // don't scroll more than one field at a time
13083 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13085 // don't scroll against the player's moving direction
13086 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13087 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13088 scroll_x = old_scroll_x;
13090 else // player has moved vertically
13092 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13093 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13094 int new_scroll_y = jy - MIDPOSY + offset_y;
13096 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13097 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13098 scroll_y = new_scroll_y;
13100 // don't scroll over playfield boundaries
13101 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13103 // don't scroll more than one field at a time
13104 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13106 // don't scroll against the player's moving direction
13107 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13108 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13109 scroll_y = old_scroll_y;
13113 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13115 if (!network.enabled && game.centered_player_nr == -1 &&
13116 !AllPlayersInVisibleScreen())
13118 scroll_x = old_scroll_x;
13119 scroll_y = old_scroll_y;
13123 ScrollScreen(player, SCROLL_INIT);
13124 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13129 player->StepFrame = 0;
13131 if (moved & MP_MOVING)
13133 if (old_jx != jx && old_jy == jy)
13134 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13135 else if (old_jx == jx && old_jy != jy)
13136 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13138 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13140 player->last_move_dir = player->MovDir;
13141 player->is_moving = TRUE;
13142 player->is_snapping = FALSE;
13143 player->is_switching = FALSE;
13144 player->is_dropping = FALSE;
13145 player->is_dropping_pressed = FALSE;
13146 player->drop_pressed_delay = 0;
13149 // should better be called here than above, but this breaks some tapes
13150 ScrollPlayer(player, SCROLL_INIT);
13155 CheckGravityMovementWhenNotMoving(player);
13157 player->is_moving = FALSE;
13159 /* at this point, the player is allowed to move, but cannot move right now
13160 (e.g. because of something blocking the way) -- ensure that the player
13161 is also allowed to move in the next frame (in old versions before 3.1.1,
13162 the player was forced to wait again for eight frames before next try) */
13164 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13165 player->move_delay = 0; // allow direct movement in the next frame
13168 if (player->move_delay == -1) // not yet initialized by DigField()
13169 player->move_delay = player->move_delay_value;
13171 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13173 TestIfPlayerTouchesBadThing(jx, jy);
13174 TestIfPlayerTouchesCustomElement(jx, jy);
13177 if (!player->active)
13178 RemovePlayer(player);
13183 void ScrollPlayer(struct PlayerInfo *player, int mode)
13185 int jx = player->jx, jy = player->jy;
13186 int last_jx = player->last_jx, last_jy = player->last_jy;
13187 int move_stepsize = TILEX / player->move_delay_value;
13189 if (!player->active)
13192 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13195 if (mode == SCROLL_INIT)
13197 player->actual_frame_counter = FrameCounter;
13198 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13200 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13201 Tile[last_jx][last_jy] == EL_EMPTY)
13203 int last_field_block_delay = 0; // start with no blocking at all
13204 int block_delay_adjustment = player->block_delay_adjustment;
13206 // if player blocks last field, add delay for exactly one move
13207 if (player->block_last_field)
13209 last_field_block_delay += player->move_delay_value;
13211 // when blocking enabled, prevent moving up despite gravity
13212 if (player->gravity && player->MovDir == MV_UP)
13213 block_delay_adjustment = -1;
13216 // add block delay adjustment (also possible when not blocking)
13217 last_field_block_delay += block_delay_adjustment;
13219 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13220 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13223 if (player->MovPos != 0) // player has not yet reached destination
13226 else if (!FrameReached(&player->actual_frame_counter, 1))
13229 if (player->MovPos != 0)
13231 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13232 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13234 // before DrawPlayer() to draw correct player graphic for this case
13235 if (player->MovPos == 0)
13236 CheckGravityMovement(player);
13239 if (player->MovPos == 0) // player reached destination field
13241 if (player->move_delay_reset_counter > 0)
13243 player->move_delay_reset_counter--;
13245 if (player->move_delay_reset_counter == 0)
13247 // continue with normal speed after quickly moving through gate
13248 HALVE_PLAYER_SPEED(player);
13250 // be able to make the next move without delay
13251 player->move_delay = 0;
13255 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13256 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13257 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13258 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13259 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13260 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13261 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13262 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13264 ExitPlayer(player);
13266 if (game.players_still_needed == 0 &&
13267 (game.friends_still_needed == 0 ||
13268 IS_SP_ELEMENT(Tile[jx][jy])))
13272 player->last_jx = jx;
13273 player->last_jy = jy;
13275 // this breaks one level: "machine", level 000
13277 int move_direction = player->MovDir;
13278 int enter_side = MV_DIR_OPPOSITE(move_direction);
13279 int leave_side = move_direction;
13280 int old_jx = last_jx;
13281 int old_jy = last_jy;
13282 int old_element = Tile[old_jx][old_jy];
13283 int new_element = Tile[jx][jy];
13285 if (IS_CUSTOM_ELEMENT(old_element))
13286 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13288 player->index_bit, leave_side);
13290 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13291 CE_PLAYER_LEAVES_X,
13292 player->index_bit, leave_side);
13294 if (IS_CUSTOM_ELEMENT(new_element))
13295 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13296 player->index_bit, enter_side);
13298 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13299 CE_PLAYER_ENTERS_X,
13300 player->index_bit, enter_side);
13302 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13303 CE_MOVE_OF_X, move_direction);
13306 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13308 TestIfPlayerTouchesBadThing(jx, jy);
13309 TestIfPlayerTouchesCustomElement(jx, jy);
13311 /* needed because pushed element has not yet reached its destination,
13312 so it would trigger a change event at its previous field location */
13313 if (!player->is_pushing)
13314 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13316 if (level.finish_dig_collect &&
13317 (player->is_digging || player->is_collecting))
13319 int last_element = player->last_removed_element;
13320 int move_direction = player->MovDir;
13321 int enter_side = MV_DIR_OPPOSITE(move_direction);
13322 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13323 CE_PLAYER_COLLECTS_X);
13325 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13326 player->index_bit, enter_side);
13328 player->last_removed_element = EL_UNDEFINED;
13331 if (!player->active)
13332 RemovePlayer(player);
13335 if (level.use_step_counter)
13336 CheckLevelTime_StepCounter();
13338 if (tape.single_step && tape.recording && !tape.pausing &&
13339 !player->programmed_action)
13340 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13342 if (!player->programmed_action)
13343 CheckSaveEngineSnapshot(player);
13347 void ScrollScreen(struct PlayerInfo *player, int mode)
13349 static unsigned int screen_frame_counter = 0;
13351 if (mode == SCROLL_INIT)
13353 // set scrolling step size according to actual player's moving speed
13354 ScrollStepSize = TILEX / player->move_delay_value;
13356 screen_frame_counter = FrameCounter;
13357 ScreenMovDir = player->MovDir;
13358 ScreenMovPos = player->MovPos;
13359 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13362 else if (!FrameReached(&screen_frame_counter, 1))
13367 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13368 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13369 redraw_mask |= REDRAW_FIELD;
13372 ScreenMovDir = MV_NONE;
13375 void CheckNextToConditions(int x, int y)
13377 int element = Tile[x][y];
13379 if (IS_PLAYER(x, y))
13380 TestIfPlayerNextToCustomElement(x, y);
13382 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13383 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13384 TestIfElementNextToCustomElement(x, y);
13387 void TestIfPlayerNextToCustomElement(int x, int y)
13389 static int xy[4][2] =
13396 static int trigger_sides[4][2] =
13398 // center side border side
13399 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13400 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13401 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13402 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13406 if (!IS_PLAYER(x, y))
13409 struct PlayerInfo *player = PLAYERINFO(x, y);
13411 if (player->is_moving)
13414 for (i = 0; i < NUM_DIRECTIONS; i++)
13416 int xx = x + xy[i][0];
13417 int yy = y + xy[i][1];
13418 int border_side = trigger_sides[i][1];
13419 int border_element;
13421 if (!IN_LEV_FIELD(xx, yy))
13424 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13425 continue; // center and border element not connected
13427 border_element = Tile[xx][yy];
13429 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13430 player->index_bit, border_side);
13431 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13432 CE_PLAYER_NEXT_TO_X,
13433 player->index_bit, border_side);
13435 /* use player element that is initially defined in the level playfield,
13436 not the player element that corresponds to the runtime player number
13437 (example: a level that contains EL_PLAYER_3 as the only player would
13438 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13440 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13441 CE_NEXT_TO_X, border_side);
13445 void TestIfPlayerTouchesCustomElement(int x, int y)
13447 static int xy[4][2] =
13454 static int trigger_sides[4][2] =
13456 // center side border side
13457 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13458 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13459 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13460 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13462 static int touch_dir[4] =
13464 MV_LEFT | MV_RIGHT,
13469 int center_element = Tile[x][y]; // should always be non-moving!
13472 for (i = 0; i < NUM_DIRECTIONS; i++)
13474 int xx = x + xy[i][0];
13475 int yy = y + xy[i][1];
13476 int center_side = trigger_sides[i][0];
13477 int border_side = trigger_sides[i][1];
13478 int border_element;
13480 if (!IN_LEV_FIELD(xx, yy))
13483 if (IS_PLAYER(x, y)) // player found at center element
13485 struct PlayerInfo *player = PLAYERINFO(x, y);
13487 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13488 border_element = Tile[xx][yy]; // may be moving!
13489 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13490 border_element = Tile[xx][yy];
13491 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13492 border_element = MovingOrBlocked2Element(xx, yy);
13494 continue; // center and border element do not touch
13496 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13497 player->index_bit, border_side);
13498 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13499 CE_PLAYER_TOUCHES_X,
13500 player->index_bit, border_side);
13503 /* use player element that is initially defined in the level playfield,
13504 not the player element that corresponds to the runtime player number
13505 (example: a level that contains EL_PLAYER_3 as the only player would
13506 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13507 int player_element = PLAYERINFO(x, y)->initial_element;
13509 CheckElementChangeBySide(xx, yy, border_element, player_element,
13510 CE_TOUCHING_X, border_side);
13513 else if (IS_PLAYER(xx, yy)) // player found at border element
13515 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13517 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13519 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13520 continue; // center and border element do not touch
13523 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13524 player->index_bit, center_side);
13525 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13526 CE_PLAYER_TOUCHES_X,
13527 player->index_bit, center_side);
13530 /* use player element that is initially defined in the level playfield,
13531 not the player element that corresponds to the runtime player number
13532 (example: a level that contains EL_PLAYER_3 as the only player would
13533 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13534 int player_element = PLAYERINFO(xx, yy)->initial_element;
13536 CheckElementChangeBySide(x, y, center_element, player_element,
13537 CE_TOUCHING_X, center_side);
13545 void TestIfElementNextToCustomElement(int x, int y)
13547 static int xy[4][2] =
13554 static int trigger_sides[4][2] =
13556 // center side border side
13557 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13558 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13559 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13560 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13562 int center_element = Tile[x][y]; // should always be non-moving!
13565 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13568 for (i = 0; i < NUM_DIRECTIONS; i++)
13570 int xx = x + xy[i][0];
13571 int yy = y + xy[i][1];
13572 int border_side = trigger_sides[i][1];
13573 int border_element;
13575 if (!IN_LEV_FIELD(xx, yy))
13578 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13579 continue; // center and border element not connected
13581 border_element = Tile[xx][yy];
13583 // check for change of center element (but change it only once)
13584 if (CheckElementChangeBySide(x, y, center_element, border_element,
13585 CE_NEXT_TO_X, border_side))
13590 void TestIfElementTouchesCustomElement(int x, int y)
13592 static int xy[4][2] =
13599 static int trigger_sides[4][2] =
13601 // center side border side
13602 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13603 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13604 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13605 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13607 static int touch_dir[4] =
13609 MV_LEFT | MV_RIGHT,
13614 boolean change_center_element = FALSE;
13615 int center_element = Tile[x][y]; // should always be non-moving!
13616 int border_element_old[NUM_DIRECTIONS];
13619 for (i = 0; i < NUM_DIRECTIONS; i++)
13621 int xx = x + xy[i][0];
13622 int yy = y + xy[i][1];
13623 int border_element;
13625 border_element_old[i] = -1;
13627 if (!IN_LEV_FIELD(xx, yy))
13630 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13631 border_element = Tile[xx][yy]; // may be moving!
13632 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13633 border_element = Tile[xx][yy];
13634 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13635 border_element = MovingOrBlocked2Element(xx, yy);
13637 continue; // center and border element do not touch
13639 border_element_old[i] = border_element;
13642 for (i = 0; i < NUM_DIRECTIONS; i++)
13644 int xx = x + xy[i][0];
13645 int yy = y + xy[i][1];
13646 int center_side = trigger_sides[i][0];
13647 int border_element = border_element_old[i];
13649 if (border_element == -1)
13652 // check for change of border element
13653 CheckElementChangeBySide(xx, yy, border_element, center_element,
13654 CE_TOUCHING_X, center_side);
13656 // (center element cannot be player, so we dont have to check this here)
13659 for (i = 0; i < NUM_DIRECTIONS; i++)
13661 int xx = x + xy[i][0];
13662 int yy = y + xy[i][1];
13663 int border_side = trigger_sides[i][1];
13664 int border_element = border_element_old[i];
13666 if (border_element == -1)
13669 // check for change of center element (but change it only once)
13670 if (!change_center_element)
13671 change_center_element =
13672 CheckElementChangeBySide(x, y, center_element, border_element,
13673 CE_TOUCHING_X, border_side);
13675 if (IS_PLAYER(xx, yy))
13677 /* use player element that is initially defined in the level playfield,
13678 not the player element that corresponds to the runtime player number
13679 (example: a level that contains EL_PLAYER_3 as the only player would
13680 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13681 int player_element = PLAYERINFO(xx, yy)->initial_element;
13683 CheckElementChangeBySide(x, y, center_element, player_element,
13684 CE_TOUCHING_X, border_side);
13689 void TestIfElementHitsCustomElement(int x, int y, int direction)
13691 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13692 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13693 int hitx = x + dx, hity = y + dy;
13694 int hitting_element = Tile[x][y];
13695 int touched_element;
13697 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13700 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13701 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13703 if (IN_LEV_FIELD(hitx, hity))
13705 int opposite_direction = MV_DIR_OPPOSITE(direction);
13706 int hitting_side = direction;
13707 int touched_side = opposite_direction;
13708 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13709 MovDir[hitx][hity] != direction ||
13710 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13716 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13717 CE_HITTING_X, touched_side);
13719 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13720 CE_HIT_BY_X, hitting_side);
13722 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13723 CE_HIT_BY_SOMETHING, opposite_direction);
13725 if (IS_PLAYER(hitx, hity))
13727 /* use player element that is initially defined in the level playfield,
13728 not the player element that corresponds to the runtime player number
13729 (example: a level that contains EL_PLAYER_3 as the only player would
13730 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13731 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13733 CheckElementChangeBySide(x, y, hitting_element, player_element,
13734 CE_HITTING_X, touched_side);
13739 // "hitting something" is also true when hitting the playfield border
13740 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13741 CE_HITTING_SOMETHING, direction);
13744 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13746 int i, kill_x = -1, kill_y = -1;
13748 int bad_element = -1;
13749 static int test_xy[4][2] =
13756 static int test_dir[4] =
13764 for (i = 0; i < NUM_DIRECTIONS; i++)
13766 int test_x, test_y, test_move_dir, test_element;
13768 test_x = good_x + test_xy[i][0];
13769 test_y = good_y + test_xy[i][1];
13771 if (!IN_LEV_FIELD(test_x, test_y))
13775 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13777 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13779 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13780 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13782 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13783 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13787 bad_element = test_element;
13793 if (kill_x != -1 || kill_y != -1)
13795 if (IS_PLAYER(good_x, good_y))
13797 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13799 if (player->shield_deadly_time_left > 0 &&
13800 !IS_INDESTRUCTIBLE(bad_element))
13801 Bang(kill_x, kill_y);
13802 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13803 KillPlayer(player);
13806 Bang(good_x, good_y);
13810 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13812 int i, kill_x = -1, kill_y = -1;
13813 int bad_element = Tile[bad_x][bad_y];
13814 static int test_xy[4][2] =
13821 static int touch_dir[4] =
13823 MV_LEFT | MV_RIGHT,
13828 static int test_dir[4] =
13836 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13839 for (i = 0; i < NUM_DIRECTIONS; i++)
13841 int test_x, test_y, test_move_dir, test_element;
13843 test_x = bad_x + test_xy[i][0];
13844 test_y = bad_y + test_xy[i][1];
13846 if (!IN_LEV_FIELD(test_x, test_y))
13850 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13852 test_element = Tile[test_x][test_y];
13854 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13855 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13857 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13858 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13860 // good thing is player or penguin that does not move away
13861 if (IS_PLAYER(test_x, test_y))
13863 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13865 if (bad_element == EL_ROBOT && player->is_moving)
13866 continue; // robot does not kill player if he is moving
13868 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13870 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13871 continue; // center and border element do not touch
13879 else if (test_element == EL_PENGUIN)
13889 if (kill_x != -1 || kill_y != -1)
13891 if (IS_PLAYER(kill_x, kill_y))
13893 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13895 if (player->shield_deadly_time_left > 0 &&
13896 !IS_INDESTRUCTIBLE(bad_element))
13897 Bang(bad_x, bad_y);
13898 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13899 KillPlayer(player);
13902 Bang(kill_x, kill_y);
13906 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13908 int bad_element = Tile[bad_x][bad_y];
13909 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13910 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13911 int test_x = bad_x + dx, test_y = bad_y + dy;
13912 int test_move_dir, test_element;
13913 int kill_x = -1, kill_y = -1;
13915 if (!IN_LEV_FIELD(test_x, test_y))
13919 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13921 test_element = Tile[test_x][test_y];
13923 if (test_move_dir != bad_move_dir)
13925 // good thing can be player or penguin that does not move away
13926 if (IS_PLAYER(test_x, test_y))
13928 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13930 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13931 player as being hit when he is moving towards the bad thing, because
13932 the "get hit by" condition would be lost after the player stops) */
13933 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13934 return; // player moves away from bad thing
13939 else if (test_element == EL_PENGUIN)
13946 if (kill_x != -1 || kill_y != -1)
13948 if (IS_PLAYER(kill_x, kill_y))
13950 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13952 if (player->shield_deadly_time_left > 0 &&
13953 !IS_INDESTRUCTIBLE(bad_element))
13954 Bang(bad_x, bad_y);
13955 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13956 KillPlayer(player);
13959 Bang(kill_x, kill_y);
13963 void TestIfPlayerTouchesBadThing(int x, int y)
13965 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13968 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13970 TestIfGoodThingHitsBadThing(x, y, move_dir);
13973 void TestIfBadThingTouchesPlayer(int x, int y)
13975 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13978 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13980 TestIfBadThingHitsGoodThing(x, y, move_dir);
13983 void TestIfFriendTouchesBadThing(int x, int y)
13985 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13988 void TestIfBadThingTouchesFriend(int x, int y)
13990 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13993 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13995 int i, kill_x = bad_x, kill_y = bad_y;
13996 static int xy[4][2] =
14004 for (i = 0; i < NUM_DIRECTIONS; i++)
14008 x = bad_x + xy[i][0];
14009 y = bad_y + xy[i][1];
14010 if (!IN_LEV_FIELD(x, y))
14013 element = Tile[x][y];
14014 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14015 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14023 if (kill_x != bad_x || kill_y != bad_y)
14024 Bang(bad_x, bad_y);
14027 void KillPlayer(struct PlayerInfo *player)
14029 int jx = player->jx, jy = player->jy;
14031 if (!player->active)
14035 Debug("game:playing:KillPlayer",
14036 "0: killed == %d, active == %d, reanimated == %d",
14037 player->killed, player->active, player->reanimated);
14040 /* the following code was introduced to prevent an infinite loop when calling
14042 -> CheckTriggeredElementChangeExt()
14043 -> ExecuteCustomElementAction()
14045 -> (infinitely repeating the above sequence of function calls)
14046 which occurs when killing the player while having a CE with the setting
14047 "kill player X when explosion of <player X>"; the solution using a new
14048 field "player->killed" was chosen for backwards compatibility, although
14049 clever use of the fields "player->active" etc. would probably also work */
14051 if (player->killed)
14055 player->killed = TRUE;
14057 // remove accessible field at the player's position
14058 Tile[jx][jy] = EL_EMPTY;
14060 // deactivate shield (else Bang()/Explode() would not work right)
14061 player->shield_normal_time_left = 0;
14062 player->shield_deadly_time_left = 0;
14065 Debug("game:playing:KillPlayer",
14066 "1: killed == %d, active == %d, reanimated == %d",
14067 player->killed, player->active, player->reanimated);
14073 Debug("game:playing:KillPlayer",
14074 "2: killed == %d, active == %d, reanimated == %d",
14075 player->killed, player->active, player->reanimated);
14078 if (player->reanimated) // killed player may have been reanimated
14079 player->killed = player->reanimated = FALSE;
14081 BuryPlayer(player);
14084 static void KillPlayerUnlessEnemyProtected(int x, int y)
14086 if (!PLAYER_ENEMY_PROTECTED(x, y))
14087 KillPlayer(PLAYERINFO(x, y));
14090 static void KillPlayerUnlessExplosionProtected(int x, int y)
14092 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14093 KillPlayer(PLAYERINFO(x, y));
14096 void BuryPlayer(struct PlayerInfo *player)
14098 int jx = player->jx, jy = player->jy;
14100 if (!player->active)
14103 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14104 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14106 RemovePlayer(player);
14108 player->buried = TRUE;
14110 if (game.all_players_gone)
14111 game.GameOver = TRUE;
14114 void RemovePlayer(struct PlayerInfo *player)
14116 int jx = player->jx, jy = player->jy;
14117 int i, found = FALSE;
14119 player->present = FALSE;
14120 player->active = FALSE;
14122 // required for some CE actions (even if the player is not active anymore)
14123 player->MovPos = 0;
14125 if (!ExplodeField[jx][jy])
14126 StorePlayer[jx][jy] = 0;
14128 if (player->is_moving)
14129 TEST_DrawLevelField(player->last_jx, player->last_jy);
14131 for (i = 0; i < MAX_PLAYERS; i++)
14132 if (stored_player[i].active)
14137 game.all_players_gone = TRUE;
14138 game.GameOver = TRUE;
14141 game.exit_x = game.robot_wheel_x = jx;
14142 game.exit_y = game.robot_wheel_y = jy;
14145 void ExitPlayer(struct PlayerInfo *player)
14147 DrawPlayer(player); // needed here only to cleanup last field
14148 RemovePlayer(player);
14150 if (game.players_still_needed > 0)
14151 game.players_still_needed--;
14154 static void SetFieldForSnapping(int x, int y, int element, int direction,
14155 int player_index_bit)
14157 struct ElementInfo *ei = &element_info[element];
14158 int direction_bit = MV_DIR_TO_BIT(direction);
14159 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14160 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14161 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14163 Tile[x][y] = EL_ELEMENT_SNAPPING;
14164 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14165 MovDir[x][y] = direction;
14166 Store[x][y] = element;
14167 Store2[x][y] = player_index_bit;
14169 ResetGfxAnimation(x, y);
14171 GfxElement[x][y] = element;
14172 GfxAction[x][y] = action;
14173 GfxDir[x][y] = direction;
14174 GfxFrame[x][y] = -1;
14177 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14178 int player_index_bit)
14180 TestIfElementTouchesCustomElement(x, y); // for empty space
14182 if (level.finish_dig_collect)
14184 int dig_side = MV_DIR_OPPOSITE(direction);
14185 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14186 CE_PLAYER_COLLECTS_X);
14188 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14189 player_index_bit, dig_side);
14190 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14191 player_index_bit, dig_side);
14196 =============================================================================
14197 checkDiagonalPushing()
14198 -----------------------------------------------------------------------------
14199 check if diagonal input device direction results in pushing of object
14200 (by checking if the alternative direction is walkable, diggable, ...)
14201 =============================================================================
14204 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14205 int x, int y, int real_dx, int real_dy)
14207 int jx, jy, dx, dy, xx, yy;
14209 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14212 // diagonal direction: check alternative direction
14217 xx = jx + (dx == 0 ? real_dx : 0);
14218 yy = jy + (dy == 0 ? real_dy : 0);
14220 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14224 =============================================================================
14226 -----------------------------------------------------------------------------
14227 x, y: field next to player (non-diagonal) to try to dig to
14228 real_dx, real_dy: direction as read from input device (can be diagonal)
14229 =============================================================================
14232 static int DigField(struct PlayerInfo *player,
14233 int oldx, int oldy, int x, int y,
14234 int real_dx, int real_dy, int mode)
14236 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14237 boolean player_was_pushing = player->is_pushing;
14238 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14239 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14240 int jx = oldx, jy = oldy;
14241 int dx = x - jx, dy = y - jy;
14242 int nextx = x + dx, nexty = y + dy;
14243 int move_direction = (dx == -1 ? MV_LEFT :
14244 dx == +1 ? MV_RIGHT :
14246 dy == +1 ? MV_DOWN : MV_NONE);
14247 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14248 int dig_side = MV_DIR_OPPOSITE(move_direction);
14249 int old_element = Tile[jx][jy];
14250 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14253 if (is_player) // function can also be called by EL_PENGUIN
14255 if (player->MovPos == 0)
14257 player->is_digging = FALSE;
14258 player->is_collecting = FALSE;
14261 if (player->MovPos == 0) // last pushing move finished
14262 player->is_pushing = FALSE;
14264 if (mode == DF_NO_PUSH) // player just stopped pushing
14266 player->is_switching = FALSE;
14267 player->push_delay = -1;
14269 return MP_NO_ACTION;
14272 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14273 old_element = Back[jx][jy];
14275 // in case of element dropped at player position, check background
14276 else if (Back[jx][jy] != EL_EMPTY &&
14277 game.engine_version >= VERSION_IDENT(2,2,0,0))
14278 old_element = Back[jx][jy];
14280 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14281 return MP_NO_ACTION; // field has no opening in this direction
14283 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14284 return MP_NO_ACTION; // field has no opening in this direction
14286 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14290 Tile[jx][jy] = player->artwork_element;
14291 InitMovingField(jx, jy, MV_DOWN);
14292 Store[jx][jy] = EL_ACID;
14293 ContinueMoving(jx, jy);
14294 BuryPlayer(player);
14296 return MP_DONT_RUN_INTO;
14299 if (player_can_move && DONT_RUN_INTO(element))
14301 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14303 return MP_DONT_RUN_INTO;
14306 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14307 return MP_NO_ACTION;
14309 collect_count = element_info[element].collect_count_initial;
14311 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14312 return MP_NO_ACTION;
14314 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14315 player_can_move = player_can_move_or_snap;
14317 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14318 game.engine_version >= VERSION_IDENT(2,2,0,0))
14320 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14321 player->index_bit, dig_side);
14322 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14323 player->index_bit, dig_side);
14325 if (element == EL_DC_LANDMINE)
14328 if (Tile[x][y] != element) // field changed by snapping
14331 return MP_NO_ACTION;
14334 if (player->gravity && is_player && !player->is_auto_moving &&
14335 canFallDown(player) && move_direction != MV_DOWN &&
14336 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14337 return MP_NO_ACTION; // player cannot walk here due to gravity
14339 if (player_can_move &&
14340 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14342 int sound_element = SND_ELEMENT(element);
14343 int sound_action = ACTION_WALKING;
14345 if (IS_RND_GATE(element))
14347 if (!player->key[RND_GATE_NR(element)])
14348 return MP_NO_ACTION;
14350 else if (IS_RND_GATE_GRAY(element))
14352 if (!player->key[RND_GATE_GRAY_NR(element)])
14353 return MP_NO_ACTION;
14355 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14357 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14358 return MP_NO_ACTION;
14360 else if (element == EL_EXIT_OPEN ||
14361 element == EL_EM_EXIT_OPEN ||
14362 element == EL_EM_EXIT_OPENING ||
14363 element == EL_STEEL_EXIT_OPEN ||
14364 element == EL_EM_STEEL_EXIT_OPEN ||
14365 element == EL_EM_STEEL_EXIT_OPENING ||
14366 element == EL_SP_EXIT_OPEN ||
14367 element == EL_SP_EXIT_OPENING)
14369 sound_action = ACTION_PASSING; // player is passing exit
14371 else if (element == EL_EMPTY)
14373 sound_action = ACTION_MOVING; // nothing to walk on
14376 // play sound from background or player, whatever is available
14377 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14378 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14380 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14382 else if (player_can_move &&
14383 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14385 if (!ACCESS_FROM(element, opposite_direction))
14386 return MP_NO_ACTION; // field not accessible from this direction
14388 if (CAN_MOVE(element)) // only fixed elements can be passed!
14389 return MP_NO_ACTION;
14391 if (IS_EM_GATE(element))
14393 if (!player->key[EM_GATE_NR(element)])
14394 return MP_NO_ACTION;
14396 else if (IS_EM_GATE_GRAY(element))
14398 if (!player->key[EM_GATE_GRAY_NR(element)])
14399 return MP_NO_ACTION;
14401 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14403 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14404 return MP_NO_ACTION;
14406 else if (IS_EMC_GATE(element))
14408 if (!player->key[EMC_GATE_NR(element)])
14409 return MP_NO_ACTION;
14411 else if (IS_EMC_GATE_GRAY(element))
14413 if (!player->key[EMC_GATE_GRAY_NR(element)])
14414 return MP_NO_ACTION;
14416 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14418 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14419 return MP_NO_ACTION;
14421 else if (element == EL_DC_GATE_WHITE ||
14422 element == EL_DC_GATE_WHITE_GRAY ||
14423 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14425 if (player->num_white_keys == 0)
14426 return MP_NO_ACTION;
14428 player->num_white_keys--;
14430 else if (IS_SP_PORT(element))
14432 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14433 element == EL_SP_GRAVITY_PORT_RIGHT ||
14434 element == EL_SP_GRAVITY_PORT_UP ||
14435 element == EL_SP_GRAVITY_PORT_DOWN)
14436 player->gravity = !player->gravity;
14437 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14438 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14439 element == EL_SP_GRAVITY_ON_PORT_UP ||
14440 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14441 player->gravity = TRUE;
14442 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14443 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14444 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14445 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14446 player->gravity = FALSE;
14449 // automatically move to the next field with double speed
14450 player->programmed_action = move_direction;
14452 if (player->move_delay_reset_counter == 0)
14454 player->move_delay_reset_counter = 2; // two double speed steps
14456 DOUBLE_PLAYER_SPEED(player);
14459 PlayLevelSoundAction(x, y, ACTION_PASSING);
14461 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14465 if (mode != DF_SNAP)
14467 GfxElement[x][y] = GFX_ELEMENT(element);
14468 player->is_digging = TRUE;
14471 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14473 // use old behaviour for old levels (digging)
14474 if (!level.finish_dig_collect)
14476 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14477 player->index_bit, dig_side);
14479 // if digging triggered player relocation, finish digging tile
14480 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14481 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14484 if (mode == DF_SNAP)
14486 if (level.block_snap_field)
14487 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14489 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14491 // use old behaviour for old levels (snapping)
14492 if (!level.finish_dig_collect)
14493 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14494 player->index_bit, dig_side);
14497 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14501 if (is_player && mode != DF_SNAP)
14503 GfxElement[x][y] = element;
14504 player->is_collecting = TRUE;
14507 if (element == EL_SPEED_PILL)
14509 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14511 else if (element == EL_EXTRA_TIME && level.time > 0)
14513 TimeLeft += level.extra_time;
14515 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14517 DisplayGameControlValues();
14519 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14521 player->shield_normal_time_left += level.shield_normal_time;
14522 if (element == EL_SHIELD_DEADLY)
14523 player->shield_deadly_time_left += level.shield_deadly_time;
14525 else if (element == EL_DYNAMITE ||
14526 element == EL_EM_DYNAMITE ||
14527 element == EL_SP_DISK_RED)
14529 if (player->inventory_size < MAX_INVENTORY_SIZE)
14530 player->inventory_element[player->inventory_size++] = element;
14532 DrawGameDoorValues();
14534 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14536 player->dynabomb_count++;
14537 player->dynabombs_left++;
14539 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14541 player->dynabomb_size++;
14543 else if (element == EL_DYNABOMB_INCREASE_POWER)
14545 player->dynabomb_xl = TRUE;
14547 else if (IS_KEY(element))
14549 player->key[KEY_NR(element)] = TRUE;
14551 DrawGameDoorValues();
14553 else if (element == EL_DC_KEY_WHITE)
14555 player->num_white_keys++;
14557 // display white keys?
14558 // DrawGameDoorValues();
14560 else if (IS_ENVELOPE(element))
14562 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14564 if (!wait_for_snapping)
14565 player->show_envelope = element;
14567 else if (element == EL_EMC_LENSES)
14569 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14571 RedrawAllInvisibleElementsForLenses();
14573 else if (element == EL_EMC_MAGNIFIER)
14575 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14577 RedrawAllInvisibleElementsForMagnifier();
14579 else if (IS_DROPPABLE(element) ||
14580 IS_THROWABLE(element)) // can be collected and dropped
14584 if (collect_count == 0)
14585 player->inventory_infinite_element = element;
14587 for (i = 0; i < collect_count; i++)
14588 if (player->inventory_size < MAX_INVENTORY_SIZE)
14589 player->inventory_element[player->inventory_size++] = element;
14591 DrawGameDoorValues();
14593 else if (collect_count > 0)
14595 game.gems_still_needed -= collect_count;
14596 if (game.gems_still_needed < 0)
14597 game.gems_still_needed = 0;
14599 game.snapshot.collected_item = TRUE;
14601 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14603 DisplayGameControlValues();
14606 RaiseScoreElement(element);
14607 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14609 // use old behaviour for old levels (collecting)
14610 if (!level.finish_dig_collect && is_player)
14612 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14613 player->index_bit, dig_side);
14615 // if collecting triggered player relocation, finish collecting tile
14616 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14617 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14620 if (mode == DF_SNAP)
14622 if (level.block_snap_field)
14623 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14625 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14627 // use old behaviour for old levels (snapping)
14628 if (!level.finish_dig_collect)
14629 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14630 player->index_bit, dig_side);
14633 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14635 if (mode == DF_SNAP && element != EL_BD_ROCK)
14636 return MP_NO_ACTION;
14638 if (CAN_FALL(element) && dy)
14639 return MP_NO_ACTION;
14641 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14642 !(element == EL_SPRING && level.use_spring_bug))
14643 return MP_NO_ACTION;
14645 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14646 ((move_direction & MV_VERTICAL &&
14647 ((element_info[element].move_pattern & MV_LEFT &&
14648 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14649 (element_info[element].move_pattern & MV_RIGHT &&
14650 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14651 (move_direction & MV_HORIZONTAL &&
14652 ((element_info[element].move_pattern & MV_UP &&
14653 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14654 (element_info[element].move_pattern & MV_DOWN &&
14655 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14656 return MP_NO_ACTION;
14658 // do not push elements already moving away faster than player
14659 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14660 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14661 return MP_NO_ACTION;
14663 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14665 if (player->push_delay_value == -1 || !player_was_pushing)
14666 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14668 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14670 if (player->push_delay_value == -1)
14671 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14673 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14675 if (!player->is_pushing)
14676 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14679 player->is_pushing = TRUE;
14680 player->is_active = TRUE;
14682 if (!(IN_LEV_FIELD(nextx, nexty) &&
14683 (IS_FREE(nextx, nexty) ||
14684 (IS_SB_ELEMENT(element) &&
14685 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14686 (IS_CUSTOM_ELEMENT(element) &&
14687 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14688 return MP_NO_ACTION;
14690 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14691 return MP_NO_ACTION;
14693 if (player->push_delay == -1) // new pushing; restart delay
14694 player->push_delay = 0;
14696 if (player->push_delay < player->push_delay_value &&
14697 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14698 element != EL_SPRING && element != EL_BALLOON)
14700 // make sure that there is no move delay before next try to push
14701 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14702 player->move_delay = 0;
14704 return MP_NO_ACTION;
14707 if (IS_CUSTOM_ELEMENT(element) &&
14708 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14710 if (!DigFieldByCE(nextx, nexty, element))
14711 return MP_NO_ACTION;
14714 if (IS_SB_ELEMENT(element))
14716 boolean sokoban_task_solved = FALSE;
14718 if (element == EL_SOKOBAN_FIELD_FULL)
14720 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14722 IncrementSokobanFieldsNeeded();
14723 IncrementSokobanObjectsNeeded();
14726 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14728 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14730 DecrementSokobanFieldsNeeded();
14731 DecrementSokobanObjectsNeeded();
14733 // sokoban object was pushed from empty field to sokoban field
14734 if (Back[x][y] == EL_EMPTY)
14735 sokoban_task_solved = TRUE;
14738 Tile[x][y] = EL_SOKOBAN_OBJECT;
14740 if (Back[x][y] == Back[nextx][nexty])
14741 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14742 else if (Back[x][y] != 0)
14743 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14746 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14749 if (sokoban_task_solved &&
14750 game.sokoban_fields_still_needed == 0 &&
14751 game.sokoban_objects_still_needed == 0 &&
14752 level.auto_exit_sokoban)
14754 game.players_still_needed = 0;
14758 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14762 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14764 InitMovingField(x, y, move_direction);
14765 GfxAction[x][y] = ACTION_PUSHING;
14767 if (mode == DF_SNAP)
14768 ContinueMoving(x, y);
14770 MovPos[x][y] = (dx != 0 ? dx : dy);
14772 Pushed[x][y] = TRUE;
14773 Pushed[nextx][nexty] = TRUE;
14775 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14776 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14778 player->push_delay_value = -1; // get new value later
14780 // check for element change _after_ element has been pushed
14781 if (game.use_change_when_pushing_bug)
14783 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14784 player->index_bit, dig_side);
14785 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14786 player->index_bit, dig_side);
14789 else if (IS_SWITCHABLE(element))
14791 if (PLAYER_SWITCHING(player, x, y))
14793 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14794 player->index_bit, dig_side);
14799 player->is_switching = TRUE;
14800 player->switch_x = x;
14801 player->switch_y = y;
14803 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14805 if (element == EL_ROBOT_WHEEL)
14807 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14809 game.robot_wheel_x = x;
14810 game.robot_wheel_y = y;
14811 game.robot_wheel_active = TRUE;
14813 TEST_DrawLevelField(x, y);
14815 else if (element == EL_SP_TERMINAL)
14819 SCAN_PLAYFIELD(xx, yy)
14821 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14825 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14827 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14829 ResetGfxAnimation(xx, yy);
14830 TEST_DrawLevelField(xx, yy);
14834 else if (IS_BELT_SWITCH(element))
14836 ToggleBeltSwitch(x, y);
14838 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14839 element == EL_SWITCHGATE_SWITCH_DOWN ||
14840 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14841 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14843 ToggleSwitchgateSwitch(x, y);
14845 else if (element == EL_LIGHT_SWITCH ||
14846 element == EL_LIGHT_SWITCH_ACTIVE)
14848 ToggleLightSwitch(x, y);
14850 else if (element == EL_TIMEGATE_SWITCH ||
14851 element == EL_DC_TIMEGATE_SWITCH)
14853 ActivateTimegateSwitch(x, y);
14855 else if (element == EL_BALLOON_SWITCH_LEFT ||
14856 element == EL_BALLOON_SWITCH_RIGHT ||
14857 element == EL_BALLOON_SWITCH_UP ||
14858 element == EL_BALLOON_SWITCH_DOWN ||
14859 element == EL_BALLOON_SWITCH_NONE ||
14860 element == EL_BALLOON_SWITCH_ANY)
14862 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14863 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14864 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14865 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14866 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14869 else if (element == EL_LAMP)
14871 Tile[x][y] = EL_LAMP_ACTIVE;
14872 game.lights_still_needed--;
14874 ResetGfxAnimation(x, y);
14875 TEST_DrawLevelField(x, y);
14877 else if (element == EL_TIME_ORB_FULL)
14879 Tile[x][y] = EL_TIME_ORB_EMPTY;
14881 if (level.time > 0 || level.use_time_orb_bug)
14883 TimeLeft += level.time_orb_time;
14884 game.no_time_limit = FALSE;
14886 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14888 DisplayGameControlValues();
14891 ResetGfxAnimation(x, y);
14892 TEST_DrawLevelField(x, y);
14894 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14895 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14899 game.ball_active = !game.ball_active;
14901 SCAN_PLAYFIELD(xx, yy)
14903 int e = Tile[xx][yy];
14905 if (game.ball_active)
14907 if (e == EL_EMC_MAGIC_BALL)
14908 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14909 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14910 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14914 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14915 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14916 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14917 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14922 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14923 player->index_bit, dig_side);
14925 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14926 player->index_bit, dig_side);
14928 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14929 player->index_bit, dig_side);
14935 if (!PLAYER_SWITCHING(player, x, y))
14937 player->is_switching = TRUE;
14938 player->switch_x = x;
14939 player->switch_y = y;
14941 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14942 player->index_bit, dig_side);
14943 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14944 player->index_bit, dig_side);
14946 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14947 player->index_bit, dig_side);
14948 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14949 player->index_bit, dig_side);
14952 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14953 player->index_bit, dig_side);
14954 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14955 player->index_bit, dig_side);
14957 return MP_NO_ACTION;
14960 player->push_delay = -1;
14962 if (is_player) // function can also be called by EL_PENGUIN
14964 if (Tile[x][y] != element) // really digged/collected something
14966 player->is_collecting = !player->is_digging;
14967 player->is_active = TRUE;
14969 player->last_removed_element = element;
14976 static boolean DigFieldByCE(int x, int y, int digging_element)
14978 int element = Tile[x][y];
14980 if (!IS_FREE(x, y))
14982 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14983 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14986 // no element can dig solid indestructible elements
14987 if (IS_INDESTRUCTIBLE(element) &&
14988 !IS_DIGGABLE(element) &&
14989 !IS_COLLECTIBLE(element))
14992 if (AmoebaNr[x][y] &&
14993 (element == EL_AMOEBA_FULL ||
14994 element == EL_BD_AMOEBA ||
14995 element == EL_AMOEBA_GROWING))
14997 AmoebaCnt[AmoebaNr[x][y]]--;
14998 AmoebaCnt2[AmoebaNr[x][y]]--;
15001 if (IS_MOVING(x, y))
15002 RemoveMovingField(x, y);
15006 TEST_DrawLevelField(x, y);
15009 // if digged element was about to explode, prevent the explosion
15010 ExplodeField[x][y] = EX_TYPE_NONE;
15012 PlayLevelSoundAction(x, y, action);
15015 Store[x][y] = EL_EMPTY;
15017 // this makes it possible to leave the removed element again
15018 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15019 Store[x][y] = element;
15024 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15026 int jx = player->jx, jy = player->jy;
15027 int x = jx + dx, y = jy + dy;
15028 int snap_direction = (dx == -1 ? MV_LEFT :
15029 dx == +1 ? MV_RIGHT :
15031 dy == +1 ? MV_DOWN : MV_NONE);
15032 boolean can_continue_snapping = (level.continuous_snapping &&
15033 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15035 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15038 if (!player->active || !IN_LEV_FIELD(x, y))
15046 if (player->MovPos == 0)
15047 player->is_pushing = FALSE;
15049 player->is_snapping = FALSE;
15051 if (player->MovPos == 0)
15053 player->is_moving = FALSE;
15054 player->is_digging = FALSE;
15055 player->is_collecting = FALSE;
15061 // prevent snapping with already pressed snap key when not allowed
15062 if (player->is_snapping && !can_continue_snapping)
15065 player->MovDir = snap_direction;
15067 if (player->MovPos == 0)
15069 player->is_moving = FALSE;
15070 player->is_digging = FALSE;
15071 player->is_collecting = FALSE;
15074 player->is_dropping = FALSE;
15075 player->is_dropping_pressed = FALSE;
15076 player->drop_pressed_delay = 0;
15078 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15081 player->is_snapping = TRUE;
15082 player->is_active = TRUE;
15084 if (player->MovPos == 0)
15086 player->is_moving = FALSE;
15087 player->is_digging = FALSE;
15088 player->is_collecting = FALSE;
15091 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15092 TEST_DrawLevelField(player->last_jx, player->last_jy);
15094 TEST_DrawLevelField(x, y);
15099 static boolean DropElement(struct PlayerInfo *player)
15101 int old_element, new_element;
15102 int dropx = player->jx, dropy = player->jy;
15103 int drop_direction = player->MovDir;
15104 int drop_side = drop_direction;
15105 int drop_element = get_next_dropped_element(player);
15107 /* do not drop an element on top of another element; when holding drop key
15108 pressed without moving, dropped element must move away before the next
15109 element can be dropped (this is especially important if the next element
15110 is dynamite, which can be placed on background for historical reasons) */
15111 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15114 if (IS_THROWABLE(drop_element))
15116 dropx += GET_DX_FROM_DIR(drop_direction);
15117 dropy += GET_DY_FROM_DIR(drop_direction);
15119 if (!IN_LEV_FIELD(dropx, dropy))
15123 old_element = Tile[dropx][dropy]; // old element at dropping position
15124 new_element = drop_element; // default: no change when dropping
15126 // check if player is active, not moving and ready to drop
15127 if (!player->active || player->MovPos || player->drop_delay > 0)
15130 // check if player has anything that can be dropped
15131 if (new_element == EL_UNDEFINED)
15134 // only set if player has anything that can be dropped
15135 player->is_dropping_pressed = TRUE;
15137 // check if drop key was pressed long enough for EM style dynamite
15138 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15141 // check if anything can be dropped at the current position
15142 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15145 // collected custom elements can only be dropped on empty fields
15146 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15149 if (old_element != EL_EMPTY)
15150 Back[dropx][dropy] = old_element; // store old element on this field
15152 ResetGfxAnimation(dropx, dropy);
15153 ResetRandomAnimationValue(dropx, dropy);
15155 if (player->inventory_size > 0 ||
15156 player->inventory_infinite_element != EL_UNDEFINED)
15158 if (player->inventory_size > 0)
15160 player->inventory_size--;
15162 DrawGameDoorValues();
15164 if (new_element == EL_DYNAMITE)
15165 new_element = EL_DYNAMITE_ACTIVE;
15166 else if (new_element == EL_EM_DYNAMITE)
15167 new_element = EL_EM_DYNAMITE_ACTIVE;
15168 else if (new_element == EL_SP_DISK_RED)
15169 new_element = EL_SP_DISK_RED_ACTIVE;
15172 Tile[dropx][dropy] = new_element;
15174 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15175 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15176 el2img(Tile[dropx][dropy]), 0);
15178 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15180 // needed if previous element just changed to "empty" in the last frame
15181 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15183 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15184 player->index_bit, drop_side);
15185 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15187 player->index_bit, drop_side);
15189 TestIfElementTouchesCustomElement(dropx, dropy);
15191 else // player is dropping a dyna bomb
15193 player->dynabombs_left--;
15195 Tile[dropx][dropy] = new_element;
15197 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15198 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15199 el2img(Tile[dropx][dropy]), 0);
15201 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15204 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15205 InitField_WithBug1(dropx, dropy, FALSE);
15207 new_element = Tile[dropx][dropy]; // element might have changed
15209 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15210 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15212 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15213 MovDir[dropx][dropy] = drop_direction;
15215 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15217 // do not cause impact style collision by dropping elements that can fall
15218 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15221 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15222 player->is_dropping = TRUE;
15224 player->drop_pressed_delay = 0;
15225 player->is_dropping_pressed = FALSE;
15227 player->drop_x = dropx;
15228 player->drop_y = dropy;
15233 // ----------------------------------------------------------------------------
15234 // game sound playing functions
15235 // ----------------------------------------------------------------------------
15237 static int *loop_sound_frame = NULL;
15238 static int *loop_sound_volume = NULL;
15240 void InitPlayLevelSound(void)
15242 int num_sounds = getSoundListSize();
15244 checked_free(loop_sound_frame);
15245 checked_free(loop_sound_volume);
15247 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15248 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15251 static void PlayLevelSound(int x, int y, int nr)
15253 int sx = SCREENX(x), sy = SCREENY(y);
15254 int volume, stereo_position;
15255 int max_distance = 8;
15256 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15258 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15259 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15262 if (!IN_LEV_FIELD(x, y) ||
15263 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15264 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15267 volume = SOUND_MAX_VOLUME;
15269 if (!IN_SCR_FIELD(sx, sy))
15271 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15272 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15274 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15277 stereo_position = (SOUND_MAX_LEFT +
15278 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15279 (SCR_FIELDX + 2 * max_distance));
15281 if (IS_LOOP_SOUND(nr))
15283 /* This assures that quieter loop sounds do not overwrite louder ones,
15284 while restarting sound volume comparison with each new game frame. */
15286 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15289 loop_sound_volume[nr] = volume;
15290 loop_sound_frame[nr] = FrameCounter;
15293 PlaySoundExt(nr, volume, stereo_position, type);
15296 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15298 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15299 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15300 y < LEVELY(BY1) ? LEVELY(BY1) :
15301 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15305 static void PlayLevelSoundAction(int x, int y, int action)
15307 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15310 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15312 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15314 if (sound_effect != SND_UNDEFINED)
15315 PlayLevelSound(x, y, sound_effect);
15318 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15321 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15323 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15324 PlayLevelSound(x, y, sound_effect);
15327 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15329 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15331 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15332 PlayLevelSound(x, y, sound_effect);
15335 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15337 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15339 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15340 StopSound(sound_effect);
15343 static int getLevelMusicNr(void)
15345 if (levelset.music[level_nr] != MUS_UNDEFINED)
15346 return levelset.music[level_nr]; // from config file
15348 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15351 static void FadeLevelSounds(void)
15356 static void FadeLevelMusic(void)
15358 int music_nr = getLevelMusicNr();
15359 char *curr_music = getCurrentlyPlayingMusicFilename();
15360 char *next_music = getMusicInfoEntryFilename(music_nr);
15362 if (!strEqual(curr_music, next_music))
15366 void FadeLevelSoundsAndMusic(void)
15372 static void PlayLevelMusic(void)
15374 int music_nr = getLevelMusicNr();
15375 char *curr_music = getCurrentlyPlayingMusicFilename();
15376 char *next_music = getMusicInfoEntryFilename(music_nr);
15378 if (!strEqual(curr_music, next_music))
15379 PlayMusicLoop(music_nr);
15382 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15384 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15386 int x = xx - offset;
15387 int y = yy - offset;
15392 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15396 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15400 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15404 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15408 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15412 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15416 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15419 case SOUND_android_clone:
15420 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15423 case SOUND_android_move:
15424 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15428 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15432 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15436 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15439 case SOUND_eater_eat:
15440 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15444 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15447 case SOUND_collect:
15448 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15451 case SOUND_diamond:
15452 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15456 // !!! CHECK THIS !!!
15458 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15460 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15464 case SOUND_wonderfall:
15465 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15469 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15473 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15477 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15481 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15485 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15489 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15493 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15497 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15500 case SOUND_exit_open:
15501 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15504 case SOUND_exit_leave:
15505 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15508 case SOUND_dynamite:
15509 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15513 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15517 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15521 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15525 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15529 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15533 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15537 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15542 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15544 int element = map_element_SP_to_RND(element_sp);
15545 int action = map_action_SP_to_RND(action_sp);
15546 int offset = (setup.sp_show_border_elements ? 0 : 1);
15547 int x = xx - offset;
15548 int y = yy - offset;
15550 PlayLevelSoundElementAction(x, y, element, action);
15553 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15555 int element = map_element_MM_to_RND(element_mm);
15556 int action = map_action_MM_to_RND(action_mm);
15558 int x = xx - offset;
15559 int y = yy - offset;
15561 if (!IS_MM_ELEMENT(element))
15562 element = EL_MM_DEFAULT;
15564 PlayLevelSoundElementAction(x, y, element, action);
15567 void PlaySound_MM(int sound_mm)
15569 int sound = map_sound_MM_to_RND(sound_mm);
15571 if (sound == SND_UNDEFINED)
15577 void PlaySoundLoop_MM(int sound_mm)
15579 int sound = map_sound_MM_to_RND(sound_mm);
15581 if (sound == SND_UNDEFINED)
15584 PlaySoundLoop(sound);
15587 void StopSound_MM(int sound_mm)
15589 int sound = map_sound_MM_to_RND(sound_mm);
15591 if (sound == SND_UNDEFINED)
15597 void RaiseScore(int value)
15599 game.score += value;
15601 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15603 DisplayGameControlValues();
15606 void RaiseScoreElement(int element)
15611 case EL_BD_DIAMOND:
15612 case EL_EMERALD_YELLOW:
15613 case EL_EMERALD_RED:
15614 case EL_EMERALD_PURPLE:
15615 case EL_SP_INFOTRON:
15616 RaiseScore(level.score[SC_EMERALD]);
15619 RaiseScore(level.score[SC_DIAMOND]);
15622 RaiseScore(level.score[SC_CRYSTAL]);
15625 RaiseScore(level.score[SC_PEARL]);
15628 case EL_BD_BUTTERFLY:
15629 case EL_SP_ELECTRON:
15630 RaiseScore(level.score[SC_BUG]);
15633 case EL_BD_FIREFLY:
15634 case EL_SP_SNIKSNAK:
15635 RaiseScore(level.score[SC_SPACESHIP]);
15638 case EL_DARK_YAMYAM:
15639 RaiseScore(level.score[SC_YAMYAM]);
15642 RaiseScore(level.score[SC_ROBOT]);
15645 RaiseScore(level.score[SC_PACMAN]);
15648 RaiseScore(level.score[SC_NUT]);
15651 case EL_EM_DYNAMITE:
15652 case EL_SP_DISK_RED:
15653 case EL_DYNABOMB_INCREASE_NUMBER:
15654 case EL_DYNABOMB_INCREASE_SIZE:
15655 case EL_DYNABOMB_INCREASE_POWER:
15656 RaiseScore(level.score[SC_DYNAMITE]);
15658 case EL_SHIELD_NORMAL:
15659 case EL_SHIELD_DEADLY:
15660 RaiseScore(level.score[SC_SHIELD]);
15662 case EL_EXTRA_TIME:
15663 RaiseScore(level.extra_time_score);
15677 case EL_DC_KEY_WHITE:
15678 RaiseScore(level.score[SC_KEY]);
15681 RaiseScore(element_info[element].collect_score);
15686 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15688 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15692 // prevent short reactivation of overlay buttons while closing door
15693 SetOverlayActive(FALSE);
15695 // door may still be open due to skipped or envelope style request
15696 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15699 if (network.enabled)
15700 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15704 FadeSkipNextFadeIn();
15706 SetGameStatus(GAME_MODE_MAIN);
15711 else // continue playing the game
15713 if (tape.playing && tape.deactivate_display)
15714 TapeDeactivateDisplayOff(TRUE);
15716 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15718 if (tape.playing && tape.deactivate_display)
15719 TapeDeactivateDisplayOn();
15723 void RequestQuitGame(boolean escape_key_pressed)
15725 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15726 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15727 level_editor_test_game);
15728 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15729 quick_quit || score_info_tape_play);
15731 RequestQuitGameExt(skip_request, quick_quit,
15732 "Do you really want to quit the game?");
15735 void RequestRestartGame(char *message)
15737 game.restart_game_message = NULL;
15739 boolean has_started_game = hasStartedNetworkGame();
15740 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15742 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15744 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15748 // needed in case of envelope request to close game panel
15749 CloseDoor(DOOR_CLOSE_1);
15751 SetGameStatus(GAME_MODE_MAIN);
15757 void CheckGameOver(void)
15759 static boolean last_game_over = FALSE;
15760 static int game_over_delay = 0;
15761 int game_over_delay_value = 50;
15762 boolean game_over = checkGameFailed();
15764 // do not handle game over if request dialog is already active
15765 if (game.request_active)
15768 // do not ask to play again if game was never actually played
15769 if (!game.GamePlayed)
15774 last_game_over = FALSE;
15775 game_over_delay = game_over_delay_value;
15780 if (game_over_delay > 0)
15787 if (last_game_over != game_over)
15788 game.restart_game_message = (hasStartedNetworkGame() ?
15789 "Game over! Play it again?" :
15792 last_game_over = game_over;
15795 boolean checkGameSolved(void)
15797 // set for all game engines if level was solved
15798 return game.LevelSolved_GameEnd;
15801 boolean checkGameFailed(void)
15803 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15804 return (game_em.game_over && !game_em.level_solved);
15805 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15806 return (game_sp.game_over && !game_sp.level_solved);
15807 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15808 return (game_mm.game_over && !game_mm.level_solved);
15809 else // GAME_ENGINE_TYPE_RND
15810 return (game.GameOver && !game.LevelSolved);
15813 boolean checkGameEnded(void)
15815 return (checkGameSolved() || checkGameFailed());
15819 // ----------------------------------------------------------------------------
15820 // random generator functions
15821 // ----------------------------------------------------------------------------
15823 unsigned int InitEngineRandom_RND(int seed)
15825 game.num_random_calls = 0;
15827 return InitEngineRandom(seed);
15830 unsigned int RND(int max)
15834 game.num_random_calls++;
15836 return GetEngineRandom(max);
15843 // ----------------------------------------------------------------------------
15844 // game engine snapshot handling functions
15845 // ----------------------------------------------------------------------------
15847 struct EngineSnapshotInfo
15849 // runtime values for custom element collect score
15850 int collect_score[NUM_CUSTOM_ELEMENTS];
15852 // runtime values for group element choice position
15853 int choice_pos[NUM_GROUP_ELEMENTS];
15855 // runtime values for belt position animations
15856 int belt_graphic[4][NUM_BELT_PARTS];
15857 int belt_anim_mode[4][NUM_BELT_PARTS];
15860 static struct EngineSnapshotInfo engine_snapshot_rnd;
15861 static char *snapshot_level_identifier = NULL;
15862 static int snapshot_level_nr = -1;
15864 static void SaveEngineSnapshotValues_RND(void)
15866 static int belt_base_active_element[4] =
15868 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15869 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15870 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15871 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15875 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15877 int element = EL_CUSTOM_START + i;
15879 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15882 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15884 int element = EL_GROUP_START + i;
15886 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15889 for (i = 0; i < 4; i++)
15891 for (j = 0; j < NUM_BELT_PARTS; j++)
15893 int element = belt_base_active_element[i] + j;
15894 int graphic = el2img(element);
15895 int anim_mode = graphic_info[graphic].anim_mode;
15897 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15898 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15903 static void LoadEngineSnapshotValues_RND(void)
15905 unsigned int num_random_calls = game.num_random_calls;
15908 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15910 int element = EL_CUSTOM_START + i;
15912 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15915 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15917 int element = EL_GROUP_START + i;
15919 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15922 for (i = 0; i < 4; i++)
15924 for (j = 0; j < NUM_BELT_PARTS; j++)
15926 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15927 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15929 graphic_info[graphic].anim_mode = anim_mode;
15933 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15935 InitRND(tape.random_seed);
15936 for (i = 0; i < num_random_calls; i++)
15940 if (game.num_random_calls != num_random_calls)
15942 Error("number of random calls out of sync");
15943 Error("number of random calls should be %d", num_random_calls);
15944 Error("number of random calls is %d", game.num_random_calls);
15946 Fail("this should not happen -- please debug");
15950 void FreeEngineSnapshotSingle(void)
15952 FreeSnapshotSingle();
15954 setString(&snapshot_level_identifier, NULL);
15955 snapshot_level_nr = -1;
15958 void FreeEngineSnapshotList(void)
15960 FreeSnapshotList();
15963 static ListNode *SaveEngineSnapshotBuffers(void)
15965 ListNode *buffers = NULL;
15967 // copy some special values to a structure better suited for the snapshot
15969 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15970 SaveEngineSnapshotValues_RND();
15971 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15972 SaveEngineSnapshotValues_EM();
15973 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15974 SaveEngineSnapshotValues_SP(&buffers);
15975 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15976 SaveEngineSnapshotValues_MM(&buffers);
15978 // save values stored in special snapshot structure
15980 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15981 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15982 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15983 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15984 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15985 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15986 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15987 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15989 // save further RND engine values
15991 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15993 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15995 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
15999 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16001 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16003 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16005 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16007 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16008 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16010 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16011 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16012 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16014 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16024 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16026 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16029 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16030 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16032 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16034 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16036 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16039 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16040 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16044 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16046 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16050 ListNode *node = engine_snapshot_list_rnd;
16053 while (node != NULL)
16055 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16060 Debug("game:playing:SaveEngineSnapshotBuffers",
16061 "size of engine snapshot: %d bytes", num_bytes);
16067 void SaveEngineSnapshotSingle(void)
16069 ListNode *buffers = SaveEngineSnapshotBuffers();
16071 // finally save all snapshot buffers to single snapshot
16072 SaveSnapshotSingle(buffers);
16074 // save level identification information
16075 setString(&snapshot_level_identifier, leveldir_current->identifier);
16076 snapshot_level_nr = level_nr;
16079 boolean CheckSaveEngineSnapshotToList(void)
16081 boolean save_snapshot =
16082 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16083 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16084 game.snapshot.changed_action) ||
16085 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16086 game.snapshot.collected_item));
16088 game.snapshot.changed_action = FALSE;
16089 game.snapshot.collected_item = FALSE;
16090 game.snapshot.save_snapshot = save_snapshot;
16092 return save_snapshot;
16095 void SaveEngineSnapshotToList(void)
16097 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16101 ListNode *buffers = SaveEngineSnapshotBuffers();
16103 // finally save all snapshot buffers to snapshot list
16104 SaveSnapshotToList(buffers);
16107 void SaveEngineSnapshotToListInitial(void)
16109 FreeEngineSnapshotList();
16111 SaveEngineSnapshotToList();
16114 static void LoadEngineSnapshotValues(void)
16116 // restore special values from snapshot structure
16118 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16119 LoadEngineSnapshotValues_RND();
16120 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16121 LoadEngineSnapshotValues_EM();
16122 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16123 LoadEngineSnapshotValues_SP();
16124 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16125 LoadEngineSnapshotValues_MM();
16128 void LoadEngineSnapshotSingle(void)
16130 LoadSnapshotSingle();
16132 LoadEngineSnapshotValues();
16135 static void LoadEngineSnapshot_Undo(int steps)
16137 LoadSnapshotFromList_Older(steps);
16139 LoadEngineSnapshotValues();
16142 static void LoadEngineSnapshot_Redo(int steps)
16144 LoadSnapshotFromList_Newer(steps);
16146 LoadEngineSnapshotValues();
16149 boolean CheckEngineSnapshotSingle(void)
16151 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16152 snapshot_level_nr == level_nr);
16155 boolean CheckEngineSnapshotList(void)
16157 return CheckSnapshotList();
16161 // ---------- new game button stuff -------------------------------------------
16168 boolean *setup_value;
16169 boolean allowed_on_tape;
16170 boolean is_touch_button;
16172 } gamebutton_info[NUM_GAME_BUTTONS] =
16175 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16176 GAME_CTRL_ID_STOP, NULL,
16177 TRUE, FALSE, "stop game"
16180 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16181 GAME_CTRL_ID_PAUSE, NULL,
16182 TRUE, FALSE, "pause game"
16185 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16186 GAME_CTRL_ID_PLAY, NULL,
16187 TRUE, FALSE, "play game"
16190 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16191 GAME_CTRL_ID_UNDO, NULL,
16192 TRUE, FALSE, "undo step"
16195 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16196 GAME_CTRL_ID_REDO, NULL,
16197 TRUE, FALSE, "redo step"
16200 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16201 GAME_CTRL_ID_SAVE, NULL,
16202 TRUE, FALSE, "save game"
16205 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16206 GAME_CTRL_ID_PAUSE2, NULL,
16207 TRUE, FALSE, "pause game"
16210 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16211 GAME_CTRL_ID_LOAD, NULL,
16212 TRUE, FALSE, "load game"
16215 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16216 GAME_CTRL_ID_PANEL_STOP, NULL,
16217 FALSE, FALSE, "stop game"
16220 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16221 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16222 FALSE, FALSE, "pause game"
16225 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16226 GAME_CTRL_ID_PANEL_PLAY, NULL,
16227 FALSE, FALSE, "play game"
16230 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16231 GAME_CTRL_ID_TOUCH_STOP, NULL,
16232 FALSE, TRUE, "stop game"
16235 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16236 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16237 FALSE, TRUE, "pause game"
16240 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16241 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16242 TRUE, FALSE, "background music on/off"
16245 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16246 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16247 TRUE, FALSE, "sound loops on/off"
16250 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16251 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16252 TRUE, FALSE, "normal sounds on/off"
16255 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16256 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16257 FALSE, FALSE, "background music on/off"
16260 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16261 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16262 FALSE, FALSE, "sound loops on/off"
16265 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16266 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16267 FALSE, FALSE, "normal sounds on/off"
16271 void CreateGameButtons(void)
16275 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16277 int graphic = gamebutton_info[i].graphic;
16278 struct GraphicInfo *gfx = &graphic_info[graphic];
16279 struct XY *pos = gamebutton_info[i].pos;
16280 struct GadgetInfo *gi;
16283 unsigned int event_mask;
16284 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16285 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16286 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16287 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16288 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16289 int gd_x = gfx->src_x;
16290 int gd_y = gfx->src_y;
16291 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16292 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16293 int gd_xa = gfx->src_x + gfx->active_xoffset;
16294 int gd_ya = gfx->src_y + gfx->active_yoffset;
16295 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16296 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16297 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16298 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16301 if (gfx->bitmap == NULL)
16303 game_gadget[id] = NULL;
16308 if (id == GAME_CTRL_ID_STOP ||
16309 id == GAME_CTRL_ID_PANEL_STOP ||
16310 id == GAME_CTRL_ID_TOUCH_STOP ||
16311 id == GAME_CTRL_ID_PLAY ||
16312 id == GAME_CTRL_ID_PANEL_PLAY ||
16313 id == GAME_CTRL_ID_SAVE ||
16314 id == GAME_CTRL_ID_LOAD)
16316 button_type = GD_TYPE_NORMAL_BUTTON;
16318 event_mask = GD_EVENT_RELEASED;
16320 else if (id == GAME_CTRL_ID_UNDO ||
16321 id == GAME_CTRL_ID_REDO)
16323 button_type = GD_TYPE_NORMAL_BUTTON;
16325 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16329 button_type = GD_TYPE_CHECK_BUTTON;
16330 checked = (gamebutton_info[i].setup_value != NULL ?
16331 *gamebutton_info[i].setup_value : FALSE);
16332 event_mask = GD_EVENT_PRESSED;
16335 gi = CreateGadget(GDI_CUSTOM_ID, id,
16336 GDI_IMAGE_ID, graphic,
16337 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16340 GDI_WIDTH, gfx->width,
16341 GDI_HEIGHT, gfx->height,
16342 GDI_TYPE, button_type,
16343 GDI_STATE, GD_BUTTON_UNPRESSED,
16344 GDI_CHECKED, checked,
16345 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16346 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16347 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16348 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16349 GDI_DIRECT_DRAW, FALSE,
16350 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16351 GDI_EVENT_MASK, event_mask,
16352 GDI_CALLBACK_ACTION, HandleGameButtons,
16356 Fail("cannot create gadget");
16358 game_gadget[id] = gi;
16362 void FreeGameButtons(void)
16366 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16367 FreeGadget(game_gadget[i]);
16370 static void UnmapGameButtonsAtSamePosition(int id)
16374 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16376 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16377 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16378 UnmapGadget(game_gadget[i]);
16381 static void UnmapGameButtonsAtSamePosition_All(void)
16383 if (setup.show_load_save_buttons)
16385 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16386 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16387 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16389 else if (setup.show_undo_redo_buttons)
16391 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16392 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16393 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16397 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16398 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16399 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16401 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16402 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16403 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16407 void MapLoadSaveButtons(void)
16409 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16410 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16412 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16413 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16416 void MapUndoRedoButtons(void)
16418 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16419 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16421 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16422 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16425 void ModifyPauseButtons(void)
16429 GAME_CTRL_ID_PAUSE,
16430 GAME_CTRL_ID_PAUSE2,
16431 GAME_CTRL_ID_PANEL_PAUSE,
16432 GAME_CTRL_ID_TOUCH_PAUSE,
16437 for (i = 0; ids[i] > -1; i++)
16438 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16441 static void MapGameButtonsExt(boolean on_tape)
16445 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16447 if ((i == GAME_CTRL_ID_UNDO ||
16448 i == GAME_CTRL_ID_REDO) &&
16449 game_status != GAME_MODE_PLAYING)
16452 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16453 MapGadget(game_gadget[i]);
16456 UnmapGameButtonsAtSamePosition_All();
16458 RedrawGameButtons();
16461 static void UnmapGameButtonsExt(boolean on_tape)
16465 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16466 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16467 UnmapGadget(game_gadget[i]);
16470 static void RedrawGameButtonsExt(boolean on_tape)
16474 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16475 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16476 RedrawGadget(game_gadget[i]);
16479 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16484 gi->checked = state;
16487 static void RedrawSoundButtonGadget(int id)
16489 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16490 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16491 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16492 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16493 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16494 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16497 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16498 RedrawGadget(game_gadget[id2]);
16501 void MapGameButtons(void)
16503 MapGameButtonsExt(FALSE);
16506 void UnmapGameButtons(void)
16508 UnmapGameButtonsExt(FALSE);
16511 void RedrawGameButtons(void)
16513 RedrawGameButtonsExt(FALSE);
16516 void MapGameButtonsOnTape(void)
16518 MapGameButtonsExt(TRUE);
16521 void UnmapGameButtonsOnTape(void)
16523 UnmapGameButtonsExt(TRUE);
16526 void RedrawGameButtonsOnTape(void)
16528 RedrawGameButtonsExt(TRUE);
16531 static void GameUndoRedoExt(void)
16533 ClearPlayerAction();
16535 tape.pausing = TRUE;
16538 UpdateAndDisplayGameControlValues();
16540 DrawCompleteVideoDisplay();
16541 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16542 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16543 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16545 ModifyPauseButtons();
16550 static void GameUndo(int steps)
16552 if (!CheckEngineSnapshotList())
16555 int tape_property_bits = tape.property_bits;
16557 LoadEngineSnapshot_Undo(steps);
16559 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16564 static void GameRedo(int steps)
16566 if (!CheckEngineSnapshotList())
16569 int tape_property_bits = tape.property_bits;
16571 LoadEngineSnapshot_Redo(steps);
16573 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16578 static void HandleGameButtonsExt(int id, int button)
16580 static boolean game_undo_executed = FALSE;
16581 int steps = BUTTON_STEPSIZE(button);
16582 boolean handle_game_buttons =
16583 (game_status == GAME_MODE_PLAYING ||
16584 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16586 if (!handle_game_buttons)
16591 case GAME_CTRL_ID_STOP:
16592 case GAME_CTRL_ID_PANEL_STOP:
16593 case GAME_CTRL_ID_TOUCH_STOP:
16598 case GAME_CTRL_ID_PAUSE:
16599 case GAME_CTRL_ID_PAUSE2:
16600 case GAME_CTRL_ID_PANEL_PAUSE:
16601 case GAME_CTRL_ID_TOUCH_PAUSE:
16602 if (network.enabled && game_status == GAME_MODE_PLAYING)
16605 SendToServer_ContinuePlaying();
16607 SendToServer_PausePlaying();
16610 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16612 game_undo_executed = FALSE;
16616 case GAME_CTRL_ID_PLAY:
16617 case GAME_CTRL_ID_PANEL_PLAY:
16618 if (game_status == GAME_MODE_MAIN)
16620 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16622 else if (tape.pausing)
16624 if (network.enabled)
16625 SendToServer_ContinuePlaying();
16627 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16631 case GAME_CTRL_ID_UNDO:
16632 // Important: When using "save snapshot when collecting an item" mode,
16633 // load last (current) snapshot for first "undo" after pressing "pause"
16634 // (else the last-but-one snapshot would be loaded, because the snapshot
16635 // pointer already points to the last snapshot when pressing "pause",
16636 // which is fine for "every step/move" mode, but not for "every collect")
16637 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16638 !game_undo_executed)
16641 game_undo_executed = TRUE;
16646 case GAME_CTRL_ID_REDO:
16650 case GAME_CTRL_ID_SAVE:
16654 case GAME_CTRL_ID_LOAD:
16658 case SOUND_CTRL_ID_MUSIC:
16659 case SOUND_CTRL_ID_PANEL_MUSIC:
16660 if (setup.sound_music)
16662 setup.sound_music = FALSE;
16666 else if (audio.music_available)
16668 setup.sound = setup.sound_music = TRUE;
16670 SetAudioMode(setup.sound);
16672 if (game_status == GAME_MODE_PLAYING)
16676 RedrawSoundButtonGadget(id);
16680 case SOUND_CTRL_ID_LOOPS:
16681 case SOUND_CTRL_ID_PANEL_LOOPS:
16682 if (setup.sound_loops)
16683 setup.sound_loops = FALSE;
16684 else if (audio.loops_available)
16686 setup.sound = setup.sound_loops = TRUE;
16688 SetAudioMode(setup.sound);
16691 RedrawSoundButtonGadget(id);
16695 case SOUND_CTRL_ID_SIMPLE:
16696 case SOUND_CTRL_ID_PANEL_SIMPLE:
16697 if (setup.sound_simple)
16698 setup.sound_simple = FALSE;
16699 else if (audio.sound_available)
16701 setup.sound = setup.sound_simple = TRUE;
16703 SetAudioMode(setup.sound);
16706 RedrawSoundButtonGadget(id);
16715 static void HandleGameButtons(struct GadgetInfo *gi)
16717 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16720 void HandleSoundButtonKeys(Key key)
16722 if (key == setup.shortcut.sound_simple)
16723 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16724 else if (key == setup.shortcut.sound_loops)
16725 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16726 else if (key == setup.shortcut.sound_music)
16727 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);