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 scores.continue_playing = TRUE;
5054 scores.next_level_nr = level_nr;
5056 LoadLevel(level_nr);
5058 SaveLevelSetup_SeriesInfo();
5062 if (scores.last_added >= 0 && setup.show_scores_after_game)
5064 SetGameStatus(GAME_MODE_SCORES);
5066 DrawHallOfFame(last_level_nr);
5068 else if (scores.continue_playing)
5070 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
5074 SetGameStatus(GAME_MODE_MAIN);
5080 static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
5081 boolean one_score_entry_per_name)
5085 if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
5088 for (i = 0; i < MAX_SCORE_ENTRIES; i++)
5090 struct ScoreEntry *entry = &list->entry[i];
5091 boolean score_is_better = (new_entry->score > entry->score);
5092 boolean score_is_equal = (new_entry->score == entry->score);
5093 boolean time_is_better = (new_entry->time < entry->time);
5094 boolean time_is_equal = (new_entry->time == entry->time);
5095 boolean better_by_score = (score_is_better ||
5096 (score_is_equal && time_is_better));
5097 boolean better_by_time = (time_is_better ||
5098 (time_is_equal && score_is_better));
5099 boolean is_better = (level.rate_time_over_score ? better_by_time :
5101 boolean entry_is_empty = (entry->score == 0 &&
5104 // prevent adding server score entries if also existing in local score file
5105 // (special case: historic score entries have an empty tape basename entry)
5106 if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
5107 !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
5109 // special case: use server score instead of local score value if higher
5110 // (historic scores might have been truncated to 16-bit values locally)
5111 if (score_is_better)
5112 entry->score = new_entry->score;
5117 if (is_better || entry_is_empty)
5119 // player has made it to the hall of fame
5121 if (i < MAX_SCORE_ENTRIES - 1)
5123 int m = MAX_SCORE_ENTRIES - 1;
5126 if (one_score_entry_per_name)
5128 for (l = i; l < MAX_SCORE_ENTRIES; l++)
5129 if (strEqual(list->entry[l].name, new_entry->name))
5132 if (m == i) // player's new highscore overwrites his old one
5136 for (l = m; l > i; l--)
5137 list->entry[l] = list->entry[l - 1];
5142 *entry = *new_entry;
5146 else if (one_score_entry_per_name &&
5147 strEqual(entry->name, new_entry->name))
5149 // player already in high score list with better score or time
5155 // special case: new score is beyond the last high score list position
5156 return MAX_SCORE_ENTRIES;
5159 void NewHighScore(int level_nr, boolean tape_saved)
5161 struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
5162 boolean one_per_name = FALSE;
5164 strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
5165 strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
5167 new_entry.score = game.score_final;
5168 new_entry.time = game.score_time_final;
5170 LoadScore(level_nr);
5172 scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
5174 if (scores.last_added >= MAX_SCORE_ENTRIES)
5176 scores.last_added = MAX_SCORE_ENTRIES - 1;
5177 scores.force_last_added = TRUE;
5179 scores.entry[scores.last_added] = new_entry;
5181 // store last added local score entry (before merging server scores)
5182 scores.last_added_local = scores.last_added;
5187 if (scores.last_added < 0)
5190 SaveScore(level_nr);
5192 // store last added local score entry (before merging server scores)
5193 scores.last_added_local = scores.last_added;
5195 if (!game.LevelSolved_SaveTape)
5198 SaveScoreTape(level_nr);
5200 if (setup.ask_for_using_api_server)
5202 setup.use_api_server =
5203 Request("Upload your score and tape to the high score server?", REQ_ASK);
5205 if (!setup.use_api_server)
5206 Request("Not using high score server! Use setup menu to enable again!",
5209 runtime.use_api_server = setup.use_api_server;
5211 // after asking for using API server once, do not ask again
5212 setup.ask_for_using_api_server = FALSE;
5214 SaveSetup_ServerSetup();
5217 SaveServerScore(level_nr, tape_saved);
5220 void MergeServerScore(void)
5222 struct ScoreEntry last_added_entry;
5223 boolean one_per_name = FALSE;
5226 if (scores.last_added >= 0)
5227 last_added_entry = scores.entry[scores.last_added];
5229 for (i = 0; i < server_scores.num_entries; i++)
5231 int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
5233 if (pos >= 0 && pos <= scores.last_added)
5234 scores.last_added++;
5237 if (scores.last_added >= MAX_SCORE_ENTRIES)
5239 scores.last_added = MAX_SCORE_ENTRIES - 1;
5240 scores.force_last_added = TRUE;
5242 scores.entry[scores.last_added] = last_added_entry;
5246 static int getElementMoveStepsizeExt(int x, int y, int direction)
5248 int element = Tile[x][y];
5249 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5250 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5251 int horiz_move = (dx != 0);
5252 int sign = (horiz_move ? dx : dy);
5253 int step = sign * element_info[element].move_stepsize;
5255 // special values for move stepsize for spring and things on conveyor belt
5258 if (CAN_FALL(element) &&
5259 y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1]))
5260 step = sign * MOVE_STEPSIZE_NORMAL / 2;
5261 else if (element == EL_SPRING)
5262 step = sign * MOVE_STEPSIZE_NORMAL * 2;
5268 static int getElementMoveStepsize(int x, int y)
5270 return getElementMoveStepsizeExt(x, y, MovDir[x][y]);
5273 void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir)
5275 if (player->GfxAction != action || player->GfxDir != dir)
5277 player->GfxAction = action;
5278 player->GfxDir = dir;
5280 player->StepFrame = 0;
5284 static void ResetGfxFrame(int x, int y)
5286 // profiling showed that "autotest" spends 10~20% of its time in this function
5287 if (DrawingDeactivatedField())
5290 int element = Tile[x][y];
5291 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
5293 if (graphic_info[graphic].anim_global_sync)
5294 GfxFrame[x][y] = FrameCounter;
5295 else if (ANIM_MODE(graphic) == ANIM_CE_VALUE)
5296 GfxFrame[x][y] = CustomValue[x][y];
5297 else if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
5298 GfxFrame[x][y] = element_info[element].collect_score;
5299 else if (ANIM_MODE(graphic) == ANIM_CE_DELAY)
5300 GfxFrame[x][y] = ChangeDelay[x][y];
5303 static void ResetGfxAnimation(int x, int y)
5305 GfxAction[x][y] = ACTION_DEFAULT;
5306 GfxDir[x][y] = MovDir[x][y];
5309 ResetGfxFrame(x, y);
5312 static void ResetRandomAnimationValue(int x, int y)
5314 GfxRandom[x][y] = INIT_GFX_RANDOM();
5317 static void InitMovingField(int x, int y, int direction)
5319 int element = Tile[x][y];
5320 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
5321 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
5324 boolean is_moving_before, is_moving_after;
5326 // check if element was/is moving or being moved before/after mode change
5327 is_moving_before = (WasJustMoving[x][y] != 0);
5328 is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0);
5330 // reset animation only for moving elements which change direction of moving
5331 // or which just started or stopped moving
5332 // (else CEs with property "can move" / "not moving" are reset each frame)
5333 if (is_moving_before != is_moving_after ||
5334 direction != MovDir[x][y])
5335 ResetGfxAnimation(x, y);
5337 MovDir[x][y] = direction;
5338 GfxDir[x][y] = direction;
5340 GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING :
5341 direction == MV_DOWN && CAN_FALL(element) ?
5342 ACTION_FALLING : ACTION_MOVING);
5344 // this is needed for CEs with property "can move" / "not moving"
5346 if (is_moving_after)
5348 if (Tile[newx][newy] == EL_EMPTY)
5349 Tile[newx][newy] = EL_BLOCKED;
5351 MovDir[newx][newy] = MovDir[x][y];
5353 CustomValue[newx][newy] = CustomValue[x][y];
5355 GfxFrame[newx][newy] = GfxFrame[x][y];
5356 GfxRandom[newx][newy] = GfxRandom[x][y];
5357 GfxAction[newx][newy] = GfxAction[x][y];
5358 GfxDir[newx][newy] = GfxDir[x][y];
5362 void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y)
5364 int direction = MovDir[x][y];
5365 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0);
5366 int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0);
5372 void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y)
5374 int oldx = x, oldy = y;
5375 int direction = MovDir[x][y];
5377 if (direction == MV_LEFT)
5379 else if (direction == MV_RIGHT)
5381 else if (direction == MV_UP)
5383 else if (direction == MV_DOWN)
5386 *comes_from_x = oldx;
5387 *comes_from_y = oldy;
5390 static int MovingOrBlocked2Element(int x, int y)
5392 int element = Tile[x][y];
5394 if (element == EL_BLOCKED)
5398 Blocked2Moving(x, y, &oldx, &oldy);
5399 return Tile[oldx][oldy];
5405 static int MovingOrBlocked2ElementIfNotLeaving(int x, int y)
5407 // like MovingOrBlocked2Element(), but if element is moving
5408 // and (x,y) is the field the moving element is just leaving,
5409 // return EL_BLOCKED instead of the element value
5410 int element = Tile[x][y];
5412 if (IS_MOVING(x, y))
5414 if (element == EL_BLOCKED)
5418 Blocked2Moving(x, y, &oldx, &oldy);
5419 return Tile[oldx][oldy];
5428 static void RemoveField(int x, int y)
5430 Tile[x][y] = EL_EMPTY;
5436 CustomValue[x][y] = 0;
5439 ChangeDelay[x][y] = 0;
5440 ChangePage[x][y] = -1;
5441 Pushed[x][y] = FALSE;
5443 GfxElement[x][y] = EL_UNDEFINED;
5444 GfxAction[x][y] = ACTION_DEFAULT;
5445 GfxDir[x][y] = MV_NONE;
5448 static void RemoveMovingField(int x, int y)
5450 int oldx = x, oldy = y, newx = x, newy = y;
5451 int element = Tile[x][y];
5452 int next_element = EL_UNDEFINED;
5454 if (element != EL_BLOCKED && !IS_MOVING(x, y))
5457 if (IS_MOVING(x, y))
5459 Moving2Blocked(x, y, &newx, &newy);
5461 if (Tile[newx][newy] != EL_BLOCKED)
5463 // element is moving, but target field is not free (blocked), but
5464 // already occupied by something different (example: acid pool);
5465 // in this case, only remove the moving field, but not the target
5467 RemoveField(oldx, oldy);
5469 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5471 TEST_DrawLevelField(oldx, oldy);
5476 else if (element == EL_BLOCKED)
5478 Blocked2Moving(x, y, &oldx, &oldy);
5479 if (!IS_MOVING(oldx, oldy))
5483 if (element == EL_BLOCKED &&
5484 (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING ||
5485 Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING ||
5486 Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING ||
5487 Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING ||
5488 Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING ||
5489 Tile[oldx][oldy] == EL_AMOEBA_DROPPING))
5490 next_element = get_next_element(Tile[oldx][oldy]);
5492 RemoveField(oldx, oldy);
5493 RemoveField(newx, newy);
5495 Store[oldx][oldy] = Store2[oldx][oldy] = 0;
5497 if (next_element != EL_UNDEFINED)
5498 Tile[oldx][oldy] = next_element;
5500 TEST_DrawLevelField(oldx, oldy);
5501 TEST_DrawLevelField(newx, newy);
5504 void DrawDynamite(int x, int y)
5506 int sx = SCREENX(x), sy = SCREENY(y);
5507 int graphic = el2img(Tile[x][y]);
5510 if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y))
5513 if (IS_WALKABLE_INSIDE(Back[x][y]))
5517 DrawLevelElement(x, y, Back[x][y]);
5518 else if (Store[x][y])
5519 DrawLevelElement(x, y, Store[x][y]);
5520 else if (game.use_masked_elements)
5521 DrawLevelElement(x, y, EL_EMPTY);
5523 frame = getGraphicAnimationFrameXY(graphic, x, y);
5525 if (Back[x][y] || Store[x][y] || game.use_masked_elements)
5526 DrawGraphicThruMask(sx, sy, graphic, frame);
5528 DrawGraphic(sx, sy, graphic, frame);
5531 static void CheckDynamite(int x, int y)
5533 if (MovDelay[x][y] != 0) // dynamite is still waiting to explode
5537 if (MovDelay[x][y] != 0)
5540 PlayLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5546 StopLevelSoundActionIfLoop(x, y, ACTION_ACTIVE);
5551 static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2)
5553 boolean num_checked_players = 0;
5556 for (i = 0; i < MAX_PLAYERS; i++)
5558 if (stored_player[i].active)
5560 int sx = stored_player[i].jx;
5561 int sy = stored_player[i].jy;
5563 if (num_checked_players == 0)
5570 *sx1 = MIN(*sx1, sx);
5571 *sy1 = MIN(*sy1, sy);
5572 *sx2 = MAX(*sx2, sx);
5573 *sy2 = MAX(*sy2, sy);
5576 num_checked_players++;
5581 static boolean checkIfAllPlayersFitToScreen_RND(void)
5583 int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0;
5585 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5587 return (sx2 - sx1 < SCR_FIELDX &&
5588 sy2 - sy1 < SCR_FIELDY);
5591 static void setScreenCenteredToAllPlayers(int *sx, int *sy)
5593 int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y;
5595 setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2);
5597 *sx = (sx1 + sx2) / 2;
5598 *sy = (sy1 + sy2) / 2;
5601 static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
5602 boolean center_screen, boolean quick_relocation)
5604 unsigned int frame_delay_value_old = GetVideoFrameDelay();
5605 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5606 boolean no_delay = (tape.warp_forward);
5607 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5608 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5609 int new_scroll_x, new_scroll_y;
5611 if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
5613 // case 1: quick relocation inside visible screen (without scrolling)
5620 if (!level.shifted_relocation || center_screen)
5622 // relocation _with_ centering of screen
5624 new_scroll_x = SCROLL_POSITION_X(x);
5625 new_scroll_y = SCROLL_POSITION_Y(y);
5629 // relocation _without_ centering of screen
5631 int center_scroll_x = SCROLL_POSITION_X(old_x);
5632 int center_scroll_y = SCROLL_POSITION_Y(old_y);
5633 int offset_x = x + (scroll_x - center_scroll_x);
5634 int offset_y = y + (scroll_y - center_scroll_y);
5636 // for new screen position, apply previous offset to center position
5637 new_scroll_x = SCROLL_POSITION_X(offset_x);
5638 new_scroll_y = SCROLL_POSITION_Y(offset_y);
5641 if (quick_relocation)
5643 // case 2: quick relocation (redraw without visible scrolling)
5645 scroll_x = new_scroll_x;
5646 scroll_y = new_scroll_y;
5653 // case 3: visible relocation (with scrolling to new position)
5655 ScrollScreen(NULL, SCROLL_GO_ON); // scroll last frame to full tile
5657 SetVideoFrameDelay(wait_delay_value);
5659 while (scroll_x != new_scroll_x || scroll_y != new_scroll_y)
5661 int dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0);
5662 int dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0);
5664 if (dx == 0 && dy == 0) // no scrolling needed at all
5670 // set values for horizontal/vertical screen scrolling (half tile size)
5671 int dir_x = (dx != 0 ? MV_HORIZONTAL : 0);
5672 int dir_y = (dy != 0 ? MV_VERTICAL : 0);
5673 int pos_x = dx * TILEX / 2;
5674 int pos_y = dy * TILEY / 2;
5675 int fx = getFieldbufferOffsetX_RND(dir_x, pos_x);
5676 int fy = getFieldbufferOffsetY_RND(dir_y, pos_y);
5678 ScrollLevel(dx, dy);
5681 // scroll in two steps of half tile size to make things smoother
5682 BlitScreenToBitmapExt_RND(window, fx, fy);
5684 // scroll second step to align at full tile size
5685 BlitScreenToBitmap(window);
5691 SetVideoFrameDelay(frame_delay_value_old);
5694 static void RelocatePlayer(int jx, int jy, int el_player_raw)
5696 int el_player = GET_PLAYER_ELEMENT(el_player_raw);
5697 int player_nr = GET_PLAYER_NR(el_player);
5698 struct PlayerInfo *player = &stored_player[player_nr];
5699 boolean ffwd_delay = (tape.playing && tape.fast_forward);
5700 boolean no_delay = (tape.warp_forward);
5701 int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
5702 int wait_delay_value = (no_delay ? 0 : frame_delay_value);
5703 int old_jx = player->jx;
5704 int old_jy = player->jy;
5705 int old_element = Tile[old_jx][old_jy];
5706 int element = Tile[jx][jy];
5707 boolean player_relocated = (old_jx != jx || old_jy != jy);
5709 int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0);
5710 int move_dir_vert = (jy < old_jy ? MV_UP : jy > old_jy ? MV_DOWN : 0);
5711 int enter_side_horiz = MV_DIR_OPPOSITE(move_dir_horiz);
5712 int enter_side_vert = MV_DIR_OPPOSITE(move_dir_vert);
5713 int leave_side_horiz = move_dir_horiz;
5714 int leave_side_vert = move_dir_vert;
5715 int enter_side = enter_side_horiz | enter_side_vert;
5716 int leave_side = leave_side_horiz | leave_side_vert;
5718 if (player->buried) // do not reanimate dead player
5721 if (!player_relocated) // no need to relocate the player
5724 if (IS_PLAYER(jx, jy)) // player already placed at new position
5726 RemoveField(jx, jy); // temporarily remove newly placed player
5727 DrawLevelField(jx, jy);
5730 if (player->present)
5732 while (player->MovPos)
5734 ScrollPlayer(player, SCROLL_GO_ON);
5735 ScrollScreen(NULL, SCROLL_GO_ON);
5737 AdvanceFrameAndPlayerCounters(player->index_nr);
5741 BackToFront_WithFrameDelay(wait_delay_value);
5744 DrawPlayer(player); // needed here only to cleanup last field
5745 DrawLevelField(player->jx, player->jy); // remove player graphic
5747 player->is_moving = FALSE;
5750 if (IS_CUSTOM_ELEMENT(old_element))
5751 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
5753 player->index_bit, leave_side);
5755 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
5757 player->index_bit, leave_side);
5759 Tile[jx][jy] = el_player;
5760 InitPlayerField(jx, jy, el_player, TRUE);
5762 /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be
5763 possible that the relocation target field did not contain a player element,
5764 but a walkable element, to which the new player was relocated -- in this
5765 case, restore that (already initialized!) element on the player field */
5766 if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element
5768 Tile[jx][jy] = element; // restore previously existing element
5771 // only visually relocate centered player
5772 DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
5773 FALSE, level.instant_relocation);
5775 TestIfPlayerTouchesBadThing(jx, jy);
5776 TestIfPlayerTouchesCustomElement(jx, jy);
5778 if (IS_CUSTOM_ELEMENT(element))
5779 CheckElementChangeByPlayer(jx, jy, element, CE_ENTERED_BY_PLAYER,
5780 player->index_bit, enter_side);
5782 CheckTriggeredElementChangeByPlayer(jx, jy, element, CE_PLAYER_ENTERS_X,
5783 player->index_bit, enter_side);
5785 if (player->is_switching)
5787 /* ensure that relocation while still switching an element does not cause
5788 a new element to be treated as also switched directly after relocation
5789 (this is important for teleporter switches that teleport the player to
5790 a place where another teleporter switch is in the same direction, which
5791 would then incorrectly be treated as immediately switched before the
5792 direction key that caused the switch was released) */
5794 player->switch_x += jx - old_jx;
5795 player->switch_y += jy - old_jy;
5799 static void Explode(int ex, int ey, int phase, int mode)
5805 // !!! eliminate this variable !!!
5806 int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
5808 if (game.explosions_delayed)
5810 ExplodeField[ex][ey] = mode;
5814 if (phase == EX_PHASE_START) // initialize 'Store[][]' field
5816 int center_element = Tile[ex][ey];
5817 int artwork_element, explosion_element; // set these values later
5819 // remove things displayed in background while burning dynamite
5820 if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey]))
5823 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
5825 // put moving element to center field (and let it explode there)
5826 center_element = MovingOrBlocked2Element(ex, ey);
5827 RemoveMovingField(ex, ey);
5828 Tile[ex][ey] = center_element;
5831 // now "center_element" is finally determined -- set related values now
5832 artwork_element = center_element; // for custom player artwork
5833 explosion_element = center_element; // for custom player artwork
5835 if (IS_PLAYER(ex, ey))
5837 int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]);
5839 artwork_element = stored_player[player_nr].artwork_element;
5841 if (level.use_explosion_element[player_nr])
5843 explosion_element = level.explosion_element[player_nr];
5844 artwork_element = explosion_element;
5848 if (mode == EX_TYPE_NORMAL ||
5849 mode == EX_TYPE_CENTER ||
5850 mode == EX_TYPE_CROSS)
5851 PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING);
5853 last_phase = element_info[explosion_element].explosion_delay + 1;
5855 for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++)
5857 int xx = x - ex + 1;
5858 int yy = y - ey + 1;
5861 if (!IN_LEV_FIELD(x, y) ||
5862 (mode & EX_TYPE_SINGLE_TILE && (x != ex || y != ey)) ||
5863 (mode == EX_TYPE_CROSS && (x != ex && y != ey)))
5866 element = Tile[x][y];
5868 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
5870 element = MovingOrBlocked2Element(x, y);
5872 if (!IS_EXPLOSION_PROOF(element))
5873 RemoveMovingField(x, y);
5876 // indestructible elements can only explode in center (but not flames)
5877 if ((IS_EXPLOSION_PROOF(element) && (x != ex || y != ey ||
5878 mode == EX_TYPE_BORDER)) ||
5879 element == EL_FLAMES)
5882 /* no idea why this was changed from 3.0.8 to 3.1.0 -- this causes buggy
5883 behaviour, for example when touching a yamyam that explodes to rocks
5884 with active deadly shield, a rock is created under the player !!! */
5885 // (case 1 (surely buggy): >= 3.1.0, case 2 (maybe buggy): <= 3.0.8)
5887 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)) &&
5888 (game.engine_version < VERSION_IDENT(3,1,0,0) ||
5889 (x == ex && y == ey && mode != EX_TYPE_BORDER)))
5891 if (IS_PLAYER(x, y) && SHIELD_ON(PLAYERINFO(x, y)))
5894 if (IS_ACTIVE_BOMB(element))
5896 // re-activate things under the bomb like gate or penguin
5897 Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY);
5904 // save walkable background elements while explosion on same tile
5905 if (IS_WALKABLE(element) && IS_INDESTRUCTIBLE(element) &&
5906 (x != ex || y != ey || mode == EX_TYPE_BORDER))
5907 Back[x][y] = element;
5909 // ignite explodable elements reached by other explosion
5910 if (element == EL_EXPLOSION)
5911 element = Store2[x][y];
5913 if (AmoebaNr[x][y] &&
5914 (element == EL_AMOEBA_FULL ||
5915 element == EL_BD_AMOEBA ||
5916 element == EL_AMOEBA_GROWING))
5918 AmoebaCnt[AmoebaNr[x][y]]--;
5919 AmoebaCnt2[AmoebaNr[x][y]]--;
5924 if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey))
5926 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1;
5928 Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr;
5930 if (PLAYERINFO(ex, ey)->use_murphy)
5931 Store[x][y] = EL_EMPTY;
5934 // !!! check this case -- currently needed for rnd_rado_negundo_v,
5935 // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
5936 else if (IS_PLAYER_ELEMENT(center_element))
5937 Store[x][y] = EL_EMPTY;
5938 else if (center_element == EL_YAMYAM)
5939 Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
5940 else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY)
5941 Store[x][y] = element_info[center_element].content.e[xx][yy];
5943 // needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE"
5944 // (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond
5945 // otherwise) -- FIX THIS !!!
5946 else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY)
5947 Store[x][y] = element_info[element].content.e[1][1];
5949 else if (!CAN_EXPLODE(element))
5950 Store[x][y] = element_info[element].content.e[1][1];
5953 Store[x][y] = EL_EMPTY;
5955 if (x != ex || y != ey || mode == EX_TYPE_BORDER ||
5956 center_element == EL_AMOEBA_TO_DIAMOND)
5957 Store2[x][y] = element;
5959 Tile[x][y] = EL_EXPLOSION;
5960 GfxElement[x][y] = artwork_element;
5962 ExplodePhase[x][y] = 1;
5963 ExplodeDelay[x][y] = last_phase;
5968 if (center_element == EL_YAMYAM)
5969 game.yamyam_content_nr =
5970 (game.yamyam_content_nr + 1) % level.num_yamyam_contents;
5982 GfxFrame[x][y] = 0; // restart explosion animation
5984 last_phase = ExplodeDelay[x][y];
5986 ExplodePhase[x][y] = (phase < last_phase ? phase + 1 : 0);
5988 // this can happen if the player leaves an explosion just in time
5989 if (GfxElement[x][y] == EL_UNDEFINED)
5990 GfxElement[x][y] = EL_EMPTY;
5992 border_element = Store2[x][y];
5993 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
5994 border_element = StorePlayer[x][y];
5996 if (phase == element_info[border_element].ignition_delay ||
5997 phase == last_phase)
5999 boolean border_explosion = FALSE;
6001 if (IS_PLAYER(x, y) && PLAYERINFO(x, y)->present &&
6002 !PLAYER_EXPLOSION_PROTECTED(x, y))
6004 KillPlayerUnlessExplosionProtected(x, y);
6005 border_explosion = TRUE;
6007 else if (CAN_EXPLODE_BY_EXPLOSION(border_element))
6009 Tile[x][y] = Store2[x][y];
6012 border_explosion = TRUE;
6014 else if (border_element == EL_AMOEBA_TO_DIAMOND)
6016 AmoebaToDiamond(x, y);
6018 border_explosion = TRUE;
6021 // if an element just explodes due to another explosion (chain-reaction),
6022 // do not immediately end the new explosion when it was the last frame of
6023 // the explosion (as it would be done in the following "if"-statement!)
6024 if (border_explosion && phase == last_phase)
6028 // this can happen if the player was just killed by an explosion
6029 if (GfxElement[x][y] == EL_UNDEFINED)
6030 GfxElement[x][y] = EL_EMPTY;
6032 if (phase == last_phase)
6036 element = Tile[x][y] = Store[x][y];
6037 Store[x][y] = Store2[x][y] = 0;
6038 GfxElement[x][y] = EL_UNDEFINED;
6040 // player can escape from explosions and might therefore be still alive
6041 if (element >= EL_PLAYER_IS_EXPLODING_1 &&
6042 element <= EL_PLAYER_IS_EXPLODING_4)
6044 int player_nr = element - EL_PLAYER_IS_EXPLODING_1;
6045 int explosion_element = EL_PLAYER_1 + player_nr;
6046 int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2);
6047 int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2);
6049 if (level.use_explosion_element[player_nr])
6050 explosion_element = level.explosion_element[player_nr];
6052 Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY :
6053 element_info[explosion_element].content.e[xx][yy]);
6056 // restore probably existing indestructible background element
6057 if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y]))
6058 element = Tile[x][y] = Back[x][y];
6061 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
6062 GfxDir[x][y] = MV_NONE;
6063 ChangeDelay[x][y] = 0;
6064 ChangePage[x][y] = -1;
6066 CustomValue[x][y] = 0;
6068 InitField_WithBug2(x, y, FALSE);
6070 TEST_DrawLevelField(x, y);
6072 TestIfElementTouchesCustomElement(x, y);
6074 if (GFX_CRUMBLED(element))
6075 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6077 if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
6078 StorePlayer[x][y] = 0;
6080 if (IS_PLAYER_ELEMENT(element))
6081 RelocatePlayer(x, y, element);
6083 else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
6085 int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING);
6086 int frame = getGraphicAnimationFrameXY(graphic, x, y);
6089 TEST_DrawLevelFieldCrumbled(x, y);
6091 if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY)
6093 DrawLevelElement(x, y, Back[x][y]);
6094 DrawGraphicThruMask(SCREENX(x), SCREENY(y), graphic, frame);
6096 else if (IS_WALKABLE_UNDER(Back[x][y]))
6098 DrawLevelGraphic(x, y, graphic, frame);
6099 DrawLevelElementThruMask(x, y, Back[x][y]);
6101 else if (!IS_WALKABLE_INSIDE(Back[x][y]))
6102 DrawLevelGraphic(x, y, graphic, frame);
6106 static void DynaExplode(int ex, int ey)
6109 int dynabomb_element = Tile[ex][ey];
6110 int dynabomb_size = 1;
6111 boolean dynabomb_xl = FALSE;
6112 struct PlayerInfo *player;
6113 static int xy[4][2] =
6121 if (IS_ACTIVE_BOMB(dynabomb_element))
6123 player = &stored_player[dynabomb_element - EL_DYNABOMB_PLAYER_1_ACTIVE];
6124 dynabomb_size = player->dynabomb_size;
6125 dynabomb_xl = player->dynabomb_xl;
6126 player->dynabombs_left++;
6129 Explode(ex, ey, EX_PHASE_START, EX_TYPE_CENTER);
6131 for (i = 0; i < NUM_DIRECTIONS; i++)
6133 for (j = 1; j <= dynabomb_size; j++)
6135 int x = ex + j * xy[i][0];
6136 int y = ey + j * xy[i][1];
6139 if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y]))
6142 element = Tile[x][y];
6144 // do not restart explosions of fields with active bombs
6145 if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y]))
6148 Explode(x, y, EX_PHASE_START, EX_TYPE_BORDER);
6150 if (element != EL_EMPTY && element != EL_EXPLOSION &&
6151 !IS_DIGGABLE(element) && !dynabomb_xl)
6157 void Bang(int x, int y)
6159 int element = MovingOrBlocked2Element(x, y);
6160 int explosion_type = EX_TYPE_NORMAL;
6162 if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y))
6164 struct PlayerInfo *player = PLAYERINFO(x, y);
6166 element = Tile[x][y] = player->initial_element;
6168 if (level.use_explosion_element[player->index_nr])
6170 int explosion_element = level.explosion_element[player->index_nr];
6172 if (element_info[explosion_element].explosion_type == EXPLODES_CROSS)
6173 explosion_type = EX_TYPE_CROSS;
6174 else if (element_info[explosion_element].explosion_type == EXPLODES_1X1)
6175 explosion_type = EX_TYPE_CENTER;
6183 case EL_BD_BUTTERFLY:
6186 case EL_DARK_YAMYAM:
6190 RaiseScoreElement(element);
6193 case EL_DYNABOMB_PLAYER_1_ACTIVE:
6194 case EL_DYNABOMB_PLAYER_2_ACTIVE:
6195 case EL_DYNABOMB_PLAYER_3_ACTIVE:
6196 case EL_DYNABOMB_PLAYER_4_ACTIVE:
6197 case EL_DYNABOMB_INCREASE_NUMBER:
6198 case EL_DYNABOMB_INCREASE_SIZE:
6199 case EL_DYNABOMB_INCREASE_POWER:
6200 explosion_type = EX_TYPE_DYNA;
6203 case EL_DC_LANDMINE:
6204 explosion_type = EX_TYPE_CENTER;
6209 case EL_LAMP_ACTIVE:
6210 case EL_AMOEBA_TO_DIAMOND:
6211 if (!IS_PLAYER(x, y)) // penguin and player may be at same field
6212 explosion_type = EX_TYPE_CENTER;
6216 if (element_info[element].explosion_type == EXPLODES_CROSS)
6217 explosion_type = EX_TYPE_CROSS;
6218 else if (element_info[element].explosion_type == EXPLODES_1X1)
6219 explosion_type = EX_TYPE_CENTER;
6223 if (explosion_type == EX_TYPE_DYNA)
6226 Explode(x, y, EX_PHASE_START, explosion_type);
6228 CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X);
6231 static void SplashAcid(int x, int y)
6233 if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) &&
6234 (!IN_LEV_FIELD(x - 1, y - 2) ||
6235 !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2))))
6236 Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT;
6238 if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) &&
6239 (!IN_LEV_FIELD(x + 1, y - 2) ||
6240 !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2))))
6241 Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT;
6243 PlayLevelSound(x, y, SND_ACID_SPLASHING);
6246 static void InitBeltMovement(void)
6248 static int belt_base_element[4] =
6250 EL_CONVEYOR_BELT_1_LEFT,
6251 EL_CONVEYOR_BELT_2_LEFT,
6252 EL_CONVEYOR_BELT_3_LEFT,
6253 EL_CONVEYOR_BELT_4_LEFT
6255 static int belt_base_active_element[4] =
6257 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6258 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6259 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6260 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6265 // set frame order for belt animation graphic according to belt direction
6266 for (i = 0; i < NUM_BELTS; i++)
6270 for (j = 0; j < NUM_BELT_PARTS; j++)
6272 int element = belt_base_active_element[belt_nr] + j;
6273 int graphic_1 = el2img(element);
6274 int graphic_2 = el2panelimg(element);
6276 if (game.belt_dir[i] == MV_LEFT)
6278 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6279 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6283 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6284 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6289 SCAN_PLAYFIELD(x, y)
6291 int element = Tile[x][y];
6293 for (i = 0; i < NUM_BELTS; i++)
6295 if (IS_BELT(element) && game.belt_dir[i] != MV_NONE)
6297 int e_belt_nr = getBeltNrFromBeltElement(element);
6300 if (e_belt_nr == belt_nr)
6302 int belt_part = Tile[x][y] - belt_base_element[belt_nr];
6304 Tile[x][y] = belt_base_active_element[belt_nr] + belt_part;
6311 static void ToggleBeltSwitch(int x, int y)
6313 static int belt_base_element[4] =
6315 EL_CONVEYOR_BELT_1_LEFT,
6316 EL_CONVEYOR_BELT_2_LEFT,
6317 EL_CONVEYOR_BELT_3_LEFT,
6318 EL_CONVEYOR_BELT_4_LEFT
6320 static int belt_base_active_element[4] =
6322 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
6323 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
6324 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
6325 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
6327 static int belt_base_switch_element[4] =
6329 EL_CONVEYOR_BELT_1_SWITCH_LEFT,
6330 EL_CONVEYOR_BELT_2_SWITCH_LEFT,
6331 EL_CONVEYOR_BELT_3_SWITCH_LEFT,
6332 EL_CONVEYOR_BELT_4_SWITCH_LEFT
6334 static int belt_move_dir[4] =
6342 int element = Tile[x][y];
6343 int belt_nr = getBeltNrFromBeltSwitchElement(element);
6344 int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4;
6345 int belt_dir = belt_move_dir[belt_dir_nr];
6348 if (!IS_BELT_SWITCH(element))
6351 game.belt_dir_nr[belt_nr] = belt_dir_nr;
6352 game.belt_dir[belt_nr] = belt_dir;
6354 if (belt_dir_nr == 3)
6357 // set frame order for belt animation graphic according to belt direction
6358 for (i = 0; i < NUM_BELT_PARTS; i++)
6360 int element = belt_base_active_element[belt_nr] + i;
6361 int graphic_1 = el2img(element);
6362 int graphic_2 = el2panelimg(element);
6364 if (belt_dir == MV_LEFT)
6366 graphic_info[graphic_1].anim_mode &= ~ANIM_REVERSE;
6367 graphic_info[graphic_2].anim_mode &= ~ANIM_REVERSE;
6371 graphic_info[graphic_1].anim_mode |= ANIM_REVERSE;
6372 graphic_info[graphic_2].anim_mode |= ANIM_REVERSE;
6376 SCAN_PLAYFIELD(xx, yy)
6378 int element = Tile[xx][yy];
6380 if (IS_BELT_SWITCH(element))
6382 int e_belt_nr = getBeltNrFromBeltSwitchElement(element);
6384 if (e_belt_nr == belt_nr)
6386 Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr;
6387 TEST_DrawLevelField(xx, yy);
6390 else if (IS_BELT(element) && belt_dir != MV_NONE)
6392 int e_belt_nr = getBeltNrFromBeltElement(element);
6394 if (e_belt_nr == belt_nr)
6396 int belt_part = Tile[xx][yy] - belt_base_element[belt_nr];
6398 Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part;
6399 TEST_DrawLevelField(xx, yy);
6402 else if (IS_BELT_ACTIVE(element) && belt_dir == MV_NONE)
6404 int e_belt_nr = getBeltNrFromBeltActiveElement(element);
6406 if (e_belt_nr == belt_nr)
6408 int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr];
6410 Tile[xx][yy] = belt_base_element[belt_nr] + belt_part;
6411 TEST_DrawLevelField(xx, yy);
6417 static void ToggleSwitchgateSwitch(int x, int y)
6421 game.switchgate_pos = !game.switchgate_pos;
6423 SCAN_PLAYFIELD(xx, yy)
6425 int element = Tile[xx][yy];
6427 if (element == EL_SWITCHGATE_SWITCH_UP)
6429 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN;
6430 TEST_DrawLevelField(xx, yy);
6432 else if (element == EL_SWITCHGATE_SWITCH_DOWN)
6434 Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP;
6435 TEST_DrawLevelField(xx, yy);
6437 else if (element == EL_DC_SWITCHGATE_SWITCH_UP)
6439 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN;
6440 TEST_DrawLevelField(xx, yy);
6442 else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN)
6444 Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP;
6445 TEST_DrawLevelField(xx, yy);
6447 else if (element == EL_SWITCHGATE_OPEN ||
6448 element == EL_SWITCHGATE_OPENING)
6450 Tile[xx][yy] = EL_SWITCHGATE_CLOSING;
6452 PlayLevelSoundAction(xx, yy, ACTION_CLOSING);
6454 else if (element == EL_SWITCHGATE_CLOSED ||
6455 element == EL_SWITCHGATE_CLOSING)
6457 Tile[xx][yy] = EL_SWITCHGATE_OPENING;
6459 PlayLevelSoundAction(xx, yy, ACTION_OPENING);
6464 static int getInvisibleActiveFromInvisibleElement(int element)
6466 return (element == EL_INVISIBLE_STEELWALL ? EL_INVISIBLE_STEELWALL_ACTIVE :
6467 element == EL_INVISIBLE_WALL ? EL_INVISIBLE_WALL_ACTIVE :
6468 element == EL_INVISIBLE_SAND ? EL_INVISIBLE_SAND_ACTIVE :
6472 static int getInvisibleFromInvisibleActiveElement(int element)
6474 return (element == EL_INVISIBLE_STEELWALL_ACTIVE ? EL_INVISIBLE_STEELWALL :
6475 element == EL_INVISIBLE_WALL_ACTIVE ? EL_INVISIBLE_WALL :
6476 element == EL_INVISIBLE_SAND_ACTIVE ? EL_INVISIBLE_SAND :
6480 static void RedrawAllLightSwitchesAndInvisibleElements(void)
6484 SCAN_PLAYFIELD(x, y)
6486 int element = Tile[x][y];
6488 if (element == EL_LIGHT_SWITCH &&
6489 game.light_time_left > 0)
6491 Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE;
6492 TEST_DrawLevelField(x, y);
6494 else if (element == EL_LIGHT_SWITCH_ACTIVE &&
6495 game.light_time_left == 0)
6497 Tile[x][y] = EL_LIGHT_SWITCH;
6498 TEST_DrawLevelField(x, y);
6500 else if (element == EL_EMC_DRIPPER &&
6501 game.light_time_left > 0)
6503 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6504 TEST_DrawLevelField(x, y);
6506 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6507 game.light_time_left == 0)
6509 Tile[x][y] = EL_EMC_DRIPPER;
6510 TEST_DrawLevelField(x, y);
6512 else if (element == EL_INVISIBLE_STEELWALL ||
6513 element == EL_INVISIBLE_WALL ||
6514 element == EL_INVISIBLE_SAND)
6516 if (game.light_time_left > 0)
6517 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6519 TEST_DrawLevelField(x, y);
6521 // uncrumble neighbour fields, if needed
6522 if (element == EL_INVISIBLE_SAND)
6523 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6525 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6526 element == EL_INVISIBLE_WALL_ACTIVE ||
6527 element == EL_INVISIBLE_SAND_ACTIVE)
6529 if (game.light_time_left == 0)
6530 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6532 TEST_DrawLevelField(x, y);
6534 // re-crumble neighbour fields, if needed
6535 if (element == EL_INVISIBLE_SAND)
6536 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6541 static void RedrawAllInvisibleElementsForLenses(void)
6545 SCAN_PLAYFIELD(x, y)
6547 int element = Tile[x][y];
6549 if (element == EL_EMC_DRIPPER &&
6550 game.lenses_time_left > 0)
6552 Tile[x][y] = EL_EMC_DRIPPER_ACTIVE;
6553 TEST_DrawLevelField(x, y);
6555 else if (element == EL_EMC_DRIPPER_ACTIVE &&
6556 game.lenses_time_left == 0)
6558 Tile[x][y] = EL_EMC_DRIPPER;
6559 TEST_DrawLevelField(x, y);
6561 else if (element == EL_INVISIBLE_STEELWALL ||
6562 element == EL_INVISIBLE_WALL ||
6563 element == EL_INVISIBLE_SAND)
6565 if (game.lenses_time_left > 0)
6566 Tile[x][y] = getInvisibleActiveFromInvisibleElement(element);
6568 TEST_DrawLevelField(x, y);
6570 // uncrumble neighbour fields, if needed
6571 if (element == EL_INVISIBLE_SAND)
6572 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6574 else if (element == EL_INVISIBLE_STEELWALL_ACTIVE ||
6575 element == EL_INVISIBLE_WALL_ACTIVE ||
6576 element == EL_INVISIBLE_SAND_ACTIVE)
6578 if (game.lenses_time_left == 0)
6579 Tile[x][y] = getInvisibleFromInvisibleActiveElement(element);
6581 TEST_DrawLevelField(x, y);
6583 // re-crumble neighbour fields, if needed
6584 if (element == EL_INVISIBLE_SAND)
6585 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
6590 static void RedrawAllInvisibleElementsForMagnifier(void)
6594 SCAN_PLAYFIELD(x, y)
6596 int element = Tile[x][y];
6598 if (element == EL_EMC_FAKE_GRASS &&
6599 game.magnify_time_left > 0)
6601 Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE;
6602 TEST_DrawLevelField(x, y);
6604 else if (element == EL_EMC_FAKE_GRASS_ACTIVE &&
6605 game.magnify_time_left == 0)
6607 Tile[x][y] = EL_EMC_FAKE_GRASS;
6608 TEST_DrawLevelField(x, y);
6610 else if (IS_GATE_GRAY(element) &&
6611 game.magnify_time_left > 0)
6613 Tile[x][y] = (IS_RND_GATE_GRAY(element) ?
6614 element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE :
6615 IS_EM_GATE_GRAY(element) ?
6616 element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE :
6617 IS_EMC_GATE_GRAY(element) ?
6618 element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE :
6619 IS_DC_GATE_GRAY(element) ?
6620 EL_DC_GATE_WHITE_GRAY_ACTIVE :
6622 TEST_DrawLevelField(x, y);
6624 else if (IS_GATE_GRAY_ACTIVE(element) &&
6625 game.magnify_time_left == 0)
6627 Tile[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ?
6628 element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY :
6629 IS_EM_GATE_GRAY_ACTIVE(element) ?
6630 element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY :
6631 IS_EMC_GATE_GRAY_ACTIVE(element) ?
6632 element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY :
6633 IS_DC_GATE_GRAY_ACTIVE(element) ?
6634 EL_DC_GATE_WHITE_GRAY :
6636 TEST_DrawLevelField(x, y);
6641 static void ToggleLightSwitch(int x, int y)
6643 int element = Tile[x][y];
6645 game.light_time_left =
6646 (element == EL_LIGHT_SWITCH ?
6647 level.time_light * FRAMES_PER_SECOND : 0);
6649 RedrawAllLightSwitchesAndInvisibleElements();
6652 static void ActivateTimegateSwitch(int x, int y)
6656 game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND;
6658 SCAN_PLAYFIELD(xx, yy)
6660 int element = Tile[xx][yy];
6662 if (element == EL_TIMEGATE_CLOSED ||
6663 element == EL_TIMEGATE_CLOSING)
6665 Tile[xx][yy] = EL_TIMEGATE_OPENING;
6666 PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING);
6670 else if (element == EL_TIMEGATE_SWITCH_ACTIVE)
6672 Tile[xx][yy] = EL_TIMEGATE_SWITCH;
6673 TEST_DrawLevelField(xx, yy);
6679 Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE :
6680 EL_DC_TIMEGATE_SWITCH_ACTIVE);
6683 static void Impact(int x, int y)
6685 boolean last_line = (y == lev_fieldy - 1);
6686 boolean object_hit = FALSE;
6687 boolean impact = (last_line || object_hit);
6688 int element = Tile[x][y];
6689 int smashed = EL_STEELWALL;
6691 if (!last_line) // check if element below was hit
6693 if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING)
6696 object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) ||
6697 MovDir[x][y + 1] != MV_DOWN ||
6698 MovPos[x][y + 1] <= TILEY / 2));
6700 // do not smash moving elements that left the smashed field in time
6701 if (game.engine_version >= VERSION_IDENT(2,2,0,7) && IS_MOVING(x, y + 1) &&
6702 ABS(MovPos[x][y + 1] + getElementMoveStepsize(x, y + 1)) >= TILEX)
6705 #if USE_QUICKSAND_IMPACT_BUGFIX
6706 if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE)
6708 RemoveMovingField(x, y + 1);
6709 Tile[x][y + 1] = EL_QUICKSAND_EMPTY;
6710 Tile[x][y + 2] = EL_ROCK;
6711 TEST_DrawLevelField(x, y + 2);
6716 if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE)
6718 RemoveMovingField(x, y + 1);
6719 Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY;
6720 Tile[x][y + 2] = EL_ROCK;
6721 TEST_DrawLevelField(x, y + 2);
6728 smashed = MovingOrBlocked2Element(x, y + 1);
6730 impact = (last_line || object_hit);
6733 if (!last_line && smashed == EL_ACID) // element falls into acid
6735 SplashAcid(x, y + 1);
6739 // !!! not sufficient for all cases -- see EL_PEARL below !!!
6740 // only reset graphic animation if graphic really changes after impact
6742 el_act_dir2img(element, GfxAction[x][y], MV_DOWN) != el2img(element))
6744 ResetGfxAnimation(x, y);
6745 TEST_DrawLevelField(x, y);
6748 if (impact && CAN_EXPLODE_IMPACT(element))
6753 else if (impact && element == EL_PEARL &&
6754 smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE)
6756 ResetGfxAnimation(x, y);
6758 Tile[x][y] = EL_PEARL_BREAKING;
6759 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6762 else if (impact && CheckElementChange(x, y, element, smashed, CE_IMPACT))
6764 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6769 if (impact && element == EL_AMOEBA_DROP)
6771 if (object_hit && IS_PLAYER(x, y + 1))
6772 KillPlayerUnlessEnemyProtected(x, y + 1);
6773 else if (object_hit && smashed == EL_PENGUIN)
6777 Tile[x][y] = EL_AMOEBA_GROWING;
6778 Store[x][y] = EL_AMOEBA_WET;
6780 ResetRandomAnimationValue(x, y);
6785 if (object_hit) // check which object was hit
6787 if ((CAN_PASS_MAGIC_WALL(element) &&
6788 (smashed == EL_MAGIC_WALL ||
6789 smashed == EL_BD_MAGIC_WALL)) ||
6790 (CAN_PASS_DC_MAGIC_WALL(element) &&
6791 smashed == EL_DC_MAGIC_WALL))
6794 int activated_magic_wall =
6795 (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE :
6796 smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE :
6797 EL_DC_MAGIC_WALL_ACTIVE);
6799 // activate magic wall / mill
6800 SCAN_PLAYFIELD(xx, yy)
6802 if (Tile[xx][yy] == smashed)
6803 Tile[xx][yy] = activated_magic_wall;
6806 game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND;
6807 game.magic_wall_active = TRUE;
6809 PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ?
6810 SND_MAGIC_WALL_ACTIVATING :
6811 smashed == EL_BD_MAGIC_WALL ?
6812 SND_BD_MAGIC_WALL_ACTIVATING :
6813 SND_DC_MAGIC_WALL_ACTIVATING));
6816 if (IS_PLAYER(x, y + 1))
6818 if (CAN_SMASH_PLAYER(element))
6820 KillPlayerUnlessEnemyProtected(x, y + 1);
6824 else if (smashed == EL_PENGUIN)
6826 if (CAN_SMASH_PLAYER(element))
6832 else if (element == EL_BD_DIAMOND)
6834 if (IS_CLASSIC_ENEMY(smashed) && IS_BD_ELEMENT(smashed))
6840 else if (((element == EL_SP_INFOTRON ||
6841 element == EL_SP_ZONK) &&
6842 (smashed == EL_SP_SNIKSNAK ||
6843 smashed == EL_SP_ELECTRON ||
6844 smashed == EL_SP_DISK_ORANGE)) ||
6845 (element == EL_SP_INFOTRON &&
6846 smashed == EL_SP_DISK_YELLOW))
6851 else if (CAN_SMASH_EVERYTHING(element))
6853 if (IS_CLASSIC_ENEMY(smashed) ||
6854 CAN_EXPLODE_SMASHED(smashed))
6859 else if (!IS_MOVING(x, y + 1) && !IS_BLOCKED(x, y + 1))
6861 if (smashed == EL_LAMP ||
6862 smashed == EL_LAMP_ACTIVE)
6867 else if (smashed == EL_NUT)
6869 Tile[x][y + 1] = EL_NUT_BREAKING;
6870 PlayLevelSound(x, y, SND_NUT_BREAKING);
6871 RaiseScoreElement(EL_NUT);
6874 else if (smashed == EL_PEARL)
6876 ResetGfxAnimation(x, y);
6878 Tile[x][y + 1] = EL_PEARL_BREAKING;
6879 PlayLevelSound(x, y, SND_PEARL_BREAKING);
6882 else if (smashed == EL_DIAMOND)
6884 Tile[x][y + 1] = EL_DIAMOND_BREAKING;
6885 PlayLevelSound(x, y, SND_DIAMOND_BREAKING);
6888 else if (IS_BELT_SWITCH(smashed))
6890 ToggleBeltSwitch(x, y + 1);
6892 else if (smashed == EL_SWITCHGATE_SWITCH_UP ||
6893 smashed == EL_SWITCHGATE_SWITCH_DOWN ||
6894 smashed == EL_DC_SWITCHGATE_SWITCH_UP ||
6895 smashed == EL_DC_SWITCHGATE_SWITCH_DOWN)
6897 ToggleSwitchgateSwitch(x, y + 1);
6899 else if (smashed == EL_LIGHT_SWITCH ||
6900 smashed == EL_LIGHT_SWITCH_ACTIVE)
6902 ToggleLightSwitch(x, y + 1);
6906 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6908 CheckElementChangeBySide(x, y + 1, smashed, element,
6909 CE_SWITCHED, CH_SIDE_TOP);
6910 CheckTriggeredElementChangeBySide(x, y + 1, smashed, CE_SWITCH_OF_X,
6916 CheckElementChange(x, y + 1, smashed, element, CE_SMASHED);
6921 // play sound of magic wall / mill
6923 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
6924 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ||
6925 Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))
6927 if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
6928 PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING);
6929 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
6930 PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING);
6931 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
6932 PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING);
6937 // play sound of object that hits the ground
6938 if (last_line || object_hit)
6939 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
6942 static void TurnRoundExt(int x, int y)
6954 { 0, 0 }, { 0, 0 }, { 0, 0 },
6959 int left, right, back;
6963 { MV_DOWN, MV_UP, MV_RIGHT },
6964 { MV_UP, MV_DOWN, MV_LEFT },
6966 { MV_LEFT, MV_RIGHT, MV_DOWN },
6970 { MV_RIGHT, MV_LEFT, MV_UP }
6973 int element = Tile[x][y];
6974 int move_pattern = element_info[element].move_pattern;
6976 int old_move_dir = MovDir[x][y];
6977 int left_dir = turn[old_move_dir].left;
6978 int right_dir = turn[old_move_dir].right;
6979 int back_dir = turn[old_move_dir].back;
6981 int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy;
6982 int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy;
6983 int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy;
6984 int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy;
6986 int left_x = x + left_dx, left_y = y + left_dy;
6987 int right_x = x + right_dx, right_y = y + right_dy;
6988 int move_x = x + move_dx, move_y = y + move_dy;
6992 if (element == EL_BUG || element == EL_BD_BUTTERFLY)
6994 TestIfBadThingTouchesOtherBadThing(x, y);
6996 if (ENEMY_CAN_ENTER_FIELD(element, right_x, right_y))
6997 MovDir[x][y] = right_dir;
6998 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
6999 MovDir[x][y] = left_dir;
7001 if (element == EL_BUG && MovDir[x][y] != old_move_dir)
7003 else if (element == EL_BD_BUTTERFLY) // && MovDir[x][y] == left_dir)
7006 else if (element == EL_SPACESHIP || element == EL_BD_FIREFLY)
7008 TestIfBadThingTouchesOtherBadThing(x, y);
7010 if (ENEMY_CAN_ENTER_FIELD(element, left_x, left_y))
7011 MovDir[x][y] = left_dir;
7012 else if (!ENEMY_CAN_ENTER_FIELD(element, move_x, move_y))
7013 MovDir[x][y] = right_dir;
7015 if (element == EL_SPACESHIP && MovDir[x][y] != old_move_dir)
7017 else if (element == EL_BD_FIREFLY) // && MovDir[x][y] == right_dir)
7020 else if (element == EL_SP_SNIKSNAK || element == EL_SP_ELECTRON)
7022 TestIfBadThingTouchesOtherBadThing(x, y);
7024 if (ELEMENT_CAN_ENTER_FIELD_BASE_4(element, left_x, left_y, 0))
7025 MovDir[x][y] = left_dir;
7026 else if (!ELEMENT_CAN_ENTER_FIELD_BASE_4(element, move_x, move_y, 0))
7027 MovDir[x][y] = right_dir;
7029 if (MovDir[x][y] != old_move_dir)
7032 else if (element == EL_YAMYAM)
7034 boolean can_turn_left = YAMYAM_CAN_ENTER_FIELD(element, left_x, left_y);
7035 boolean can_turn_right = YAMYAM_CAN_ENTER_FIELD(element, right_x, right_y);
7037 if (can_turn_left && can_turn_right)
7038 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7039 else if (can_turn_left)
7040 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7041 else if (can_turn_right)
7042 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7044 MovDir[x][y] = back_dir;
7046 MovDelay[x][y] = 16 + 16 * RND(3);
7048 else if (element == EL_DARK_YAMYAM)
7050 boolean can_turn_left = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7052 boolean can_turn_right = DARK_YAMYAM_CAN_ENTER_FIELD(element,
7055 if (can_turn_left && can_turn_right)
7056 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7057 else if (can_turn_left)
7058 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7059 else if (can_turn_right)
7060 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7062 MovDir[x][y] = back_dir;
7064 MovDelay[x][y] = 16 + 16 * RND(3);
7066 else if (element == EL_PACMAN)
7068 boolean can_turn_left = PACMAN_CAN_ENTER_FIELD(element, left_x, left_y);
7069 boolean can_turn_right = PACMAN_CAN_ENTER_FIELD(element, right_x, right_y);
7071 if (can_turn_left && can_turn_right)
7072 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7073 else if (can_turn_left)
7074 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7075 else if (can_turn_right)
7076 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7078 MovDir[x][y] = back_dir;
7080 MovDelay[x][y] = 6 + RND(40);
7082 else if (element == EL_PIG)
7084 boolean can_turn_left = PIG_CAN_ENTER_FIELD(element, left_x, left_y);
7085 boolean can_turn_right = PIG_CAN_ENTER_FIELD(element, right_x, right_y);
7086 boolean can_move_on = PIG_CAN_ENTER_FIELD(element, move_x, move_y);
7087 boolean should_turn_left, should_turn_right, should_move_on;
7089 int rnd = RND(rnd_value);
7091 should_turn_left = (can_turn_left &&
7093 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + left_dx,
7094 y + back_dy + left_dy)));
7095 should_turn_right = (can_turn_right &&
7097 IN_LEV_FIELD_AND_NOT_FREE(x + back_dx + right_dx,
7098 y + back_dy + right_dy)));
7099 should_move_on = (can_move_on &&
7102 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + left_dx,
7103 y + move_dy + left_dy) ||
7104 IN_LEV_FIELD_AND_NOT_FREE(x + move_dx + right_dx,
7105 y + move_dy + right_dy)));
7107 if (should_turn_left || should_turn_right || should_move_on)
7109 if (should_turn_left && should_turn_right && should_move_on)
7110 MovDir[x][y] = (rnd < rnd_value / 3 ? left_dir :
7111 rnd < 2 * rnd_value / 3 ? right_dir :
7113 else if (should_turn_left && should_turn_right)
7114 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7115 else if (should_turn_left && should_move_on)
7116 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : old_move_dir);
7117 else if (should_turn_right && should_move_on)
7118 MovDir[x][y] = (rnd < rnd_value / 2 ? right_dir : old_move_dir);
7119 else if (should_turn_left)
7120 MovDir[x][y] = left_dir;
7121 else if (should_turn_right)
7122 MovDir[x][y] = right_dir;
7123 else if (should_move_on)
7124 MovDir[x][y] = old_move_dir;
7126 else if (can_move_on && rnd > rnd_value / 8)
7127 MovDir[x][y] = old_move_dir;
7128 else if (can_turn_left && can_turn_right)
7129 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7130 else if (can_turn_left && rnd > rnd_value / 8)
7131 MovDir[x][y] = left_dir;
7132 else if (can_turn_right && rnd > rnd_value/8)
7133 MovDir[x][y] = right_dir;
7135 MovDir[x][y] = back_dir;
7137 xx = x + move_xy[MovDir[x][y]].dx;
7138 yy = y + move_xy[MovDir[x][y]].dy;
7140 if (!IN_LEV_FIELD(xx, yy) ||
7141 (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy])))
7142 MovDir[x][y] = old_move_dir;
7146 else if (element == EL_DRAGON)
7148 boolean can_turn_left = DRAGON_CAN_ENTER_FIELD(element, left_x, left_y);
7149 boolean can_turn_right = DRAGON_CAN_ENTER_FIELD(element, right_x, right_y);
7150 boolean can_move_on = DRAGON_CAN_ENTER_FIELD(element, move_x, move_y);
7152 int rnd = RND(rnd_value);
7154 if (can_move_on && rnd > rnd_value / 8)
7155 MovDir[x][y] = old_move_dir;
7156 else if (can_turn_left && can_turn_right)
7157 MovDir[x][y] = (rnd < rnd_value / 2 ? left_dir : right_dir);
7158 else if (can_turn_left && rnd > rnd_value / 8)
7159 MovDir[x][y] = left_dir;
7160 else if (can_turn_right && rnd > rnd_value / 8)
7161 MovDir[x][y] = right_dir;
7163 MovDir[x][y] = back_dir;
7165 xx = x + move_xy[MovDir[x][y]].dx;
7166 yy = y + move_xy[MovDir[x][y]].dy;
7168 if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy))
7169 MovDir[x][y] = old_move_dir;
7173 else if (element == EL_MOLE)
7175 boolean can_move_on =
7176 (MOLE_CAN_ENTER_FIELD(element, move_x, move_y,
7177 IS_AMOEBOID(Tile[move_x][move_y]) ||
7178 Tile[move_x][move_y] == EL_AMOEBA_SHRINKING));
7181 boolean can_turn_left =
7182 (MOLE_CAN_ENTER_FIELD(element, left_x, left_y,
7183 IS_AMOEBOID(Tile[left_x][left_y])));
7185 boolean can_turn_right =
7186 (MOLE_CAN_ENTER_FIELD(element, right_x, right_y,
7187 IS_AMOEBOID(Tile[right_x][right_y])));
7189 if (can_turn_left && can_turn_right)
7190 MovDir[x][y] = (RND(2) ? left_dir : right_dir);
7191 else if (can_turn_left)
7192 MovDir[x][y] = left_dir;
7194 MovDir[x][y] = right_dir;
7197 if (MovDir[x][y] != old_move_dir)
7200 else if (element == EL_BALLOON)
7202 MovDir[x][y] = game.wind_direction;
7205 else if (element == EL_SPRING)
7207 if (MovDir[x][y] & MV_HORIZONTAL)
7209 if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) &&
7210 !SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7212 Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE;
7213 ResetGfxAnimation(move_x, move_y);
7214 TEST_DrawLevelField(move_x, move_y);
7216 MovDir[x][y] = back_dir;
7218 else if (!SPRING_CAN_ENTER_FIELD(element, move_x, move_y) ||
7219 SPRING_CAN_ENTER_FIELD(element, x, y + 1))
7220 MovDir[x][y] = MV_NONE;
7225 else if (element == EL_ROBOT ||
7226 element == EL_SATELLITE ||
7227 element == EL_PENGUIN ||
7228 element == EL_EMC_ANDROID)
7230 int attr_x = -1, attr_y = -1;
7232 if (game.all_players_gone)
7234 attr_x = game.exit_x;
7235 attr_y = game.exit_y;
7241 for (i = 0; i < MAX_PLAYERS; i++)
7243 struct PlayerInfo *player = &stored_player[i];
7244 int jx = player->jx, jy = player->jy;
7246 if (!player->active)
7250 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7258 if (element == EL_ROBOT &&
7259 game.robot_wheel_x >= 0 &&
7260 game.robot_wheel_y >= 0 &&
7261 (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE ||
7262 game.engine_version < VERSION_IDENT(3,1,0,0)))
7264 attr_x = game.robot_wheel_x;
7265 attr_y = game.robot_wheel_y;
7268 if (element == EL_PENGUIN)
7271 static int xy[4][2] =
7279 for (i = 0; i < NUM_DIRECTIONS; i++)
7281 int ex = x + xy[i][0];
7282 int ey = y + xy[i][1];
7284 if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN ||
7285 Tile[ex][ey] == EL_EM_EXIT_OPEN ||
7286 Tile[ex][ey] == EL_STEEL_EXIT_OPEN ||
7287 Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN))
7296 MovDir[x][y] = MV_NONE;
7298 MovDir[x][y] |= (game.all_players_gone ? MV_RIGHT : MV_LEFT);
7299 else if (attr_x > x)
7300 MovDir[x][y] |= (game.all_players_gone ? MV_LEFT : MV_RIGHT);
7302 MovDir[x][y] |= (game.all_players_gone ? MV_DOWN : MV_UP);
7303 else if (attr_y > y)
7304 MovDir[x][y] |= (game.all_players_gone ? MV_UP : MV_DOWN);
7306 if (element == EL_ROBOT)
7310 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7311 MovDir[x][y] &= (RND(2) ? MV_HORIZONTAL : MV_VERTICAL);
7312 Moving2Blocked(x, y, &newx, &newy);
7314 if (IN_LEV_FIELD(newx, newy) && IS_FREE_OR_PLAYER(newx, newy))
7315 MovDelay[x][y] = 8 + 8 * !RND(3);
7317 MovDelay[x][y] = 16;
7319 else if (element == EL_PENGUIN)
7325 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7327 boolean first_horiz = RND(2);
7328 int new_move_dir = MovDir[x][y];
7331 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7332 Moving2Blocked(x, y, &newx, &newy);
7334 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7338 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7339 Moving2Blocked(x, y, &newx, &newy);
7341 if (PENGUIN_CAN_ENTER_FIELD(element, newx, newy))
7344 MovDir[x][y] = old_move_dir;
7348 else if (element == EL_SATELLITE)
7354 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7356 boolean first_horiz = RND(2);
7357 int new_move_dir = MovDir[x][y];
7360 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7361 Moving2Blocked(x, y, &newx, &newy);
7363 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7367 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7368 Moving2Blocked(x, y, &newx, &newy);
7370 if (SATELLITE_CAN_ENTER_FIELD(newx, newy))
7373 MovDir[x][y] = old_move_dir;
7377 else if (element == EL_EMC_ANDROID)
7379 static int check_pos[16] =
7381 -1, // 0 => (invalid)
7384 -1, // 3 => (invalid)
7386 0, // 5 => MV_LEFT | MV_UP
7387 2, // 6 => MV_RIGHT | MV_UP
7388 -1, // 7 => (invalid)
7390 6, // 9 => MV_LEFT | MV_DOWN
7391 4, // 10 => MV_RIGHT | MV_DOWN
7392 -1, // 11 => (invalid)
7393 -1, // 12 => (invalid)
7394 -1, // 13 => (invalid)
7395 -1, // 14 => (invalid)
7396 -1, // 15 => (invalid)
7404 { -1, -1, MV_LEFT | MV_UP },
7406 { +1, -1, MV_RIGHT | MV_UP },
7407 { +1, 0, MV_RIGHT },
7408 { +1, +1, MV_RIGHT | MV_DOWN },
7410 { -1, +1, MV_LEFT | MV_DOWN },
7413 int start_pos, check_order;
7414 boolean can_clone = FALSE;
7417 // check if there is any free field around current position
7418 for (i = 0; i < 8; i++)
7420 int newx = x + check_xy[i].dx;
7421 int newy = y + check_xy[i].dy;
7423 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7431 if (can_clone) // randomly find an element to clone
7435 start_pos = check_pos[RND(8)];
7436 check_order = (RND(2) ? -1 : +1);
7438 for (i = 0; i < 8; i++)
7440 int pos_raw = start_pos + i * check_order;
7441 int pos = (pos_raw + 8) % 8;
7442 int newx = x + check_xy[pos].dx;
7443 int newy = y + check_xy[pos].dy;
7445 if (ANDROID_CAN_CLONE_FIELD(newx, newy))
7447 element_info[element].move_leave_type = LEAVE_TYPE_LIMITED;
7448 element_info[element].move_leave_element = EL_TRIGGER_ELEMENT;
7450 Store[x][y] = Tile[newx][newy];
7459 if (can_clone) // randomly find a direction to move
7463 start_pos = check_pos[RND(8)];
7464 check_order = (RND(2) ? -1 : +1);
7466 for (i = 0; i < 8; i++)
7468 int pos_raw = start_pos + i * check_order;
7469 int pos = (pos_raw + 8) % 8;
7470 int newx = x + check_xy[pos].dx;
7471 int newy = y + check_xy[pos].dy;
7472 int new_move_dir = check_xy[pos].dir;
7474 if (IN_LEV_FIELD_AND_IS_FREE(newx, newy))
7476 MovDir[x][y] = new_move_dir;
7477 MovDelay[x][y] = level.android_clone_time * 8 + 1;
7486 if (can_clone) // cloning and moving successful
7489 // cannot clone -- try to move towards player
7491 start_pos = check_pos[MovDir[x][y] & 0x0f];
7492 check_order = (RND(2) ? -1 : +1);
7494 for (i = 0; i < 3; i++)
7496 // first check start_pos, then previous/next or (next/previous) pos
7497 int pos_raw = start_pos + (i < 2 ? i : -1) * check_order;
7498 int pos = (pos_raw + 8) % 8;
7499 int newx = x + check_xy[pos].dx;
7500 int newy = y + check_xy[pos].dy;
7501 int new_move_dir = check_xy[pos].dir;
7503 if (IS_PLAYER(newx, newy))
7506 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
7508 MovDir[x][y] = new_move_dir;
7509 MovDelay[x][y] = level.android_move_time * 8 + 1;
7516 else if (move_pattern == MV_TURNING_LEFT ||
7517 move_pattern == MV_TURNING_RIGHT ||
7518 move_pattern == MV_TURNING_LEFT_RIGHT ||
7519 move_pattern == MV_TURNING_RIGHT_LEFT ||
7520 move_pattern == MV_TURNING_RANDOM ||
7521 move_pattern == MV_ALL_DIRECTIONS)
7523 boolean can_turn_left =
7524 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y);
7525 boolean can_turn_right =
7526 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y);
7528 if (element_info[element].move_stepsize == 0) // "not moving"
7531 if (move_pattern == MV_TURNING_LEFT)
7532 MovDir[x][y] = left_dir;
7533 else if (move_pattern == MV_TURNING_RIGHT)
7534 MovDir[x][y] = right_dir;
7535 else if (move_pattern == MV_TURNING_LEFT_RIGHT)
7536 MovDir[x][y] = (can_turn_left || !can_turn_right ? left_dir : right_dir);
7537 else if (move_pattern == MV_TURNING_RIGHT_LEFT)
7538 MovDir[x][y] = (can_turn_right || !can_turn_left ? right_dir : left_dir);
7539 else if (move_pattern == MV_TURNING_RANDOM)
7540 MovDir[x][y] = (can_turn_left && !can_turn_right ? left_dir :
7541 can_turn_right && !can_turn_left ? right_dir :
7542 RND(2) ? left_dir : right_dir);
7543 else if (can_turn_left && can_turn_right)
7544 MovDir[x][y] = (RND(3) ? (RND(2) ? left_dir : right_dir) : back_dir);
7545 else if (can_turn_left)
7546 MovDir[x][y] = (RND(2) ? left_dir : back_dir);
7547 else if (can_turn_right)
7548 MovDir[x][y] = (RND(2) ? right_dir : back_dir);
7550 MovDir[x][y] = back_dir;
7552 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7554 else if (move_pattern == MV_HORIZONTAL ||
7555 move_pattern == MV_VERTICAL)
7557 if (move_pattern & old_move_dir)
7558 MovDir[x][y] = back_dir;
7559 else if (move_pattern == MV_HORIZONTAL)
7560 MovDir[x][y] = (RND(2) ? MV_LEFT : MV_RIGHT);
7561 else if (move_pattern == MV_VERTICAL)
7562 MovDir[x][y] = (RND(2) ? MV_UP : MV_DOWN);
7564 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7566 else if (move_pattern & MV_ANY_DIRECTION)
7568 MovDir[x][y] = move_pattern;
7569 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7571 else if (move_pattern & MV_WIND_DIRECTION)
7573 MovDir[x][y] = game.wind_direction;
7574 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7576 else if (move_pattern == MV_ALONG_LEFT_SIDE)
7578 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y))
7579 MovDir[x][y] = left_dir;
7580 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7581 MovDir[x][y] = right_dir;
7583 if (MovDir[x][y] != old_move_dir)
7584 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7586 else if (move_pattern == MV_ALONG_RIGHT_SIDE)
7588 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y))
7589 MovDir[x][y] = right_dir;
7590 else if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7591 MovDir[x][y] = left_dir;
7593 if (MovDir[x][y] != old_move_dir)
7594 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7596 else if (move_pattern == MV_TOWARDS_PLAYER ||
7597 move_pattern == MV_AWAY_FROM_PLAYER)
7599 int attr_x = -1, attr_y = -1;
7601 boolean move_away = (move_pattern == MV_AWAY_FROM_PLAYER);
7603 if (game.all_players_gone)
7605 attr_x = game.exit_x;
7606 attr_y = game.exit_y;
7612 for (i = 0; i < MAX_PLAYERS; i++)
7614 struct PlayerInfo *player = &stored_player[i];
7615 int jx = player->jx, jy = player->jy;
7617 if (!player->active)
7621 ABS(jx - x) + ABS(jy - y) < ABS(attr_x - x) + ABS(attr_y - y))
7629 MovDir[x][y] = MV_NONE;
7631 MovDir[x][y] |= (move_away ? MV_RIGHT : MV_LEFT);
7632 else if (attr_x > x)
7633 MovDir[x][y] |= (move_away ? MV_LEFT : MV_RIGHT);
7635 MovDir[x][y] |= (move_away ? MV_DOWN : MV_UP);
7636 else if (attr_y > y)
7637 MovDir[x][y] |= (move_away ? MV_UP : MV_DOWN);
7639 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7641 if (MovDir[x][y] & MV_HORIZONTAL && MovDir[x][y] & MV_VERTICAL)
7643 boolean first_horiz = RND(2);
7644 int new_move_dir = MovDir[x][y];
7646 if (element_info[element].move_stepsize == 0) // "not moving"
7648 first_horiz = (ABS(attr_x - x) >= ABS(attr_y - y));
7649 MovDir[x][y] &= (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7655 new_move_dir & (first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7656 Moving2Blocked(x, y, &newx, &newy);
7658 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7662 new_move_dir & (!first_horiz ? MV_HORIZONTAL : MV_VERTICAL);
7663 Moving2Blocked(x, y, &newx, &newy);
7665 if (CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
7668 MovDir[x][y] = old_move_dir;
7671 else if (move_pattern == MV_WHEN_PUSHED ||
7672 move_pattern == MV_WHEN_DROPPED)
7674 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, move_x, move_y))
7675 MovDir[x][y] = MV_NONE;
7679 else if (move_pattern & MV_MAZE_RUNNER_STYLE)
7681 static int test_xy[7][2] =
7691 static int test_dir[7] =
7701 boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER);
7702 int move_preference = -1000000; // start with very low preference
7703 int new_move_dir = MV_NONE;
7704 int start_test = RND(4);
7707 for (i = 0; i < NUM_DIRECTIONS; i++)
7709 int move_dir = test_dir[start_test + i];
7710 int move_dir_preference;
7712 xx = x + test_xy[start_test + i][0];
7713 yy = y + test_xy[start_test + i][1];
7715 if (hunter_mode && IN_LEV_FIELD(xx, yy) &&
7716 (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING))
7718 new_move_dir = move_dir;
7723 if (!CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, xx, yy))
7726 move_dir_preference = -1 * RunnerVisit[xx][yy];
7727 if (hunter_mode && PlayerVisit[xx][yy] > 0)
7728 move_dir_preference = PlayerVisit[xx][yy];
7730 if (move_dir_preference > move_preference)
7732 // prefer field that has not been visited for the longest time
7733 move_preference = move_dir_preference;
7734 new_move_dir = move_dir;
7736 else if (move_dir_preference == move_preference &&
7737 move_dir == old_move_dir)
7739 // prefer last direction when all directions are preferred equally
7740 move_preference = move_dir_preference;
7741 new_move_dir = move_dir;
7745 MovDir[x][y] = new_move_dir;
7746 if (old_move_dir != new_move_dir)
7747 MovDelay[x][y] = GET_NEW_MOVE_DELAY(element);
7751 static void TurnRound(int x, int y)
7753 int direction = MovDir[x][y];
7757 GfxDir[x][y] = MovDir[x][y];
7759 if (direction != MovDir[x][y])
7763 GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction);
7765 ResetGfxFrame(x, y);
7768 static boolean JustBeingPushed(int x, int y)
7772 for (i = 0; i < MAX_PLAYERS; i++)
7774 struct PlayerInfo *player = &stored_player[i];
7776 if (player->active && player->is_pushing && player->MovPos)
7778 int next_jx = player->jx + (player->jx - player->last_jx);
7779 int next_jy = player->jy + (player->jy - player->last_jy);
7781 if (x == next_jx && y == next_jy)
7789 static void StartMoving(int x, int y)
7791 boolean started_moving = FALSE; // some elements can fall _and_ move
7792 int element = Tile[x][y];
7797 if (MovDelay[x][y] == 0)
7798 GfxAction[x][y] = ACTION_DEFAULT;
7800 if (CAN_FALL(element) && y < lev_fieldy - 1)
7802 if ((x > 0 && IS_PLAYER(x - 1, y)) ||
7803 (x < lev_fieldx - 1 && IS_PLAYER(x + 1, y)))
7804 if (JustBeingPushed(x, y))
7807 if (element == EL_QUICKSAND_FULL)
7809 if (IS_FREE(x, y + 1))
7811 InitMovingField(x, y, MV_DOWN);
7812 started_moving = TRUE;
7814 Tile[x][y] = EL_QUICKSAND_EMPTYING;
7815 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7816 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7817 Store[x][y] = EL_ROCK;
7819 Store[x][y] = EL_ROCK;
7822 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7824 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7826 if (!MovDelay[x][y])
7828 MovDelay[x][y] = TILEY + 1;
7830 ResetGfxAnimation(x, y);
7831 ResetGfxAnimation(x, y + 1);
7836 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7837 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7844 Tile[x][y] = EL_QUICKSAND_EMPTY;
7845 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7846 Store[x][y + 1] = Store[x][y];
7849 PlayLevelSoundAction(x, y, ACTION_FILLING);
7851 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7853 if (!MovDelay[x][y])
7855 MovDelay[x][y] = TILEY + 1;
7857 ResetGfxAnimation(x, y);
7858 ResetGfxAnimation(x, y + 1);
7863 DrawLevelElement(x, y, EL_QUICKSAND_EMPTYING);
7864 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7871 Tile[x][y] = EL_QUICKSAND_EMPTY;
7872 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7873 Store[x][y + 1] = Store[x][y];
7876 PlayLevelSoundAction(x, y, ACTION_FILLING);
7879 else if (element == EL_QUICKSAND_FAST_FULL)
7881 if (IS_FREE(x, y + 1))
7883 InitMovingField(x, y, MV_DOWN);
7884 started_moving = TRUE;
7886 Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING;
7887 #if USE_QUICKSAND_BD_ROCK_BUGFIX
7888 if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK)
7889 Store[x][y] = EL_ROCK;
7891 Store[x][y] = EL_ROCK;
7894 PlayLevelSoundAction(x, y, ACTION_EMPTYING);
7896 else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7898 if (!MovDelay[x][y])
7900 MovDelay[x][y] = TILEY + 1;
7902 ResetGfxAnimation(x, y);
7903 ResetGfxAnimation(x, y + 1);
7908 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7909 DrawLevelElement(x, y + 1, EL_QUICKSAND_FAST_FILLING);
7916 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7917 Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL;
7918 Store[x][y + 1] = Store[x][y];
7921 PlayLevelSoundAction(x, y, ACTION_FILLING);
7923 else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7925 if (!MovDelay[x][y])
7927 MovDelay[x][y] = TILEY + 1;
7929 ResetGfxAnimation(x, y);
7930 ResetGfxAnimation(x, y + 1);
7935 DrawLevelElement(x, y, EL_QUICKSAND_FAST_EMPTYING);
7936 DrawLevelElement(x, y + 1, EL_QUICKSAND_FILLING);
7943 Tile[x][y] = EL_QUICKSAND_FAST_EMPTY;
7944 Tile[x][y + 1] = EL_QUICKSAND_FULL;
7945 Store[x][y + 1] = Store[x][y];
7948 PlayLevelSoundAction(x, y, ACTION_FILLING);
7951 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7952 Tile[x][y + 1] == EL_QUICKSAND_EMPTY)
7954 InitMovingField(x, y, MV_DOWN);
7955 started_moving = TRUE;
7957 Tile[x][y] = EL_QUICKSAND_FILLING;
7958 Store[x][y] = element;
7960 PlayLevelSoundAction(x, y, ACTION_FILLING);
7962 else if ((element == EL_ROCK || element == EL_BD_ROCK) &&
7963 Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY)
7965 InitMovingField(x, y, MV_DOWN);
7966 started_moving = TRUE;
7968 Tile[x][y] = EL_QUICKSAND_FAST_FILLING;
7969 Store[x][y] = element;
7971 PlayLevelSoundAction(x, y, ACTION_FILLING);
7973 else if (element == EL_MAGIC_WALL_FULL)
7975 if (IS_FREE(x, y + 1))
7977 InitMovingField(x, y, MV_DOWN);
7978 started_moving = TRUE;
7980 Tile[x][y] = EL_MAGIC_WALL_EMPTYING;
7981 Store[x][y] = EL_CHANGED(Store[x][y]);
7983 else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE)
7985 if (!MovDelay[x][y])
7986 MovDelay[x][y] = TILEY / 4 + 1;
7995 Tile[x][y] = EL_MAGIC_WALL_ACTIVE;
7996 Tile[x][y + 1] = EL_MAGIC_WALL_FULL;
7997 Store[x][y + 1] = EL_CHANGED(Store[x][y]);
8001 else if (element == EL_BD_MAGIC_WALL_FULL)
8003 if (IS_FREE(x, y + 1))
8005 InitMovingField(x, y, MV_DOWN);
8006 started_moving = TRUE;
8008 Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING;
8009 Store[x][y] = EL_CHANGED_BD(Store[x][y]);
8011 else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)
8013 if (!MovDelay[x][y])
8014 MovDelay[x][y] = TILEY / 4 + 1;
8023 Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE;
8024 Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL;
8025 Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]);
8029 else if (element == EL_DC_MAGIC_WALL_FULL)
8031 if (IS_FREE(x, y + 1))
8033 InitMovingField(x, y, MV_DOWN);
8034 started_moving = TRUE;
8036 Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING;
8037 Store[x][y] = EL_CHANGED_DC(Store[x][y]);
8039 else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)
8041 if (!MovDelay[x][y])
8042 MovDelay[x][y] = TILEY / 4 + 1;
8051 Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE;
8052 Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL;
8053 Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]);
8057 else if ((CAN_PASS_MAGIC_WALL(element) &&
8058 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ||
8059 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) ||
8060 (CAN_PASS_DC_MAGIC_WALL(element) &&
8061 (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)))
8064 InitMovingField(x, y, MV_DOWN);
8065 started_moving = TRUE;
8068 (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING :
8069 Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING :
8070 EL_DC_MAGIC_WALL_FILLING);
8071 Store[x][y] = element;
8073 else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID)
8075 SplashAcid(x, y + 1);
8077 InitMovingField(x, y, MV_DOWN);
8078 started_moving = TRUE;
8080 Store[x][y] = EL_ACID;
8083 (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8084 CheckImpact[x][y] && !IS_FREE(x, y + 1)) ||
8085 (game.engine_version >= VERSION_IDENT(3,0,7,0) &&
8086 CAN_FALL(element) && WasJustFalling[x][y] &&
8087 (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) ||
8089 (game.engine_version < VERSION_IDENT(2,2,0,7) &&
8090 CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] &&
8091 (Tile[x][y + 1] == EL_BLOCKED)))
8093 /* this is needed for a special case not covered by calling "Impact()"
8094 from "ContinueMoving()": if an element moves to a tile directly below
8095 another element which was just falling on that tile (which was empty
8096 in the previous frame), the falling element above would just stop
8097 instead of smashing the element below (in previous version, the above
8098 element was just checked for "moving" instead of "falling", resulting
8099 in incorrect smashes caused by horizontal movement of the above
8100 element; also, the case of the player being the element to smash was
8101 simply not covered here... :-/ ) */
8103 CheckCollision[x][y] = 0;
8104 CheckImpact[x][y] = 0;
8108 else if (IS_FREE(x, y + 1) && element == EL_SPRING && level.use_spring_bug)
8110 if (MovDir[x][y] == MV_NONE)
8112 InitMovingField(x, y, MV_DOWN);
8113 started_moving = TRUE;
8116 else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING)
8118 if (WasJustFalling[x][y]) // prevent animation from being restarted
8119 MovDir[x][y] = MV_DOWN;
8121 InitMovingField(x, y, MV_DOWN);
8122 started_moving = TRUE;
8124 else if (element == EL_AMOEBA_DROP)
8126 Tile[x][y] = EL_AMOEBA_GROWING;
8127 Store[x][y] = EL_AMOEBA_WET;
8129 else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) ||
8130 (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) &&
8131 !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] &&
8132 element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE)
8134 boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) &&
8135 (IS_FREE(x - 1, y + 1) ||
8136 Tile[x - 1][y + 1] == EL_ACID));
8137 boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) &&
8138 (IS_FREE(x + 1, y + 1) ||
8139 Tile[x + 1][y + 1] == EL_ACID));
8140 boolean can_fall_any = (can_fall_left || can_fall_right);
8141 boolean can_fall_both = (can_fall_left && can_fall_right);
8142 int slippery_type = element_info[Tile[x][y + 1]].slippery_type;
8144 if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM)
8146 if (slippery_type == SLIPPERY_ANY_LEFT_RIGHT && can_fall_both)
8147 can_fall_right = FALSE;
8148 else if (slippery_type == SLIPPERY_ANY_RIGHT_LEFT && can_fall_both)
8149 can_fall_left = FALSE;
8150 else if (slippery_type == SLIPPERY_ONLY_LEFT)
8151 can_fall_right = FALSE;
8152 else if (slippery_type == SLIPPERY_ONLY_RIGHT)
8153 can_fall_left = FALSE;
8155 can_fall_any = (can_fall_left || can_fall_right);
8156 can_fall_both = FALSE;
8161 if (element == EL_BD_ROCK || element == EL_BD_DIAMOND)
8162 can_fall_right = FALSE; // slip down on left side
8164 can_fall_left = !(can_fall_right = RND(2));
8166 can_fall_both = FALSE;
8171 // if not determined otherwise, prefer left side for slipping down
8172 InitMovingField(x, y, can_fall_left ? MV_LEFT : MV_RIGHT);
8173 started_moving = TRUE;
8176 else if (IS_BELT_ACTIVE(Tile[x][y + 1]))
8178 boolean left_is_free = (x > 0 && IS_FREE(x - 1, y));
8179 boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y));
8180 int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]);
8181 int belt_dir = game.belt_dir[belt_nr];
8183 if ((belt_dir == MV_LEFT && left_is_free) ||
8184 (belt_dir == MV_RIGHT && right_is_free))
8186 int nextx = (belt_dir == MV_LEFT ? x - 1 : x + 1);
8188 InitMovingField(x, y, belt_dir);
8189 started_moving = TRUE;
8191 Pushed[x][y] = TRUE;
8192 Pushed[nextx][y] = TRUE;
8194 GfxAction[x][y] = ACTION_DEFAULT;
8198 MovDir[x][y] = 0; // if element was moving, stop it
8203 // not "else if" because of elements that can fall and move (EL_SPRING)
8204 if (CAN_MOVE(element) && !started_moving)
8206 int move_pattern = element_info[element].move_pattern;
8209 Moving2Blocked(x, y, &newx, &newy);
8211 if (IS_PUSHABLE(element) && JustBeingPushed(x, y))
8214 if (game.engine_version >= VERSION_IDENT(3,1,0,0) &&
8215 CheckCollision[x][y] && !IN_LEV_FIELD_AND_IS_FREE(newx, newy))
8217 WasJustMoving[x][y] = 0;
8218 CheckCollision[x][y] = 0;
8220 TestIfElementHitsCustomElement(x, y, MovDir[x][y]);
8222 if (Tile[x][y] != element) // element has changed
8226 if (!MovDelay[x][y]) // start new movement phase
8228 // all objects that can change their move direction after each step
8229 // (YAMYAM, DARK_YAMYAM and PACMAN go straight until they hit a wall
8231 if (element != EL_YAMYAM &&
8232 element != EL_DARK_YAMYAM &&
8233 element != EL_PACMAN &&
8234 !(move_pattern & MV_ANY_DIRECTION) &&
8235 move_pattern != MV_TURNING_LEFT &&
8236 move_pattern != MV_TURNING_RIGHT &&
8237 move_pattern != MV_TURNING_LEFT_RIGHT &&
8238 move_pattern != MV_TURNING_RIGHT_LEFT &&
8239 move_pattern != MV_TURNING_RANDOM)
8243 if (MovDelay[x][y] && (element == EL_BUG ||
8244 element == EL_SPACESHIP ||
8245 element == EL_SP_SNIKSNAK ||
8246 element == EL_SP_ELECTRON ||
8247 element == EL_MOLE))
8248 TEST_DrawLevelField(x, y);
8252 if (MovDelay[x][y]) // wait some time before next movement
8256 if (element == EL_ROBOT ||
8257 element == EL_YAMYAM ||
8258 element == EL_DARK_YAMYAM)
8260 DrawLevelElementAnimationIfNeeded(x, y, element);
8261 PlayLevelSoundAction(x, y, ACTION_WAITING);
8263 else if (element == EL_SP_ELECTRON)
8264 DrawLevelElementAnimationIfNeeded(x, y, element);
8265 else if (element == EL_DRAGON)
8268 int dir = MovDir[x][y];
8269 int dx = (dir == MV_LEFT ? -1 : dir == MV_RIGHT ? +1 : 0);
8270 int dy = (dir == MV_UP ? -1 : dir == MV_DOWN ? +1 : 0);
8271 int graphic = (dir == MV_LEFT ? IMG_FLAMES_1_LEFT :
8272 dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT :
8273 dir == MV_UP ? IMG_FLAMES_1_UP :
8274 dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY);
8275 int frame = getGraphicAnimationFrameXY(graphic, x, y);
8277 GfxAction[x][y] = ACTION_ATTACKING;
8279 if (IS_PLAYER(x, y))
8280 DrawPlayerField(x, y);
8282 TEST_DrawLevelField(x, y);
8284 PlayLevelSoundActionIfLoop(x, y, ACTION_ATTACKING);
8286 for (i = 1; i <= 3; i++)
8288 int xx = x + i * dx;
8289 int yy = y + i * dy;
8290 int sx = SCREENX(xx);
8291 int sy = SCREENY(yy);
8292 int flame_graphic = graphic + (i - 1);
8294 if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy]))
8299 int flamed = MovingOrBlocked2Element(xx, yy);
8301 if (IS_CLASSIC_ENEMY(flamed) || CAN_EXPLODE_BY_DRAGONFIRE(flamed))
8304 RemoveMovingField(xx, yy);
8306 ChangeDelay[xx][yy] = 0;
8308 Tile[xx][yy] = EL_FLAMES;
8310 if (IN_SCR_FIELD(sx, sy))
8312 TEST_DrawLevelFieldCrumbled(xx, yy);
8313 DrawScreenGraphic(sx, sy, flame_graphic, frame);
8318 if (Tile[xx][yy] == EL_FLAMES)
8319 Tile[xx][yy] = EL_EMPTY;
8320 TEST_DrawLevelField(xx, yy);
8325 if (MovDelay[x][y]) // element still has to wait some time
8327 PlayLevelSoundAction(x, y, ACTION_WAITING);
8333 // now make next step
8335 Moving2Blocked(x, y, &newx, &newy); // get next screen position
8337 if (DONT_COLLIDE_WITH(element) &&
8338 IN_LEV_FIELD(newx, newy) && IS_PLAYER(newx, newy) &&
8339 !PLAYER_ENEMY_PROTECTED(newx, newy))
8341 TestIfBadThingRunsIntoPlayer(x, y, MovDir[x][y]);
8346 else if (CAN_MOVE_INTO_ACID(element) &&
8347 IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID &&
8348 !IS_MV_DIAGONAL(MovDir[x][y]) &&
8349 (MovDir[x][y] == MV_DOWN ||
8350 game.engine_version >= VERSION_IDENT(3,1,0,0)))
8352 SplashAcid(newx, newy);
8353 Store[x][y] = EL_ACID;
8355 else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy))
8357 if (Tile[newx][newy] == EL_EXIT_OPEN ||
8358 Tile[newx][newy] == EL_EM_EXIT_OPEN ||
8359 Tile[newx][newy] == EL_STEEL_EXIT_OPEN ||
8360 Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN)
8363 TEST_DrawLevelField(x, y);
8365 PlayLevelSound(newx, newy, SND_PENGUIN_PASSING);
8366 if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy)))
8367 DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0);
8369 game.friends_still_needed--;
8370 if (!game.friends_still_needed &&
8372 game.all_players_gone)
8377 else if (IS_FOOD_PENGUIN(Tile[newx][newy]))
8379 if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING)
8380 TEST_DrawLevelField(newx, newy);
8382 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
8384 else if (!IS_FREE(newx, newy))
8386 GfxAction[x][y] = ACTION_WAITING;
8388 if (IS_PLAYER(x, y))
8389 DrawPlayerField(x, y);
8391 TEST_DrawLevelField(x, y);
8396 else if (element == EL_PIG && IN_LEV_FIELD(newx, newy))
8398 if (IS_FOOD_PIG(Tile[newx][newy]))
8400 if (IS_MOVING(newx, newy))
8401 RemoveMovingField(newx, newy);
8404 Tile[newx][newy] = EL_EMPTY;
8405 TEST_DrawLevelField(newx, newy);
8408 PlayLevelSound(x, y, SND_PIG_DIGGING);
8410 else if (!IS_FREE(newx, newy))
8412 if (IS_PLAYER(x, y))
8413 DrawPlayerField(x, y);
8415 TEST_DrawLevelField(x, y);
8420 else if (element == EL_EMC_ANDROID && IN_LEV_FIELD(newx, newy))
8422 if (Store[x][y] != EL_EMPTY)
8424 boolean can_clone = FALSE;
8427 // check if element to clone is still there
8428 for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++)
8430 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y])
8438 // cannot clone or target field not free anymore -- do not clone
8439 if (!can_clone || !ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8440 Store[x][y] = EL_EMPTY;
8443 if (ANDROID_CAN_ENTER_FIELD(element, newx, newy))
8445 if (IS_MV_DIAGONAL(MovDir[x][y]))
8447 int diagonal_move_dir = MovDir[x][y];
8448 int stored = Store[x][y];
8449 int change_delay = 8;
8452 // android is moving diagonally
8454 CreateField(x, y, EL_DIAGONAL_SHRINKING);
8456 Store[x][y] = (stored == EL_ACID ? EL_EMPTY : stored);
8457 GfxElement[x][y] = EL_EMC_ANDROID;
8458 GfxAction[x][y] = ACTION_SHRINKING;
8459 GfxDir[x][y] = diagonal_move_dir;
8460 ChangeDelay[x][y] = change_delay;
8462 if (Store[x][y] == EL_EMPTY)
8463 Store[x][y] = GfxElementEmpty[x][y];
8465 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],
8468 DrawLevelGraphicAnimation(x, y, graphic);
8469 PlayLevelSoundAction(x, y, ACTION_SHRINKING);
8471 if (Tile[newx][newy] == EL_ACID)
8473 SplashAcid(newx, newy);
8478 CreateField(newx, newy, EL_DIAGONAL_GROWING);
8480 Store[newx][newy] = EL_EMC_ANDROID;
8481 GfxElement[newx][newy] = EL_EMC_ANDROID;
8482 GfxAction[newx][newy] = ACTION_GROWING;
8483 GfxDir[newx][newy] = diagonal_move_dir;
8484 ChangeDelay[newx][newy] = change_delay;
8486 graphic = el_act_dir2img(GfxElement[newx][newy],
8487 GfxAction[newx][newy], GfxDir[newx][newy]);
8489 DrawLevelGraphicAnimation(newx, newy, graphic);
8490 PlayLevelSoundAction(newx, newy, ACTION_GROWING);
8496 Tile[newx][newy] = EL_EMPTY;
8497 TEST_DrawLevelField(newx, newy);
8499 PlayLevelSoundAction(x, y, ACTION_DIGGING);
8502 else if (!IS_FREE(newx, newy))
8507 else if (IS_CUSTOM_ELEMENT(element) &&
8508 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, newx, newy))
8510 if (!DigFieldByCE(newx, newy, element))
8513 if (move_pattern & MV_MAZE_RUNNER_STYLE)
8515 RunnerVisit[x][y] = FrameCounter;
8516 PlayerVisit[x][y] /= 8; // expire player visit path
8519 else if (element == EL_DRAGON && IN_LEV_FIELD(newx, newy))
8521 if (!IS_FREE(newx, newy))
8523 if (IS_PLAYER(x, y))
8524 DrawPlayerField(x, y);
8526 TEST_DrawLevelField(x, y);
8532 boolean wanna_flame = !RND(10);
8533 int dx = newx - x, dy = newy - y;
8534 int newx1 = newx + 1 * dx, newy1 = newy + 1 * dy;
8535 int newx2 = newx + 2 * dx, newy2 = newy + 2 * dy;
8536 int element1 = (IN_LEV_FIELD(newx1, newy1) ?
8537 MovingOrBlocked2Element(newx1, newy1) : EL_STEELWALL);
8538 int element2 = (IN_LEV_FIELD(newx2, newy2) ?
8539 MovingOrBlocked2Element(newx2, newy2) : EL_STEELWALL);
8542 IS_CLASSIC_ENEMY(element1) ||
8543 IS_CLASSIC_ENEMY(element2)) &&
8544 element1 != EL_DRAGON && element2 != EL_DRAGON &&
8545 element1 != EL_FLAMES && element2 != EL_FLAMES)
8547 ResetGfxAnimation(x, y);
8548 GfxAction[x][y] = ACTION_ATTACKING;
8550 if (IS_PLAYER(x, y))
8551 DrawPlayerField(x, y);
8553 TEST_DrawLevelField(x, y);
8555 PlayLevelSound(x, y, SND_DRAGON_ATTACKING);
8557 MovDelay[x][y] = 50;
8559 Tile[newx][newy] = EL_FLAMES;
8560 if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY)
8561 Tile[newx1][newy1] = EL_FLAMES;
8562 if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY)
8563 Tile[newx2][newy2] = EL_FLAMES;
8569 else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8570 Tile[newx][newy] == EL_DIAMOND)
8572 if (IS_MOVING(newx, newy))
8573 RemoveMovingField(newx, newy);
8576 Tile[newx][newy] = EL_EMPTY;
8577 TEST_DrawLevelField(newx, newy);
8580 PlayLevelSound(x, y, SND_YAMYAM_DIGGING);
8582 else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) &&
8583 IS_FOOD_DARK_YAMYAM(Tile[newx][newy]))
8585 if (AmoebaNr[newx][newy])
8587 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8588 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8589 Tile[newx][newy] == EL_BD_AMOEBA)
8590 AmoebaCnt[AmoebaNr[newx][newy]]--;
8593 if (IS_MOVING(newx, newy))
8595 RemoveMovingField(newx, newy);
8599 Tile[newx][newy] = EL_EMPTY;
8600 TEST_DrawLevelField(newx, newy);
8603 PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING);
8605 else if ((element == EL_PACMAN || element == EL_MOLE)
8606 && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy]))
8608 if (AmoebaNr[newx][newy])
8610 AmoebaCnt2[AmoebaNr[newx][newy]]--;
8611 if (Tile[newx][newy] == EL_AMOEBA_FULL ||
8612 Tile[newx][newy] == EL_BD_AMOEBA)
8613 AmoebaCnt[AmoebaNr[newx][newy]]--;
8616 if (element == EL_MOLE)
8618 Tile[newx][newy] = EL_AMOEBA_SHRINKING;
8619 PlayLevelSound(x, y, SND_MOLE_DIGGING);
8621 ResetGfxAnimation(x, y);
8622 GfxAction[x][y] = ACTION_DIGGING;
8623 TEST_DrawLevelField(x, y);
8625 MovDelay[newx][newy] = 0; // start amoeba shrinking delay
8627 return; // wait for shrinking amoeba
8629 else // element == EL_PACMAN
8631 Tile[newx][newy] = EL_EMPTY;
8632 TEST_DrawLevelField(newx, newy);
8633 PlayLevelSound(x, y, SND_PACMAN_DIGGING);
8636 else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) &&
8637 (Tile[newx][newy] == EL_AMOEBA_SHRINKING ||
8638 (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy])))
8640 // wait for shrinking amoeba to completely disappear
8643 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy))
8645 // object was running against a wall
8649 if (GFX_ELEMENT(element) != EL_SAND) // !!! FIX THIS (crumble) !!!
8650 DrawLevelElementAnimation(x, y, element);
8652 if (DONT_TOUCH(element))
8653 TestIfBadThingTouchesPlayer(x, y);
8658 InitMovingField(x, y, MovDir[x][y]);
8660 PlayLevelSoundAction(x, y, ACTION_MOVING);
8664 ContinueMoving(x, y);
8667 void ContinueMoving(int x, int y)
8669 int element = Tile[x][y];
8670 struct ElementInfo *ei = &element_info[element];
8671 int direction = MovDir[x][y];
8672 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
8673 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
8674 int newx = x + dx, newy = y + dy;
8675 int stored = Store[x][y];
8676 int stored_new = Store[newx][newy];
8677 boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y));
8678 boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y));
8679 boolean last_line = (newy == lev_fieldy - 1);
8680 boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0);
8682 if (pushed_by_player) // special case: moving object pushed by player
8684 MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos));
8686 else if (use_step_delay) // special case: moving object has step delay
8688 if (!MovDelay[x][y])
8689 MovPos[x][y] += getElementMoveStepsize(x, y);
8694 MovDelay[x][y] = GET_NEW_STEP_DELAY(element);
8698 TEST_DrawLevelField(x, y);
8700 return; // element is still waiting
8703 else // normal case: generically moving object
8705 MovPos[x][y] += getElementMoveStepsize(x, y);
8708 if (ABS(MovPos[x][y]) < TILEX)
8710 TEST_DrawLevelField(x, y);
8712 return; // element is still moving
8715 // element reached destination field
8717 Tile[x][y] = EL_EMPTY;
8718 Tile[newx][newy] = element;
8719 MovPos[x][y] = 0; // force "not moving" for "crumbled sand"
8721 if (Store[x][y] == EL_ACID) // element is moving into acid pool
8723 element = Tile[newx][newy] = EL_ACID;
8725 else if (element == EL_MOLE)
8727 Tile[x][y] = EL_SAND;
8729 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8731 else if (element == EL_QUICKSAND_FILLING)
8733 element = Tile[newx][newy] = get_next_element(element);
8734 Store[newx][newy] = Store[x][y];
8736 else if (element == EL_QUICKSAND_EMPTYING)
8738 Tile[x][y] = get_next_element(element);
8739 element = Tile[newx][newy] = Store[x][y];
8741 else if (element == EL_QUICKSAND_FAST_FILLING)
8743 element = Tile[newx][newy] = get_next_element(element);
8744 Store[newx][newy] = Store[x][y];
8746 else if (element == EL_QUICKSAND_FAST_EMPTYING)
8748 Tile[x][y] = get_next_element(element);
8749 element = Tile[newx][newy] = Store[x][y];
8751 else if (element == EL_MAGIC_WALL_FILLING)
8753 element = Tile[newx][newy] = get_next_element(element);
8754 if (!game.magic_wall_active)
8755 element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD;
8756 Store[newx][newy] = Store[x][y];
8758 else if (element == EL_MAGIC_WALL_EMPTYING)
8760 Tile[x][y] = get_next_element(element);
8761 if (!game.magic_wall_active)
8762 Tile[x][y] = EL_MAGIC_WALL_DEAD;
8763 element = Tile[newx][newy] = Store[x][y];
8765 InitField(newx, newy, FALSE);
8767 else if (element == EL_BD_MAGIC_WALL_FILLING)
8769 element = Tile[newx][newy] = get_next_element(element);
8770 if (!game.magic_wall_active)
8771 element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD;
8772 Store[newx][newy] = Store[x][y];
8774 else if (element == EL_BD_MAGIC_WALL_EMPTYING)
8776 Tile[x][y] = get_next_element(element);
8777 if (!game.magic_wall_active)
8778 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
8779 element = Tile[newx][newy] = Store[x][y];
8781 InitField(newx, newy, FALSE);
8783 else if (element == EL_DC_MAGIC_WALL_FILLING)
8785 element = Tile[newx][newy] = get_next_element(element);
8786 if (!game.magic_wall_active)
8787 element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD;
8788 Store[newx][newy] = Store[x][y];
8790 else if (element == EL_DC_MAGIC_WALL_EMPTYING)
8792 Tile[x][y] = get_next_element(element);
8793 if (!game.magic_wall_active)
8794 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
8795 element = Tile[newx][newy] = Store[x][y];
8797 InitField(newx, newy, FALSE);
8799 else if (element == EL_AMOEBA_DROPPING)
8801 Tile[x][y] = get_next_element(element);
8802 element = Tile[newx][newy] = Store[x][y];
8804 else if (element == EL_SOKOBAN_OBJECT)
8807 Tile[x][y] = Back[x][y];
8809 if (Back[newx][newy])
8810 Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL;
8812 Back[x][y] = Back[newx][newy] = 0;
8815 Store[x][y] = EL_EMPTY;
8820 MovDelay[newx][newy] = 0;
8822 if (CAN_CHANGE_OR_HAS_ACTION(element))
8824 // copy element change control values to new field
8825 ChangeDelay[newx][newy] = ChangeDelay[x][y];
8826 ChangePage[newx][newy] = ChangePage[x][y];
8827 ChangeCount[newx][newy] = ChangeCount[x][y];
8828 ChangeEvent[newx][newy] = ChangeEvent[x][y];
8831 CustomValue[newx][newy] = CustomValue[x][y];
8833 ChangeDelay[x][y] = 0;
8834 ChangePage[x][y] = -1;
8835 ChangeCount[x][y] = 0;
8836 ChangeEvent[x][y] = -1;
8838 CustomValue[x][y] = 0;
8840 // copy animation control values to new field
8841 GfxFrame[newx][newy] = GfxFrame[x][y];
8842 GfxRandom[newx][newy] = GfxRandom[x][y]; // keep same random value
8843 GfxAction[newx][newy] = GfxAction[x][y]; // keep action one frame
8844 GfxDir[newx][newy] = GfxDir[x][y]; // keep element direction
8846 Pushed[x][y] = Pushed[newx][newy] = FALSE;
8848 // some elements can leave other elements behind after moving
8849 if (ei->move_leave_element != EL_EMPTY &&
8850 (ei->move_leave_type == LEAVE_TYPE_UNLIMITED || stored != EL_EMPTY) &&
8851 (!IS_PLAYER(x, y) || IS_WALKABLE(ei->move_leave_element)))
8853 int move_leave_element = ei->move_leave_element;
8855 // this makes it possible to leave the removed element again
8856 if (ei->move_leave_element == EL_TRIGGER_ELEMENT)
8857 move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored);
8859 Tile[x][y] = move_leave_element;
8861 if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS)
8862 MovDir[x][y] = direction;
8864 InitField(x, y, FALSE);
8866 if (GFX_CRUMBLED(Tile[x][y]))
8867 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
8869 if (IS_PLAYER_ELEMENT(move_leave_element))
8870 RelocatePlayer(x, y, move_leave_element);
8873 // do this after checking for left-behind element
8874 ResetGfxAnimation(x, y); // reset animation values for old field
8876 if (!CAN_MOVE(element) ||
8877 (CAN_FALL(element) && direction == MV_DOWN &&
8878 (element == EL_SPRING ||
8879 element_info[element].move_pattern == MV_WHEN_PUSHED ||
8880 element_info[element].move_pattern == MV_WHEN_DROPPED)))
8881 GfxDir[x][y] = MovDir[newx][newy] = 0;
8883 TEST_DrawLevelField(x, y);
8884 TEST_DrawLevelField(newx, newy);
8886 Stop[newx][newy] = TRUE; // ignore this element until the next frame
8888 // prevent pushed element from moving on in pushed direction
8889 if (pushed_by_player && CAN_MOVE(element) &&
8890 element_info[element].move_pattern & MV_ANY_DIRECTION &&
8891 !(element_info[element].move_pattern & direction))
8892 TurnRound(newx, newy);
8894 // prevent elements on conveyor belt from moving on in last direction
8895 if (pushed_by_conveyor && CAN_FALL(element) &&
8896 direction & MV_HORIZONTAL)
8897 MovDir[newx][newy] = 0;
8899 if (!pushed_by_player)
8901 int nextx = newx + dx, nexty = newy + dy;
8902 boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty);
8904 WasJustMoving[newx][newy] = CHECK_DELAY_MOVING;
8906 if (CAN_FALL(element) && direction == MV_DOWN)
8907 WasJustFalling[newx][newy] = CHECK_DELAY_FALLING;
8909 if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again)
8910 CheckCollision[newx][newy] = CHECK_DELAY_COLLISION;
8912 if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again)
8913 CheckImpact[newx][newy] = CHECK_DELAY_IMPACT;
8916 if (DONT_TOUCH(element)) // object may be nasty to player or others
8918 TestIfBadThingTouchesPlayer(newx, newy);
8919 TestIfBadThingTouchesFriend(newx, newy);
8921 if (!IS_CUSTOM_ELEMENT(element))
8922 TestIfBadThingTouchesOtherBadThing(newx, newy);
8924 else if (element == EL_PENGUIN)
8925 TestIfFriendTouchesBadThing(newx, newy);
8927 if (DONT_GET_HIT_BY(element))
8929 TestIfGoodThingGetsHitByBadThing(newx, newy, direction);
8932 // give the player one last chance (one more frame) to move away
8933 if (CAN_FALL(element) && direction == MV_DOWN &&
8934 (last_line || (!IS_FREE(x, newy + 1) &&
8935 (!IS_PLAYER(x, newy + 1) ||
8936 game.engine_version < VERSION_IDENT(3,1,1,0)))))
8939 if (pushed_by_player && !game.use_change_when_pushing_bug)
8941 int push_side = MV_DIR_OPPOSITE(direction);
8942 struct PlayerInfo *player = PLAYERINFO(x, y);
8944 CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER,
8945 player->index_bit, push_side);
8946 CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X,
8947 player->index_bit, push_side);
8950 if (element == EL_EMC_ANDROID && pushed_by_player) // make another move
8951 MovDelay[newx][newy] = 1;
8953 CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction);
8955 TestIfElementTouchesCustomElement(x, y); // empty or new element
8956 TestIfElementHitsCustomElement(newx, newy, direction);
8957 TestIfPlayerTouchesCustomElement(newx, newy);
8958 TestIfElementTouchesCustomElement(newx, newy);
8960 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY &&
8961 IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element))
8962 CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X,
8963 MV_DIR_OPPOSITE(direction));
8966 int AmoebaNeighbourNr(int ax, int ay)
8969 int element = Tile[ax][ay];
8971 static int xy[4][2] =
8979 for (i = 0; i < NUM_DIRECTIONS; i++)
8981 int x = ax + xy[i][0];
8982 int y = ay + xy[i][1];
8984 if (!IN_LEV_FIELD(x, y))
8987 if (Tile[x][y] == element && AmoebaNr[x][y] > 0)
8988 group_nr = AmoebaNr[x][y];
8994 static void AmoebaMerge(int ax, int ay)
8996 int i, x, y, xx, yy;
8997 int new_group_nr = AmoebaNr[ax][ay];
8998 static int xy[4][2] =
9006 if (new_group_nr == 0)
9009 for (i = 0; i < NUM_DIRECTIONS; i++)
9014 if (!IN_LEV_FIELD(x, y))
9017 if ((Tile[x][y] == EL_AMOEBA_FULL ||
9018 Tile[x][y] == EL_BD_AMOEBA ||
9019 Tile[x][y] == EL_AMOEBA_DEAD) &&
9020 AmoebaNr[x][y] != new_group_nr)
9022 int old_group_nr = AmoebaNr[x][y];
9024 if (old_group_nr == 0)
9027 AmoebaCnt[new_group_nr] += AmoebaCnt[old_group_nr];
9028 AmoebaCnt[old_group_nr] = 0;
9029 AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr];
9030 AmoebaCnt2[old_group_nr] = 0;
9032 SCAN_PLAYFIELD(xx, yy)
9034 if (AmoebaNr[xx][yy] == old_group_nr)
9035 AmoebaNr[xx][yy] = new_group_nr;
9041 void AmoebaToDiamond(int ax, int ay)
9045 if (Tile[ax][ay] == EL_AMOEBA_DEAD)
9047 int group_nr = AmoebaNr[ax][ay];
9052 Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay);
9053 Debug("game:playing:AmoebaToDiamond", "This should never happen!");
9059 SCAN_PLAYFIELD(x, y)
9061 if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr)
9064 Tile[x][y] = EL_AMOEBA_TO_DIAMOND;
9068 PlayLevelSound(ax, ay, (IS_GEM(level.amoeba_content) ?
9069 SND_AMOEBA_TURNING_TO_GEM :
9070 SND_AMOEBA_TURNING_TO_ROCK));
9075 static int xy[4][2] =
9083 for (i = 0; i < NUM_DIRECTIONS; i++)
9088 if (!IN_LEV_FIELD(x, y))
9091 if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND)
9093 PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ?
9094 SND_AMOEBA_TURNING_TO_GEM :
9095 SND_AMOEBA_TURNING_TO_ROCK));
9102 static void AmoebaToDiamondBD(int ax, int ay, int new_element)
9105 int group_nr = AmoebaNr[ax][ay];
9106 boolean done = FALSE;
9111 Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay);
9112 Debug("game:playing:AmoebaToDiamondBD", "This should never happen!");
9118 SCAN_PLAYFIELD(x, y)
9120 if (AmoebaNr[x][y] == group_nr &&
9121 (Tile[x][y] == EL_AMOEBA_DEAD ||
9122 Tile[x][y] == EL_BD_AMOEBA ||
9123 Tile[x][y] == EL_AMOEBA_GROWING))
9126 Tile[x][y] = new_element;
9127 InitField(x, y, FALSE);
9128 TEST_DrawLevelField(x, y);
9134 PlayLevelSound(ax, ay, (new_element == EL_BD_ROCK ?
9135 SND_BD_AMOEBA_TURNING_TO_ROCK :
9136 SND_BD_AMOEBA_TURNING_TO_GEM));
9139 static void AmoebaGrowing(int x, int y)
9141 static unsigned int sound_delay = 0;
9142 static unsigned int sound_delay_value = 0;
9144 if (!MovDelay[x][y]) // start new growing cycle
9148 if (DelayReached(&sound_delay, sound_delay_value))
9150 PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING);
9151 sound_delay_value = 30;
9155 if (MovDelay[x][y]) // wait some time before growing bigger
9158 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9160 int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING,
9161 6 - MovDelay[x][y]);
9163 DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame);
9166 if (!MovDelay[x][y])
9168 Tile[x][y] = Store[x][y];
9170 TEST_DrawLevelField(x, y);
9175 static void AmoebaShrinking(int x, int y)
9177 static unsigned int sound_delay = 0;
9178 static unsigned int sound_delay_value = 0;
9180 if (!MovDelay[x][y]) // start new shrinking cycle
9184 if (DelayReached(&sound_delay, sound_delay_value))
9185 sound_delay_value = 30;
9188 if (MovDelay[x][y]) // wait some time before shrinking
9191 if (MovDelay[x][y]/2 && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9193 int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING,
9194 6 - MovDelay[x][y]);
9196 DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame);
9199 if (!MovDelay[x][y])
9201 Tile[x][y] = EL_EMPTY;
9202 TEST_DrawLevelField(x, y);
9204 // don't let mole enter this field in this cycle;
9205 // (give priority to objects falling to this field from above)
9211 static void AmoebaReproduce(int ax, int ay)
9214 int element = Tile[ax][ay];
9215 int graphic = el2img(element);
9216 int newax = ax, neway = ay;
9217 boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER);
9218 static int xy[4][2] =
9226 if (!level.amoeba_speed && element != EL_EMC_DRIPPER)
9228 Tile[ax][ay] = EL_AMOEBA_DEAD;
9229 TEST_DrawLevelField(ax, ay);
9233 if (IS_ANIMATED(graphic))
9234 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9236 if (!MovDelay[ax][ay]) // start making new amoeba field
9237 MovDelay[ax][ay] = RND(FRAMES_PER_SECOND * 25 / (1 + level.amoeba_speed));
9239 if (MovDelay[ax][ay]) // wait some time before making new amoeba
9242 if (MovDelay[ax][ay])
9246 if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER
9249 int x = ax + xy[start][0];
9250 int y = ay + xy[start][1];
9252 if (!IN_LEV_FIELD(x, y))
9255 if (IS_FREE(x, y) ||
9256 CAN_GROW_INTO(Tile[x][y]) ||
9257 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9258 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9264 if (newax == ax && neway == ay)
9267 else // normal or "filled" (BD style) amoeba
9270 boolean waiting_for_player = FALSE;
9272 for (i = 0; i < NUM_DIRECTIONS; i++)
9274 int j = (start + i) % 4;
9275 int x = ax + xy[j][0];
9276 int y = ay + xy[j][1];
9278 if (!IN_LEV_FIELD(x, y))
9281 if (IS_FREE(x, y) ||
9282 CAN_GROW_INTO(Tile[x][y]) ||
9283 Tile[x][y] == EL_QUICKSAND_EMPTY ||
9284 Tile[x][y] == EL_QUICKSAND_FAST_EMPTY)
9290 else if (IS_PLAYER(x, y))
9291 waiting_for_player = TRUE;
9294 if (newax == ax && neway == ay) // amoeba cannot grow
9296 if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA))
9298 Tile[ax][ay] = EL_AMOEBA_DEAD;
9299 TEST_DrawLevelField(ax, ay);
9300 AmoebaCnt[AmoebaNr[ax][ay]]--;
9302 if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead
9304 if (element == EL_AMOEBA_FULL)
9305 AmoebaToDiamond(ax, ay);
9306 else if (element == EL_BD_AMOEBA)
9307 AmoebaToDiamondBD(ax, ay, level.amoeba_content);
9312 else if (element == EL_AMOEBA_FULL || element == EL_BD_AMOEBA)
9314 // amoeba gets larger by growing in some direction
9316 int new_group_nr = AmoebaNr[ax][ay];
9319 if (new_group_nr == 0)
9321 Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d",
9323 Debug("game:playing:AmoebaReproduce", "This should never happen!");
9329 AmoebaNr[newax][neway] = new_group_nr;
9330 AmoebaCnt[new_group_nr]++;
9331 AmoebaCnt2[new_group_nr]++;
9333 // if amoeba touches other amoeba(s) after growing, unify them
9334 AmoebaMerge(newax, neway);
9336 if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200)
9338 AmoebaToDiamondBD(newax, neway, EL_BD_ROCK);
9344 if (!can_drop || neway < ay || !IS_FREE(newax, neway) ||
9345 (neway == lev_fieldy - 1 && newax != ax))
9347 Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba
9348 Store[newax][neway] = element;
9350 else if (neway == ay || element == EL_EMC_DRIPPER)
9352 Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba
9354 PlayLevelSoundAction(newax, neway, ACTION_GROWING);
9358 InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba
9359 Tile[ax][ay] = EL_AMOEBA_DROPPING;
9360 Store[ax][ay] = EL_AMOEBA_DROP;
9361 ContinueMoving(ax, ay);
9365 TEST_DrawLevelField(newax, neway);
9368 static void Life(int ax, int ay)
9372 int element = Tile[ax][ay];
9373 int graphic = el2img(element);
9374 int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life :
9376 boolean changed = FALSE;
9378 if (IS_ANIMATED(graphic))
9379 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9384 if (!MovDelay[ax][ay]) // start new "game of life" cycle
9385 MovDelay[ax][ay] = life_time;
9387 if (MovDelay[ax][ay]) // wait some time before next cycle
9390 if (MovDelay[ax][ay])
9394 for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++)
9396 int xx = ax+x1, yy = ay+y1;
9397 int old_element = Tile[xx][yy];
9398 int num_neighbours = 0;
9400 if (!IN_LEV_FIELD(xx, yy))
9403 for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++)
9405 int x = xx+x2, y = yy+y2;
9407 if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy))
9410 boolean is_player_cell = (element == EL_GAME_OF_LIFE && IS_PLAYER(x, y));
9411 boolean is_neighbour = FALSE;
9413 if (level.use_life_bugs)
9415 (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) ||
9416 (IS_FREE(x, y) && Stop[x][y]));
9419 (Last[x][y] == element || is_player_cell);
9425 boolean is_free = FALSE;
9427 if (level.use_life_bugs)
9428 is_free = (IS_FREE(xx, yy));
9430 is_free = (IS_FREE(xx, yy) && Last[xx][yy] == EL_EMPTY);
9432 if (xx == ax && yy == ay) // field in the middle
9434 if (num_neighbours < life_parameter[0] ||
9435 num_neighbours > life_parameter[1])
9437 Tile[xx][yy] = EL_EMPTY;
9438 if (Tile[xx][yy] != old_element)
9439 TEST_DrawLevelField(xx, yy);
9440 Stop[xx][yy] = TRUE;
9444 else if (is_free || CAN_GROW_INTO(Tile[xx][yy]))
9445 { // free border field
9446 if (num_neighbours >= life_parameter[2] &&
9447 num_neighbours <= life_parameter[3])
9449 Tile[xx][yy] = element;
9450 MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1);
9451 if (Tile[xx][yy] != old_element)
9452 TEST_DrawLevelField(xx, yy);
9453 Stop[xx][yy] = TRUE;
9460 PlayLevelSound(ax, ay, element == EL_BIOMAZE ? SND_BIOMAZE_GROWING :
9461 SND_GAME_OF_LIFE_GROWING);
9464 static void InitRobotWheel(int x, int y)
9466 ChangeDelay[x][y] = level.time_wheel * FRAMES_PER_SECOND;
9469 static void RunRobotWheel(int x, int y)
9471 PlayLevelSound(x, y, SND_ROBOT_WHEEL_ACTIVE);
9474 static void StopRobotWheel(int x, int y)
9476 if (game.robot_wheel_x == x &&
9477 game.robot_wheel_y == y)
9479 game.robot_wheel_x = -1;
9480 game.robot_wheel_y = -1;
9481 game.robot_wheel_active = FALSE;
9485 static void InitTimegateWheel(int x, int y)
9487 ChangeDelay[x][y] = level.time_timegate * FRAMES_PER_SECOND;
9490 static void RunTimegateWheel(int x, int y)
9492 PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE);
9495 static void InitMagicBallDelay(int x, int y)
9497 ChangeDelay[x][y] = (level.ball_time + 1) * 8 + 1;
9500 static void ActivateMagicBall(int bx, int by)
9504 if (level.ball_random)
9506 int pos_border = RND(8); // select one of the eight border elements
9507 int pos_content = (pos_border > 3 ? pos_border + 1 : pos_border);
9508 int xx = pos_content % 3;
9509 int yy = pos_content / 3;
9514 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9515 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9519 for (y = by - 1; y <= by + 1; y++) for (x = bx - 1; x <= bx + 1; x++)
9521 int xx = x - bx + 1;
9522 int yy = y - by + 1;
9524 if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY)
9525 CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]);
9529 game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents;
9532 static void CheckExit(int x, int y)
9534 if (game.gems_still_needed > 0 ||
9535 game.sokoban_fields_still_needed > 0 ||
9536 game.sokoban_objects_still_needed > 0 ||
9537 game.lights_still_needed > 0)
9539 int element = Tile[x][y];
9540 int graphic = el2img(element);
9542 if (IS_ANIMATED(graphic))
9543 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9548 // do not re-open exit door closed after last player
9549 if (game.all_players_gone)
9552 Tile[x][y] = EL_EXIT_OPENING;
9554 PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING);
9557 static void CheckExitEM(int x, int y)
9559 if (game.gems_still_needed > 0 ||
9560 game.sokoban_fields_still_needed > 0 ||
9561 game.sokoban_objects_still_needed > 0 ||
9562 game.lights_still_needed > 0)
9564 int element = Tile[x][y];
9565 int graphic = el2img(element);
9567 if (IS_ANIMATED(graphic))
9568 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9573 // do not re-open exit door closed after last player
9574 if (game.all_players_gone)
9577 Tile[x][y] = EL_EM_EXIT_OPENING;
9579 PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING);
9582 static void CheckExitSteel(int x, int y)
9584 if (game.gems_still_needed > 0 ||
9585 game.sokoban_fields_still_needed > 0 ||
9586 game.sokoban_objects_still_needed > 0 ||
9587 game.lights_still_needed > 0)
9589 int element = Tile[x][y];
9590 int graphic = el2img(element);
9592 if (IS_ANIMATED(graphic))
9593 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9598 // do not re-open exit door closed after last player
9599 if (game.all_players_gone)
9602 Tile[x][y] = EL_STEEL_EXIT_OPENING;
9604 PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING);
9607 static void CheckExitSteelEM(int x, int y)
9609 if (game.gems_still_needed > 0 ||
9610 game.sokoban_fields_still_needed > 0 ||
9611 game.sokoban_objects_still_needed > 0 ||
9612 game.lights_still_needed > 0)
9614 int element = Tile[x][y];
9615 int graphic = el2img(element);
9617 if (IS_ANIMATED(graphic))
9618 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9623 // do not re-open exit door closed after last player
9624 if (game.all_players_gone)
9627 Tile[x][y] = EL_EM_STEEL_EXIT_OPENING;
9629 PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING);
9632 static void CheckExitSP(int x, int y)
9634 if (game.gems_still_needed > 0)
9636 int element = Tile[x][y];
9637 int graphic = el2img(element);
9639 if (IS_ANIMATED(graphic))
9640 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
9645 // do not re-open exit door closed after last player
9646 if (game.all_players_gone)
9649 Tile[x][y] = EL_SP_EXIT_OPENING;
9651 PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING);
9654 static void CloseAllOpenTimegates(void)
9658 SCAN_PLAYFIELD(x, y)
9660 int element = Tile[x][y];
9662 if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING)
9664 Tile[x][y] = EL_TIMEGATE_CLOSING;
9666 PlayLevelSoundAction(x, y, ACTION_CLOSING);
9671 static void DrawTwinkleOnField(int x, int y)
9673 if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y))
9676 if (Tile[x][y] == EL_BD_DIAMOND)
9679 if (MovDelay[x][y] == 0) // next animation frame
9680 MovDelay[x][y] = 11 * !GetSimpleRandom(500);
9682 if (MovDelay[x][y] != 0) // wait some time before next frame
9686 DrawLevelElementAnimation(x, y, Tile[x][y]);
9688 if (MovDelay[x][y] != 0)
9690 int frame = getGraphicAnimationFrame(IMG_TWINKLE_WHITE,
9691 10 - MovDelay[x][y]);
9693 DrawGraphicThruMask(SCREENX(x), SCREENY(y), IMG_TWINKLE_WHITE, frame);
9698 static void MauerWaechst(int x, int y)
9702 if (!MovDelay[x][y]) // next animation frame
9703 MovDelay[x][y] = 3 * delay;
9705 if (MovDelay[x][y]) // wait some time before next frame
9709 if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
9711 int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]);
9712 int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]);
9714 DrawLevelGraphic(x, y, graphic, frame);
9717 if (!MovDelay[x][y])
9719 if (MovDir[x][y] == MV_LEFT)
9721 if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y]))
9722 TEST_DrawLevelField(x - 1, y);
9724 else if (MovDir[x][y] == MV_RIGHT)
9726 if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y]))
9727 TEST_DrawLevelField(x + 1, y);
9729 else if (MovDir[x][y] == MV_UP)
9731 if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1]))
9732 TEST_DrawLevelField(x, y - 1);
9736 if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1]))
9737 TEST_DrawLevelField(x, y + 1);
9740 Tile[x][y] = Store[x][y];
9742 GfxDir[x][y] = MovDir[x][y] = MV_NONE;
9743 TEST_DrawLevelField(x, y);
9748 static void MauerAbleger(int ax, int ay)
9750 int element = Tile[ax][ay];
9751 int graphic = el2img(element);
9752 boolean oben_frei = FALSE, unten_frei = FALSE;
9753 boolean links_frei = FALSE, rechts_frei = FALSE;
9754 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9755 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9756 boolean new_wall = FALSE;
9758 if (IS_ANIMATED(graphic))
9759 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9761 if (!MovDelay[ax][ay]) // start building new wall
9762 MovDelay[ax][ay] = 6;
9764 if (MovDelay[ax][ay]) // wait some time before building new wall
9767 if (MovDelay[ax][ay])
9771 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9773 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9775 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9777 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9780 if (element == EL_EXPANDABLE_WALL_VERTICAL ||
9781 element == EL_EXPANDABLE_WALL_ANY)
9785 Tile[ax][ay - 1] = EL_EXPANDABLE_WALL_GROWING;
9786 Store[ax][ay - 1] = element;
9787 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9788 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9789 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_WALL_GROWING_UP, 0);
9794 Tile[ax][ay + 1] = EL_EXPANDABLE_WALL_GROWING;
9795 Store[ax][ay + 1] = element;
9796 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9797 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9798 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_WALL_GROWING_DOWN, 0);
9803 if (element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9804 element == EL_EXPANDABLE_WALL_ANY ||
9805 element == EL_EXPANDABLE_WALL ||
9806 element == EL_BD_EXPANDABLE_WALL)
9810 Tile[ax - 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9811 Store[ax - 1][ay] = element;
9812 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9813 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9814 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_WALL_GROWING_LEFT, 0);
9820 Tile[ax + 1][ay] = EL_EXPANDABLE_WALL_GROWING;
9821 Store[ax + 1][ay] = element;
9822 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9823 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9824 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0);
9829 if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei))
9830 TEST_DrawLevelField(ax, ay);
9832 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9834 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9835 unten_massiv = TRUE;
9836 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9837 links_massiv = TRUE;
9838 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9839 rechts_massiv = TRUE;
9841 if (((oben_massiv && unten_massiv) ||
9842 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
9843 element == EL_EXPANDABLE_WALL) &&
9844 ((links_massiv && rechts_massiv) ||
9845 element == EL_EXPANDABLE_WALL_VERTICAL))
9846 Tile[ax][ay] = EL_WALL;
9849 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9852 static void MauerAblegerStahl(int ax, int ay)
9854 int element = Tile[ax][ay];
9855 int graphic = el2img(element);
9856 boolean oben_frei = FALSE, unten_frei = FALSE;
9857 boolean links_frei = FALSE, rechts_frei = FALSE;
9858 boolean oben_massiv = FALSE, unten_massiv = FALSE;
9859 boolean links_massiv = FALSE, rechts_massiv = FALSE;
9860 boolean new_wall = FALSE;
9862 if (IS_ANIMATED(graphic))
9863 DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic);
9865 if (!MovDelay[ax][ay]) // start building new wall
9866 MovDelay[ax][ay] = 6;
9868 if (MovDelay[ax][ay]) // wait some time before building new wall
9871 if (MovDelay[ax][ay])
9875 if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1))
9877 if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1))
9879 if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay))
9881 if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay))
9884 if (element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
9885 element == EL_EXPANDABLE_STEELWALL_ANY)
9889 Tile[ax][ay - 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9890 Store[ax][ay - 1] = element;
9891 GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP;
9892 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1)))
9893 DrawLevelGraphic(ax, ay - 1, IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0);
9898 Tile[ax][ay + 1] = EL_EXPANDABLE_STEELWALL_GROWING;
9899 Store[ax][ay + 1] = element;
9900 GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN;
9901 if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1)))
9902 DrawLevelGraphic(ax, ay + 1, IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0);
9907 if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
9908 element == EL_EXPANDABLE_STEELWALL_ANY)
9912 Tile[ax - 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9913 Store[ax - 1][ay] = element;
9914 GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT;
9915 if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay)))
9916 DrawLevelGraphic(ax - 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0);
9922 Tile[ax + 1][ay] = EL_EXPANDABLE_STEELWALL_GROWING;
9923 Store[ax + 1][ay] = element;
9924 GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT;
9925 if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay)))
9926 DrawLevelGraphic(ax + 1, ay, IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0);
9931 if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1]))
9933 if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1]))
9934 unten_massiv = TRUE;
9935 if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay]))
9936 links_massiv = TRUE;
9937 if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay]))
9938 rechts_massiv = TRUE;
9940 if (((oben_massiv && unten_massiv) ||
9941 element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) &&
9942 ((links_massiv && rechts_massiv) ||
9943 element == EL_EXPANDABLE_STEELWALL_VERTICAL))
9944 Tile[ax][ay] = EL_STEELWALL;
9947 PlayLevelSoundAction(ax, ay, ACTION_GROWING);
9950 static void CheckForDragon(int x, int y)
9953 boolean dragon_found = FALSE;
9954 static int xy[4][2] =
9962 for (i = 0; i < NUM_DIRECTIONS; i++)
9964 for (j = 0; j < 4; j++)
9966 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9968 if (IN_LEV_FIELD(xx, yy) &&
9969 (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON))
9971 if (Tile[xx][yy] == EL_DRAGON)
9972 dragon_found = TRUE;
9981 for (i = 0; i < NUM_DIRECTIONS; i++)
9983 for (j = 0; j < 3; j++)
9985 int xx = x + j * xy[i][0], yy = y + j * xy[i][1];
9987 if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES)
9989 Tile[xx][yy] = EL_EMPTY;
9990 TEST_DrawLevelField(xx, yy);
9999 static void InitBuggyBase(int x, int y)
10001 int element = Tile[x][y];
10002 int activating_delay = FRAMES_PER_SECOND / 4;
10004 ChangeDelay[x][y] =
10005 (element == EL_SP_BUGGY_BASE ?
10006 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND) - activating_delay :
10007 element == EL_SP_BUGGY_BASE_ACTIVATING ?
10009 element == EL_SP_BUGGY_BASE_ACTIVE ?
10010 1 * FRAMES_PER_SECOND + RND(1 * FRAMES_PER_SECOND) : 1);
10013 static void WarnBuggyBase(int x, int y)
10016 static int xy[4][2] =
10024 for (i = 0; i < NUM_DIRECTIONS; i++)
10026 int xx = x + xy[i][0];
10027 int yy = y + xy[i][1];
10029 if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy))
10031 PlayLevelSound(x, y, SND_SP_BUGGY_BASE_ACTIVE);
10038 static void InitTrap(int x, int y)
10040 ChangeDelay[x][y] = 2 * FRAMES_PER_SECOND + RND(5 * FRAMES_PER_SECOND);
10043 static void ActivateTrap(int x, int y)
10045 PlayLevelSound(x, y, SND_TRAP_ACTIVATING);
10048 static void ChangeActiveTrap(int x, int y)
10050 int graphic = IMG_TRAP_ACTIVE;
10052 // if new animation frame was drawn, correct crumbled sand border
10053 if (IS_NEW_FRAME(GfxFrame[x][y], graphic))
10054 TEST_DrawLevelFieldCrumbled(x, y);
10057 static int getSpecialActionElement(int element, int number, int base_element)
10059 return (element != EL_EMPTY ? element :
10060 number != -1 ? base_element + number - 1 :
10064 static int getModifiedActionNumber(int value_old, int operator, int operand,
10065 int value_min, int value_max)
10067 int value_new = (operator == CA_MODE_SET ? operand :
10068 operator == CA_MODE_ADD ? value_old + operand :
10069 operator == CA_MODE_SUBTRACT ? value_old - operand :
10070 operator == CA_MODE_MULTIPLY ? value_old * operand :
10071 operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
10072 operator == CA_MODE_MODULO ? value_old % MAX(1, operand) :
10075 return (value_new < value_min ? value_min :
10076 value_new > value_max ? value_max :
10080 static void ExecuteCustomElementAction(int x, int y, int element, int page)
10082 struct ElementInfo *ei = &element_info[element];
10083 struct ElementChangeInfo *change = &ei->change_page[page];
10084 int target_element = change->target_element;
10085 int action_type = change->action_type;
10086 int action_mode = change->action_mode;
10087 int action_arg = change->action_arg;
10088 int action_element = change->action_element;
10091 if (!change->has_action)
10094 // ---------- determine action paramater values -----------------------------
10096 int level_time_value =
10097 (level.time > 0 ? TimeLeft :
10100 int action_arg_element_raw =
10101 (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
10102 action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
10103 action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
10104 action_arg == CA_ARG_ELEMENT_ACTION ? change->action_element :
10105 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ? change->actual_trigger_element:
10106 action_arg == CA_ARG_INVENTORY_RM_TARGET ? change->target_element :
10107 action_arg == CA_ARG_INVENTORY_RM_ACTION ? change->action_element :
10109 int action_arg_element = GetElementFromGroupElement(action_arg_element_raw);
10111 int action_arg_direction =
10112 (action_arg >= CA_ARG_DIRECTION_LEFT &&
10113 action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION :
10114 action_arg == CA_ARG_DIRECTION_TRIGGER ?
10115 change->actual_trigger_side :
10116 action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ?
10117 MV_DIR_OPPOSITE(change->actual_trigger_side) :
10120 int action_arg_number_min =
10121 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_NOT_MOVING :
10124 int action_arg_number_max =
10125 (action_type == CA_SET_PLAYER_SPEED ? STEPSIZE_EVEN_FASTER :
10126 action_type == CA_SET_LEVEL_GEMS ? 999 :
10127 action_type == CA_SET_LEVEL_TIME ? 9999 :
10128 action_type == CA_SET_LEVEL_SCORE ? 99999 :
10129 action_type == CA_SET_CE_VALUE ? 9999 :
10130 action_type == CA_SET_CE_SCORE ? 9999 :
10133 int action_arg_number_reset =
10134 (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] :
10135 action_type == CA_SET_LEVEL_GEMS ? level.gems_needed :
10136 action_type == CA_SET_LEVEL_TIME ? level.time :
10137 action_type == CA_SET_LEVEL_SCORE ? 0 :
10138 action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) :
10139 action_type == CA_SET_CE_SCORE ? 0 :
10142 int action_arg_number =
10143 (action_arg <= CA_ARG_MAX ? action_arg :
10144 action_arg >= CA_ARG_SPEED_NOT_MOVING &&
10145 action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) :
10146 action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset :
10147 action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min :
10148 action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max :
10149 action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset :
10150 action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] :
10151 action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
10152 action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) :
10153 action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value :
10154 action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? game.gems_still_needed :
10155 action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? game.score :
10156 action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element):
10157 action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value:
10158 action_arg == CA_ARG_ELEMENT_CV_ACTION ? GET_NEW_CE_VALUE(action_element):
10159 action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) :
10160 action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score:
10161 action_arg == CA_ARG_ELEMENT_CS_ACTION ? GET_CE_SCORE(action_element) :
10162 action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element :
10163 action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element :
10164 action_arg == CA_ARG_ELEMENT_NR_ACTION ? change->action_element :
10167 int action_arg_number_old =
10168 (action_type == CA_SET_LEVEL_GEMS ? game.gems_still_needed :
10169 action_type == CA_SET_LEVEL_TIME ? TimeLeft :
10170 action_type == CA_SET_LEVEL_SCORE ? game.score :
10171 action_type == CA_SET_CE_VALUE ? CustomValue[x][y] :
10172 action_type == CA_SET_CE_SCORE ? ei->collect_score :
10175 int action_arg_number_new =
10176 getModifiedActionNumber(action_arg_number_old,
10177 action_mode, action_arg_number,
10178 action_arg_number_min, action_arg_number_max);
10180 int trigger_player_bits =
10181 (change->actual_trigger_player_bits != CH_PLAYER_NONE ?
10182 change->actual_trigger_player_bits : change->trigger_player);
10184 int action_arg_player_bits =
10185 (action_arg >= CA_ARG_PLAYER_1 &&
10186 action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
10187 action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits :
10188 action_arg == CA_ARG_PLAYER_ACTION ? 1 << GET_PLAYER_NR(action_element) :
10191 // ---------- execute action -----------------------------------------------
10193 switch (action_type)
10200 // ---------- level actions ----------------------------------------------
10202 case CA_RESTART_LEVEL:
10204 game.restart_level = TRUE;
10209 case CA_SHOW_ENVELOPE:
10211 int element = getSpecialActionElement(action_arg_element,
10212 action_arg_number, EL_ENVELOPE_1);
10214 if (IS_ENVELOPE(element))
10215 local_player->show_envelope = element;
10220 case CA_SET_LEVEL_TIME:
10222 if (level.time > 0) // only modify limited time value
10224 TimeLeft = action_arg_number_new;
10226 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
10228 DisplayGameControlValues();
10230 if (!TimeLeft && setup.time_limit)
10231 for (i = 0; i < MAX_PLAYERS; i++)
10232 KillPlayer(&stored_player[i]);
10238 case CA_SET_LEVEL_SCORE:
10240 game.score = action_arg_number_new;
10242 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
10244 DisplayGameControlValues();
10249 case CA_SET_LEVEL_GEMS:
10251 game.gems_still_needed = action_arg_number_new;
10253 game.snapshot.collected_item = TRUE;
10255 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
10257 DisplayGameControlValues();
10262 case CA_SET_LEVEL_WIND:
10264 game.wind_direction = action_arg_direction;
10269 case CA_SET_LEVEL_RANDOM_SEED:
10271 // ensure that setting a new random seed while playing is predictable
10272 InitRND(action_arg_number_new ? action_arg_number_new : RND(1000000) + 1);
10277 // ---------- player actions ---------------------------------------------
10279 case CA_MOVE_PLAYER:
10280 case CA_MOVE_PLAYER_NEW:
10282 // automatically move to the next field in specified direction
10283 for (i = 0; i < MAX_PLAYERS; i++)
10284 if (trigger_player_bits & (1 << i))
10285 if (action_type == CA_MOVE_PLAYER ||
10286 stored_player[i].MovPos == 0)
10287 stored_player[i].programmed_action = action_arg_direction;
10292 case CA_EXIT_PLAYER:
10294 for (i = 0; i < MAX_PLAYERS; i++)
10295 if (action_arg_player_bits & (1 << i))
10296 ExitPlayer(&stored_player[i]);
10298 if (game.players_still_needed == 0)
10304 case CA_KILL_PLAYER:
10306 for (i = 0; i < MAX_PLAYERS; i++)
10307 if (action_arg_player_bits & (1 << i))
10308 KillPlayer(&stored_player[i]);
10313 case CA_SET_PLAYER_KEYS:
10315 int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE);
10316 int element = getSpecialActionElement(action_arg_element,
10317 action_arg_number, EL_KEY_1);
10319 if (IS_KEY(element))
10321 for (i = 0; i < MAX_PLAYERS; i++)
10323 if (trigger_player_bits & (1 << i))
10325 stored_player[i].key[KEY_NR(element)] = key_state;
10327 DrawGameDoorValues();
10335 case CA_SET_PLAYER_SPEED:
10337 for (i = 0; i < MAX_PLAYERS; i++)
10339 if (trigger_player_bits & (1 << i))
10341 int move_stepsize = TILEX / stored_player[i].move_delay_value;
10343 if (action_arg == CA_ARG_SPEED_FASTER &&
10344 stored_player[i].cannot_move)
10346 action_arg_number = STEPSIZE_VERY_SLOW;
10348 else if (action_arg == CA_ARG_SPEED_SLOWER ||
10349 action_arg == CA_ARG_SPEED_FASTER)
10351 action_arg_number = 2;
10352 action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE :
10355 else if (action_arg == CA_ARG_NUMBER_RESET)
10357 action_arg_number = level.initial_player_stepsize[i];
10361 getModifiedActionNumber(move_stepsize,
10364 action_arg_number_min,
10365 action_arg_number_max);
10367 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE);
10374 case CA_SET_PLAYER_SHIELD:
10376 for (i = 0; i < MAX_PLAYERS; i++)
10378 if (trigger_player_bits & (1 << i))
10380 if (action_arg == CA_ARG_SHIELD_OFF)
10382 stored_player[i].shield_normal_time_left = 0;
10383 stored_player[i].shield_deadly_time_left = 0;
10385 else if (action_arg == CA_ARG_SHIELD_NORMAL)
10387 stored_player[i].shield_normal_time_left = 999999;
10389 else if (action_arg == CA_ARG_SHIELD_DEADLY)
10391 stored_player[i].shield_normal_time_left = 999999;
10392 stored_player[i].shield_deadly_time_left = 999999;
10400 case CA_SET_PLAYER_GRAVITY:
10402 for (i = 0; i < MAX_PLAYERS; i++)
10404 if (trigger_player_bits & (1 << i))
10406 stored_player[i].gravity =
10407 (action_arg == CA_ARG_GRAVITY_OFF ? FALSE :
10408 action_arg == CA_ARG_GRAVITY_ON ? TRUE :
10409 action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity :
10410 stored_player[i].gravity);
10417 case CA_SET_PLAYER_ARTWORK:
10419 for (i = 0; i < MAX_PLAYERS; i++)
10421 if (trigger_player_bits & (1 << i))
10423 int artwork_element = action_arg_element;
10425 if (action_arg == CA_ARG_ELEMENT_RESET)
10427 (level.use_artwork_element[i] ? level.artwork_element[i] :
10428 stored_player[i].element_nr);
10430 if (stored_player[i].artwork_element != artwork_element)
10431 stored_player[i].Frame = 0;
10433 stored_player[i].artwork_element = artwork_element;
10435 SetPlayerWaiting(&stored_player[i], FALSE);
10437 // set number of special actions for bored and sleeping animation
10438 stored_player[i].num_special_action_bored =
10439 get_num_special_action(artwork_element,
10440 ACTION_BORING_1, ACTION_BORING_LAST);
10441 stored_player[i].num_special_action_sleeping =
10442 get_num_special_action(artwork_element,
10443 ACTION_SLEEPING_1, ACTION_SLEEPING_LAST);
10450 case CA_SET_PLAYER_INVENTORY:
10452 for (i = 0; i < MAX_PLAYERS; i++)
10454 struct PlayerInfo *player = &stored_player[i];
10457 if (trigger_player_bits & (1 << i))
10459 int inventory_element = action_arg_element;
10461 if (action_arg == CA_ARG_ELEMENT_TARGET ||
10462 action_arg == CA_ARG_ELEMENT_TRIGGER ||
10463 action_arg == CA_ARG_ELEMENT_ACTION)
10465 int element = inventory_element;
10466 int collect_count = element_info[element].collect_count_initial;
10468 if (!IS_CUSTOM_ELEMENT(element))
10471 if (collect_count == 0)
10472 player->inventory_infinite_element = element;
10474 for (k = 0; k < collect_count; k++)
10475 if (player->inventory_size < MAX_INVENTORY_SIZE)
10476 player->inventory_element[player->inventory_size++] =
10479 else if (action_arg == CA_ARG_INVENTORY_RM_TARGET ||
10480 action_arg == CA_ARG_INVENTORY_RM_TRIGGER ||
10481 action_arg == CA_ARG_INVENTORY_RM_ACTION)
10483 if (player->inventory_infinite_element != EL_UNDEFINED &&
10484 IS_EQUAL_OR_IN_GROUP(player->inventory_infinite_element,
10485 action_arg_element_raw))
10486 player->inventory_infinite_element = EL_UNDEFINED;
10488 for (k = 0, j = 0; j < player->inventory_size; j++)
10490 if (!IS_EQUAL_OR_IN_GROUP(player->inventory_element[j],
10491 action_arg_element_raw))
10492 player->inventory_element[k++] = player->inventory_element[j];
10495 player->inventory_size = k;
10497 else if (action_arg == CA_ARG_INVENTORY_RM_FIRST)
10499 if (player->inventory_size > 0)
10501 for (j = 0; j < player->inventory_size - 1; j++)
10502 player->inventory_element[j] = player->inventory_element[j + 1];
10504 player->inventory_size--;
10507 else if (action_arg == CA_ARG_INVENTORY_RM_LAST)
10509 if (player->inventory_size > 0)
10510 player->inventory_size--;
10512 else if (action_arg == CA_ARG_INVENTORY_RM_ALL)
10514 player->inventory_infinite_element = EL_UNDEFINED;
10515 player->inventory_size = 0;
10517 else if (action_arg == CA_ARG_INVENTORY_RESET)
10519 player->inventory_infinite_element = EL_UNDEFINED;
10520 player->inventory_size = 0;
10522 if (level.use_initial_inventory[i])
10524 for (j = 0; j < level.initial_inventory_size[i]; j++)
10526 int element = level.initial_inventory_content[i][j];
10527 int collect_count = element_info[element].collect_count_initial;
10529 if (!IS_CUSTOM_ELEMENT(element))
10532 if (collect_count == 0)
10533 player->inventory_infinite_element = element;
10535 for (k = 0; k < collect_count; k++)
10536 if (player->inventory_size < MAX_INVENTORY_SIZE)
10537 player->inventory_element[player->inventory_size++] =
10548 // ---------- CE actions -------------------------------------------------
10550 case CA_SET_CE_VALUE:
10552 int last_ce_value = CustomValue[x][y];
10554 CustomValue[x][y] = action_arg_number_new;
10556 if (CustomValue[x][y] != last_ce_value)
10558 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES);
10559 CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X);
10561 if (CustomValue[x][y] == 0)
10563 // reset change counter (else CE_VALUE_GETS_ZERO would not work)
10564 ChangeCount[x][y] = 0; // allow at least one more change
10566 CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
10567 CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
10574 case CA_SET_CE_SCORE:
10576 int last_ce_score = ei->collect_score;
10578 ei->collect_score = action_arg_number_new;
10580 if (ei->collect_score != last_ce_score)
10582 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES);
10583 CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X);
10585 if (ei->collect_score == 0)
10589 // reset change counter (else CE_SCORE_GETS_ZERO would not work)
10590 ChangeCount[x][y] = 0; // allow at least one more change
10592 CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
10593 CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
10596 This is a very special case that seems to be a mixture between
10597 CheckElementChange() and CheckTriggeredElementChange(): while
10598 the first one only affects single elements that are triggered
10599 directly, the second one affects multiple elements in the playfield
10600 that are triggered indirectly by another element. This is a third
10601 case: Changing the CE score always affects multiple identical CEs,
10602 so every affected CE must be checked, not only the single CE for
10603 which the CE score was changed in the first place (as every instance
10604 of that CE shares the same CE score, and therefore also can change)!
10606 SCAN_PLAYFIELD(xx, yy)
10608 if (Tile[xx][yy] == element)
10609 CheckElementChange(xx, yy, element, EL_UNDEFINED,
10610 CE_SCORE_GETS_ZERO);
10618 case CA_SET_CE_ARTWORK:
10620 int artwork_element = action_arg_element;
10621 boolean reset_frame = FALSE;
10624 if (action_arg == CA_ARG_ELEMENT_RESET)
10625 artwork_element = (ei->use_gfx_element ? ei->gfx_element_initial :
10628 if (ei->gfx_element != artwork_element)
10629 reset_frame = TRUE;
10631 ei->gfx_element = artwork_element;
10633 SCAN_PLAYFIELD(xx, yy)
10635 if (Tile[xx][yy] == element)
10639 ResetGfxAnimation(xx, yy);
10640 ResetRandomAnimationValue(xx, yy);
10643 TEST_DrawLevelField(xx, yy);
10650 // ---------- engine actions ---------------------------------------------
10652 case CA_SET_ENGINE_SCAN_MODE:
10654 InitPlayfieldScanMode(action_arg);
10664 static void CreateFieldExt(int x, int y, int element, boolean is_change)
10666 int old_element = Tile[x][y];
10667 int new_element = GetElementFromGroupElement(element);
10668 int previous_move_direction = MovDir[x][y];
10669 int last_ce_value = CustomValue[x][y];
10670 boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
10671 boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
10672 boolean add_player_onto_element = (new_element_is_player &&
10673 new_element != EL_SOKOBAN_FIELD_PLAYER &&
10674 IS_WALKABLE(old_element));
10676 if (!add_player_onto_element)
10678 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
10679 RemoveMovingField(x, y);
10683 Tile[x][y] = new_element;
10685 if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS)
10686 MovDir[x][y] = previous_move_direction;
10688 if (element_info[new_element].use_last_ce_value)
10689 CustomValue[x][y] = last_ce_value;
10691 InitField_WithBug1(x, y, FALSE);
10693 new_element = Tile[x][y]; // element may have changed
10695 ResetGfxAnimation(x, y);
10696 ResetRandomAnimationValue(x, y);
10698 TEST_DrawLevelField(x, y);
10700 if (GFX_CRUMBLED(new_element))
10701 TEST_DrawLevelFieldCrumbledNeighbours(x, y);
10704 // check if element under the player changes from accessible to unaccessible
10705 // (needed for special case of dropping element which then changes)
10706 // (must be checked after creating new element for walkable group elements)
10707 if (IS_PLAYER(x, y) && !player_explosion_protected &&
10708 IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
10715 // "ChangeCount" not set yet to allow "entered by player" change one time
10716 if (new_element_is_player)
10717 RelocatePlayer(x, y, new_element);
10720 ChangeCount[x][y]++; // count number of changes in the same frame
10722 TestIfBadThingTouchesPlayer(x, y);
10723 TestIfPlayerTouchesCustomElement(x, y);
10724 TestIfElementTouchesCustomElement(x, y);
10727 static void CreateField(int x, int y, int element)
10729 CreateFieldExt(x, y, element, FALSE);
10732 static void CreateElementFromChange(int x, int y, int element)
10734 element = GET_VALID_RUNTIME_ELEMENT(element);
10736 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
10738 int old_element = Tile[x][y];
10740 // prevent changed element from moving in same engine frame
10741 // unless both old and new element can either fall or move
10742 if ((!CAN_FALL(old_element) || !CAN_FALL(element)) &&
10743 (!CAN_MOVE(old_element) || !CAN_MOVE(element)))
10747 CreateFieldExt(x, y, element, TRUE);
10750 static boolean ChangeElement(int x, int y, int element, int page)
10752 struct ElementInfo *ei = &element_info[element];
10753 struct ElementChangeInfo *change = &ei->change_page[page];
10754 int ce_value = CustomValue[x][y];
10755 int ce_score = ei->collect_score;
10756 int target_element;
10757 int old_element = Tile[x][y];
10759 // always use default change event to prevent running into a loop
10760 if (ChangeEvent[x][y] == -1)
10761 ChangeEvent[x][y] = CE_DELAY;
10763 if (ChangeEvent[x][y] == CE_DELAY)
10765 // reset actual trigger element, trigger player and action element
10766 change->actual_trigger_element = EL_EMPTY;
10767 change->actual_trigger_player = EL_EMPTY;
10768 change->actual_trigger_player_bits = CH_PLAYER_NONE;
10769 change->actual_trigger_side = CH_SIDE_NONE;
10770 change->actual_trigger_ce_value = 0;
10771 change->actual_trigger_ce_score = 0;
10774 // do not change elements more than a specified maximum number of changes
10775 if (ChangeCount[x][y] >= game.max_num_changes_per_frame)
10778 ChangeCount[x][y]++; // count number of changes in the same frame
10780 if (change->explode)
10787 if (change->use_target_content)
10789 boolean complete_replace = TRUE;
10790 boolean can_replace[3][3];
10793 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10796 boolean is_walkable;
10797 boolean is_diggable;
10798 boolean is_collectible;
10799 boolean is_removable;
10800 boolean is_destructible;
10801 int ex = x + xx - 1;
10802 int ey = y + yy - 1;
10803 int content_element = change->target_content.e[xx][yy];
10806 can_replace[xx][yy] = TRUE;
10808 if (ex == x && ey == y) // do not check changing element itself
10811 if (content_element == EL_EMPTY_SPACE)
10813 can_replace[xx][yy] = FALSE; // do not replace border with space
10818 if (!IN_LEV_FIELD(ex, ey))
10820 can_replace[xx][yy] = FALSE;
10821 complete_replace = FALSE;
10828 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10829 e = MovingOrBlocked2Element(ex, ey);
10831 is_empty = (IS_FREE(ex, ey) ||
10832 (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
10834 is_walkable = (is_empty || IS_WALKABLE(e));
10835 is_diggable = (is_empty || IS_DIGGABLE(e));
10836 is_collectible = (is_empty || IS_COLLECTIBLE(e));
10837 is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
10838 is_removable = (is_diggable || is_collectible);
10840 can_replace[xx][yy] =
10841 (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
10842 (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
10843 (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
10844 (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
10845 (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
10846 (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
10847 !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
10849 if (!can_replace[xx][yy])
10850 complete_replace = FALSE;
10853 if (!change->only_if_complete || complete_replace)
10855 boolean something_has_changed = FALSE;
10857 if (change->only_if_complete && change->use_random_replace &&
10858 RND(100) < change->random_percentage)
10861 for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
10863 int ex = x + xx - 1;
10864 int ey = y + yy - 1;
10865 int content_element;
10867 if (can_replace[xx][yy] && (!change->use_random_replace ||
10868 RND(100) < change->random_percentage))
10870 if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
10871 RemoveMovingField(ex, ey);
10873 ChangeEvent[ex][ey] = ChangeEvent[x][y];
10875 content_element = change->target_content.e[xx][yy];
10876 target_element = GET_TARGET_ELEMENT(element, content_element, change,
10877 ce_value, ce_score);
10879 CreateElementFromChange(ex, ey, target_element);
10881 something_has_changed = TRUE;
10883 // for symmetry reasons, freeze newly created border elements
10884 if (ex != x || ey != y)
10885 Stop[ex][ey] = TRUE; // no more moving in this frame
10889 if (something_has_changed)
10891 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10892 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10898 target_element = GET_TARGET_ELEMENT(element, change->target_element, change,
10899 ce_value, ce_score);
10901 if (element == EL_DIAGONAL_GROWING ||
10902 element == EL_DIAGONAL_SHRINKING)
10904 target_element = Store[x][y];
10906 Store[x][y] = EL_EMPTY;
10909 // special case: element changes to player (and may be kept if walkable)
10910 if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
10911 CreateElementFromChange(x, y, EL_EMPTY);
10913 CreateElementFromChange(x, y, target_element);
10915 PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
10916 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + page);
10919 // this uses direct change before indirect change
10920 CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
10925 static void HandleElementChange(int x, int y, int page)
10927 int element = MovingOrBlocked2Element(x, y);
10928 struct ElementInfo *ei = &element_info[element];
10929 struct ElementChangeInfo *change = &ei->change_page[page];
10930 boolean handle_action_before_change = FALSE;
10933 if (!CAN_CHANGE_OR_HAS_ACTION(element) &&
10934 !CAN_CHANGE_OR_HAS_ACTION(Back[x][y]))
10936 Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')",
10937 x, y, element, element_info[element].token_name);
10938 Debug("game:playing:HandleElementChange", "This should never happen!");
10942 // this can happen with classic bombs on walkable, changing elements
10943 if (!CAN_CHANGE_OR_HAS_ACTION(element))
10948 if (ChangeDelay[x][y] == 0) // initialize element change
10950 ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
10952 if (change->can_change)
10954 // !!! not clear why graphic animation should be reset at all here !!!
10955 // !!! UPDATE: but is needed for correct Snake Bite tail animation !!!
10956 // !!! SOLUTION: do not reset if graphics engine set to 4 or above !!!
10959 GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
10961 When using an animation frame delay of 1 (this only happens with
10962 "sp_zonk.moving.left/right" in the classic graphics), the default
10963 (non-moving) animation shows wrong animation frames (while the
10964 moving animation, like "sp_zonk.moving.left/right", is correct,
10965 so this graphical bug never shows up with the classic graphics).
10966 For an animation with 4 frames, this causes wrong frames 0,0,1,2
10967 be drawn instead of the correct frames 0,1,2,3. This is caused by
10968 "GfxFrame[][]" being reset *twice* (in two successive frames) after
10969 an element change: First when the change delay ("ChangeDelay[][]")
10970 counter has reached zero after decrementing, then a second time in
10971 the next frame (after "GfxFrame[][]" was already incremented) when
10972 "ChangeDelay[][]" is reset to the initial delay value again.
10974 This causes frame 0 to be drawn twice, while the last frame won't
10975 be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
10977 As some animations may already be cleverly designed around this bug
10978 (at least the "Snake Bite" snake tail animation does this), it cannot
10979 simply be fixed here without breaking such existing animations.
10980 Unfortunately, it cannot easily be detected if a graphics set was
10981 designed "before" or "after" the bug was fixed. As a workaround,
10982 a new graphics set option "game.graphics_engine_version" was added
10983 to be able to specify the game's major release version for which the
10984 graphics set was designed, which can then be used to decide if the
10985 bugfix should be used (version 4 and above) or not (version 3 or
10986 below, or if no version was specified at all, as with old sets).
10988 (The wrong/fixed animation frames can be tested with the test level set
10989 "test_gfxframe" and level "000", which contains a specially prepared
10990 custom element at level position (x/y) == (11/9) which uses the zonk
10991 animation mentioned above. Using "game.graphics_engine_version: 4"
10992 fixes the wrong animation frames, showing the correct frames 0,1,2,3.
10993 This can also be seen from the debug output for this test element.)
10996 // when a custom element is about to change (for example by change delay),
10997 // do not reset graphic animation when the custom element is moving
10998 if (game.graphics_engine_version < 4 &&
11001 ResetGfxAnimation(x, y);
11002 ResetRandomAnimationValue(x, y);
11005 if (change->pre_change_function)
11006 change->pre_change_function(x, y);
11010 ChangeDelay[x][y]--;
11012 if (ChangeDelay[x][y] != 0) // continue element change
11014 if (change->can_change)
11016 int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
11018 if (IS_ANIMATED(graphic))
11019 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
11021 if (change->change_function)
11022 change->change_function(x, y);
11025 else // finish element change
11027 if (ChangePage[x][y] != -1) // remember page from delayed change
11029 page = ChangePage[x][y];
11030 ChangePage[x][y] = -1;
11032 change = &ei->change_page[page];
11035 if (IS_MOVING(x, y)) // never change a running system ;-)
11037 ChangeDelay[x][y] = 1; // try change after next move step
11038 ChangePage[x][y] = page; // remember page to use for change
11043 // special case: set new level random seed before changing element
11044 if (change->has_action && change->action_type == CA_SET_LEVEL_RANDOM_SEED)
11045 handle_action_before_change = TRUE;
11047 if (change->has_action && handle_action_before_change)
11048 ExecuteCustomElementAction(x, y, element, page);
11050 if (change->can_change)
11052 if (ChangeElement(x, y, element, page))
11054 if (change->post_change_function)
11055 change->post_change_function(x, y);
11059 if (change->has_action && !handle_action_before_change)
11060 ExecuteCustomElementAction(x, y, element, page);
11064 static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
11065 int trigger_element,
11067 int trigger_player,
11071 boolean change_done_any = FALSE;
11072 int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
11075 if (!(trigger_events[trigger_element][trigger_event]))
11078 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11080 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
11082 int element = EL_CUSTOM_START + i;
11083 boolean change_done = FALSE;
11086 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11087 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11090 for (p = 0; p < element_info[element].num_change_pages; p++)
11092 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11094 if (change->can_change_or_has_action &&
11095 change->has_event[trigger_event] &&
11096 change->trigger_side & trigger_side &&
11097 change->trigger_player & trigger_player &&
11098 change->trigger_page & trigger_page_bits &&
11099 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
11101 change->actual_trigger_element = trigger_element;
11102 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11103 change->actual_trigger_player_bits = trigger_player;
11104 change->actual_trigger_side = trigger_side;
11105 change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
11106 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11108 if ((change->can_change && !change_done) || change->has_action)
11112 SCAN_PLAYFIELD(x, y)
11114 if (Tile[x][y] == element)
11116 if (change->can_change && !change_done)
11118 // if element already changed in this frame, not only prevent
11119 // another element change (checked in ChangeElement()), but
11120 // also prevent additional element actions for this element
11122 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11123 !level.use_action_after_change_bug)
11126 ChangeDelay[x][y] = 1;
11127 ChangeEvent[x][y] = trigger_event;
11129 HandleElementChange(x, y, p);
11131 else if (change->has_action)
11133 // if element already changed in this frame, not only prevent
11134 // another element change (checked in ChangeElement()), but
11135 // also prevent additional element actions for this element
11137 if (ChangeCount[x][y] >= game.max_num_changes_per_frame &&
11138 !level.use_action_after_change_bug)
11141 ExecuteCustomElementAction(x, y, element, p);
11142 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11147 if (change->can_change)
11149 change_done = TRUE;
11150 change_done_any = TRUE;
11157 RECURSION_LOOP_DETECTION_END();
11159 return change_done_any;
11162 static boolean CheckElementChangeExt(int x, int y,
11164 int trigger_element,
11166 int trigger_player,
11169 boolean change_done = FALSE;
11172 if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
11173 !HAS_ANY_CHANGE_EVENT(element, trigger_event))
11176 if (Tile[x][y] == EL_BLOCKED)
11178 Blocked2Moving(x, y, &x, &y);
11179 element = Tile[x][y];
11182 // check if element has already changed or is about to change after moving
11183 if ((game.engine_version < VERSION_IDENT(3,2,0,7) &&
11184 Tile[x][y] != element) ||
11186 (game.engine_version >= VERSION_IDENT(3,2,0,7) &&
11187 (ChangeCount[x][y] >= game.max_num_changes_per_frame ||
11188 ChangePage[x][y] != -1)))
11191 RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
11193 for (p = 0; p < element_info[element].num_change_pages; p++)
11195 struct ElementChangeInfo *change = &element_info[element].change_page[p];
11197 /* check trigger element for all events where the element that is checked
11198 for changing interacts with a directly adjacent element -- this is
11199 different to element changes that affect other elements to change on the
11200 whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */
11201 boolean check_trigger_element =
11202 (trigger_event == CE_NEXT_TO_X ||
11203 trigger_event == CE_TOUCHING_X ||
11204 trigger_event == CE_HITTING_X ||
11205 trigger_event == CE_HIT_BY_X ||
11206 trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3
11208 if (change->can_change_or_has_action &&
11209 change->has_event[trigger_event] &&
11210 change->trigger_side & trigger_side &&
11211 change->trigger_player & trigger_player &&
11212 (!check_trigger_element ||
11213 IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element)))
11215 change->actual_trigger_element = trigger_element;
11216 change->actual_trigger_player = GET_PLAYER_FROM_BITS(trigger_player);
11217 change->actual_trigger_player_bits = trigger_player;
11218 change->actual_trigger_side = trigger_side;
11219 change->actual_trigger_ce_value = CustomValue[x][y];
11220 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11222 // special case: trigger element not at (x,y) position for some events
11223 if (check_trigger_element)
11235 { 0, 0 }, { 0, 0 }, { 0, 0 },
11239 int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx;
11240 int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy;
11242 change->actual_trigger_ce_value = CustomValue[xx][yy];
11243 change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
11246 if (change->can_change && !change_done)
11248 ChangeDelay[x][y] = 1;
11249 ChangeEvent[x][y] = trigger_event;
11251 HandleElementChange(x, y, p);
11253 change_done = TRUE;
11255 else if (change->has_action)
11257 ExecuteCustomElementAction(x, y, element, p);
11258 PlayLevelSoundElementAction(x, y, element, ACTION_PAGE_1 + p);
11263 RECURSION_LOOP_DETECTION_END();
11265 return change_done;
11268 static void PlayPlayerSound(struct PlayerInfo *player)
11270 int jx = player->jx, jy = player->jy;
11271 int sound_element = player->artwork_element;
11272 int last_action = player->last_action_waiting;
11273 int action = player->action_waiting;
11275 if (player->is_waiting)
11277 if (action != last_action)
11278 PlayLevelSoundElementAction(jx, jy, sound_element, action);
11280 PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action);
11284 if (action != last_action)
11285 StopSound(element_info[sound_element].sound[last_action]);
11287 if (last_action == ACTION_SLEEPING)
11288 PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING);
11292 static void PlayAllPlayersSound(void)
11296 for (i = 0; i < MAX_PLAYERS; i++)
11297 if (stored_player[i].active)
11298 PlayPlayerSound(&stored_player[i]);
11301 static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting)
11303 boolean last_waiting = player->is_waiting;
11304 int move_dir = player->MovDir;
11306 player->dir_waiting = move_dir;
11307 player->last_action_waiting = player->action_waiting;
11311 if (!last_waiting) // not waiting -> waiting
11313 player->is_waiting = TRUE;
11315 player->frame_counter_bored =
11317 game.player_boring_delay_fixed +
11318 GetSimpleRandom(game.player_boring_delay_random);
11319 player->frame_counter_sleeping =
11321 game.player_sleeping_delay_fixed +
11322 GetSimpleRandom(game.player_sleeping_delay_random);
11324 InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir);
11327 if (game.player_sleeping_delay_fixed +
11328 game.player_sleeping_delay_random > 0 &&
11329 player->anim_delay_counter == 0 &&
11330 player->post_delay_counter == 0 &&
11331 FrameCounter >= player->frame_counter_sleeping)
11332 player->is_sleeping = TRUE;
11333 else if (game.player_boring_delay_fixed +
11334 game.player_boring_delay_random > 0 &&
11335 FrameCounter >= player->frame_counter_bored)
11336 player->is_bored = TRUE;
11338 player->action_waiting = (player->is_sleeping ? ACTION_SLEEPING :
11339 player->is_bored ? ACTION_BORING :
11342 if (player->is_sleeping && player->use_murphy)
11344 // special case for sleeping Murphy when leaning against non-free tile
11346 if (!IN_LEV_FIELD(player->jx - 1, player->jy) ||
11347 (Tile[player->jx - 1][player->jy] != EL_EMPTY &&
11348 !IS_MOVING(player->jx - 1, player->jy)))
11349 move_dir = MV_LEFT;
11350 else if (!IN_LEV_FIELD(player->jx + 1, player->jy) ||
11351 (Tile[player->jx + 1][player->jy] != EL_EMPTY &&
11352 !IS_MOVING(player->jx + 1, player->jy)))
11353 move_dir = MV_RIGHT;
11355 player->is_sleeping = FALSE;
11357 player->dir_waiting = move_dir;
11360 if (player->is_sleeping)
11362 if (player->num_special_action_sleeping > 0)
11364 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11366 int last_special_action = player->special_action_sleeping;
11367 int num_special_action = player->num_special_action_sleeping;
11368 int special_action =
11369 (last_special_action == ACTION_DEFAULT ? ACTION_SLEEPING_1 :
11370 last_special_action == ACTION_SLEEPING ? ACTION_SLEEPING :
11371 last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ?
11372 last_special_action + 1 : ACTION_SLEEPING);
11373 int special_graphic =
11374 el_act_dir2img(player->artwork_element, special_action, move_dir);
11376 player->anim_delay_counter =
11377 graphic_info[special_graphic].anim_delay_fixed +
11378 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11379 player->post_delay_counter =
11380 graphic_info[special_graphic].post_delay_fixed +
11381 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11383 player->special_action_sleeping = special_action;
11386 if (player->anim_delay_counter > 0)
11388 player->action_waiting = player->special_action_sleeping;
11389 player->anim_delay_counter--;
11391 else if (player->post_delay_counter > 0)
11393 player->post_delay_counter--;
11397 else if (player->is_bored)
11399 if (player->num_special_action_bored > 0)
11401 if (player->anim_delay_counter == 0 && player->post_delay_counter == 0)
11403 int special_action =
11404 ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored);
11405 int special_graphic =
11406 el_act_dir2img(player->artwork_element, special_action, move_dir);
11408 player->anim_delay_counter =
11409 graphic_info[special_graphic].anim_delay_fixed +
11410 GetSimpleRandom(graphic_info[special_graphic].anim_delay_random);
11411 player->post_delay_counter =
11412 graphic_info[special_graphic].post_delay_fixed +
11413 GetSimpleRandom(graphic_info[special_graphic].post_delay_random);
11415 player->special_action_bored = special_action;
11418 if (player->anim_delay_counter > 0)
11420 player->action_waiting = player->special_action_bored;
11421 player->anim_delay_counter--;
11423 else if (player->post_delay_counter > 0)
11425 player->post_delay_counter--;
11430 else if (last_waiting) // waiting -> not waiting
11432 player->is_waiting = FALSE;
11433 player->is_bored = FALSE;
11434 player->is_sleeping = FALSE;
11436 player->frame_counter_bored = -1;
11437 player->frame_counter_sleeping = -1;
11439 player->anim_delay_counter = 0;
11440 player->post_delay_counter = 0;
11442 player->dir_waiting = player->MovDir;
11443 player->action_waiting = ACTION_DEFAULT;
11445 player->special_action_bored = ACTION_DEFAULT;
11446 player->special_action_sleeping = ACTION_DEFAULT;
11450 static void CheckSaveEngineSnapshot(struct PlayerInfo *player)
11452 if ((!player->is_moving && player->was_moving) ||
11453 (player->MovPos == 0 && player->was_moving) ||
11454 (player->is_snapping && !player->was_snapping) ||
11455 (player->is_dropping && !player->was_dropping))
11457 if (!CheckSaveEngineSnapshotToList())
11460 player->was_moving = FALSE;
11461 player->was_snapping = TRUE;
11462 player->was_dropping = TRUE;
11466 if (player->is_moving)
11467 player->was_moving = TRUE;
11469 if (!player->is_snapping)
11470 player->was_snapping = FALSE;
11472 if (!player->is_dropping)
11473 player->was_dropping = FALSE;
11476 static struct MouseActionInfo mouse_action_last = { 0 };
11477 struct MouseActionInfo mouse_action = player->effective_mouse_action;
11478 boolean new_released = (!mouse_action.button && mouse_action_last.button);
11481 CheckSaveEngineSnapshotToList();
11483 mouse_action_last = mouse_action;
11486 static void CheckSingleStepMode(struct PlayerInfo *player)
11488 if (tape.single_step && tape.recording && !tape.pausing)
11490 // as it is called "single step mode", just return to pause mode when the
11491 // player stopped moving after one tile (or never starts moving at all)
11492 // (reverse logic needed here in case single step mode used in team mode)
11493 if (player->is_moving ||
11494 player->is_pushing ||
11495 player->is_dropping_pressed ||
11496 player->effective_mouse_action.button)
11497 game.enter_single_step_mode = FALSE;
11500 CheckSaveEngineSnapshot(player);
11503 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
11505 int left = player_action & JOY_LEFT;
11506 int right = player_action & JOY_RIGHT;
11507 int up = player_action & JOY_UP;
11508 int down = player_action & JOY_DOWN;
11509 int button1 = player_action & JOY_BUTTON_1;
11510 int button2 = player_action & JOY_BUTTON_2;
11511 int dx = (left ? -1 : right ? 1 : 0);
11512 int dy = (up ? -1 : down ? 1 : 0);
11514 if (!player->active || tape.pausing)
11520 SnapField(player, dx, dy);
11524 DropElement(player);
11526 MovePlayer(player, dx, dy);
11529 CheckSingleStepMode(player);
11531 SetPlayerWaiting(player, FALSE);
11533 return player_action;
11537 // no actions for this player (no input at player's configured device)
11539 DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH);
11540 SnapField(player, 0, 0);
11541 CheckGravityMovementWhenNotMoving(player);
11543 if (player->MovPos == 0)
11544 SetPlayerWaiting(player, TRUE);
11546 if (player->MovPos == 0) // needed for tape.playing
11547 player->is_moving = FALSE;
11549 player->is_dropping = FALSE;
11550 player->is_dropping_pressed = FALSE;
11551 player->drop_pressed_delay = 0;
11553 CheckSingleStepMode(player);
11559 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
11562 if (!tape.use_mouse_actions)
11565 mouse_action->lx = tape_action[TAPE_ACTION_LX];
11566 mouse_action->ly = tape_action[TAPE_ACTION_LY];
11567 mouse_action->button = tape_action[TAPE_ACTION_BUTTON];
11570 static void SetTapeActionFromMouseAction(byte *tape_action,
11571 struct MouseActionInfo *mouse_action)
11573 if (!tape.use_mouse_actions)
11576 tape_action[TAPE_ACTION_LX] = mouse_action->lx;
11577 tape_action[TAPE_ACTION_LY] = mouse_action->ly;
11578 tape_action[TAPE_ACTION_BUTTON] = mouse_action->button;
11581 static void CheckLevelSolved(void)
11583 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11585 if (game_em.level_solved &&
11586 !game_em.game_over) // game won
11590 game_em.game_over = TRUE;
11592 game.all_players_gone = TRUE;
11595 if (game_em.game_over) // game lost
11596 game.all_players_gone = TRUE;
11598 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
11600 if (game_sp.level_solved &&
11601 !game_sp.game_over) // game won
11605 game_sp.game_over = TRUE;
11607 game.all_players_gone = TRUE;
11610 if (game_sp.game_over) // game lost
11611 game.all_players_gone = TRUE;
11613 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
11615 if (game_mm.level_solved &&
11616 !game_mm.game_over) // game won
11620 game_mm.game_over = TRUE;
11622 game.all_players_gone = TRUE;
11625 if (game_mm.game_over) // game lost
11626 game.all_players_gone = TRUE;
11630 static void CheckLevelTime_StepCounter(void)
11640 if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved)
11641 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11643 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11645 DisplayGameControlValues();
11647 if (!TimeLeft && setup.time_limit && !game.LevelSolved)
11648 for (i = 0; i < MAX_PLAYERS; i++)
11649 KillPlayer(&stored_player[i]);
11651 else if (game.no_time_limit && !game.all_players_gone)
11653 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11655 DisplayGameControlValues();
11659 static void CheckLevelTime(void)
11663 if (TimeFrames >= FRAMES_PER_SECOND)
11668 for (i = 0; i < MAX_PLAYERS; i++)
11670 struct PlayerInfo *player = &stored_player[i];
11672 if (SHIELD_ON(player))
11674 player->shield_normal_time_left--;
11676 if (player->shield_deadly_time_left > 0)
11677 player->shield_deadly_time_left--;
11681 if (!game.LevelSolved && !level.use_step_counter)
11689 if (TimeLeft <= 10 && setup.time_limit)
11690 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
11692 /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value
11693 is reset from other values in UpdateGameDoorValues() -- FIX THIS */
11695 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
11697 if (!TimeLeft && setup.time_limit)
11699 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
11700 game_em.lev->killed_out_of_time = TRUE;
11702 for (i = 0; i < MAX_PLAYERS; i++)
11703 KillPlayer(&stored_player[i]);
11706 else if (game.no_time_limit && !game.all_players_gone)
11708 game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
11711 game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
11714 if (tape.recording || tape.playing)
11715 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
11718 if (tape.recording || tape.playing)
11719 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
11721 UpdateAndDisplayGameControlValues();
11724 void AdvanceFrameAndPlayerCounters(int player_nr)
11728 // advance frame counters (global frame counter and time frame counter)
11732 // advance player counters (counters for move delay, move animation etc.)
11733 for (i = 0; i < MAX_PLAYERS; i++)
11735 boolean advance_player_counters = (player_nr == -1 || player_nr == i);
11736 int move_delay_value = stored_player[i].move_delay_value;
11737 int move_frames = MOVE_DELAY_NORMAL_SPEED / move_delay_value;
11739 if (!advance_player_counters) // not all players may be affected
11742 if (move_frames == 0) // less than one move per game frame
11744 int stepsize = TILEX / move_delay_value;
11745 int delay = move_delay_value / MOVE_DELAY_NORMAL_SPEED;
11746 int count = (stored_player[i].is_moving ?
11747 ABS(stored_player[i].MovPos) / stepsize : FrameCounter);
11749 if (count % delay == 0)
11753 stored_player[i].Frame += move_frames;
11755 if (stored_player[i].MovPos != 0)
11756 stored_player[i].StepFrame += move_frames;
11758 if (stored_player[i].move_delay > 0)
11759 stored_player[i].move_delay--;
11761 // due to bugs in previous versions, counter must count up, not down
11762 if (stored_player[i].push_delay != -1)
11763 stored_player[i].push_delay++;
11765 if (stored_player[i].drop_delay > 0)
11766 stored_player[i].drop_delay--;
11768 if (stored_player[i].is_dropping_pressed)
11769 stored_player[i].drop_pressed_delay++;
11773 void StartGameActions(boolean init_network_game, boolean record_tape,
11776 unsigned int new_random_seed = InitRND(random_seed);
11779 TapeStartRecording(new_random_seed);
11781 if (setup.auto_pause_on_start && !tape.pausing)
11782 TapeTogglePause(TAPE_TOGGLE_MANUAL);
11784 if (init_network_game)
11786 SendToServer_LevelFile();
11787 SendToServer_StartPlaying();
11795 static void GameActionsExt(void)
11798 static unsigned int game_frame_delay = 0;
11800 unsigned int game_frame_delay_value;
11801 byte *recorded_player_action;
11802 byte summarized_player_action = 0;
11803 byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
11806 // detect endless loops, caused by custom element programming
11807 if (recursion_loop_detected && recursion_loop_depth == 0)
11809 char *message = getStringCat3("Internal Error! Element ",
11810 EL_NAME(recursion_loop_element),
11811 " caused endless loop! Quit the game?");
11813 Warn("element '%s' caused endless loop in game engine",
11814 EL_NAME(recursion_loop_element));
11816 RequestQuitGameExt(program.headless, level_editor_test_game, message);
11818 recursion_loop_detected = FALSE; // if game should be continued
11825 if (game.restart_level)
11826 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
11828 CheckLevelSolved();
11830 if (game.LevelSolved && !game.LevelSolved_GameEnd)
11833 if (game.all_players_gone && !TAPE_IS_STOPPED(tape))
11836 if (game_status != GAME_MODE_PLAYING) // status might have changed
11839 game_frame_delay_value =
11840 (tape.playing && tape.fast_forward ? FfwdFrameDelay : GameFrameDelay);
11842 if (tape.playing && tape.warp_forward && !tape.pausing)
11843 game_frame_delay_value = 0;
11845 SetVideoFrameDelay(game_frame_delay_value);
11847 // (de)activate virtual buttons depending on current game status
11848 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
11850 if (game.all_players_gone) // if no players there to be controlled anymore
11851 SetOverlayActive(FALSE);
11852 else if (!tape.playing) // if game continues after tape stopped playing
11853 SetOverlayActive(TRUE);
11858 // ---------- main game synchronization point ----------
11860 int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11862 Debug("game:playing:skip", "skip == %d", skip);
11865 // ---------- main game synchronization point ----------
11867 WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
11871 if (network_playing && !network_player_action_received)
11873 // try to get network player actions in time
11875 // last chance to get network player actions without main loop delay
11876 HandleNetworking();
11878 // game was quit by network peer
11879 if (game_status != GAME_MODE_PLAYING)
11882 // check if network player actions still missing and game still running
11883 if (!network_player_action_received && !checkGameEnded())
11884 return; // failed to get network player actions in time
11886 // do not yet reset "network_player_action_received" (for tape.pausing)
11892 // at this point we know that we really continue executing the game
11894 network_player_action_received = FALSE;
11896 // when playing tape, read previously recorded player input from tape data
11897 recorded_player_action = (tape.playing ? TapePlayAction() : NULL);
11899 local_player->effective_mouse_action = local_player->mouse_action;
11901 if (recorded_player_action != NULL)
11902 SetMouseActionFromTapeAction(&local_player->effective_mouse_action,
11903 recorded_player_action);
11905 // TapePlayAction() may return NULL when toggling to "pause before death"
11909 if (tape.set_centered_player)
11911 game.centered_player_nr_next = tape.centered_player_nr_next;
11912 game.set_centered_player = TRUE;
11915 for (i = 0; i < MAX_PLAYERS; i++)
11917 summarized_player_action |= stored_player[i].action;
11919 if (!network_playing && (game.team_mode || tape.playing))
11920 stored_player[i].effective_action = stored_player[i].action;
11923 if (network_playing && !checkGameEnded())
11924 SendToServer_MovePlayer(summarized_player_action);
11926 // summarize all actions at local players mapped input device position
11927 // (this allows using different input devices in single player mode)
11928 if (!network.enabled && !game.team_mode)
11929 stored_player[map_player_action[local_player->index_nr]].effective_action =
11930 summarized_player_action;
11932 // summarize all actions at centered player in local team mode
11933 if (tape.recording &&
11934 setup.team_mode && !network.enabled &&
11935 setup.input_on_focus &&
11936 game.centered_player_nr != -1)
11938 for (i = 0; i < MAX_PLAYERS; i++)
11939 stored_player[map_player_action[i]].effective_action =
11940 (i == game.centered_player_nr ? summarized_player_action : 0);
11943 if (recorded_player_action != NULL)
11944 for (i = 0; i < MAX_PLAYERS; i++)
11945 stored_player[i].effective_action = recorded_player_action[i];
11947 for (i = 0; i < MAX_PLAYERS; i++)
11949 tape_action[i] = stored_player[i].effective_action;
11951 /* (this may happen in the RND game engine if a player was not present on
11952 the playfield on level start, but appeared later from a custom element */
11953 if (setup.team_mode &&
11956 !tape.player_participates[i])
11957 tape.player_participates[i] = TRUE;
11960 SetTapeActionFromMouseAction(tape_action,
11961 &local_player->effective_mouse_action);
11963 // only record actions from input devices, but not programmed actions
11964 if (tape.recording)
11965 TapeRecordAction(tape_action);
11967 // remember if game was played (especially after tape stopped playing)
11968 if (!tape.playing && summarized_player_action)
11969 game.GamePlayed = TRUE;
11971 #if USE_NEW_PLAYER_ASSIGNMENTS
11972 // !!! also map player actions in single player mode !!!
11973 // if (game.team_mode)
11976 byte mapped_action[MAX_PLAYERS];
11978 #if DEBUG_PLAYER_ACTIONS
11979 for (i = 0; i < MAX_PLAYERS; i++)
11980 DebugContinued("", "%d, ", stored_player[i].effective_action);
11983 for (i = 0; i < MAX_PLAYERS; i++)
11984 mapped_action[i] = stored_player[map_player_action[i]].effective_action;
11986 for (i = 0; i < MAX_PLAYERS; i++)
11987 stored_player[i].effective_action = mapped_action[i];
11989 #if DEBUG_PLAYER_ACTIONS
11990 DebugContinued("", "=> ");
11991 for (i = 0; i < MAX_PLAYERS; i++)
11992 DebugContinued("", "%d, ", stored_player[i].effective_action);
11993 DebugContinued("game:playing:player", "\n");
11996 #if DEBUG_PLAYER_ACTIONS
11999 for (i = 0; i < MAX_PLAYERS; i++)
12000 DebugContinued("", "%d, ", stored_player[i].effective_action);
12001 DebugContinued("game:playing:player", "\n");
12006 for (i = 0; i < MAX_PLAYERS; i++)
12008 // allow engine snapshot in case of changed movement attempt
12009 if ((game.snapshot.last_action[i] & KEY_MOTION) !=
12010 (stored_player[i].effective_action & KEY_MOTION))
12011 game.snapshot.changed_action = TRUE;
12013 // allow engine snapshot in case of snapping/dropping attempt
12014 if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
12015 (stored_player[i].effective_action & KEY_BUTTON) != 0)
12016 game.snapshot.changed_action = TRUE;
12018 game.snapshot.last_action[i] = stored_player[i].effective_action;
12021 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
12023 GameActions_EM_Main();
12025 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
12027 GameActions_SP_Main();
12029 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
12031 GameActions_MM_Main();
12035 GameActions_RND_Main();
12038 BlitScreenToBitmap(backbuffer);
12040 CheckLevelSolved();
12043 AdvanceFrameAndPlayerCounters(-1); // advance counters for all players
12045 if (global.show_frames_per_second)
12047 static unsigned int fps_counter = 0;
12048 static int fps_frames = 0;
12049 unsigned int fps_delay_ms = Counter() - fps_counter;
12053 if (fps_delay_ms >= 500) // calculate FPS every 0.5 seconds
12055 global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
12058 fps_counter = Counter();
12060 // always draw FPS to screen after FPS value was updated
12061 redraw_mask |= REDRAW_FPS;
12064 // only draw FPS if no screen areas are deactivated (invisible warp mode)
12065 if (GetDrawDeactivationMask() == REDRAW_NONE)
12066 redraw_mask |= REDRAW_FPS;
12070 static void GameActions_CheckSaveEngineSnapshot(void)
12072 if (!game.snapshot.save_snapshot)
12075 // clear flag for saving snapshot _before_ saving snapshot
12076 game.snapshot.save_snapshot = FALSE;
12078 SaveEngineSnapshotToList();
12081 void GameActions(void)
12085 GameActions_CheckSaveEngineSnapshot();
12088 void GameActions_EM_Main(void)
12090 byte effective_action[MAX_PLAYERS];
12091 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12094 for (i = 0; i < MAX_PLAYERS; i++)
12095 effective_action[i] = stored_player[i].effective_action;
12097 GameActions_EM(effective_action, warp_mode);
12100 void GameActions_SP_Main(void)
12102 byte effective_action[MAX_PLAYERS];
12103 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12106 for (i = 0; i < MAX_PLAYERS; i++)
12107 effective_action[i] = stored_player[i].effective_action;
12109 GameActions_SP(effective_action, warp_mode);
12111 for (i = 0; i < MAX_PLAYERS; i++)
12113 if (stored_player[i].force_dropping)
12114 stored_player[i].action |= KEY_BUTTON_DROP;
12116 stored_player[i].force_dropping = FALSE;
12120 void GameActions_MM_Main(void)
12122 boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing);
12124 GameActions_MM(local_player->effective_mouse_action, warp_mode);
12127 void GameActions_RND_Main(void)
12132 void GameActions_RND(void)
12134 static struct MouseActionInfo mouse_action_last = { 0 };
12135 struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
12136 int magic_wall_x = 0, magic_wall_y = 0;
12137 int i, x, y, element, graphic, last_gfx_frame;
12139 InitPlayfieldScanModeVars();
12141 if (game.engine_version >= VERSION_IDENT(3,2,0,7))
12143 SCAN_PLAYFIELD(x, y)
12145 ChangeCount[x][y] = 0;
12146 ChangeEvent[x][y] = -1;
12150 if (game.set_centered_player)
12152 boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND();
12154 // switching to "all players" only possible if all players fit to screen
12155 if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen)
12157 game.centered_player_nr_next = game.centered_player_nr;
12158 game.set_centered_player = FALSE;
12161 // do not switch focus to non-existing (or non-active) player
12162 if (game.centered_player_nr_next >= 0 &&
12163 !stored_player[game.centered_player_nr_next].active)
12165 game.centered_player_nr_next = game.centered_player_nr;
12166 game.set_centered_player = FALSE;
12170 if (game.set_centered_player &&
12171 ScreenMovPos == 0) // screen currently aligned at tile position
12175 if (game.centered_player_nr_next == -1)
12177 setScreenCenteredToAllPlayers(&sx, &sy);
12181 sx = stored_player[game.centered_player_nr_next].jx;
12182 sy = stored_player[game.centered_player_nr_next].jy;
12185 game.centered_player_nr = game.centered_player_nr_next;
12186 game.set_centered_player = FALSE;
12188 DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
12189 DrawGameDoorValues();
12192 // check single step mode (set flag and clear again if any player is active)
12193 game.enter_single_step_mode =
12194 (tape.single_step && tape.recording && !tape.pausing);
12196 for (i = 0; i < MAX_PLAYERS; i++)
12198 int actual_player_action = stored_player[i].effective_action;
12201 /* !!! THIS BREAKS THE FOLLOWING TAPES: !!!
12202 - rnd_equinox_tetrachloride 048
12203 - rnd_equinox_tetrachloride_ii 096
12204 - rnd_emanuel_schmieg 002
12205 - doctor_sloan_ww 001, 020
12207 if (stored_player[i].MovPos == 0)
12208 CheckGravityMovement(&stored_player[i]);
12211 // overwrite programmed action with tape action
12212 if (stored_player[i].programmed_action)
12213 actual_player_action = stored_player[i].programmed_action;
12215 PlayerActions(&stored_player[i], actual_player_action);
12217 ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
12220 // single step pause mode may already have been toggled by "ScrollPlayer()"
12221 if (game.enter_single_step_mode && !tape.pausing)
12222 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
12224 ScrollScreen(NULL, SCROLL_GO_ON);
12226 /* for backwards compatibility, the following code emulates a fixed bug that
12227 occured when pushing elements (causing elements that just made their last
12228 pushing step to already (if possible) make their first falling step in the
12229 same game frame, which is bad); this code is also needed to use the famous
12230 "spring push bug" which is used in older levels and might be wanted to be
12231 used also in newer levels, but in this case the buggy pushing code is only
12232 affecting the "spring" element and no other elements */
12234 if (game.engine_version < VERSION_IDENT(2,2,0,7) || level.use_spring_bug)
12236 for (i = 0; i < MAX_PLAYERS; i++)
12238 struct PlayerInfo *player = &stored_player[i];
12239 int x = player->jx;
12240 int y = player->jy;
12242 if (player->active && player->is_pushing && player->is_moving &&
12244 (game.engine_version < VERSION_IDENT(2,2,0,7) ||
12245 Tile[x][y] == EL_SPRING))
12247 ContinueMoving(x, y);
12249 // continue moving after pushing (this is actually a bug)
12250 if (!IS_MOVING(x, y))
12251 Stop[x][y] = FALSE;
12256 SCAN_PLAYFIELD(x, y)
12258 Last[x][y] = Tile[x][y];
12260 ChangeCount[x][y] = 0;
12261 ChangeEvent[x][y] = -1;
12263 // this must be handled before main playfield loop
12264 if (Tile[x][y] == EL_PLAYER_IS_LEAVING)
12267 if (MovDelay[x][y] <= 0)
12271 if (Tile[x][y] == EL_ELEMENT_SNAPPING)
12274 if (MovDelay[x][y] <= 0)
12276 int element = Store[x][y];
12277 int move_direction = MovDir[x][y];
12278 int player_index_bit = Store2[x][y];
12284 TEST_DrawLevelField(x, y);
12286 TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
12288 if (IS_ENVELOPE(element))
12289 local_player->show_envelope = element;
12294 if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1)
12296 Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1",
12298 Debug("game:playing:GameActions_RND", "This should never happen!");
12300 ChangePage[x][y] = -1;
12304 Stop[x][y] = FALSE;
12305 if (WasJustMoving[x][y] > 0)
12306 WasJustMoving[x][y]--;
12307 if (WasJustFalling[x][y] > 0)
12308 WasJustFalling[x][y]--;
12309 if (CheckCollision[x][y] > 0)
12310 CheckCollision[x][y]--;
12311 if (CheckImpact[x][y] > 0)
12312 CheckImpact[x][y]--;
12316 /* reset finished pushing action (not done in ContinueMoving() to allow
12317 continuous pushing animation for elements with zero push delay) */
12318 if (GfxAction[x][y] == ACTION_PUSHING && !IS_MOVING(x, y))
12320 ResetGfxAnimation(x, y);
12321 TEST_DrawLevelField(x, y);
12325 if (IS_BLOCKED(x, y))
12329 Blocked2Moving(x, y, &oldx, &oldy);
12330 if (!IS_MOVING(oldx, oldy))
12332 Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!");
12333 Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y);
12334 Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy);
12335 Debug("game:playing:GameActions_RND", "This should never happen!");
12341 if (mouse_action.button)
12343 int new_button = (mouse_action.button && mouse_action_last.button == 0);
12344 int ch_button = CH_SIDE_FROM_BUTTON(mouse_action.button);
12346 x = mouse_action.lx;
12347 y = mouse_action.ly;
12348 element = Tile[x][y];
12352 CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button);
12353 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X,
12357 CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button);
12358 CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X,
12361 if (level.use_step_counter)
12363 boolean counted_click = FALSE;
12365 // element clicked that can change when clicked/pressed
12366 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
12367 (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
12368 HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE)))
12369 counted_click = TRUE;
12371 // element clicked that can trigger change when clicked/pressed
12372 if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] ||
12373 trigger_events[element][CE_MOUSE_PRESSED_ON_X])
12374 counted_click = TRUE;
12376 if (new_button && counted_click)
12377 CheckLevelTime_StepCounter();
12381 SCAN_PLAYFIELD(x, y)
12383 element = Tile[x][y];
12384 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12385 last_gfx_frame = GfxFrame[x][y];
12387 if (element == EL_EMPTY)
12388 graphic = el2img(GfxElementEmpty[x][y]);
12390 ResetGfxFrame(x, y);
12392 if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y])
12393 DrawLevelGraphicAnimation(x, y, graphic);
12395 if (ANIM_MODE(graphic) == ANIM_RANDOM &&
12396 IS_NEXT_FRAME(GfxFrame[x][y], graphic))
12397 ResetRandomAnimationValue(x, y);
12399 SetRandomAnimationValue(x, y);
12401 PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]);
12403 if (IS_INACTIVE(element))
12405 if (IS_ANIMATED(graphic))
12406 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12411 // this may take place after moving, so 'element' may have changed
12412 if (IS_CHANGING(x, y) &&
12413 (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y]))
12415 int page = element_info[element].event_page_nr[CE_DELAY];
12417 HandleElementChange(x, y, page);
12419 element = Tile[x][y];
12420 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12423 CheckNextToConditions(x, y);
12425 if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element)))
12429 element = Tile[x][y];
12430 graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
12432 if (IS_ANIMATED(graphic) &&
12433 !IS_MOVING(x, y) &&
12435 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12437 if (IS_GEM(element) || element == EL_SP_INFOTRON)
12438 TEST_DrawTwinkleOnField(x, y);
12440 else if (element == EL_ACID)
12443 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12445 else if ((element == EL_EXIT_OPEN ||
12446 element == EL_EM_EXIT_OPEN ||
12447 element == EL_SP_EXIT_OPEN ||
12448 element == EL_STEEL_EXIT_OPEN ||
12449 element == EL_EM_STEEL_EXIT_OPEN ||
12450 element == EL_SP_TERMINAL ||
12451 element == EL_SP_TERMINAL_ACTIVE ||
12452 element == EL_EXTRA_TIME ||
12453 element == EL_SHIELD_NORMAL ||
12454 element == EL_SHIELD_DEADLY) &&
12455 IS_ANIMATED(graphic))
12456 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12457 else if (IS_MOVING(x, y))
12458 ContinueMoving(x, y);
12459 else if (IS_ACTIVE_BOMB(element))
12460 CheckDynamite(x, y);
12461 else if (element == EL_AMOEBA_GROWING)
12462 AmoebaGrowing(x, y);
12463 else if (element == EL_AMOEBA_SHRINKING)
12464 AmoebaShrinking(x, y);
12466 #if !USE_NEW_AMOEBA_CODE
12467 else if (IS_AMOEBALIVE(element))
12468 AmoebaReproduce(x, y);
12471 else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE)
12473 else if (element == EL_EXIT_CLOSED)
12475 else if (element == EL_EM_EXIT_CLOSED)
12477 else if (element == EL_STEEL_EXIT_CLOSED)
12478 CheckExitSteel(x, y);
12479 else if (element == EL_EM_STEEL_EXIT_CLOSED)
12480 CheckExitSteelEM(x, y);
12481 else if (element == EL_SP_EXIT_CLOSED)
12483 else if (element == EL_EXPANDABLE_WALL_GROWING ||
12484 element == EL_EXPANDABLE_STEELWALL_GROWING)
12485 MauerWaechst(x, y);
12486 else if (element == EL_EXPANDABLE_WALL ||
12487 element == EL_EXPANDABLE_WALL_HORIZONTAL ||
12488 element == EL_EXPANDABLE_WALL_VERTICAL ||
12489 element == EL_EXPANDABLE_WALL_ANY ||
12490 element == EL_BD_EXPANDABLE_WALL)
12491 MauerAbleger(x, y);
12492 else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL ||
12493 element == EL_EXPANDABLE_STEELWALL_VERTICAL ||
12494 element == EL_EXPANDABLE_STEELWALL_ANY)
12495 MauerAblegerStahl(x, y);
12496 else if (element == EL_FLAMES)
12497 CheckForDragon(x, y);
12498 else if (element == EL_EXPLOSION)
12499 ; // drawing of correct explosion animation is handled separately
12500 else if (element == EL_ELEMENT_SNAPPING ||
12501 element == EL_DIAGONAL_SHRINKING ||
12502 element == EL_DIAGONAL_GROWING)
12504 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]);
12506 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12508 else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y))
12509 DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
12511 if (IS_BELT_ACTIVE(element))
12512 PlayLevelSoundAction(x, y, ACTION_ACTIVE);
12514 if (game.magic_wall_active)
12516 int jx = local_player->jx, jy = local_player->jy;
12518 // play the element sound at the position nearest to the player
12519 if ((element == EL_MAGIC_WALL_FULL ||
12520 element == EL_MAGIC_WALL_ACTIVE ||
12521 element == EL_MAGIC_WALL_EMPTYING ||
12522 element == EL_BD_MAGIC_WALL_FULL ||
12523 element == EL_BD_MAGIC_WALL_ACTIVE ||
12524 element == EL_BD_MAGIC_WALL_EMPTYING ||
12525 element == EL_DC_MAGIC_WALL_FULL ||
12526 element == EL_DC_MAGIC_WALL_ACTIVE ||
12527 element == EL_DC_MAGIC_WALL_EMPTYING) &&
12528 ABS(x - jx) + ABS(y - jy) <
12529 ABS(magic_wall_x - jx) + ABS(magic_wall_y - jy))
12537 #if USE_NEW_AMOEBA_CODE
12538 // new experimental amoeba growth stuff
12539 if (!(FrameCounter % 8))
12541 static unsigned int random = 1684108901;
12543 for (i = 0; i < level.amoeba_speed * 28 / 8; i++)
12545 x = RND(lev_fieldx);
12546 y = RND(lev_fieldy);
12547 element = Tile[x][y];
12549 if (!IS_PLAYER(x,y) &&
12550 (element == EL_EMPTY ||
12551 CAN_GROW_INTO(element) ||
12552 element == EL_QUICKSAND_EMPTY ||
12553 element == EL_QUICKSAND_FAST_EMPTY ||
12554 element == EL_ACID_SPLASH_LEFT ||
12555 element == EL_ACID_SPLASH_RIGHT))
12557 if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) ||
12558 (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) ||
12559 (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) ||
12560 (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET))
12561 Tile[x][y] = EL_AMOEBA_DROP;
12564 random = random * 129 + 1;
12569 game.explosions_delayed = FALSE;
12571 SCAN_PLAYFIELD(x, y)
12573 element = Tile[x][y];
12575 if (ExplodeField[x][y])
12576 Explode(x, y, EX_PHASE_START, ExplodeField[x][y]);
12577 else if (element == EL_EXPLOSION)
12578 Explode(x, y, ExplodePhase[x][y], EX_TYPE_NORMAL);
12580 ExplodeField[x][y] = EX_TYPE_NONE;
12583 game.explosions_delayed = TRUE;
12585 if (game.magic_wall_active)
12587 if (!(game.magic_wall_time_left % 4))
12589 int element = Tile[magic_wall_x][magic_wall_y];
12591 if (element == EL_BD_MAGIC_WALL_FULL ||
12592 element == EL_BD_MAGIC_WALL_ACTIVE ||
12593 element == EL_BD_MAGIC_WALL_EMPTYING)
12594 PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE);
12595 else if (element == EL_DC_MAGIC_WALL_FULL ||
12596 element == EL_DC_MAGIC_WALL_ACTIVE ||
12597 element == EL_DC_MAGIC_WALL_EMPTYING)
12598 PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE);
12600 PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE);
12603 if (game.magic_wall_time_left > 0)
12605 game.magic_wall_time_left--;
12607 if (!game.magic_wall_time_left)
12609 SCAN_PLAYFIELD(x, y)
12611 element = Tile[x][y];
12613 if (element == EL_MAGIC_WALL_ACTIVE ||
12614 element == EL_MAGIC_WALL_FULL)
12616 Tile[x][y] = EL_MAGIC_WALL_DEAD;
12617 TEST_DrawLevelField(x, y);
12619 else if (element == EL_BD_MAGIC_WALL_ACTIVE ||
12620 element == EL_BD_MAGIC_WALL_FULL)
12622 Tile[x][y] = EL_BD_MAGIC_WALL_DEAD;
12623 TEST_DrawLevelField(x, y);
12625 else if (element == EL_DC_MAGIC_WALL_ACTIVE ||
12626 element == EL_DC_MAGIC_WALL_FULL)
12628 Tile[x][y] = EL_DC_MAGIC_WALL_DEAD;
12629 TEST_DrawLevelField(x, y);
12633 game.magic_wall_active = FALSE;
12638 if (game.light_time_left > 0)
12640 game.light_time_left--;
12642 if (game.light_time_left == 0)
12643 RedrawAllLightSwitchesAndInvisibleElements();
12646 if (game.timegate_time_left > 0)
12648 game.timegate_time_left--;
12650 if (game.timegate_time_left == 0)
12651 CloseAllOpenTimegates();
12654 if (game.lenses_time_left > 0)
12656 game.lenses_time_left--;
12658 if (game.lenses_time_left == 0)
12659 RedrawAllInvisibleElementsForLenses();
12662 if (game.magnify_time_left > 0)
12664 game.magnify_time_left--;
12666 if (game.magnify_time_left == 0)
12667 RedrawAllInvisibleElementsForMagnifier();
12670 for (i = 0; i < MAX_PLAYERS; i++)
12672 struct PlayerInfo *player = &stored_player[i];
12674 if (SHIELD_ON(player))
12676 if (player->shield_deadly_time_left)
12677 PlayLevelSound(player->jx, player->jy, SND_SHIELD_DEADLY_ACTIVE);
12678 else if (player->shield_normal_time_left)
12679 PlayLevelSound(player->jx, player->jy, SND_SHIELD_NORMAL_ACTIVE);
12683 #if USE_DELAYED_GFX_REDRAW
12684 SCAN_PLAYFIELD(x, y)
12686 if (GfxRedraw[x][y] != GFX_REDRAW_NONE)
12688 /* !!! PROBLEM: THIS REDRAWS THE PLAYFIELD _AFTER_ THE SCAN, BUT TILES
12689 !!! MAY HAVE CHANGED AFTER BEING DRAWN DURING PLAYFIELD SCAN !!! */
12691 if (GfxRedraw[x][y] & GFX_REDRAW_TILE)
12692 DrawLevelField(x, y);
12694 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED)
12695 DrawLevelFieldCrumbled(x, y);
12697 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_CRUMBLED_NEIGHBOURS)
12698 DrawLevelFieldCrumbledNeighbours(x, y);
12700 if (GfxRedraw[x][y] & GFX_REDRAW_TILE_TWINKLED)
12701 DrawTwinkleOnField(x, y);
12704 GfxRedraw[x][y] = GFX_REDRAW_NONE;
12709 PlayAllPlayersSound();
12711 for (i = 0; i < MAX_PLAYERS; i++)
12713 struct PlayerInfo *player = &stored_player[i];
12715 if (player->show_envelope != 0 && (!player->active ||
12716 player->MovPos == 0))
12718 ShowEnvelope(player->show_envelope - EL_ENVELOPE_1);
12720 player->show_envelope = 0;
12724 // use random number generator in every frame to make it less predictable
12725 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
12728 mouse_action_last = mouse_action;
12731 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
12733 int min_x = x, min_y = y, max_x = x, max_y = y;
12734 int scr_fieldx = getScreenFieldSizeX();
12735 int scr_fieldy = getScreenFieldSizeY();
12738 for (i = 0; i < MAX_PLAYERS; i++)
12740 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12742 if (!stored_player[i].active || &stored_player[i] == player)
12745 min_x = MIN(min_x, jx);
12746 min_y = MIN(min_y, jy);
12747 max_x = MAX(max_x, jx);
12748 max_y = MAX(max_y, jy);
12751 return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
12754 static boolean AllPlayersInVisibleScreen(void)
12758 for (i = 0; i < MAX_PLAYERS; i++)
12760 int jx = stored_player[i].jx, jy = stored_player[i].jy;
12762 if (!stored_player[i].active)
12765 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
12772 void ScrollLevel(int dx, int dy)
12774 int scroll_offset = 2 * TILEX_VAR;
12777 BlitBitmap(drawto_field, drawto_field,
12778 FX + TILEX_VAR * (dx == -1) - scroll_offset,
12779 FY + TILEY_VAR * (dy == -1) - scroll_offset,
12780 SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset,
12781 SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset,
12782 FX + TILEX_VAR * (dx == 1) - scroll_offset,
12783 FY + TILEY_VAR * (dy == 1) - scroll_offset);
12787 x = (dx == 1 ? BX1 : BX2);
12788 for (y = BY1; y <= BY2; y++)
12789 DrawScreenField(x, y);
12794 y = (dy == 1 ? BY1 : BY2);
12795 for (x = BX1; x <= BX2; x++)
12796 DrawScreenField(x, y);
12799 redraw_mask |= REDRAW_FIELD;
12802 static boolean canFallDown(struct PlayerInfo *player)
12804 int jx = player->jx, jy = player->jy;
12806 return (IN_LEV_FIELD(jx, jy + 1) &&
12807 (IS_FREE(jx, jy + 1) ||
12808 (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) &&
12809 IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) &&
12810 !IS_WALKABLE_INSIDE(Tile[jx][jy]));
12813 static boolean canPassField(int x, int y, int move_dir)
12815 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12816 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12817 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12818 int nextx = x + dx;
12819 int nexty = y + dy;
12820 int element = Tile[x][y];
12822 return (IS_PASSABLE_FROM(element, opposite_dir) &&
12823 !CAN_MOVE(element) &&
12824 IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) &&
12825 IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) &&
12826 (level.can_pass_to_walkable || IS_FREE(nextx, nexty)));
12829 static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir)
12831 int opposite_dir = MV_DIR_OPPOSITE(move_dir);
12832 int dx = (move_dir & MV_LEFT ? -1 : move_dir & MV_RIGHT ? +1 : 0);
12833 int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0);
12837 return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) &&
12838 IS_GRAVITY_REACHABLE(Tile[newx][newy]) &&
12839 (IS_DIGGABLE(Tile[newx][newy]) ||
12840 IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) ||
12841 canPassField(newx, newy, move_dir)));
12844 static void CheckGravityMovement(struct PlayerInfo *player)
12846 if (player->gravity && !player->programmed_action)
12848 int move_dir_horizontal = player->effective_action & MV_HORIZONTAL;
12849 int move_dir_vertical = player->effective_action & MV_VERTICAL;
12850 boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1);
12851 int jx = player->jx, jy = player->jy;
12852 boolean player_is_moving_to_valid_field =
12853 (!player_is_snapping &&
12854 (canMoveToValidFieldWithGravity(jx, jy, move_dir_horizontal) ||
12855 canMoveToValidFieldWithGravity(jx, jy, move_dir_vertical)));
12856 boolean player_can_fall_down = canFallDown(player);
12858 if (player_can_fall_down &&
12859 !player_is_moving_to_valid_field)
12860 player->programmed_action = MV_DOWN;
12864 static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player)
12866 return CheckGravityMovement(player);
12868 if (player->gravity && !player->programmed_action)
12870 int jx = player->jx, jy = player->jy;
12871 boolean field_under_player_is_free =
12872 (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1));
12873 boolean player_is_standing_on_valid_field =
12874 (IS_WALKABLE_INSIDE(Tile[jx][jy]) ||
12875 (IS_WALKABLE(Tile[jx][jy]) &&
12876 !(element_info[Tile[jx][jy]].access_direction & MV_DOWN)));
12878 if (field_under_player_is_free && !player_is_standing_on_valid_field)
12879 player->programmed_action = MV_DOWN;
12884 MovePlayerOneStep()
12885 -----------------------------------------------------------------------------
12886 dx, dy: direction (non-diagonal) to try to move the player to
12887 real_dx, real_dy: direction as read from input device (can be diagonal)
12890 boolean MovePlayerOneStep(struct PlayerInfo *player,
12891 int dx, int dy, int real_dx, int real_dy)
12893 int jx = player->jx, jy = player->jy;
12894 int new_jx = jx + dx, new_jy = jy + dy;
12896 boolean player_can_move = !player->cannot_move;
12898 if (!player->active || (!dx && !dy))
12899 return MP_NO_ACTION;
12901 player->MovDir = (dx < 0 ? MV_LEFT :
12902 dx > 0 ? MV_RIGHT :
12904 dy > 0 ? MV_DOWN : MV_NONE);
12906 if (!IN_LEV_FIELD(new_jx, new_jy))
12907 return MP_NO_ACTION;
12909 if (!player_can_move)
12911 if (player->MovPos == 0)
12913 player->is_moving = FALSE;
12914 player->is_digging = FALSE;
12915 player->is_collecting = FALSE;
12916 player->is_snapping = FALSE;
12917 player->is_pushing = FALSE;
12921 if (!network.enabled && game.centered_player_nr == -1 &&
12922 !AllPlayersInSight(player, new_jx, new_jy))
12923 return MP_NO_ACTION;
12925 can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG);
12926 if (can_move != MP_MOVING)
12929 // check if DigField() has caused relocation of the player
12930 if (player->jx != jx || player->jy != jy)
12931 return MP_NO_ACTION; // <-- !!! CHECK THIS [-> MP_ACTION ?] !!!
12933 StorePlayer[jx][jy] = 0;
12934 player->last_jx = jx;
12935 player->last_jy = jy;
12936 player->jx = new_jx;
12937 player->jy = new_jy;
12938 StorePlayer[new_jx][new_jy] = player->element_nr;
12940 if (player->move_delay_value_next != -1)
12942 player->move_delay_value = player->move_delay_value_next;
12943 player->move_delay_value_next = -1;
12947 (dx > 0 || dy > 0 ? -1 : 1) * (TILEX - TILEX / player->move_delay_value);
12949 player->step_counter++;
12951 PlayerVisit[jx][jy] = FrameCounter;
12953 player->is_moving = TRUE;
12956 // should better be called in MovePlayer(), but this breaks some tapes
12957 ScrollPlayer(player, SCROLL_INIT);
12963 boolean MovePlayer(struct PlayerInfo *player, int dx, int dy)
12965 int jx = player->jx, jy = player->jy;
12966 int old_jx = jx, old_jy = jy;
12967 int moved = MP_NO_ACTION;
12969 if (!player->active)
12974 if (player->MovPos == 0)
12976 player->is_moving = FALSE;
12977 player->is_digging = FALSE;
12978 player->is_collecting = FALSE;
12979 player->is_snapping = FALSE;
12980 player->is_pushing = FALSE;
12986 if (player->move_delay > 0)
12989 player->move_delay = -1; // set to "uninitialized" value
12991 // store if player is automatically moved to next field
12992 player->is_auto_moving = (player->programmed_action != MV_NONE);
12994 // remove the last programmed player action
12995 player->programmed_action = 0;
12997 if (player->MovPos)
12999 // should only happen if pre-1.2 tape recordings are played
13000 // this is only for backward compatibility
13002 int original_move_delay_value = player->move_delay_value;
13005 Debug("game:playing:MovePlayer",
13006 "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]",
13010 // scroll remaining steps with finest movement resolution
13011 player->move_delay_value = MOVE_DELAY_NORMAL_SPEED;
13013 while (player->MovPos)
13015 ScrollPlayer(player, SCROLL_GO_ON);
13016 ScrollScreen(NULL, SCROLL_GO_ON);
13018 AdvanceFrameAndPlayerCounters(player->index_nr);
13021 BackToFront_WithFrameDelay(0);
13024 player->move_delay_value = original_move_delay_value;
13027 player->is_active = FALSE;
13029 if (player->last_move_dir & MV_HORIZONTAL)
13031 if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy)))
13032 moved |= MovePlayerOneStep(player, dx, 0, dx, dy);
13036 if (!(moved |= MovePlayerOneStep(player, dx, 0, dx, dy)))
13037 moved |= MovePlayerOneStep(player, 0, dy, dx, dy);
13040 if (!moved && !player->is_active)
13042 player->is_moving = FALSE;
13043 player->is_digging = FALSE;
13044 player->is_collecting = FALSE;
13045 player->is_snapping = FALSE;
13046 player->is_pushing = FALSE;
13052 if (moved & MP_MOVING && !ScreenMovPos &&
13053 (player->index_nr == game.centered_player_nr ||
13054 game.centered_player_nr == -1))
13056 int old_scroll_x = scroll_x, old_scroll_y = scroll_y;
13058 if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy)))
13060 // actual player has left the screen -- scroll in that direction
13061 if (jx != old_jx) // player has moved horizontally
13062 scroll_x += (jx - old_jx);
13063 else // player has moved vertically
13064 scroll_y += (jy - old_jy);
13068 int offset_raw = game.scroll_delay_value;
13070 if (jx != old_jx) // player has moved horizontally
13072 int offset = MIN(offset_raw, (SCR_FIELDX - 2) / 2);
13073 int offset_x = offset * (player->MovDir == MV_LEFT ? +1 : -1);
13074 int new_scroll_x = jx - MIDPOSX + offset_x;
13076 if ((player->MovDir == MV_LEFT && scroll_x > new_scroll_x) ||
13077 (player->MovDir == MV_RIGHT && scroll_x < new_scroll_x))
13078 scroll_x = new_scroll_x;
13080 // don't scroll over playfield boundaries
13081 scroll_x = MIN(MAX(SBX_Left, scroll_x), SBX_Right);
13083 // don't scroll more than one field at a time
13084 scroll_x = old_scroll_x + SIGN(scroll_x - old_scroll_x);
13086 // don't scroll against the player's moving direction
13087 if ((player->MovDir == MV_LEFT && scroll_x > old_scroll_x) ||
13088 (player->MovDir == MV_RIGHT && scroll_x < old_scroll_x))
13089 scroll_x = old_scroll_x;
13091 else // player has moved vertically
13093 int offset = MIN(offset_raw, (SCR_FIELDY - 2) / 2);
13094 int offset_y = offset * (player->MovDir == MV_UP ? +1 : -1);
13095 int new_scroll_y = jy - MIDPOSY + offset_y;
13097 if ((player->MovDir == MV_UP && scroll_y > new_scroll_y) ||
13098 (player->MovDir == MV_DOWN && scroll_y < new_scroll_y))
13099 scroll_y = new_scroll_y;
13101 // don't scroll over playfield boundaries
13102 scroll_y = MIN(MAX(SBY_Upper, scroll_y), SBY_Lower);
13104 // don't scroll more than one field at a time
13105 scroll_y = old_scroll_y + SIGN(scroll_y - old_scroll_y);
13107 // don't scroll against the player's moving direction
13108 if ((player->MovDir == MV_UP && scroll_y > old_scroll_y) ||
13109 (player->MovDir == MV_DOWN && scroll_y < old_scroll_y))
13110 scroll_y = old_scroll_y;
13114 if (scroll_x != old_scroll_x || scroll_y != old_scroll_y)
13116 if (!network.enabled && game.centered_player_nr == -1 &&
13117 !AllPlayersInVisibleScreen())
13119 scroll_x = old_scroll_x;
13120 scroll_y = old_scroll_y;
13124 ScrollScreen(player, SCROLL_INIT);
13125 ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y);
13130 player->StepFrame = 0;
13132 if (moved & MP_MOVING)
13134 if (old_jx != jx && old_jy == jy)
13135 player->MovDir = (old_jx < jx ? MV_RIGHT : MV_LEFT);
13136 else if (old_jx == jx && old_jy != jy)
13137 player->MovDir = (old_jy < jy ? MV_DOWN : MV_UP);
13139 TEST_DrawLevelField(jx, jy); // for "crumbled sand"
13141 player->last_move_dir = player->MovDir;
13142 player->is_moving = TRUE;
13143 player->is_snapping = FALSE;
13144 player->is_switching = FALSE;
13145 player->is_dropping = FALSE;
13146 player->is_dropping_pressed = FALSE;
13147 player->drop_pressed_delay = 0;
13150 // should better be called here than above, but this breaks some tapes
13151 ScrollPlayer(player, SCROLL_INIT);
13156 CheckGravityMovementWhenNotMoving(player);
13158 player->is_moving = FALSE;
13160 /* at this point, the player is allowed to move, but cannot move right now
13161 (e.g. because of something blocking the way) -- ensure that the player
13162 is also allowed to move in the next frame (in old versions before 3.1.1,
13163 the player was forced to wait again for eight frames before next try) */
13165 if (game.engine_version >= VERSION_IDENT(3,1,1,0))
13166 player->move_delay = 0; // allow direct movement in the next frame
13169 if (player->move_delay == -1) // not yet initialized by DigField()
13170 player->move_delay = player->move_delay_value;
13172 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13174 TestIfPlayerTouchesBadThing(jx, jy);
13175 TestIfPlayerTouchesCustomElement(jx, jy);
13178 if (!player->active)
13179 RemovePlayer(player);
13184 void ScrollPlayer(struct PlayerInfo *player, int mode)
13186 int jx = player->jx, jy = player->jy;
13187 int last_jx = player->last_jx, last_jy = player->last_jy;
13188 int move_stepsize = TILEX / player->move_delay_value;
13190 if (!player->active)
13193 if (player->MovPos == 0 && mode == SCROLL_GO_ON) // player not moving
13196 if (mode == SCROLL_INIT)
13198 player->actual_frame_counter = FrameCounter;
13199 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13201 if ((player->block_last_field || player->block_delay_adjustment > 0) &&
13202 Tile[last_jx][last_jy] == EL_EMPTY)
13204 int last_field_block_delay = 0; // start with no blocking at all
13205 int block_delay_adjustment = player->block_delay_adjustment;
13207 // if player blocks last field, add delay for exactly one move
13208 if (player->block_last_field)
13210 last_field_block_delay += player->move_delay_value;
13212 // when blocking enabled, prevent moving up despite gravity
13213 if (player->gravity && player->MovDir == MV_UP)
13214 block_delay_adjustment = -1;
13217 // add block delay adjustment (also possible when not blocking)
13218 last_field_block_delay += block_delay_adjustment;
13220 Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING;
13221 MovDelay[last_jx][last_jy] = last_field_block_delay + 1;
13224 if (player->MovPos != 0) // player has not yet reached destination
13227 else if (!FrameReached(&player->actual_frame_counter, 1))
13230 if (player->MovPos != 0)
13232 player->MovPos += (player->MovPos > 0 ? -1 : 1) * move_stepsize;
13233 player->GfxPos = move_stepsize * (player->MovPos / move_stepsize);
13235 // before DrawPlayer() to draw correct player graphic for this case
13236 if (player->MovPos == 0)
13237 CheckGravityMovement(player);
13240 if (player->MovPos == 0) // player reached destination field
13242 if (player->move_delay_reset_counter > 0)
13244 player->move_delay_reset_counter--;
13246 if (player->move_delay_reset_counter == 0)
13248 // continue with normal speed after quickly moving through gate
13249 HALVE_PLAYER_SPEED(player);
13251 // be able to make the next move without delay
13252 player->move_delay = 0;
13256 if (Tile[jx][jy] == EL_EXIT_OPEN ||
13257 Tile[jx][jy] == EL_EM_EXIT_OPEN ||
13258 Tile[jx][jy] == EL_EM_EXIT_OPENING ||
13259 Tile[jx][jy] == EL_STEEL_EXIT_OPEN ||
13260 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN ||
13261 Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING ||
13262 Tile[jx][jy] == EL_SP_EXIT_OPEN ||
13263 Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case
13265 ExitPlayer(player);
13267 if (game.players_still_needed == 0 &&
13268 (game.friends_still_needed == 0 ||
13269 IS_SP_ELEMENT(Tile[jx][jy])))
13273 player->last_jx = jx;
13274 player->last_jy = jy;
13276 // this breaks one level: "machine", level 000
13278 int move_direction = player->MovDir;
13279 int enter_side = MV_DIR_OPPOSITE(move_direction);
13280 int leave_side = move_direction;
13281 int old_jx = last_jx;
13282 int old_jy = last_jy;
13283 int old_element = Tile[old_jx][old_jy];
13284 int new_element = Tile[jx][jy];
13286 if (IS_CUSTOM_ELEMENT(old_element))
13287 CheckElementChangeByPlayer(old_jx, old_jy, old_element,
13289 player->index_bit, leave_side);
13291 CheckTriggeredElementChangeByPlayer(old_jx, old_jy, old_element,
13292 CE_PLAYER_LEAVES_X,
13293 player->index_bit, leave_side);
13295 if (IS_CUSTOM_ELEMENT(new_element))
13296 CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER,
13297 player->index_bit, enter_side);
13299 CheckTriggeredElementChangeByPlayer(jx, jy, new_element,
13300 CE_PLAYER_ENTERS_X,
13301 player->index_bit, enter_side);
13303 CheckTriggeredElementChangeBySide(jx, jy, player->initial_element,
13304 CE_MOVE_OF_X, move_direction);
13307 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13309 TestIfPlayerTouchesBadThing(jx, jy);
13310 TestIfPlayerTouchesCustomElement(jx, jy);
13312 /* needed because pushed element has not yet reached its destination,
13313 so it would trigger a change event at its previous field location */
13314 if (!player->is_pushing)
13315 TestIfElementTouchesCustomElement(jx, jy); // for empty space
13317 if (level.finish_dig_collect &&
13318 (player->is_digging || player->is_collecting))
13320 int last_element = player->last_removed_element;
13321 int move_direction = player->MovDir;
13322 int enter_side = MV_DIR_OPPOSITE(move_direction);
13323 int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
13324 CE_PLAYER_COLLECTS_X);
13326 CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
13327 player->index_bit, enter_side);
13329 player->last_removed_element = EL_UNDEFINED;
13332 if (!player->active)
13333 RemovePlayer(player);
13336 if (level.use_step_counter)
13337 CheckLevelTime_StepCounter();
13339 if (tape.single_step && tape.recording && !tape.pausing &&
13340 !player->programmed_action)
13341 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
13343 if (!player->programmed_action)
13344 CheckSaveEngineSnapshot(player);
13348 void ScrollScreen(struct PlayerInfo *player, int mode)
13350 static unsigned int screen_frame_counter = 0;
13352 if (mode == SCROLL_INIT)
13354 // set scrolling step size according to actual player's moving speed
13355 ScrollStepSize = TILEX / player->move_delay_value;
13357 screen_frame_counter = FrameCounter;
13358 ScreenMovDir = player->MovDir;
13359 ScreenMovPos = player->MovPos;
13360 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13363 else if (!FrameReached(&screen_frame_counter, 1))
13368 ScreenMovPos += (ScreenMovPos > 0 ? -1 : 1) * ScrollStepSize;
13369 ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize);
13370 redraw_mask |= REDRAW_FIELD;
13373 ScreenMovDir = MV_NONE;
13376 void CheckNextToConditions(int x, int y)
13378 int element = Tile[x][y];
13380 if (IS_PLAYER(x, y))
13381 TestIfPlayerNextToCustomElement(x, y);
13383 if (CAN_CHANGE_OR_HAS_ACTION(element) &&
13384 HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X))
13385 TestIfElementNextToCustomElement(x, y);
13388 void TestIfPlayerNextToCustomElement(int x, int y)
13390 static int xy[4][2] =
13397 static int trigger_sides[4][2] =
13399 // center side border side
13400 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13401 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13402 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13403 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13407 if (!IS_PLAYER(x, y))
13410 struct PlayerInfo *player = PLAYERINFO(x, y);
13412 if (player->is_moving)
13415 for (i = 0; i < NUM_DIRECTIONS; i++)
13417 int xx = x + xy[i][0];
13418 int yy = y + xy[i][1];
13419 int border_side = trigger_sides[i][1];
13420 int border_element;
13422 if (!IN_LEV_FIELD(xx, yy))
13425 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13426 continue; // center and border element not connected
13428 border_element = Tile[xx][yy];
13430 CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER,
13431 player->index_bit, border_side);
13432 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13433 CE_PLAYER_NEXT_TO_X,
13434 player->index_bit, border_side);
13436 /* use player element that is initially defined in the level playfield,
13437 not the player element that corresponds to the runtime player number
13438 (example: a level that contains EL_PLAYER_3 as the only player would
13439 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13441 CheckElementChangeBySide(xx, yy, border_element, player->initial_element,
13442 CE_NEXT_TO_X, border_side);
13446 void TestIfPlayerTouchesCustomElement(int x, int y)
13448 static int xy[4][2] =
13455 static int trigger_sides[4][2] =
13457 // center side border side
13458 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13459 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13460 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13461 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13463 static int touch_dir[4] =
13465 MV_LEFT | MV_RIGHT,
13470 int center_element = Tile[x][y]; // should always be non-moving!
13473 for (i = 0; i < NUM_DIRECTIONS; i++)
13475 int xx = x + xy[i][0];
13476 int yy = y + xy[i][1];
13477 int center_side = trigger_sides[i][0];
13478 int border_side = trigger_sides[i][1];
13479 int border_element;
13481 if (!IN_LEV_FIELD(xx, yy))
13484 if (IS_PLAYER(x, y)) // player found at center element
13486 struct PlayerInfo *player = PLAYERINFO(x, y);
13488 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13489 border_element = Tile[xx][yy]; // may be moving!
13490 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13491 border_element = Tile[xx][yy];
13492 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13493 border_element = MovingOrBlocked2Element(xx, yy);
13495 continue; // center and border element do not touch
13497 CheckElementChangeByPlayer(xx, yy, border_element, CE_TOUCHED_BY_PLAYER,
13498 player->index_bit, border_side);
13499 CheckTriggeredElementChangeByPlayer(xx, yy, border_element,
13500 CE_PLAYER_TOUCHES_X,
13501 player->index_bit, border_side);
13504 /* use player element that is initially defined in the level playfield,
13505 not the player element that corresponds to the runtime player number
13506 (example: a level that contains EL_PLAYER_3 as the only player would
13507 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13508 int player_element = PLAYERINFO(x, y)->initial_element;
13510 CheckElementChangeBySide(xx, yy, border_element, player_element,
13511 CE_TOUCHING_X, border_side);
13514 else if (IS_PLAYER(xx, yy)) // player found at border element
13516 struct PlayerInfo *player = PLAYERINFO(xx, yy);
13518 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13520 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13521 continue; // center and border element do not touch
13524 CheckElementChangeByPlayer(x, y, center_element, CE_TOUCHED_BY_PLAYER,
13525 player->index_bit, center_side);
13526 CheckTriggeredElementChangeByPlayer(x, y, center_element,
13527 CE_PLAYER_TOUCHES_X,
13528 player->index_bit, center_side);
13531 /* use player element that is initially defined in the level playfield,
13532 not the player element that corresponds to the runtime player number
13533 (example: a level that contains EL_PLAYER_3 as the only player would
13534 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13535 int player_element = PLAYERINFO(xx, yy)->initial_element;
13537 CheckElementChangeBySide(x, y, center_element, player_element,
13538 CE_TOUCHING_X, center_side);
13546 void TestIfElementNextToCustomElement(int x, int y)
13548 static int xy[4][2] =
13555 static int trigger_sides[4][2] =
13557 // center side border side
13558 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13559 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13560 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13561 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13563 int center_element = Tile[x][y]; // should always be non-moving!
13566 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
13569 for (i = 0; i < NUM_DIRECTIONS; i++)
13571 int xx = x + xy[i][0];
13572 int yy = y + xy[i][1];
13573 int border_side = trigger_sides[i][1];
13574 int border_element;
13576 if (!IN_LEV_FIELD(xx, yy))
13579 if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy))
13580 continue; // center and border element not connected
13582 border_element = Tile[xx][yy];
13584 // check for change of center element (but change it only once)
13585 if (CheckElementChangeBySide(x, y, center_element, border_element,
13586 CE_NEXT_TO_X, border_side))
13591 void TestIfElementTouchesCustomElement(int x, int y)
13593 static int xy[4][2] =
13600 static int trigger_sides[4][2] =
13602 // center side border side
13603 { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top
13604 { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left
13605 { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right
13606 { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom
13608 static int touch_dir[4] =
13610 MV_LEFT | MV_RIGHT,
13615 boolean change_center_element = FALSE;
13616 int center_element = Tile[x][y]; // should always be non-moving!
13617 int border_element_old[NUM_DIRECTIONS];
13620 for (i = 0; i < NUM_DIRECTIONS; i++)
13622 int xx = x + xy[i][0];
13623 int yy = y + xy[i][1];
13624 int border_element;
13626 border_element_old[i] = -1;
13628 if (!IN_LEV_FIELD(xx, yy))
13631 if (game.engine_version < VERSION_IDENT(3,0,7,0))
13632 border_element = Tile[xx][yy]; // may be moving!
13633 else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy))
13634 border_element = Tile[xx][yy];
13635 else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching
13636 border_element = MovingOrBlocked2Element(xx, yy);
13638 continue; // center and border element do not touch
13640 border_element_old[i] = border_element;
13643 for (i = 0; i < NUM_DIRECTIONS; i++)
13645 int xx = x + xy[i][0];
13646 int yy = y + xy[i][1];
13647 int center_side = trigger_sides[i][0];
13648 int border_element = border_element_old[i];
13650 if (border_element == -1)
13653 // check for change of border element
13654 CheckElementChangeBySide(xx, yy, border_element, center_element,
13655 CE_TOUCHING_X, center_side);
13657 // (center element cannot be player, so we dont have to check this here)
13660 for (i = 0; i < NUM_DIRECTIONS; i++)
13662 int xx = x + xy[i][0];
13663 int yy = y + xy[i][1];
13664 int border_side = trigger_sides[i][1];
13665 int border_element = border_element_old[i];
13667 if (border_element == -1)
13670 // check for change of center element (but change it only once)
13671 if (!change_center_element)
13672 change_center_element =
13673 CheckElementChangeBySide(x, y, center_element, border_element,
13674 CE_TOUCHING_X, border_side);
13676 if (IS_PLAYER(xx, yy))
13678 /* use player element that is initially defined in the level playfield,
13679 not the player element that corresponds to the runtime player number
13680 (example: a level that contains EL_PLAYER_3 as the only player would
13681 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13682 int player_element = PLAYERINFO(xx, yy)->initial_element;
13684 CheckElementChangeBySide(x, y, center_element, player_element,
13685 CE_TOUCHING_X, border_side);
13690 void TestIfElementHitsCustomElement(int x, int y, int direction)
13692 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
13693 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
13694 int hitx = x + dx, hity = y + dy;
13695 int hitting_element = Tile[x][y];
13696 int touched_element;
13698 if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity))
13701 touched_element = (IN_LEV_FIELD(hitx, hity) ?
13702 MovingOrBlocked2Element(hitx, hity) : EL_STEELWALL);
13704 if (IN_LEV_FIELD(hitx, hity))
13706 int opposite_direction = MV_DIR_OPPOSITE(direction);
13707 int hitting_side = direction;
13708 int touched_side = opposite_direction;
13709 boolean object_hit = (!IS_MOVING(hitx, hity) ||
13710 MovDir[hitx][hity] != direction ||
13711 ABS(MovPos[hitx][hity]) <= TILEY / 2);
13717 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13718 CE_HITTING_X, touched_side);
13720 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13721 CE_HIT_BY_X, hitting_side);
13723 CheckElementChangeBySide(hitx, hity, touched_element, hitting_element,
13724 CE_HIT_BY_SOMETHING, opposite_direction);
13726 if (IS_PLAYER(hitx, hity))
13728 /* use player element that is initially defined in the level playfield,
13729 not the player element that corresponds to the runtime player number
13730 (example: a level that contains EL_PLAYER_3 as the only player would
13731 incorrectly give EL_PLAYER_1 for "player->element_nr") */
13732 int player_element = PLAYERINFO(hitx, hity)->initial_element;
13734 CheckElementChangeBySide(x, y, hitting_element, player_element,
13735 CE_HITTING_X, touched_side);
13740 // "hitting something" is also true when hitting the playfield border
13741 CheckElementChangeBySide(x, y, hitting_element, touched_element,
13742 CE_HITTING_SOMETHING, direction);
13745 void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir)
13747 int i, kill_x = -1, kill_y = -1;
13749 int bad_element = -1;
13750 static int test_xy[4][2] =
13757 static int test_dir[4] =
13765 for (i = 0; i < NUM_DIRECTIONS; i++)
13767 int test_x, test_y, test_move_dir, test_element;
13769 test_x = good_x + test_xy[i][0];
13770 test_y = good_y + test_xy[i][1];
13772 if (!IN_LEV_FIELD(test_x, test_y))
13776 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13778 test_element = MovingOrBlocked2ElementIfNotLeaving(test_x, test_y);
13780 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13781 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13783 if ((DONT_RUN_INTO(test_element) && good_move_dir == test_dir[i]) ||
13784 (DONT_TOUCH(test_element) && test_move_dir != test_dir[i]))
13788 bad_element = test_element;
13794 if (kill_x != -1 || kill_y != -1)
13796 if (IS_PLAYER(good_x, good_y))
13798 struct PlayerInfo *player = PLAYERINFO(good_x, good_y);
13800 if (player->shield_deadly_time_left > 0 &&
13801 !IS_INDESTRUCTIBLE(bad_element))
13802 Bang(kill_x, kill_y);
13803 else if (!PLAYER_ENEMY_PROTECTED(good_x, good_y))
13804 KillPlayer(player);
13807 Bang(good_x, good_y);
13811 void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir)
13813 int i, kill_x = -1, kill_y = -1;
13814 int bad_element = Tile[bad_x][bad_y];
13815 static int test_xy[4][2] =
13822 static int touch_dir[4] =
13824 MV_LEFT | MV_RIGHT,
13829 static int test_dir[4] =
13837 if (bad_element == EL_EXPLOSION) // skip just exploding bad things
13840 for (i = 0; i < NUM_DIRECTIONS; i++)
13842 int test_x, test_y, test_move_dir, test_element;
13844 test_x = bad_x + test_xy[i][0];
13845 test_y = bad_y + test_xy[i][1];
13847 if (!IN_LEV_FIELD(test_x, test_y))
13851 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13853 test_element = Tile[test_x][test_y];
13855 /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing;
13856 2nd case: DONT_TOUCH style bad thing does not move away from good thing
13858 if ((DONT_RUN_INTO(bad_element) && bad_move_dir == test_dir[i]) ||
13859 (DONT_TOUCH(bad_element) && test_move_dir != test_dir[i]))
13861 // good thing is player or penguin that does not move away
13862 if (IS_PLAYER(test_x, test_y))
13864 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13866 if (bad_element == EL_ROBOT && player->is_moving)
13867 continue; // robot does not kill player if he is moving
13869 if (game.engine_version >= VERSION_IDENT(3,0,7,0))
13871 if (player->MovPos != 0 && !(player->MovDir & touch_dir[i]))
13872 continue; // center and border element do not touch
13880 else if (test_element == EL_PENGUIN)
13890 if (kill_x != -1 || kill_y != -1)
13892 if (IS_PLAYER(kill_x, kill_y))
13894 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13896 if (player->shield_deadly_time_left > 0 &&
13897 !IS_INDESTRUCTIBLE(bad_element))
13898 Bang(bad_x, bad_y);
13899 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13900 KillPlayer(player);
13903 Bang(kill_x, kill_y);
13907 void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir)
13909 int bad_element = Tile[bad_x][bad_y];
13910 int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0);
13911 int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0);
13912 int test_x = bad_x + dx, test_y = bad_y + dy;
13913 int test_move_dir, test_element;
13914 int kill_x = -1, kill_y = -1;
13916 if (!IN_LEV_FIELD(test_x, test_y))
13920 (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE);
13922 test_element = Tile[test_x][test_y];
13924 if (test_move_dir != bad_move_dir)
13926 // good thing can be player or penguin that does not move away
13927 if (IS_PLAYER(test_x, test_y))
13929 struct PlayerInfo *player = PLAYERINFO(test_x, test_y);
13931 /* (note: in comparison to DONT_RUN_TO and DONT_TOUCH, also handle the
13932 player as being hit when he is moving towards the bad thing, because
13933 the "get hit by" condition would be lost after the player stops) */
13934 if (player->MovPos != 0 && player->MovDir == bad_move_dir)
13935 return; // player moves away from bad thing
13940 else if (test_element == EL_PENGUIN)
13947 if (kill_x != -1 || kill_y != -1)
13949 if (IS_PLAYER(kill_x, kill_y))
13951 struct PlayerInfo *player = PLAYERINFO(kill_x, kill_y);
13953 if (player->shield_deadly_time_left > 0 &&
13954 !IS_INDESTRUCTIBLE(bad_element))
13955 Bang(bad_x, bad_y);
13956 else if (!PLAYER_ENEMY_PROTECTED(kill_x, kill_y))
13957 KillPlayer(player);
13960 Bang(kill_x, kill_y);
13964 void TestIfPlayerTouchesBadThing(int x, int y)
13966 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13969 void TestIfPlayerRunsIntoBadThing(int x, int y, int move_dir)
13971 TestIfGoodThingHitsBadThing(x, y, move_dir);
13974 void TestIfBadThingTouchesPlayer(int x, int y)
13976 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13979 void TestIfBadThingRunsIntoPlayer(int x, int y, int move_dir)
13981 TestIfBadThingHitsGoodThing(x, y, move_dir);
13984 void TestIfFriendTouchesBadThing(int x, int y)
13986 TestIfGoodThingHitsBadThing(x, y, MV_NONE);
13989 void TestIfBadThingTouchesFriend(int x, int y)
13991 TestIfBadThingHitsGoodThing(x, y, MV_NONE);
13994 void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y)
13996 int i, kill_x = bad_x, kill_y = bad_y;
13997 static int xy[4][2] =
14005 for (i = 0; i < NUM_DIRECTIONS; i++)
14009 x = bad_x + xy[i][0];
14010 y = bad_y + xy[i][1];
14011 if (!IN_LEV_FIELD(x, y))
14014 element = Tile[x][y];
14015 if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE ||
14016 element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP)
14024 if (kill_x != bad_x || kill_y != bad_y)
14025 Bang(bad_x, bad_y);
14028 void KillPlayer(struct PlayerInfo *player)
14030 int jx = player->jx, jy = player->jy;
14032 if (!player->active)
14036 Debug("game:playing:KillPlayer",
14037 "0: killed == %d, active == %d, reanimated == %d",
14038 player->killed, player->active, player->reanimated);
14041 /* the following code was introduced to prevent an infinite loop when calling
14043 -> CheckTriggeredElementChangeExt()
14044 -> ExecuteCustomElementAction()
14046 -> (infinitely repeating the above sequence of function calls)
14047 which occurs when killing the player while having a CE with the setting
14048 "kill player X when explosion of <player X>"; the solution using a new
14049 field "player->killed" was chosen for backwards compatibility, although
14050 clever use of the fields "player->active" etc. would probably also work */
14052 if (player->killed)
14056 player->killed = TRUE;
14058 // remove accessible field at the player's position
14059 Tile[jx][jy] = EL_EMPTY;
14061 // deactivate shield (else Bang()/Explode() would not work right)
14062 player->shield_normal_time_left = 0;
14063 player->shield_deadly_time_left = 0;
14066 Debug("game:playing:KillPlayer",
14067 "1: killed == %d, active == %d, reanimated == %d",
14068 player->killed, player->active, player->reanimated);
14074 Debug("game:playing:KillPlayer",
14075 "2: killed == %d, active == %d, reanimated == %d",
14076 player->killed, player->active, player->reanimated);
14079 if (player->reanimated) // killed player may have been reanimated
14080 player->killed = player->reanimated = FALSE;
14082 BuryPlayer(player);
14085 static void KillPlayerUnlessEnemyProtected(int x, int y)
14087 if (!PLAYER_ENEMY_PROTECTED(x, y))
14088 KillPlayer(PLAYERINFO(x, y));
14091 static void KillPlayerUnlessExplosionProtected(int x, int y)
14093 if (!PLAYER_EXPLOSION_PROTECTED(x, y))
14094 KillPlayer(PLAYERINFO(x, y));
14097 void BuryPlayer(struct PlayerInfo *player)
14099 int jx = player->jx, jy = player->jy;
14101 if (!player->active)
14104 PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING);
14105 PlayLevelSound(jx, jy, SND_GAME_LOSING);
14107 RemovePlayer(player);
14109 player->buried = TRUE;
14111 if (game.all_players_gone)
14112 game.GameOver = TRUE;
14115 void RemovePlayer(struct PlayerInfo *player)
14117 int jx = player->jx, jy = player->jy;
14118 int i, found = FALSE;
14120 player->present = FALSE;
14121 player->active = FALSE;
14123 // required for some CE actions (even if the player is not active anymore)
14124 player->MovPos = 0;
14126 if (!ExplodeField[jx][jy])
14127 StorePlayer[jx][jy] = 0;
14129 if (player->is_moving)
14130 TEST_DrawLevelField(player->last_jx, player->last_jy);
14132 for (i = 0; i < MAX_PLAYERS; i++)
14133 if (stored_player[i].active)
14138 game.all_players_gone = TRUE;
14139 game.GameOver = TRUE;
14142 game.exit_x = game.robot_wheel_x = jx;
14143 game.exit_y = game.robot_wheel_y = jy;
14146 void ExitPlayer(struct PlayerInfo *player)
14148 DrawPlayer(player); // needed here only to cleanup last field
14149 RemovePlayer(player);
14151 if (game.players_still_needed > 0)
14152 game.players_still_needed--;
14155 static void SetFieldForSnapping(int x, int y, int element, int direction,
14156 int player_index_bit)
14158 struct ElementInfo *ei = &element_info[element];
14159 int direction_bit = MV_DIR_TO_BIT(direction);
14160 int graphic_snapping = ei->direction_graphic[ACTION_SNAPPING][direction_bit];
14161 int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING :
14162 IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING);
14164 Tile[x][y] = EL_ELEMENT_SNAPPING;
14165 MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
14166 MovDir[x][y] = direction;
14167 Store[x][y] = element;
14168 Store2[x][y] = player_index_bit;
14170 ResetGfxAnimation(x, y);
14172 GfxElement[x][y] = element;
14173 GfxAction[x][y] = action;
14174 GfxDir[x][y] = direction;
14175 GfxFrame[x][y] = -1;
14178 static void TestFieldAfterSnapping(int x, int y, int element, int direction,
14179 int player_index_bit)
14181 TestIfElementTouchesCustomElement(x, y); // for empty space
14183 if (level.finish_dig_collect)
14185 int dig_side = MV_DIR_OPPOSITE(direction);
14186 int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
14187 CE_PLAYER_COLLECTS_X);
14189 CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
14190 player_index_bit, dig_side);
14191 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14192 player_index_bit, dig_side);
14197 =============================================================================
14198 checkDiagonalPushing()
14199 -----------------------------------------------------------------------------
14200 check if diagonal input device direction results in pushing of object
14201 (by checking if the alternative direction is walkable, diggable, ...)
14202 =============================================================================
14205 static boolean checkDiagonalPushing(struct PlayerInfo *player,
14206 int x, int y, int real_dx, int real_dy)
14208 int jx, jy, dx, dy, xx, yy;
14210 if (real_dx == 0 || real_dy == 0) // no diagonal direction => push
14213 // diagonal direction: check alternative direction
14218 xx = jx + (dx == 0 ? real_dx : 0);
14219 yy = jy + (dy == 0 ? real_dy : 0);
14221 return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy]));
14225 =============================================================================
14227 -----------------------------------------------------------------------------
14228 x, y: field next to player (non-diagonal) to try to dig to
14229 real_dx, real_dy: direction as read from input device (can be diagonal)
14230 =============================================================================
14233 static int DigField(struct PlayerInfo *player,
14234 int oldx, int oldy, int x, int y,
14235 int real_dx, int real_dy, int mode)
14237 boolean is_player = (IS_PLAYER(oldx, oldy) || mode != DF_DIG);
14238 boolean player_was_pushing = player->is_pushing;
14239 boolean player_can_move = (!player->cannot_move && mode != DF_SNAP);
14240 boolean player_can_move_or_snap = (!player->cannot_move || mode == DF_SNAP);
14241 int jx = oldx, jy = oldy;
14242 int dx = x - jx, dy = y - jy;
14243 int nextx = x + dx, nexty = y + dy;
14244 int move_direction = (dx == -1 ? MV_LEFT :
14245 dx == +1 ? MV_RIGHT :
14247 dy == +1 ? MV_DOWN : MV_NONE);
14248 int opposite_direction = MV_DIR_OPPOSITE(move_direction);
14249 int dig_side = MV_DIR_OPPOSITE(move_direction);
14250 int old_element = Tile[jx][jy];
14251 int element = MovingOrBlocked2ElementIfNotLeaving(x, y);
14254 if (is_player) // function can also be called by EL_PENGUIN
14256 if (player->MovPos == 0)
14258 player->is_digging = FALSE;
14259 player->is_collecting = FALSE;
14262 if (player->MovPos == 0) // last pushing move finished
14263 player->is_pushing = FALSE;
14265 if (mode == DF_NO_PUSH) // player just stopped pushing
14267 player->is_switching = FALSE;
14268 player->push_delay = -1;
14270 return MP_NO_ACTION;
14273 if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0))
14274 old_element = Back[jx][jy];
14276 // in case of element dropped at player position, check background
14277 else if (Back[jx][jy] != EL_EMPTY &&
14278 game.engine_version >= VERSION_IDENT(2,2,0,0))
14279 old_element = Back[jx][jy];
14281 if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction))
14282 return MP_NO_ACTION; // field has no opening in this direction
14284 if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction))
14285 return MP_NO_ACTION; // field has no opening in this direction
14287 if (player_can_move && element == EL_ACID && move_direction == MV_DOWN)
14291 Tile[jx][jy] = player->artwork_element;
14292 InitMovingField(jx, jy, MV_DOWN);
14293 Store[jx][jy] = EL_ACID;
14294 ContinueMoving(jx, jy);
14295 BuryPlayer(player);
14297 return MP_DONT_RUN_INTO;
14300 if (player_can_move && DONT_RUN_INTO(element))
14302 TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir);
14304 return MP_DONT_RUN_INTO;
14307 if (IS_MOVING(x, y) || IS_PLAYER(x, y))
14308 return MP_NO_ACTION;
14310 collect_count = element_info[element].collect_count_initial;
14312 if (!is_player && !IS_COLLECTIBLE(element)) // penguin cannot collect it
14313 return MP_NO_ACTION;
14315 if (game.engine_version < VERSION_IDENT(2,2,0,0))
14316 player_can_move = player_can_move_or_snap;
14318 if (mode == DF_SNAP && !IS_SNAPPABLE(element) &&
14319 game.engine_version >= VERSION_IDENT(2,2,0,0))
14321 CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER,
14322 player->index_bit, dig_side);
14323 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14324 player->index_bit, dig_side);
14326 if (element == EL_DC_LANDMINE)
14329 if (Tile[x][y] != element) // field changed by snapping
14332 return MP_NO_ACTION;
14335 if (player->gravity && is_player && !player->is_auto_moving &&
14336 canFallDown(player) && move_direction != MV_DOWN &&
14337 !canMoveToValidFieldWithGravity(jx, jy, move_direction))
14338 return MP_NO_ACTION; // player cannot walk here due to gravity
14340 if (player_can_move &&
14341 IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction))
14343 int sound_element = SND_ELEMENT(element);
14344 int sound_action = ACTION_WALKING;
14346 if (IS_RND_GATE(element))
14348 if (!player->key[RND_GATE_NR(element)])
14349 return MP_NO_ACTION;
14351 else if (IS_RND_GATE_GRAY(element))
14353 if (!player->key[RND_GATE_GRAY_NR(element)])
14354 return MP_NO_ACTION;
14356 else if (IS_RND_GATE_GRAY_ACTIVE(element))
14358 if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)])
14359 return MP_NO_ACTION;
14361 else if (element == EL_EXIT_OPEN ||
14362 element == EL_EM_EXIT_OPEN ||
14363 element == EL_EM_EXIT_OPENING ||
14364 element == EL_STEEL_EXIT_OPEN ||
14365 element == EL_EM_STEEL_EXIT_OPEN ||
14366 element == EL_EM_STEEL_EXIT_OPENING ||
14367 element == EL_SP_EXIT_OPEN ||
14368 element == EL_SP_EXIT_OPENING)
14370 sound_action = ACTION_PASSING; // player is passing exit
14372 else if (element == EL_EMPTY)
14374 sound_action = ACTION_MOVING; // nothing to walk on
14377 // play sound from background or player, whatever is available
14378 if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED)
14379 PlayLevelSoundElementAction(x, y, sound_element, sound_action);
14381 PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action);
14383 else if (player_can_move &&
14384 IS_PASSABLE(element) && canPassField(x, y, move_direction))
14386 if (!ACCESS_FROM(element, opposite_direction))
14387 return MP_NO_ACTION; // field not accessible from this direction
14389 if (CAN_MOVE(element)) // only fixed elements can be passed!
14390 return MP_NO_ACTION;
14392 if (IS_EM_GATE(element))
14394 if (!player->key[EM_GATE_NR(element)])
14395 return MP_NO_ACTION;
14397 else if (IS_EM_GATE_GRAY(element))
14399 if (!player->key[EM_GATE_GRAY_NR(element)])
14400 return MP_NO_ACTION;
14402 else if (IS_EM_GATE_GRAY_ACTIVE(element))
14404 if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)])
14405 return MP_NO_ACTION;
14407 else if (IS_EMC_GATE(element))
14409 if (!player->key[EMC_GATE_NR(element)])
14410 return MP_NO_ACTION;
14412 else if (IS_EMC_GATE_GRAY(element))
14414 if (!player->key[EMC_GATE_GRAY_NR(element)])
14415 return MP_NO_ACTION;
14417 else if (IS_EMC_GATE_GRAY_ACTIVE(element))
14419 if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)])
14420 return MP_NO_ACTION;
14422 else if (element == EL_DC_GATE_WHITE ||
14423 element == EL_DC_GATE_WHITE_GRAY ||
14424 element == EL_DC_GATE_WHITE_GRAY_ACTIVE)
14426 if (player->num_white_keys == 0)
14427 return MP_NO_ACTION;
14429 player->num_white_keys--;
14431 else if (IS_SP_PORT(element))
14433 if (element == EL_SP_GRAVITY_PORT_LEFT ||
14434 element == EL_SP_GRAVITY_PORT_RIGHT ||
14435 element == EL_SP_GRAVITY_PORT_UP ||
14436 element == EL_SP_GRAVITY_PORT_DOWN)
14437 player->gravity = !player->gravity;
14438 else if (element == EL_SP_GRAVITY_ON_PORT_LEFT ||
14439 element == EL_SP_GRAVITY_ON_PORT_RIGHT ||
14440 element == EL_SP_GRAVITY_ON_PORT_UP ||
14441 element == EL_SP_GRAVITY_ON_PORT_DOWN)
14442 player->gravity = TRUE;
14443 else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT ||
14444 element == EL_SP_GRAVITY_OFF_PORT_RIGHT ||
14445 element == EL_SP_GRAVITY_OFF_PORT_UP ||
14446 element == EL_SP_GRAVITY_OFF_PORT_DOWN)
14447 player->gravity = FALSE;
14450 // automatically move to the next field with double speed
14451 player->programmed_action = move_direction;
14453 if (player->move_delay_reset_counter == 0)
14455 player->move_delay_reset_counter = 2; // two double speed steps
14457 DOUBLE_PLAYER_SPEED(player);
14460 PlayLevelSoundAction(x, y, ACTION_PASSING);
14462 else if (player_can_move_or_snap && IS_DIGGABLE(element))
14466 if (mode != DF_SNAP)
14468 GfxElement[x][y] = GFX_ELEMENT(element);
14469 player->is_digging = TRUE;
14472 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
14474 // use old behaviour for old levels (digging)
14475 if (!level.finish_dig_collect)
14477 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
14478 player->index_bit, dig_side);
14480 // if digging triggered player relocation, finish digging tile
14481 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14482 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14485 if (mode == DF_SNAP)
14487 if (level.block_snap_field)
14488 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14490 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14492 // use old behaviour for old levels (snapping)
14493 if (!level.finish_dig_collect)
14494 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14495 player->index_bit, dig_side);
14498 else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
14502 if (is_player && mode != DF_SNAP)
14504 GfxElement[x][y] = element;
14505 player->is_collecting = TRUE;
14508 if (element == EL_SPEED_PILL)
14510 player->move_delay_value = MOVE_DELAY_HIGH_SPEED;
14512 else if (element == EL_EXTRA_TIME && level.time > 0)
14514 TimeLeft += level.extra_time;
14516 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14518 DisplayGameControlValues();
14520 else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY)
14522 player->shield_normal_time_left += level.shield_normal_time;
14523 if (element == EL_SHIELD_DEADLY)
14524 player->shield_deadly_time_left += level.shield_deadly_time;
14526 else if (element == EL_DYNAMITE ||
14527 element == EL_EM_DYNAMITE ||
14528 element == EL_SP_DISK_RED)
14530 if (player->inventory_size < MAX_INVENTORY_SIZE)
14531 player->inventory_element[player->inventory_size++] = element;
14533 DrawGameDoorValues();
14535 else if (element == EL_DYNABOMB_INCREASE_NUMBER)
14537 player->dynabomb_count++;
14538 player->dynabombs_left++;
14540 else if (element == EL_DYNABOMB_INCREASE_SIZE)
14542 player->dynabomb_size++;
14544 else if (element == EL_DYNABOMB_INCREASE_POWER)
14546 player->dynabomb_xl = TRUE;
14548 else if (IS_KEY(element))
14550 player->key[KEY_NR(element)] = TRUE;
14552 DrawGameDoorValues();
14554 else if (element == EL_DC_KEY_WHITE)
14556 player->num_white_keys++;
14558 // display white keys?
14559 // DrawGameDoorValues();
14561 else if (IS_ENVELOPE(element))
14563 boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
14565 if (!wait_for_snapping)
14566 player->show_envelope = element;
14568 else if (element == EL_EMC_LENSES)
14570 game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND;
14572 RedrawAllInvisibleElementsForLenses();
14574 else if (element == EL_EMC_MAGNIFIER)
14576 game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND;
14578 RedrawAllInvisibleElementsForMagnifier();
14580 else if (IS_DROPPABLE(element) ||
14581 IS_THROWABLE(element)) // can be collected and dropped
14585 if (collect_count == 0)
14586 player->inventory_infinite_element = element;
14588 for (i = 0; i < collect_count; i++)
14589 if (player->inventory_size < MAX_INVENTORY_SIZE)
14590 player->inventory_element[player->inventory_size++] = element;
14592 DrawGameDoorValues();
14594 else if (collect_count > 0)
14596 game.gems_still_needed -= collect_count;
14597 if (game.gems_still_needed < 0)
14598 game.gems_still_needed = 0;
14600 game.snapshot.collected_item = TRUE;
14602 game_panel_controls[GAME_PANEL_GEMS].value = game.gems_still_needed;
14604 DisplayGameControlValues();
14607 RaiseScoreElement(element);
14608 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
14610 // use old behaviour for old levels (collecting)
14611 if (!level.finish_dig_collect && is_player)
14613 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
14614 player->index_bit, dig_side);
14616 // if collecting triggered player relocation, finish collecting tile
14617 if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
14618 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14621 if (mode == DF_SNAP)
14623 if (level.block_snap_field)
14624 SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
14626 TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
14628 // use old behaviour for old levels (snapping)
14629 if (!level.finish_dig_collect)
14630 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
14631 player->index_bit, dig_side);
14634 else if (player_can_move_or_snap && IS_PUSHABLE(element))
14636 if (mode == DF_SNAP && element != EL_BD_ROCK)
14637 return MP_NO_ACTION;
14639 if (CAN_FALL(element) && dy)
14640 return MP_NO_ACTION;
14642 if (CAN_FALL(element) && IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1) &&
14643 !(element == EL_SPRING && level.use_spring_bug))
14644 return MP_NO_ACTION;
14646 if (CAN_MOVE(element) && GET_MAX_MOVE_DELAY(element) == 0 &&
14647 ((move_direction & MV_VERTICAL &&
14648 ((element_info[element].move_pattern & MV_LEFT &&
14649 IN_LEV_FIELD(x - 1, y) && IS_FREE(x - 1, y)) ||
14650 (element_info[element].move_pattern & MV_RIGHT &&
14651 IN_LEV_FIELD(x + 1, y) && IS_FREE(x + 1, y)))) ||
14652 (move_direction & MV_HORIZONTAL &&
14653 ((element_info[element].move_pattern & MV_UP &&
14654 IN_LEV_FIELD(x, y - 1) && IS_FREE(x, y - 1)) ||
14655 (element_info[element].move_pattern & MV_DOWN &&
14656 IN_LEV_FIELD(x, y + 1) && IS_FREE(x, y + 1))))))
14657 return MP_NO_ACTION;
14659 // do not push elements already moving away faster than player
14660 if (CAN_MOVE(element) && MovDir[x][y] == move_direction &&
14661 ABS(getElementMoveStepsize(x, y)) > MOVE_STEPSIZE_NORMAL)
14662 return MP_NO_ACTION;
14664 if (game.engine_version >= VERSION_IDENT(3,1,0,0))
14666 if (player->push_delay_value == -1 || !player_was_pushing)
14667 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14669 else if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14671 if (player->push_delay_value == -1)
14672 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14674 else if (game.engine_version >= VERSION_IDENT(2,2,0,7))
14676 if (!player->is_pushing)
14677 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14680 player->is_pushing = TRUE;
14681 player->is_active = TRUE;
14683 if (!(IN_LEV_FIELD(nextx, nexty) &&
14684 (IS_FREE(nextx, nexty) ||
14685 (IS_SB_ELEMENT(element) &&
14686 Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) ||
14687 (IS_CUSTOM_ELEMENT(element) &&
14688 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty)))))
14689 return MP_NO_ACTION;
14691 if (!checkDiagonalPushing(player, x, y, real_dx, real_dy))
14692 return MP_NO_ACTION;
14694 if (player->push_delay == -1) // new pushing; restart delay
14695 player->push_delay = 0;
14697 if (player->push_delay < player->push_delay_value &&
14698 !(tape.playing && tape.file_version < FILE_VERSION_2_0) &&
14699 element != EL_SPRING && element != EL_BALLOON)
14701 // make sure that there is no move delay before next try to push
14702 if (game.engine_version >= VERSION_IDENT(3,0,7,1))
14703 player->move_delay = 0;
14705 return MP_NO_ACTION;
14708 if (IS_CUSTOM_ELEMENT(element) &&
14709 CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))
14711 if (!DigFieldByCE(nextx, nexty, element))
14712 return MP_NO_ACTION;
14715 if (IS_SB_ELEMENT(element))
14717 boolean sokoban_task_solved = FALSE;
14719 if (element == EL_SOKOBAN_FIELD_FULL)
14721 Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
14723 IncrementSokobanFieldsNeeded();
14724 IncrementSokobanObjectsNeeded();
14727 if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
14729 Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
14731 DecrementSokobanFieldsNeeded();
14732 DecrementSokobanObjectsNeeded();
14734 // sokoban object was pushed from empty field to sokoban field
14735 if (Back[x][y] == EL_EMPTY)
14736 sokoban_task_solved = TRUE;
14739 Tile[x][y] = EL_SOKOBAN_OBJECT;
14741 if (Back[x][y] == Back[nextx][nexty])
14742 PlayLevelSoundAction(x, y, ACTION_PUSHING);
14743 else if (Back[x][y] != 0)
14744 PlayLevelSoundElementAction(x, y, EL_SOKOBAN_FIELD_FULL,
14747 PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
14750 if (sokoban_task_solved &&
14751 game.sokoban_fields_still_needed == 0 &&
14752 game.sokoban_objects_still_needed == 0 &&
14753 level.auto_exit_sokoban)
14755 game.players_still_needed = 0;
14759 PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING);
14763 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
14765 InitMovingField(x, y, move_direction);
14766 GfxAction[x][y] = ACTION_PUSHING;
14768 if (mode == DF_SNAP)
14769 ContinueMoving(x, y);
14771 MovPos[x][y] = (dx != 0 ? dx : dy);
14773 Pushed[x][y] = TRUE;
14774 Pushed[nextx][nexty] = TRUE;
14776 if (game.engine_version < VERSION_IDENT(2,2,0,7))
14777 player->push_delay_value = GET_NEW_PUSH_DELAY(element);
14779 player->push_delay_value = -1; // get new value later
14781 // check for element change _after_ element has been pushed
14782 if (game.use_change_when_pushing_bug)
14784 CheckElementChangeByPlayer(x, y, element, CE_PUSHED_BY_PLAYER,
14785 player->index_bit, dig_side);
14786 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PUSHES_X,
14787 player->index_bit, dig_side);
14790 else if (IS_SWITCHABLE(element))
14792 if (PLAYER_SWITCHING(player, x, y))
14794 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14795 player->index_bit, dig_side);
14800 player->is_switching = TRUE;
14801 player->switch_x = x;
14802 player->switch_y = y;
14804 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
14806 if (element == EL_ROBOT_WHEEL)
14808 Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE;
14810 game.robot_wheel_x = x;
14811 game.robot_wheel_y = y;
14812 game.robot_wheel_active = TRUE;
14814 TEST_DrawLevelField(x, y);
14816 else if (element == EL_SP_TERMINAL)
14820 SCAN_PLAYFIELD(xx, yy)
14822 if (Tile[xx][yy] == EL_SP_DISK_YELLOW)
14826 else if (Tile[xx][yy] == EL_SP_TERMINAL)
14828 Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE;
14830 ResetGfxAnimation(xx, yy);
14831 TEST_DrawLevelField(xx, yy);
14835 else if (IS_BELT_SWITCH(element))
14837 ToggleBeltSwitch(x, y);
14839 else if (element == EL_SWITCHGATE_SWITCH_UP ||
14840 element == EL_SWITCHGATE_SWITCH_DOWN ||
14841 element == EL_DC_SWITCHGATE_SWITCH_UP ||
14842 element == EL_DC_SWITCHGATE_SWITCH_DOWN)
14844 ToggleSwitchgateSwitch(x, y);
14846 else if (element == EL_LIGHT_SWITCH ||
14847 element == EL_LIGHT_SWITCH_ACTIVE)
14849 ToggleLightSwitch(x, y);
14851 else if (element == EL_TIMEGATE_SWITCH ||
14852 element == EL_DC_TIMEGATE_SWITCH)
14854 ActivateTimegateSwitch(x, y);
14856 else if (element == EL_BALLOON_SWITCH_LEFT ||
14857 element == EL_BALLOON_SWITCH_RIGHT ||
14858 element == EL_BALLOON_SWITCH_UP ||
14859 element == EL_BALLOON_SWITCH_DOWN ||
14860 element == EL_BALLOON_SWITCH_NONE ||
14861 element == EL_BALLOON_SWITCH_ANY)
14863 game.wind_direction = (element == EL_BALLOON_SWITCH_LEFT ? MV_LEFT :
14864 element == EL_BALLOON_SWITCH_RIGHT ? MV_RIGHT :
14865 element == EL_BALLOON_SWITCH_UP ? MV_UP :
14866 element == EL_BALLOON_SWITCH_DOWN ? MV_DOWN :
14867 element == EL_BALLOON_SWITCH_NONE ? MV_NONE :
14870 else if (element == EL_LAMP)
14872 Tile[x][y] = EL_LAMP_ACTIVE;
14873 game.lights_still_needed--;
14875 ResetGfxAnimation(x, y);
14876 TEST_DrawLevelField(x, y);
14878 else if (element == EL_TIME_ORB_FULL)
14880 Tile[x][y] = EL_TIME_ORB_EMPTY;
14882 if (level.time > 0 || level.use_time_orb_bug)
14884 TimeLeft += level.time_orb_time;
14885 game.no_time_limit = FALSE;
14887 game_panel_controls[GAME_PANEL_TIME].value = TimeLeft;
14889 DisplayGameControlValues();
14892 ResetGfxAnimation(x, y);
14893 TEST_DrawLevelField(x, y);
14895 else if (element == EL_EMC_MAGIC_BALL_SWITCH ||
14896 element == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14900 game.ball_active = !game.ball_active;
14902 SCAN_PLAYFIELD(xx, yy)
14904 int e = Tile[xx][yy];
14906 if (game.ball_active)
14908 if (e == EL_EMC_MAGIC_BALL)
14909 CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
14910 else if (e == EL_EMC_MAGIC_BALL_SWITCH)
14911 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH_ACTIVE);
14915 if (e == EL_EMC_MAGIC_BALL_ACTIVE)
14916 CreateField(xx, yy, EL_EMC_MAGIC_BALL);
14917 else if (e == EL_EMC_MAGIC_BALL_SWITCH_ACTIVE)
14918 CreateField(xx, yy, EL_EMC_MAGIC_BALL_SWITCH);
14923 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14924 player->index_bit, dig_side);
14926 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14927 player->index_bit, dig_side);
14929 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14930 player->index_bit, dig_side);
14936 if (!PLAYER_SWITCHING(player, x, y))
14938 player->is_switching = TRUE;
14939 player->switch_x = x;
14940 player->switch_y = y;
14942 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED,
14943 player->index_bit, dig_side);
14944 CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X,
14945 player->index_bit, dig_side);
14947 CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER,
14948 player->index_bit, dig_side);
14949 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X,
14950 player->index_bit, dig_side);
14953 CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER,
14954 player->index_bit, dig_side);
14955 CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X,
14956 player->index_bit, dig_side);
14958 return MP_NO_ACTION;
14961 player->push_delay = -1;
14963 if (is_player) // function can also be called by EL_PENGUIN
14965 if (Tile[x][y] != element) // really digged/collected something
14967 player->is_collecting = !player->is_digging;
14968 player->is_active = TRUE;
14970 player->last_removed_element = element;
14977 static boolean DigFieldByCE(int x, int y, int digging_element)
14979 int element = Tile[x][y];
14981 if (!IS_FREE(x, y))
14983 int action = (IS_DIGGABLE(element) ? ACTION_DIGGING :
14984 IS_COLLECTIBLE(element) ? ACTION_COLLECTING :
14987 // no element can dig solid indestructible elements
14988 if (IS_INDESTRUCTIBLE(element) &&
14989 !IS_DIGGABLE(element) &&
14990 !IS_COLLECTIBLE(element))
14993 if (AmoebaNr[x][y] &&
14994 (element == EL_AMOEBA_FULL ||
14995 element == EL_BD_AMOEBA ||
14996 element == EL_AMOEBA_GROWING))
14998 AmoebaCnt[AmoebaNr[x][y]]--;
14999 AmoebaCnt2[AmoebaNr[x][y]]--;
15002 if (IS_MOVING(x, y))
15003 RemoveMovingField(x, y);
15007 TEST_DrawLevelField(x, y);
15010 // if digged element was about to explode, prevent the explosion
15011 ExplodeField[x][y] = EX_TYPE_NONE;
15013 PlayLevelSoundAction(x, y, action);
15016 Store[x][y] = EL_EMPTY;
15018 // this makes it possible to leave the removed element again
15019 if (IS_EQUAL_OR_IN_GROUP(element, MOVE_ENTER_EL(digging_element)))
15020 Store[x][y] = element;
15025 static boolean SnapField(struct PlayerInfo *player, int dx, int dy)
15027 int jx = player->jx, jy = player->jy;
15028 int x = jx + dx, y = jy + dy;
15029 int snap_direction = (dx == -1 ? MV_LEFT :
15030 dx == +1 ? MV_RIGHT :
15032 dy == +1 ? MV_DOWN : MV_NONE);
15033 boolean can_continue_snapping = (level.continuous_snapping &&
15034 WasJustFalling[x][y] < CHECK_DELAY_FALLING);
15036 if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0))
15039 if (!player->active || !IN_LEV_FIELD(x, y))
15047 if (player->MovPos == 0)
15048 player->is_pushing = FALSE;
15050 player->is_snapping = FALSE;
15052 if (player->MovPos == 0)
15054 player->is_moving = FALSE;
15055 player->is_digging = FALSE;
15056 player->is_collecting = FALSE;
15062 // prevent snapping with already pressed snap key when not allowed
15063 if (player->is_snapping && !can_continue_snapping)
15066 player->MovDir = snap_direction;
15068 if (player->MovPos == 0)
15070 player->is_moving = FALSE;
15071 player->is_digging = FALSE;
15072 player->is_collecting = FALSE;
15075 player->is_dropping = FALSE;
15076 player->is_dropping_pressed = FALSE;
15077 player->drop_pressed_delay = 0;
15079 if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION)
15082 player->is_snapping = TRUE;
15083 player->is_active = TRUE;
15085 if (player->MovPos == 0)
15087 player->is_moving = FALSE;
15088 player->is_digging = FALSE;
15089 player->is_collecting = FALSE;
15092 if (player->MovPos != 0) // prevent graphic bugs in versions < 2.2.0
15093 TEST_DrawLevelField(player->last_jx, player->last_jy);
15095 TEST_DrawLevelField(x, y);
15100 static boolean DropElement(struct PlayerInfo *player)
15102 int old_element, new_element;
15103 int dropx = player->jx, dropy = player->jy;
15104 int drop_direction = player->MovDir;
15105 int drop_side = drop_direction;
15106 int drop_element = get_next_dropped_element(player);
15108 /* do not drop an element on top of another element; when holding drop key
15109 pressed without moving, dropped element must move away before the next
15110 element can be dropped (this is especially important if the next element
15111 is dynamite, which can be placed on background for historical reasons) */
15112 if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY)
15115 if (IS_THROWABLE(drop_element))
15117 dropx += GET_DX_FROM_DIR(drop_direction);
15118 dropy += GET_DY_FROM_DIR(drop_direction);
15120 if (!IN_LEV_FIELD(dropx, dropy))
15124 old_element = Tile[dropx][dropy]; // old element at dropping position
15125 new_element = drop_element; // default: no change when dropping
15127 // check if player is active, not moving and ready to drop
15128 if (!player->active || player->MovPos || player->drop_delay > 0)
15131 // check if player has anything that can be dropped
15132 if (new_element == EL_UNDEFINED)
15135 // only set if player has anything that can be dropped
15136 player->is_dropping_pressed = TRUE;
15138 // check if drop key was pressed long enough for EM style dynamite
15139 if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40)
15142 // check if anything can be dropped at the current position
15143 if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION)
15146 // collected custom elements can only be dropped on empty fields
15147 if (IS_CUSTOM_ELEMENT(new_element) && old_element != EL_EMPTY)
15150 if (old_element != EL_EMPTY)
15151 Back[dropx][dropy] = old_element; // store old element on this field
15153 ResetGfxAnimation(dropx, dropy);
15154 ResetRandomAnimationValue(dropx, dropy);
15156 if (player->inventory_size > 0 ||
15157 player->inventory_infinite_element != EL_UNDEFINED)
15159 if (player->inventory_size > 0)
15161 player->inventory_size--;
15163 DrawGameDoorValues();
15165 if (new_element == EL_DYNAMITE)
15166 new_element = EL_DYNAMITE_ACTIVE;
15167 else if (new_element == EL_EM_DYNAMITE)
15168 new_element = EL_EM_DYNAMITE_ACTIVE;
15169 else if (new_element == EL_SP_DISK_RED)
15170 new_element = EL_SP_DISK_RED_ACTIVE;
15173 Tile[dropx][dropy] = new_element;
15175 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15176 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15177 el2img(Tile[dropx][dropy]), 0);
15179 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15181 // needed if previous element just changed to "empty" in the last frame
15182 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15184 CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER,
15185 player->index_bit, drop_side);
15186 CheckTriggeredElementChangeByPlayer(dropx, dropy, new_element,
15188 player->index_bit, drop_side);
15190 TestIfElementTouchesCustomElement(dropx, dropy);
15192 else // player is dropping a dyna bomb
15194 player->dynabombs_left--;
15196 Tile[dropx][dropy] = new_element;
15198 if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy)))
15199 DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy),
15200 el2img(Tile[dropx][dropy]), 0);
15202 PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING);
15205 if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change
15206 InitField_WithBug1(dropx, dropy, FALSE);
15208 new_element = Tile[dropx][dropy]; // element might have changed
15210 if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
15211 element_info[new_element].move_pattern == MV_WHEN_DROPPED)
15213 if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
15214 MovDir[dropx][dropy] = drop_direction;
15216 ChangeCount[dropx][dropy] = 0; // allow at least one more change
15218 // do not cause impact style collision by dropping elements that can fall
15219 CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION;
15222 player->drop_delay = GET_NEW_DROP_DELAY(drop_element);
15223 player->is_dropping = TRUE;
15225 player->drop_pressed_delay = 0;
15226 player->is_dropping_pressed = FALSE;
15228 player->drop_x = dropx;
15229 player->drop_y = dropy;
15234 // ----------------------------------------------------------------------------
15235 // game sound playing functions
15236 // ----------------------------------------------------------------------------
15238 static int *loop_sound_frame = NULL;
15239 static int *loop_sound_volume = NULL;
15241 void InitPlayLevelSound(void)
15243 int num_sounds = getSoundListSize();
15245 checked_free(loop_sound_frame);
15246 checked_free(loop_sound_volume);
15248 loop_sound_frame = checked_calloc(num_sounds * sizeof(int));
15249 loop_sound_volume = checked_calloc(num_sounds * sizeof(int));
15252 static void PlayLevelSound(int x, int y, int nr)
15254 int sx = SCREENX(x), sy = SCREENY(y);
15255 int volume, stereo_position;
15256 int max_distance = 8;
15257 int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND);
15259 if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) ||
15260 (!setup.sound_loops && IS_LOOP_SOUND(nr)))
15263 if (!IN_LEV_FIELD(x, y) ||
15264 sx < -max_distance || sx >= SCR_FIELDX + max_distance ||
15265 sy < -max_distance || sy >= SCR_FIELDY + max_distance)
15268 volume = SOUND_MAX_VOLUME;
15270 if (!IN_SCR_FIELD(sx, sy))
15272 int dx = ABS(sx - SCR_FIELDX / 2) - SCR_FIELDX / 2;
15273 int dy = ABS(sy - SCR_FIELDY / 2) - SCR_FIELDY / 2;
15275 volume -= volume * (dx > dy ? dx : dy) / max_distance;
15278 stereo_position = (SOUND_MAX_LEFT +
15279 (sx + max_distance) * SOUND_MAX_LEFT2RIGHT /
15280 (SCR_FIELDX + 2 * max_distance));
15282 if (IS_LOOP_SOUND(nr))
15284 /* This assures that quieter loop sounds do not overwrite louder ones,
15285 while restarting sound volume comparison with each new game frame. */
15287 if (loop_sound_volume[nr] > volume && loop_sound_frame[nr] == FrameCounter)
15290 loop_sound_volume[nr] = volume;
15291 loop_sound_frame[nr] = FrameCounter;
15294 PlaySoundExt(nr, volume, stereo_position, type);
15297 static void PlayLevelSoundNearest(int x, int y, int sound_action)
15299 PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) :
15300 x > LEVELX(BX2) ? LEVELX(BX2) : x,
15301 y < LEVELY(BY1) ? LEVELY(BY1) :
15302 y > LEVELY(BY2) ? LEVELY(BY2) : y,
15306 static void PlayLevelSoundAction(int x, int y, int action)
15308 PlayLevelSoundElementAction(x, y, Tile[x][y], action);
15311 static void PlayLevelSoundElementAction(int x, int y, int element, int action)
15313 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15315 if (sound_effect != SND_UNDEFINED)
15316 PlayLevelSound(x, y, sound_effect);
15319 static void PlayLevelSoundElementActionIfLoop(int x, int y, int element,
15322 int sound_effect = element_info[SND_ELEMENT(element)].sound[action];
15324 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15325 PlayLevelSound(x, y, sound_effect);
15328 static void PlayLevelSoundActionIfLoop(int x, int y, int action)
15330 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15332 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15333 PlayLevelSound(x, y, sound_effect);
15336 static void StopLevelSoundActionIfLoop(int x, int y, int action)
15338 int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action];
15340 if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect))
15341 StopSound(sound_effect);
15344 static int getLevelMusicNr(void)
15346 if (levelset.music[level_nr] != MUS_UNDEFINED)
15347 return levelset.music[level_nr]; // from config file
15349 return MAP_NOCONF_MUSIC(level_nr); // from music dir
15352 static void FadeLevelSounds(void)
15357 static void FadeLevelMusic(void)
15359 int music_nr = getLevelMusicNr();
15360 char *curr_music = getCurrentlyPlayingMusicFilename();
15361 char *next_music = getMusicInfoEntryFilename(music_nr);
15363 if (!strEqual(curr_music, next_music))
15367 void FadeLevelSoundsAndMusic(void)
15373 static void PlayLevelMusic(void)
15375 int music_nr = getLevelMusicNr();
15376 char *curr_music = getCurrentlyPlayingMusicFilename();
15377 char *next_music = getMusicInfoEntryFilename(music_nr);
15379 if (!strEqual(curr_music, next_music))
15380 PlayMusicLoop(music_nr);
15383 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
15385 int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
15387 int x = xx - offset;
15388 int y = yy - offset;
15393 PlayLevelSoundElementAction(x, y, element, ACTION_WALKING);
15397 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15401 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15405 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15409 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15413 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15417 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15420 case SOUND_android_clone:
15421 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15424 case SOUND_android_move:
15425 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15429 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15433 PlayLevelSoundElementAction(x, y, element, ACTION_EATING);
15437 PlayLevelSoundElementAction(x, y, element, ACTION_WAITING);
15440 case SOUND_eater_eat:
15441 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15445 PlayLevelSoundElementAction(x, y, element, ACTION_MOVING);
15448 case SOUND_collect:
15449 PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
15452 case SOUND_diamond:
15453 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15457 // !!! CHECK THIS !!!
15459 PlayLevelSoundElementAction(x, y, element, ACTION_BREAKING);
15461 PlayLevelSoundElementAction(x, y, element, ACTION_SMASHED_BY_ROCK);
15465 case SOUND_wonderfall:
15466 PlayLevelSoundElementAction(x, y, element, ACTION_FILLING);
15470 PlayLevelSoundElementAction(x, y, element, ACTION_IMPACT);
15474 PlayLevelSoundElementAction(x, y, element, ACTION_PUSHING);
15478 PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
15482 PlayLevelSoundElementAction(x, y, element, ACTION_SPLASHING);
15486 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15490 PlayLevelSoundElementAction(x, y, element, ACTION_GROWING);
15494 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15498 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15501 case SOUND_exit_open:
15502 PlayLevelSoundElementAction(x, y, element, ACTION_OPENING);
15505 case SOUND_exit_leave:
15506 PlayLevelSoundElementAction(x, y, element, ACTION_PASSING);
15509 case SOUND_dynamite:
15510 PlayLevelSoundElementAction(x, y, element, ACTION_DROPPING);
15514 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15518 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVATING);
15522 PlayLevelSoundElementAction(x, y, element, ACTION_ACTIVE);
15526 PlayLevelSoundElementAction(x, y, element, ACTION_EXPLODING);
15530 PlayLevelSoundElementAction(x, y, element, ACTION_DYING);
15534 PlaySound(SND_GAME_RUNNING_OUT_OF_TIME);
15538 PlayLevelSoundElementAction(x, y, element, ACTION_DEFAULT);
15543 void PlayLevelSound_SP(int xx, int yy, int element_sp, int action_sp)
15545 int element = map_element_SP_to_RND(element_sp);
15546 int action = map_action_SP_to_RND(action_sp);
15547 int offset = (setup.sp_show_border_elements ? 0 : 1);
15548 int x = xx - offset;
15549 int y = yy - offset;
15551 PlayLevelSoundElementAction(x, y, element, action);
15554 void PlayLevelSound_MM(int xx, int yy, int element_mm, int action_mm)
15556 int element = map_element_MM_to_RND(element_mm);
15557 int action = map_action_MM_to_RND(action_mm);
15559 int x = xx - offset;
15560 int y = yy - offset;
15562 if (!IS_MM_ELEMENT(element))
15563 element = EL_MM_DEFAULT;
15565 PlayLevelSoundElementAction(x, y, element, action);
15568 void PlaySound_MM(int sound_mm)
15570 int sound = map_sound_MM_to_RND(sound_mm);
15572 if (sound == SND_UNDEFINED)
15578 void PlaySoundLoop_MM(int sound_mm)
15580 int sound = map_sound_MM_to_RND(sound_mm);
15582 if (sound == SND_UNDEFINED)
15585 PlaySoundLoop(sound);
15588 void StopSound_MM(int sound_mm)
15590 int sound = map_sound_MM_to_RND(sound_mm);
15592 if (sound == SND_UNDEFINED)
15598 void RaiseScore(int value)
15600 game.score += value;
15602 game_panel_controls[GAME_PANEL_SCORE].value = game.score;
15604 DisplayGameControlValues();
15607 void RaiseScoreElement(int element)
15612 case EL_BD_DIAMOND:
15613 case EL_EMERALD_YELLOW:
15614 case EL_EMERALD_RED:
15615 case EL_EMERALD_PURPLE:
15616 case EL_SP_INFOTRON:
15617 RaiseScore(level.score[SC_EMERALD]);
15620 RaiseScore(level.score[SC_DIAMOND]);
15623 RaiseScore(level.score[SC_CRYSTAL]);
15626 RaiseScore(level.score[SC_PEARL]);
15629 case EL_BD_BUTTERFLY:
15630 case EL_SP_ELECTRON:
15631 RaiseScore(level.score[SC_BUG]);
15634 case EL_BD_FIREFLY:
15635 case EL_SP_SNIKSNAK:
15636 RaiseScore(level.score[SC_SPACESHIP]);
15639 case EL_DARK_YAMYAM:
15640 RaiseScore(level.score[SC_YAMYAM]);
15643 RaiseScore(level.score[SC_ROBOT]);
15646 RaiseScore(level.score[SC_PACMAN]);
15649 RaiseScore(level.score[SC_NUT]);
15652 case EL_EM_DYNAMITE:
15653 case EL_SP_DISK_RED:
15654 case EL_DYNABOMB_INCREASE_NUMBER:
15655 case EL_DYNABOMB_INCREASE_SIZE:
15656 case EL_DYNABOMB_INCREASE_POWER:
15657 RaiseScore(level.score[SC_DYNAMITE]);
15659 case EL_SHIELD_NORMAL:
15660 case EL_SHIELD_DEADLY:
15661 RaiseScore(level.score[SC_SHIELD]);
15663 case EL_EXTRA_TIME:
15664 RaiseScore(level.extra_time_score);
15678 case EL_DC_KEY_WHITE:
15679 RaiseScore(level.score[SC_KEY]);
15682 RaiseScore(element_info[element].collect_score);
15687 void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
15689 if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
15693 // prevent short reactivation of overlay buttons while closing door
15694 SetOverlayActive(FALSE);
15696 // door may still be open due to skipped or envelope style request
15697 CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1);
15700 if (network.enabled)
15701 SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
15705 FadeSkipNextFadeIn();
15707 SetGameStatus(GAME_MODE_MAIN);
15712 else // continue playing the game
15714 if (tape.playing && tape.deactivate_display)
15715 TapeDeactivateDisplayOff(TRUE);
15717 OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
15719 if (tape.playing && tape.deactivate_display)
15720 TapeDeactivateDisplayOn();
15724 void RequestQuitGame(boolean escape_key_pressed)
15726 boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game);
15727 boolean quick_quit = ((escape_key_pressed && !ask_on_escape) ||
15728 level_editor_test_game);
15729 boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game ||
15730 quick_quit || score_info_tape_play);
15732 RequestQuitGameExt(skip_request, quick_quit,
15733 "Do you really want to quit the game?");
15736 void RequestRestartGame(char *message)
15738 game.restart_game_message = NULL;
15740 boolean has_started_game = hasStartedNetworkGame();
15741 int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM);
15743 if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game)
15745 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
15749 // needed in case of envelope request to close game panel
15750 CloseDoor(DOOR_CLOSE_1);
15752 SetGameStatus(GAME_MODE_MAIN);
15758 void CheckGameOver(void)
15760 static boolean last_game_over = FALSE;
15761 static int game_over_delay = 0;
15762 int game_over_delay_value = 50;
15763 boolean game_over = checkGameFailed();
15765 // do not handle game over if request dialog is already active
15766 if (game.request_active)
15769 // do not ask to play again if game was never actually played
15770 if (!game.GamePlayed)
15775 last_game_over = FALSE;
15776 game_over_delay = game_over_delay_value;
15781 if (game_over_delay > 0)
15788 if (last_game_over != game_over)
15789 game.restart_game_message = (hasStartedNetworkGame() ?
15790 "Game over! Play it again?" :
15793 last_game_over = game_over;
15796 boolean checkGameSolved(void)
15798 // set for all game engines if level was solved
15799 return game.LevelSolved_GameEnd;
15802 boolean checkGameFailed(void)
15804 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15805 return (game_em.game_over && !game_em.level_solved);
15806 else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15807 return (game_sp.game_over && !game_sp.level_solved);
15808 else if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15809 return (game_mm.game_over && !game_mm.level_solved);
15810 else // GAME_ENGINE_TYPE_RND
15811 return (game.GameOver && !game.LevelSolved);
15814 boolean checkGameEnded(void)
15816 return (checkGameSolved() || checkGameFailed());
15820 // ----------------------------------------------------------------------------
15821 // random generator functions
15822 // ----------------------------------------------------------------------------
15824 unsigned int InitEngineRandom_RND(int seed)
15826 game.num_random_calls = 0;
15828 return InitEngineRandom(seed);
15831 unsigned int RND(int max)
15835 game.num_random_calls++;
15837 return GetEngineRandom(max);
15844 // ----------------------------------------------------------------------------
15845 // game engine snapshot handling functions
15846 // ----------------------------------------------------------------------------
15848 struct EngineSnapshotInfo
15850 // runtime values for custom element collect score
15851 int collect_score[NUM_CUSTOM_ELEMENTS];
15853 // runtime values for group element choice position
15854 int choice_pos[NUM_GROUP_ELEMENTS];
15856 // runtime values for belt position animations
15857 int belt_graphic[4][NUM_BELT_PARTS];
15858 int belt_anim_mode[4][NUM_BELT_PARTS];
15861 static struct EngineSnapshotInfo engine_snapshot_rnd;
15862 static char *snapshot_level_identifier = NULL;
15863 static int snapshot_level_nr = -1;
15865 static void SaveEngineSnapshotValues_RND(void)
15867 static int belt_base_active_element[4] =
15869 EL_CONVEYOR_BELT_1_LEFT_ACTIVE,
15870 EL_CONVEYOR_BELT_2_LEFT_ACTIVE,
15871 EL_CONVEYOR_BELT_3_LEFT_ACTIVE,
15872 EL_CONVEYOR_BELT_4_LEFT_ACTIVE
15876 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15878 int element = EL_CUSTOM_START + i;
15880 engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score;
15883 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15885 int element = EL_GROUP_START + i;
15887 engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos;
15890 for (i = 0; i < 4; i++)
15892 for (j = 0; j < NUM_BELT_PARTS; j++)
15894 int element = belt_base_active_element[i] + j;
15895 int graphic = el2img(element);
15896 int anim_mode = graphic_info[graphic].anim_mode;
15898 engine_snapshot_rnd.belt_graphic[i][j] = graphic;
15899 engine_snapshot_rnd.belt_anim_mode[i][j] = anim_mode;
15904 static void LoadEngineSnapshotValues_RND(void)
15906 unsigned int num_random_calls = game.num_random_calls;
15909 for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
15911 int element = EL_CUSTOM_START + i;
15913 element_info[element].collect_score = engine_snapshot_rnd.collect_score[i];
15916 for (i = 0; i < NUM_GROUP_ELEMENTS; i++)
15918 int element = EL_GROUP_START + i;
15920 element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i];
15923 for (i = 0; i < 4; i++)
15925 for (j = 0; j < NUM_BELT_PARTS; j++)
15927 int graphic = engine_snapshot_rnd.belt_graphic[i][j];
15928 int anim_mode = engine_snapshot_rnd.belt_anim_mode[i][j];
15930 graphic_info[graphic].anim_mode = anim_mode;
15934 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15936 InitRND(tape.random_seed);
15937 for (i = 0; i < num_random_calls; i++)
15941 if (game.num_random_calls != num_random_calls)
15943 Error("number of random calls out of sync");
15944 Error("number of random calls should be %d", num_random_calls);
15945 Error("number of random calls is %d", game.num_random_calls);
15947 Fail("this should not happen -- please debug");
15951 void FreeEngineSnapshotSingle(void)
15953 FreeSnapshotSingle();
15955 setString(&snapshot_level_identifier, NULL);
15956 snapshot_level_nr = -1;
15959 void FreeEngineSnapshotList(void)
15961 FreeSnapshotList();
15964 static ListNode *SaveEngineSnapshotBuffers(void)
15966 ListNode *buffers = NULL;
15968 // copy some special values to a structure better suited for the snapshot
15970 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15971 SaveEngineSnapshotValues_RND();
15972 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15973 SaveEngineSnapshotValues_EM();
15974 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15975 SaveEngineSnapshotValues_SP(&buffers);
15976 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15977 SaveEngineSnapshotValues_MM(&buffers);
15979 // save values stored in special snapshot structure
15981 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
15982 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
15983 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
15984 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
15985 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
15986 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
15987 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
15988 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_mm));
15990 // save further RND engine values
15992 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player));
15993 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game));
15994 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape));
15996 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter));
15997 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames));
15998 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed));
15999 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft));
16000 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime));
16002 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir));
16003 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos));
16004 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos));
16006 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize));
16008 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt));
16009 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2));
16011 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile));
16012 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos));
16013 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir));
16014 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay));
16015 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay));
16016 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage));
16017 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue));
16018 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store));
16019 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2));
16020 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer));
16021 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back));
16022 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr));
16023 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving));
16024 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling));
16025 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision));
16026 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact));
16027 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop));
16028 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed));
16030 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount));
16031 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent));
16033 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase));
16034 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay));
16035 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField));
16037 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit));
16038 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit));
16040 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame));
16041 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom));
16042 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic));
16043 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement));
16044 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction));
16045 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir));
16047 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x));
16048 SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y));
16051 ListNode *node = engine_snapshot_list_rnd;
16054 while (node != NULL)
16056 num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size;
16061 Debug("game:playing:SaveEngineSnapshotBuffers",
16062 "size of engine snapshot: %d bytes", num_bytes);
16068 void SaveEngineSnapshotSingle(void)
16070 ListNode *buffers = SaveEngineSnapshotBuffers();
16072 // finally save all snapshot buffers to single snapshot
16073 SaveSnapshotSingle(buffers);
16075 // save level identification information
16076 setString(&snapshot_level_identifier, leveldir_current->identifier);
16077 snapshot_level_nr = level_nr;
16080 boolean CheckSaveEngineSnapshotToList(void)
16082 boolean save_snapshot =
16083 ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) ||
16084 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE &&
16085 game.snapshot.changed_action) ||
16086 (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16087 game.snapshot.collected_item));
16089 game.snapshot.changed_action = FALSE;
16090 game.snapshot.collected_item = FALSE;
16091 game.snapshot.save_snapshot = save_snapshot;
16093 return save_snapshot;
16096 void SaveEngineSnapshotToList(void)
16098 if (game.snapshot.mode == SNAPSHOT_MODE_OFF ||
16102 ListNode *buffers = SaveEngineSnapshotBuffers();
16104 // finally save all snapshot buffers to snapshot list
16105 SaveSnapshotToList(buffers);
16108 void SaveEngineSnapshotToListInitial(void)
16110 FreeEngineSnapshotList();
16112 SaveEngineSnapshotToList();
16115 static void LoadEngineSnapshotValues(void)
16117 // restore special values from snapshot structure
16119 if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
16120 LoadEngineSnapshotValues_RND();
16121 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
16122 LoadEngineSnapshotValues_EM();
16123 if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
16124 LoadEngineSnapshotValues_SP();
16125 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
16126 LoadEngineSnapshotValues_MM();
16129 void LoadEngineSnapshotSingle(void)
16131 LoadSnapshotSingle();
16133 LoadEngineSnapshotValues();
16136 static void LoadEngineSnapshot_Undo(int steps)
16138 LoadSnapshotFromList_Older(steps);
16140 LoadEngineSnapshotValues();
16143 static void LoadEngineSnapshot_Redo(int steps)
16145 LoadSnapshotFromList_Newer(steps);
16147 LoadEngineSnapshotValues();
16150 boolean CheckEngineSnapshotSingle(void)
16152 return (strEqual(snapshot_level_identifier, leveldir_current->identifier) &&
16153 snapshot_level_nr == level_nr);
16156 boolean CheckEngineSnapshotList(void)
16158 return CheckSnapshotList();
16162 // ---------- new game button stuff -------------------------------------------
16169 boolean *setup_value;
16170 boolean allowed_on_tape;
16171 boolean is_touch_button;
16173 } gamebutton_info[NUM_GAME_BUTTONS] =
16176 IMG_GFX_GAME_BUTTON_STOP, &game.button.stop,
16177 GAME_CTRL_ID_STOP, NULL,
16178 TRUE, FALSE, "stop game"
16181 IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause,
16182 GAME_CTRL_ID_PAUSE, NULL,
16183 TRUE, FALSE, "pause game"
16186 IMG_GFX_GAME_BUTTON_PLAY, &game.button.play,
16187 GAME_CTRL_ID_PLAY, NULL,
16188 TRUE, FALSE, "play game"
16191 IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo,
16192 GAME_CTRL_ID_UNDO, NULL,
16193 TRUE, FALSE, "undo step"
16196 IMG_GFX_GAME_BUTTON_REDO, &game.button.redo,
16197 GAME_CTRL_ID_REDO, NULL,
16198 TRUE, FALSE, "redo step"
16201 IMG_GFX_GAME_BUTTON_SAVE, &game.button.save,
16202 GAME_CTRL_ID_SAVE, NULL,
16203 TRUE, FALSE, "save game"
16206 IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2,
16207 GAME_CTRL_ID_PAUSE2, NULL,
16208 TRUE, FALSE, "pause game"
16211 IMG_GFX_GAME_BUTTON_LOAD, &game.button.load,
16212 GAME_CTRL_ID_LOAD, NULL,
16213 TRUE, FALSE, "load game"
16216 IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop,
16217 GAME_CTRL_ID_PANEL_STOP, NULL,
16218 FALSE, FALSE, "stop game"
16221 IMG_GFX_GAME_BUTTON_PANEL_PAUSE, &game.button.panel_pause,
16222 GAME_CTRL_ID_PANEL_PAUSE, NULL,
16223 FALSE, FALSE, "pause game"
16226 IMG_GFX_GAME_BUTTON_PANEL_PLAY, &game.button.panel_play,
16227 GAME_CTRL_ID_PANEL_PLAY, NULL,
16228 FALSE, FALSE, "play game"
16231 IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop,
16232 GAME_CTRL_ID_TOUCH_STOP, NULL,
16233 FALSE, TRUE, "stop game"
16236 IMG_GFX_GAME_BUTTON_TOUCH_PAUSE, &game.button.touch_pause,
16237 GAME_CTRL_ID_TOUCH_PAUSE, NULL,
16238 FALSE, TRUE, "pause game"
16241 IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music,
16242 SOUND_CTRL_ID_MUSIC, &setup.sound_music,
16243 TRUE, FALSE, "background music on/off"
16246 IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops,
16247 SOUND_CTRL_ID_LOOPS, &setup.sound_loops,
16248 TRUE, FALSE, "sound loops on/off"
16251 IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple,
16252 SOUND_CTRL_ID_SIMPLE, &setup.sound_simple,
16253 TRUE, FALSE, "normal sounds on/off"
16256 IMG_GFX_GAME_BUTTON_PANEL_SOUND_MUSIC, &game.button.panel_sound_music,
16257 SOUND_CTRL_ID_PANEL_MUSIC, &setup.sound_music,
16258 FALSE, FALSE, "background music on/off"
16261 IMG_GFX_GAME_BUTTON_PANEL_SOUND_LOOPS, &game.button.panel_sound_loops,
16262 SOUND_CTRL_ID_PANEL_LOOPS, &setup.sound_loops,
16263 FALSE, FALSE, "sound loops on/off"
16266 IMG_GFX_GAME_BUTTON_PANEL_SOUND_SIMPLE, &game.button.panel_sound_simple,
16267 SOUND_CTRL_ID_PANEL_SIMPLE, &setup.sound_simple,
16268 FALSE, FALSE, "normal sounds on/off"
16272 void CreateGameButtons(void)
16276 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16278 int graphic = gamebutton_info[i].graphic;
16279 struct GraphicInfo *gfx = &graphic_info[graphic];
16280 struct XY *pos = gamebutton_info[i].pos;
16281 struct GadgetInfo *gi;
16284 unsigned int event_mask;
16285 boolean is_touch_button = gamebutton_info[i].is_touch_button;
16286 boolean allowed_on_tape = gamebutton_info[i].allowed_on_tape;
16287 boolean on_tape = (tape.show_game_buttons && allowed_on_tape);
16288 int base_x = (is_touch_button ? 0 : on_tape ? VX : DX);
16289 int base_y = (is_touch_button ? 0 : on_tape ? VY : DY);
16290 int gd_x = gfx->src_x;
16291 int gd_y = gfx->src_y;
16292 int gd_xp = gfx->src_x + gfx->pressed_xoffset;
16293 int gd_yp = gfx->src_y + gfx->pressed_yoffset;
16294 int gd_xa = gfx->src_x + gfx->active_xoffset;
16295 int gd_ya = gfx->src_y + gfx->active_yoffset;
16296 int gd_xap = gfx->src_x + gfx->active_xoffset + gfx->pressed_xoffset;
16297 int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
16298 int x = (is_touch_button ? pos->x : GDI_ACTIVE_POS(pos->x));
16299 int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y));
16302 if (gfx->bitmap == NULL)
16304 game_gadget[id] = NULL;
16309 if (id == GAME_CTRL_ID_STOP ||
16310 id == GAME_CTRL_ID_PANEL_STOP ||
16311 id == GAME_CTRL_ID_TOUCH_STOP ||
16312 id == GAME_CTRL_ID_PLAY ||
16313 id == GAME_CTRL_ID_PANEL_PLAY ||
16314 id == GAME_CTRL_ID_SAVE ||
16315 id == GAME_CTRL_ID_LOAD)
16317 button_type = GD_TYPE_NORMAL_BUTTON;
16319 event_mask = GD_EVENT_RELEASED;
16321 else if (id == GAME_CTRL_ID_UNDO ||
16322 id == GAME_CTRL_ID_REDO)
16324 button_type = GD_TYPE_NORMAL_BUTTON;
16326 event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED;
16330 button_type = GD_TYPE_CHECK_BUTTON;
16331 checked = (gamebutton_info[i].setup_value != NULL ?
16332 *gamebutton_info[i].setup_value : FALSE);
16333 event_mask = GD_EVENT_PRESSED;
16336 gi = CreateGadget(GDI_CUSTOM_ID, id,
16337 GDI_IMAGE_ID, graphic,
16338 GDI_INFO_TEXT, gamebutton_info[i].infotext,
16341 GDI_WIDTH, gfx->width,
16342 GDI_HEIGHT, gfx->height,
16343 GDI_TYPE, button_type,
16344 GDI_STATE, GD_BUTTON_UNPRESSED,
16345 GDI_CHECKED, checked,
16346 GDI_DESIGN_UNPRESSED, gfx->bitmap, gd_x, gd_y,
16347 GDI_DESIGN_PRESSED, gfx->bitmap, gd_xp, gd_yp,
16348 GDI_ALT_DESIGN_UNPRESSED, gfx->bitmap, gd_xa, gd_ya,
16349 GDI_ALT_DESIGN_PRESSED, gfx->bitmap, gd_xap, gd_yap,
16350 GDI_DIRECT_DRAW, FALSE,
16351 GDI_OVERLAY_TOUCH_BUTTON, is_touch_button,
16352 GDI_EVENT_MASK, event_mask,
16353 GDI_CALLBACK_ACTION, HandleGameButtons,
16357 Fail("cannot create gadget");
16359 game_gadget[id] = gi;
16363 void FreeGameButtons(void)
16367 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16368 FreeGadget(game_gadget[i]);
16371 static void UnmapGameButtonsAtSamePosition(int id)
16375 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16377 gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
16378 gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
16379 UnmapGadget(game_gadget[i]);
16382 static void UnmapGameButtonsAtSamePosition_All(void)
16384 if (setup.show_load_save_buttons)
16386 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16387 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16388 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16390 else if (setup.show_undo_redo_buttons)
16392 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16393 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
16394 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16398 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
16399 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
16400 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
16402 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_STOP);
16403 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PAUSE);
16404 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PANEL_PLAY);
16408 void MapLoadSaveButtons(void)
16410 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
16411 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
16413 MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
16414 MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
16417 void MapUndoRedoButtons(void)
16419 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
16420 UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
16422 MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
16423 MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
16426 void ModifyPauseButtons(void)
16430 GAME_CTRL_ID_PAUSE,
16431 GAME_CTRL_ID_PAUSE2,
16432 GAME_CTRL_ID_PANEL_PAUSE,
16433 GAME_CTRL_ID_TOUCH_PAUSE,
16438 for (i = 0; ids[i] > -1; i++)
16439 ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
16442 static void MapGameButtonsExt(boolean on_tape)
16446 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16448 if ((i == GAME_CTRL_ID_UNDO ||
16449 i == GAME_CTRL_ID_REDO) &&
16450 game_status != GAME_MODE_PLAYING)
16453 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16454 MapGadget(game_gadget[i]);
16457 UnmapGameButtonsAtSamePosition_All();
16459 RedrawGameButtons();
16462 static void UnmapGameButtonsExt(boolean on_tape)
16466 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16467 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16468 UnmapGadget(game_gadget[i]);
16471 static void RedrawGameButtonsExt(boolean on_tape)
16475 for (i = 0; i < NUM_GAME_BUTTONS; i++)
16476 if (!on_tape || gamebutton_info[i].allowed_on_tape)
16477 RedrawGadget(game_gadget[i]);
16480 static void SetGadgetState(struct GadgetInfo *gi, boolean state)
16485 gi->checked = state;
16488 static void RedrawSoundButtonGadget(int id)
16490 int id2 = (id == SOUND_CTRL_ID_MUSIC ? SOUND_CTRL_ID_PANEL_MUSIC :
16491 id == SOUND_CTRL_ID_LOOPS ? SOUND_CTRL_ID_PANEL_LOOPS :
16492 id == SOUND_CTRL_ID_SIMPLE ? SOUND_CTRL_ID_PANEL_SIMPLE :
16493 id == SOUND_CTRL_ID_PANEL_MUSIC ? SOUND_CTRL_ID_MUSIC :
16494 id == SOUND_CTRL_ID_PANEL_LOOPS ? SOUND_CTRL_ID_LOOPS :
16495 id == SOUND_CTRL_ID_PANEL_SIMPLE ? SOUND_CTRL_ID_SIMPLE :
16498 SetGadgetState(game_gadget[id2], *gamebutton_info[id2].setup_value);
16499 RedrawGadget(game_gadget[id2]);
16502 void MapGameButtons(void)
16504 MapGameButtonsExt(FALSE);
16507 void UnmapGameButtons(void)
16509 UnmapGameButtonsExt(FALSE);
16512 void RedrawGameButtons(void)
16514 RedrawGameButtonsExt(FALSE);
16517 void MapGameButtonsOnTape(void)
16519 MapGameButtonsExt(TRUE);
16522 void UnmapGameButtonsOnTape(void)
16524 UnmapGameButtonsExt(TRUE);
16527 void RedrawGameButtonsOnTape(void)
16529 RedrawGameButtonsExt(TRUE);
16532 static void GameUndoRedoExt(void)
16534 ClearPlayerAction();
16536 tape.pausing = TRUE;
16539 UpdateAndDisplayGameControlValues();
16541 DrawCompleteVideoDisplay();
16542 DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime);
16543 DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
16544 DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
16546 ModifyPauseButtons();
16551 static void GameUndo(int steps)
16553 if (!CheckEngineSnapshotList())
16556 int tape_property_bits = tape.property_bits;
16558 LoadEngineSnapshot_Undo(steps);
16560 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16565 static void GameRedo(int steps)
16567 if (!CheckEngineSnapshotList())
16570 int tape_property_bits = tape.property_bits;
16572 LoadEngineSnapshot_Redo(steps);
16574 tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
16579 static void HandleGameButtonsExt(int id, int button)
16581 static boolean game_undo_executed = FALSE;
16582 int steps = BUTTON_STEPSIZE(button);
16583 boolean handle_game_buttons =
16584 (game_status == GAME_MODE_PLAYING ||
16585 (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
16587 if (!handle_game_buttons)
16592 case GAME_CTRL_ID_STOP:
16593 case GAME_CTRL_ID_PANEL_STOP:
16594 case GAME_CTRL_ID_TOUCH_STOP:
16599 case GAME_CTRL_ID_PAUSE:
16600 case GAME_CTRL_ID_PAUSE2:
16601 case GAME_CTRL_ID_PANEL_PAUSE:
16602 case GAME_CTRL_ID_TOUCH_PAUSE:
16603 if (network.enabled && game_status == GAME_MODE_PLAYING)
16606 SendToServer_ContinuePlaying();
16608 SendToServer_PausePlaying();
16611 TapeTogglePause(TAPE_TOGGLE_MANUAL);
16613 game_undo_executed = FALSE;
16617 case GAME_CTRL_ID_PLAY:
16618 case GAME_CTRL_ID_PANEL_PLAY:
16619 if (game_status == GAME_MODE_MAIN)
16621 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
16623 else if (tape.pausing)
16625 if (network.enabled)
16626 SendToServer_ContinuePlaying();
16628 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
16632 case GAME_CTRL_ID_UNDO:
16633 // Important: When using "save snapshot when collecting an item" mode,
16634 // load last (current) snapshot for first "undo" after pressing "pause"
16635 // (else the last-but-one snapshot would be loaded, because the snapshot
16636 // pointer already points to the last snapshot when pressing "pause",
16637 // which is fine for "every step/move" mode, but not for "every collect")
16638 if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT &&
16639 !game_undo_executed)
16642 game_undo_executed = TRUE;
16647 case GAME_CTRL_ID_REDO:
16651 case GAME_CTRL_ID_SAVE:
16655 case GAME_CTRL_ID_LOAD:
16659 case SOUND_CTRL_ID_MUSIC:
16660 case SOUND_CTRL_ID_PANEL_MUSIC:
16661 if (setup.sound_music)
16663 setup.sound_music = FALSE;
16667 else if (audio.music_available)
16669 setup.sound = setup.sound_music = TRUE;
16671 SetAudioMode(setup.sound);
16673 if (game_status == GAME_MODE_PLAYING)
16677 RedrawSoundButtonGadget(id);
16681 case SOUND_CTRL_ID_LOOPS:
16682 case SOUND_CTRL_ID_PANEL_LOOPS:
16683 if (setup.sound_loops)
16684 setup.sound_loops = FALSE;
16685 else if (audio.loops_available)
16687 setup.sound = setup.sound_loops = TRUE;
16689 SetAudioMode(setup.sound);
16692 RedrawSoundButtonGadget(id);
16696 case SOUND_CTRL_ID_SIMPLE:
16697 case SOUND_CTRL_ID_PANEL_SIMPLE:
16698 if (setup.sound_simple)
16699 setup.sound_simple = FALSE;
16700 else if (audio.sound_available)
16702 setup.sound = setup.sound_simple = TRUE;
16704 SetAudioMode(setup.sound);
16707 RedrawSoundButtonGadget(id);
16716 static void HandleGameButtons(struct GadgetInfo *gi)
16718 HandleGameButtonsExt(gi->custom_id, gi->event.button);
16721 void HandleSoundButtonKeys(Key key)
16723 if (key == setup.shortcut.sound_simple)
16724 ClickOnGadget(game_gadget[SOUND_CTRL_ID_SIMPLE], MB_LEFTBUTTON);
16725 else if (key == setup.shortcut.sound_loops)
16726 ClickOnGadget(game_gadget[SOUND_CTRL_ID_LOOPS], MB_LEFTBUTTON);
16727 else if (key == setup.shortcut.sound_music)
16728 ClickOnGadget(game_gadget[SOUND_CTRL_ID_MUSIC], MB_LEFTBUTTON);